Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
fbbf69e
Rebase to dev3.7
dices Sep 28, 2018
b8c968c
Adding 3.7 to travis configuration
dices Sep 22, 2018
38f1dc1
Updated documentation to resolve warnings introduced with the longer …
dices Sep 23, 2018
e40c78d
Fixed reference to deprecated asynchronous
dices Sep 28, 2018
44668d9
Merge pull request #2 from dices/Py37
dices Sep 28, 2018
e0829bd
Adding gmp disable to fix pypy build issues
dices Sep 28, 2018
06627b8
Adding gmp disable to fix pypy build issues
dices Sep 28, 2018
e11bff7
Removing travis python 3.7 configuration
dices Oct 2, 2018
0fce5b5
Adding asserts for Payload Endianness
EricDuminil Oct 8, 2018
3838f5f
Fixing example of Payload. Same Endianness for builder and decoder.
EricDuminil Oct 8, 2018
826a76c
Merge pull request #350 from EricDuminil/master
dhoomakethu Oct 9, 2018
13adf0a
Fix Sql db slave context validate and get methods - #139
dhoomakethu Oct 9, 2018
cd1cb1c
Merge branch 'dev3.7' of https://github.com/riptideio/pymodbus into d…
dices Oct 10, 2018
05afbb0
Merge pull request #351 from riptideio/#139-SqlDb-Context
dhoomakethu Oct 15, 2018
d00a8cd
#353 - debugging, Add debug logs to check size of avaialble data in r…
dhoomakethu Oct 16, 2018
e85057a
#353 Provide an option to disable inter char timeouts
dhoomakethu Oct 17, 2018
c0a2359
#353 Bump version, update changelog
dhoomakethu Oct 18, 2018
aef3e0a
Merge pull request #355 from riptideio/#353-Error-Reading-Registers
dhoomakethu Oct 23, 2018
e09fed7
Merge pull request #346 from dices/dev3.7
dhoomakethu Oct 23, 2018
b3281fe
Merge pull request #362 from riptideio/dev3.7
dhoomakethu Jan 12, 2019
7e1c728
check self.socket (#354)
mpf82 Jan 14, 2019
d14318f
Fix typo (#378)
kimhanse Jan 26, 2019
9ff42d1
Pymodbus 2.2.0 (#375)
dhoomakethu Apr 18, 2019
9dcdca8
Fix docs (#407)
dhoomakethu Apr 19, 2019
c42d619
Merge branch 'master' into dev
dhoomakethu Apr 19, 2019
14af637
Remove pycrypto dep (#411)
tracernz Apr 22, 2019
13384e4
Fix --upgrade option in install dependencies (#413)
acanidio May 13, 2019
92b4428
Padding for odd sized responses (#425)
tcplomp Jul 25, 2019
ca74132
README update: REPL stands for Read Evaluate **Print** Loop (#426)
alecjohanson Jul 30, 2019
6a6ebde
Drop python 3.4 support (#440)
tracernz Sep 9, 2019
b6429fc
Re-enable travis python 3.7 builds (#441)
tracernz Sep 9, 2019
68932a3
Update __init__.py (#436)
hackerboygn Sep 9, 2019
c645605
Use SPDX identifier to specify the exact license type (#427)
yegorich Sep 9, 2019
e6da559
asyncio server implementation (#400)
memetb Sep 9, 2019
050b03c
Add option to repl allowing Modbus RTU framing on a TCP socket (#447)
Sekenre Sep 26, 2019
cc6e976
Fix asynci server test failures on python3.6 and below
Oct 7, 2019
507e8a0
Bump version to 2.2.0rc1, update six requirements and Changelog
Oct 7, 2019
fedea33
Support multiple Python versions to fix test error from PR #400 (#444)
starnight Oct 17, 2019
4bdf738
Add TLS feature for Modbus synchronous (#446)
starnight Oct 28, 2019
3fb83cc
Fix #461 - Udp client/server , Fix #401 - package license with source…
dhoomakethu Oct 29, 2019
b97659d
Fix examples, Merge #431
dhoomakethu Oct 29, 2019
632d300
#401 Move license to root folder from docs
dhoomakethu Oct 29, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Add TLS feature for Modbus synchronous (#446)
* Add TLS feature for Modbus synchronous

Modbus.org released MODBUS/TCP Security Protocol Specification [1],
which focuses variant of the Mobdbus/TCP protocol utilizing Transport
Layer Security (TLS). This patch enables the Modbus over TLS feature as
ModbusTlsClient with the Python builtin module ssl - TLS/SSL wrapper for
socket objects.

[1]: http://modbus.org/docs/MB-TCP-Security-v21_2018-07-24.pdf

* Implement MODBUS TLS synchronous server

Since we have the MODBUS TLS synchronous client, we can also have the
MODBUS TLS synchronous server.
  • Loading branch information
starnight authored and dhoomakethu committed Oct 28, 2019
commit 4bdf7381ce29240dcf83d678a0af18128dd601c1
5 changes: 5 additions & 0 deletions examples/common/synchronous_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
# import the various server implementations
# --------------------------------------------------------------------------- #
from pymodbus.server.sync import StartTcpServer
from pymodbus.server.sync import StartTlsServer
from pymodbus.server.sync import StartUdpServer
from pymodbus.server.sync import StartSerialServer

Expand Down Expand Up @@ -117,6 +118,10 @@ def run_server():
# StartTcpServer(context, identity=identity,
# framer=ModbusRtuFramer, address=("0.0.0.0", 5020))

# TLS
# StartTlsServer(context, identity=identity, certfile="server.crt",
# keyfile="server.key", address=("0.0.0.0", 8020))

# Udp:
# StartUdpServer(context, identity=identity, address=("0.0.0.0", 5020))

Expand Down
34 changes: 34 additions & 0 deletions examples/contrib/modbus_tls_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
#!/usr/bin/env python
"""
Simple Modbus TCP over TLS client
---------------------------------------------------------------------------

This is a simple example of writing a modbus TCP over TLS client that uses
Python builtin module ssl - TLS/SSL wrapper for socket objects for the TLS
feature.
"""
# -------------------------------------------------------------------------- #
# import neccessary libraries
# -------------------------------------------------------------------------- #
import ssl
from pymodbus.client.sync import ModbusTlsClient

# -------------------------------------------------------------------------- #
# the TLS detail security can be set in SSLContext which is the context here
# -------------------------------------------------------------------------- #
context = ssl.create_default_context()
context.options |= ssl.OP_NO_SSLv2
context.options |= ssl.OP_NO_SSLv3
context.options |= ssl.OP_NO_TLSv1
context.options |= ssl.OP_NO_TLSv1_1

# -------------------------------------------------------------------------- #
# pass SSLContext which is the context here to ModbusTcpClient()
# -------------------------------------------------------------------------- #
client = ModbusTlsClient('test.host.com', 8020, sslctx=context)
client.connect()

result = client.read_coils(1, 8)
print(result.bits)

client.close()
114 changes: 113 additions & 1 deletion pymodbus/client/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import select
import serial
import time
import ssl
import sys
from functools import partial
from pymodbus.constants import Defaults
Expand All @@ -13,6 +14,7 @@
from pymodbus.transaction import DictTransactionManager
from pymodbus.transaction import ModbusSocketFramer, ModbusBinaryFramer
from pymodbus.transaction import ModbusAsciiFramer, ModbusRtuFramer
from pymodbus.transaction import ModbusTlsFramer
from pymodbus.client.common import ModbusClientMixin

# --------------------------------------------------------------------------- #
Expand Down Expand Up @@ -297,6 +299,116 @@ def __repr__(self):
"port={self.port}, timeout={self.timeout}>"
).format(self.__class__.__name__, hex(id(self)), self=self)

# --------------------------------------------------------------------------- #
# Modbus TLS Client Transport Implementation
# --------------------------------------------------------------------------- #


class ModbusTlsClient(ModbusTcpClient):
""" Implementation of a modbus tls client
"""

def __init__(self, host='localhost', port=Defaults.TLSPort, sslctx=None,
framer=ModbusTlsFramer, **kwargs):
""" Initialize a client instance

:param host: The host to connect to (default localhost)
:param port: The modbus port to connect to (default 802)
:param sslctx: The SSLContext to use for TLS (default None and auto create)
:param source_address: The source address tuple to bind to (default ('', 0))
:param timeout: The timeout to use for this socket (default Defaults.Timeout)
:param framer: The modbus framer to use (default ModbusSocketFramer)

.. note:: The host argument will accept ipv4 and ipv6 hosts
"""
self.sslctx = sslctx
if self.sslctx is None:
self.sslctx = ssl.create_default_context()
# According to MODBUS/TCP Security Protocol Specification, it is
# TLSv2 at least
self.sslctx.options |= ssl.OP_NO_TLSv1_1
self.sslctx.options |= ssl.OP_NO_TLSv1
self.sslctx.options |= ssl.OP_NO_SSLv3
self.sslctx.options |= ssl.OP_NO_SSLv2
ModbusTcpClient.__init__(self, host, port, framer, **kwargs)

def connect(self):
""" Connect to the modbus tls server

:returns: True if connection succeeded, False otherwise
"""
if self.socket: return True
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(self.source_address)
self.socket = self.sslctx.wrap_socket(sock, server_side=False,
server_hostname=self.host)
self.socket.settimeout(self.timeout)
self.socket.connect((self.host, self.port))
except socket.error as msg:
_logger.error('Connection to (%s, %s) '
'failed: %s' % (self.host, self.port, msg))
self.close()
return self.socket is not None

def _recv(self, size):
""" Reads data from the underlying descriptor

:param size: The number of bytes to read
:return: The bytes read
"""
if not self.socket:
raise ConnectionException(self.__str__())

# socket.recv(size) waits until it gets some data from the host but
# not necessarily the entire response that can be fragmented in
# many packets.
# To avoid the splitted responses to be recognized as invalid
# messages and to be discarded, loops socket.recv until full data
# is received or timeout is expired.
# If timeout expires returns the read data, also if its length is
# less than the expected size.
timeout = self.timeout

# If size isn't specified read 1 byte at a time.
if size is None:
recv_size = 1
else:
recv_size = size

data = b''
time_ = time.time()
end = time_ + timeout
while recv_size > 0:
data += self.socket.recv(recv_size)
time_ = time.time()

# If size isn't specified continue to read until timeout expires.
if size:
recv_size = size - len(data)

# Timeout is reduced also if some data has been received in order
# to avoid infinite loops when there isn't an expected response
# size and the slave sends noisy data continuosly.
if time_ > end:
break

return data

def __str__(self):
""" Builds a string representation of the connection

:returns: The string representation
"""
return "ModbusTlsClient(%s:%s)" % (self.host, self.port)

def __repr__(self):
return (
"<{} at {} socket={self.socket}, ipaddr={self.host}, "
"port={self.port}, sslctx={self.sslctx}, timeout={self.timeout}>"
).format(self.__class__.__name__, hex(id(self)), self=self)


# --------------------------------------------------------------------------- #
# Modbus UDP Client Transport Implementation
# --------------------------------------------------------------------------- #
Expand Down Expand Up @@ -594,5 +706,5 @@ def __repr__(self):


__all__ = [
"ModbusTcpClient", "ModbusUdpClient", "ModbusSerialClient"
"ModbusTcpClient", "ModbusTlsClient", "ModbusUdpClient", "ModbusSerialClient"
]
5 changes: 5 additions & 0 deletions pymodbus/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,10 @@ class Defaults(Singleton):

The default modbus tcp server port (502)

.. attribute:: TLSPort

The default modbus tcp over tls server port (802)

.. attribute:: Retries

The default number of times a client should retry the given
Expand Down Expand Up @@ -99,6 +103,7 @@ class Defaults(Singleton):

'''
Port = 502
TLSPort = 802
Retries = 3
RetryOnEmpty = False
Timeout = 3
Expand Down
2 changes: 2 additions & 0 deletions pymodbus/framer/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
# Transaction Id, Protocol ID, Length, Unit ID, Function Code
SOCKET_FRAME_HEADER = BYTE_ORDER + 'HHH' + FRAME_HEADER

# Function Code
TLS_FRAME_HEADER = BYTE_ORDER + 'B'

class ModbusFramer(IModbusFramer):
"""
Expand Down
Loading