Python 實現Ethernet/IP 通訊
阿新 • • 發佈:2018-12-11
EtherNet / IP是為了在乙太網中使用CIP協議而進行的封裝.EtherNet / IP的CIP幀封裝了命令,資料點和訊息等資訊.CIP幀包括CIP裝置配置檔案資料包的其餘部分是乙太網/ IP幀,CIP幀通過它們在乙太網上傳輸。EIP一般使用TCP / UDP的44818埠執行,還有一個2222埠,這兩個埠分別實現隱式訊息傳遞和顯示訊息傳遞兩種方式。客戶端/伺服器訊息,而隱訊息為I / O訊息。
詳細資訊可參考下面文章
https://www.cnblogs.com/blacksunny/p/7202815.html
下面是Python 實現的程式碼,轉載自https://github.com/paperwork/pyenip/blob/605ad6d026865e3378542d4428ec975e7c26d2e4/ethernetip.py
""" | |
Copyright (C) 2014 Sebastian Block | |
This program is free software; you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation; either version 2 of the License. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License along | |
with this program; if not, write to the Free Software Foundation, Inc., | |
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. | |
""" | |
import random | |
import select | |
import socket | |
import struct | |
import threading | |
import time | |
from .dpkt import dpkt | |
ENIP_TCP_PORT = 44818 | |
ENIP_UDP_PORT = 2222 | |
#/* Common Services */ | |
CI_SRV_GET_ALL = 0x01 | |
CI_SRV_SET_ATTR_ALL = 0x02 | |
CI_SRV_GET_ATTR_LIST = 0x03 | |
CI_SRV_SET_ATTR_LIST = 0x04 | |
CI_SRV_RESET = 0x05 | |
CI_SRV_START = 0x06 | |
CI_SRV_STOP = 0x07 | |
CI_SRV_CREATE = 0x08 | |
CI_SRV_DELETE = 0x09 | |
CI_SRV_MULTIPLE_SRV = 0x0A | |
CI_SRV_APPLY_ATTR = 0x0D | |
CI_SRV_GET_ATTR_SINGLE = 0x0E | |
CI_SRV_SET_ATTR_SINGLE = 0x10 | |
CI_SRV_FIND_NEXT_OBJ = 0x11 | |
CI_SRV_RESTORE = 0x15 | |
CI_SRV_SAVE = 0x16 | |
CI_SRV_NOP = 0x17 | |
CI_SRV_GET_MEMBER = 0x18 | |
CI_SRV_SET_MEMBER = 0x19 | |
CI_SRV_INSERT_MEMBER = 0x1A | |
CI_SRV_REMOVE_MEMBER = 0x1B | |
CI_SRV_GROUP_SYNC = 0x1C | |
CI_SRV_FORWARD_CLOSE = 0x4E | |
CI_SRV_UNCONN_SEND = 0x52 | |
CI_SRV_FORWARD_OPEN = 0x54 | |
#/* List of Objects */ | |
CIP_OBJ_IDENTITY = 0x01 | |
CIP_OBJ_MESSAGE_ROUTER = 0x02 | |
CIP_OBJ_ASSEMBLY = 0x04 | |
CIP_OBJ_CONNECTION = 0x05 | |
CIP_OBJ_CONNMANAGER = 0x06 | |
CIP_OBJ_DLR = 0x47 | |
CIP_OBJ_QOS = 0x48 | |
CIP_OBJ_BASE_SWITCH = 0x51 | |
CIP_OBJ_SNMP = 0x52 | |
CIP_OBJ_POWER_MANAGEM = 0x53 | |
CIP_OBJ_RSTP_BRIDGE = 0x54 | |
CIP_OBJ_RSTP_PORT = 0x55 | |
CIP_OBJ_PRP = 0x56 | |
CIP_OBJ_PRP_NODE_TABLE = 0x57 | |
CIP_OBJ_CONN_CONF = 0xF3 | |
CIP_OBJ_PORT = 0xF4 | |
CIP_OBJ_TCPIP = 0xF5 | |
CIP_OBJ_ETHERNET_LINK = 0xF6 | |
#/* The following are CIP (Ethernet/IP) Generic error codes */ | |
CIP_ROUTER_ERROR_SUCCESS = 0x00 # We done good... | |
CIP_ROUTER_ERROR_FAILURE = 0x01 # Connection failure | |
CIP_ROUTER_ERROR_NO_RESOURCE = 0x02 # Resource(s) unavailable | |
CIP_ROUTER_ERROR_INVALID_PARAMETER_VALUE = 0x03 # Obj specific data bad | |
CIP_ROUTER_ERROR_INVALID_SEG_TYPE = 0x04 # Invalid segment type in path | |
CIP_ROUTER_ERROR_INVALID_DESTINATION = 0x05 # Invalid segment value in path | |
CIP_ROUTER_ERROR_PARTIAL_DATA = 0x06 # Not all expected data sent | |
CIP_ROUTER_ERROR_CONN_LOST = 0x07 # Messaging connection lost | |
CIP_ROUTER_ERROR_BAD_SERVICE = 0x08 # Unimplemented service code | |
CIP_ROUTER_ERROR_BAD_ATTR_DATA = 0x09 # Bad attribute data value | |
CIP_ROUTER_ERROR_ATTR_LIST_ERROR = 0x0A # Get/set attr list failed | |
CIP_ROUTER_ERROR_ALREADY_IN_REQUESTED_MODE = 0x0B # Obj already in requested mode | |
CIP_ROUTER_ERROR_OBJECT_STATE_CONFLICT = 0x0C # Obj not in proper mode | |
CIP_ROUTER_ERROR_OBJ_ALREADY_EXISTS = 0x0D # Object already created | |
CIP_ROUTER_ERROR_ATTR_NOT_SETTABLE = 0x0E # Set of get only attr tried | |
CIP_ROUTER_ERROR_PERMISSION_DENIED = 0x0F # Insufficient access permission | |
CIP_ROUTER_ERROR_DEV_IN_WRONG_STATE = 0x10 # Device not in proper mode | |
CIP_ROUTER_ERROR_REPLY_DATA_TOO_LARGE = 0x11 # Response packet too large | |
CIP_ROUTER_ERROR_FRAGMENT_PRIMITIVE = 0x12 # Primitive value will fragment | |
CIP_ROUTER_ERROR_NOT_ENOUGH_DATA = 0x13 # Goldilocks complaint #1 | |
CIP_ROUTER_ERROR_ATTR_NOT_SUPPORTED = 0x14 # Attribute is undefined | |
CIP_ROUTER_ERROR_TOO_MUCH_DATA = 0x15 # Goldilocks complaint #2 | |
CIP_ROUTER_ERROR_OBJ_DOES_NOT_EXIST = 0x16 # Non-existant object specified | |
CIP_ROUTER_ERROR_NO_FRAGMENTATION = 0x17 # Fragmentation not active | |
CIP_ROUTER_ERROR_DATA_NOT_SAVED = 0x18 # Attr data not previously saved | |
CIP_ROUTER_ERROR_DATA_WRITE_FAILURE = 0x19 # Attr data not saved this time | |
CIP_ROUTER_ERROR_REQUEST_TOO_LARGE = 0x1A # Routing failure on request | |
CIP_ROUTER_ERROR_RESPONSE_TOO_LARGE = 0x1B # Routing failure on response | |
CIP_ROUTER_ERROR_MISSING_LIST_DATA = 0x1C # Attr data not found in list | |
CIP_ROUTER_ERROR_INVALID_LIST_STATUS = 0x1D # Returned list of attr w/status | |
CIP_ROUTER_ERROR_SERVICE_ERROR = 0x1E # Embedded service failed | |
CIP_ROUTER_ERROR_VENDOR_SPECIFIC = 0x1F # Vendor specific error | |
CIP_ROUTER_ERROR_INVALID_PARAMETER = 0x20 # Invalid parameter | |
CIP_ROUTER_ERROR_WRITE_ONCE_FAILURE = 0x21 # Write once previously done | |
CIP_ROUTER_ERROR_INVALID_REPLY = 0x22 # Invalid reply received | |
CIP_ROUTER_ERROR_BAD_KEY_IN_PATH = 0x25 # Electronic key in path failed | |
CIP_ROUTER_ERROR_BAD_PATH_SIZE = 0x26 # Invalid path size | |
CIP_ROUTER_ERROR_UNEXPECTED_ATTR = 0x27 # Cannot set attr at this time | |
CIP_ROUTER_ERROR_INVALID_MEMBER = 0x28 # Member ID in list nonexistant | |
CIP_ROUTER_ERROR_MEMBER_NOT_SETTABLE = 0x29 # Cannot set value of member | |
CIP_ROUTER_ERROR_UNKNOWN_MODBUS_ERROR = 0x2B # Unhandled Modbus Error | |
CIP_ROUTER_ERROR_STILL_PROCESSING = 0xFF # Special marker to indicate we haven't finished processing the request yet | |
# Extended status in Forward open response | |
CIP_FWD_OPEN_EXTENDED_STATUS_INVALID_CONFIGURATION_SIZE = 0x126 | |
class EncapsulationPacket(dpkt.Packet): | |
# commands | |
ENCAP_CMD_NOP = 0x0000 | |
ENCAP_CMD_LISTSERVICES = 0x0004 | |
ENCAP_CMD_LISTIDENTITY = 0x0063 | |
ENCAP_CMD_LISTINTERFACES = 0x0064 | |
ENCAP_CMD_REGISTERSESSION = 0x0065 | |
ENCAP_CMD_UNREGISTERSESSION = 0x0066 | |
ENCAP_CMD_SENDRRDATA = 0x006F | |
ENCAP_CMD_SENDUNITDATA = 0x0070 | |
ENCAP_CMD_INDICATESTATUS = 0x0072 | |
ENCAP_CMD_CANCEL = 0x0073 | |
# status | |
ENCAP_STATUS_SUCCESS = 0x0000 | |
ENCAP_STATUS_INVALID_CMD = 0x0001 | |
ENCAP_STATUS_OUT_OF_MEMORY = 0x0002 | |
ENCAP_STATUS_INCORRECT_DATA = 0x0003 | |
ENCAP_STATUS_INVALID_LENGTH = 0x0065 | |
ENCAP_STATUS_UNSUPPORTED_VERSION = 0x0069 | |
__byte_order__ = '<' | |
__hdr__ = (('command', 'H', 0), | |
('length', 'H', 0), | |
('session', 'I', 0), | |
('status', 'I', 0), | |
('sender_context', '8s', bytes([0,0,0,0,0,0,0,0])), | |
('options', 'I', 0)) | |
class CommandSpecificData(dpkt.Packet): | |
# type ID | |
TYPE_ID_NULL = 0x0000 | |
TYPE_ID_LIST_IDENT_RESPONSE = 0x000C | |
TYPE_ID_CONNECTION_BASED = 0x00A1 | |
TYPE_ID_CONNECTED_TRANSPORT_PACKET = 0x00B1 | |
TYPE_ID_UNCONNECTED_MESSAGE = 0x00B2 | |
TYPE_ID_LISTSERVICES_RESPONSE = 0x0100 | |
TYPE_ID_SOCKADDR_INFO_ORIG_TARGET = 0x8000 | |
TYPE_ID_SOCKADDR_INFO_TARGET_ORIG = 0x8001 | |
TYPE_ID_SEQUENCED_ADDRESS = 0x8002 | |
__byte_order__ = '<' | |
__hdr__ = (('item_count', 'H', 0), | |
('type_id', 'H', 0), | |
('length', 'H', 0)) | |
class UnconnectedDataItem(dpkt.Packet): | |
UNCONN_DATA_ITEM_SERVICE_REQUEST = 0x00 | |
UNCONN_DATA_ITEM_SERVICE_RESPONSE = 0x80 | |
__byte_order__ = '<' | |
__hdr__ = (('type_id', 'H', 0), | |
('length', 'H', 0), | |
('service', 'B', 0)) | |
class UnconnectedDataItemHdr(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('type_id', 'H', 0), | |
('length', 'H', 0)) | |
class UnconnectedDataItemResp(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('type_id', 'H', 0), | |
('length', 'H', 0), | |
('service', 'B', 0), | |
('resv', 'B', 0), | |
('status', 'B', 0), | |
('additional_status_size', 'B', 0)) | |
class ForwardOpenReq(dpkt.Packet): | |
# network connection parameter bit offsets | |
FORWARD_OPEN_CONN_PARAM_BIT_CONN_SIZE = 0 | |
FORWARD_OPEN_CONN_PARAM_BIT_FIXED_VAR = 9 | |
FORWARD_OPEN_CONN_PARAM_BIT_PRIORITY = 10 | |
FORWARD_OPEN_CONN_PARAM_BIT_CONN_TYPE = 13 | |
FORWARD_OPEN_CONN_PARAM_BIT_REDAN_OWN = 15 | |
__byte_order__ = '<' | |
__hdr__ = (('mkpath', '5s', b"00000"), # len, 2 path | |
('prio_tick', 'B', 0x0A), # 4 Bit prio, 4 Bit tick time | |
('timeout_ticks', 'B', 0xF0), | |
('otconnid', 'I', 0xdeadbeaf), | |
('toconnid', 'I', 0xaffedead), | |
('conn_serial', 'H', 0x4949), | |
('vendor', 'H', 1), | |
('orig_serial', 'I', 0xbeeff00d), | |
('multiplier', 'B', 1), | |
('reserved', '3s', b"000"), | |
('otrpi', 'I', 0x186a0), # 100 ms | |
('otparams', 'H', 0x480c), | |
('torpi', 'I', 0x186A0), # 100 ms | |
('toparams', 'H', 0x2808), | |
('type_trigger', 'B', 0x01), | |
('plen', 'B', 9)) | |
class ForwardOpenResp(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('reserved', '3s', ''), | |
('otconnid', 'I', 0), | |
('toconnid', 'I', 0), | |
('conn_serial', 'H', 0), | |
('vendor', 'H', 0), | |
('orig_serial', 'I', 0), | |
('otapi', 'I', 0), | |
('toapi', 'I', 0), | |
('appl_reply_size', 'B', 0), | |
('reserved2', 'B', 0) | |
) | |
class ForwardCloseReq(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('mkpath', '5s', b"00000"), # len, 2 path | |
('prio_tick', 'B', 0x0A), # 4 Bit prio, 4 Bit tick time | |
('timeout_ticks', 'B', 0xF0), | |
('conn_serial', 'H', 0x4949), | |
('vendor', 'H', 1), | |
('orig_serial', 'I', 0xbeeff00d), | |
('plen', 'B', 9), | |
('reserved', 'B', 0) | |
) | |
class ForwardCloseResp(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('reserved', '3s', ''), | |
('conn_serial', 'H', 0), | |
('vendor', 'H', 0), | |
('orig_serial', 'I', 0), | |
('appl_reply_size', 'B', 0), | |
('reserved2', 'B', 0) | |
) | |
class RegisterSessionPacket(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('protocol_version', 'H', 1), | |
('option_flags', 'H', 0)) | |
class ListServicesReply(dpkt.Packet): | |
# capcability flags | |
CAP_CIP_ENCAP_VIA_TCP = 0x0020 | |
CAP_CIP_VIA_UDP = 0x0100 | |
__byte_order__ = '<' | |
__hdr__ = (('version', 'H', 0), | |
('capability_flags', 'H', 0), | |
('name_of_service', '16s', '')) | |
class ListIdentifyReply(dpkt.Packet): | |
# status is a bit encoded word | |
LIST_IDENT_STATUS_OWNED = 0x0001 | |
LIST_IDENT_STATUS_CONFIGURED = 0x0004 | |
LIST_IDENT_STATUS_EXTENDED_DEVICE_STATUS = 0x00F0 | |
LIST_IDENT_STATUS_MINOR_RECOVERABLE_FAULT = 0x0100 | |
LIST_IDENT_STATUS_MINOR_UNRECOVERABLE_FAULT = 0x0200 | |
LIST_IDENT_STATUS_MAJOR_RECOVERABLE_FAULT = 0x0400 | |
LIST_IDENT_STATUS_MAJOR_UNRECOVERABLE_FAULT = 0x0800 | |
LIST_IDENT_STATUS_EXTENDED_DEVICE_STATUS2 = 0xF000 | |
# states | |
LIST_IDENT_STATE_NONEXISTENT = 0x00 | |
LIST_IDENT_STATE_SELF_TESTING = 0x01 | |
LIST_IDENT_STATE_STANDBY = 0x02 | |
LIST_IDENT_STATE_OPERATIONAL = 0x03 | |
LIST_IDENT_STATE_RECOVERABLE_FAULT = 0x04 | |
LIST_IDENT_STATE_UNRECOVERABLE_FAULT = 0x05 | |
LIST_IDENT_STATE_DEFAULT = 0xFF | |
__byte_order__ = '<' | |
__hdr__ = (('version', 'H', 1), | |
('socket_addr', '16s', ''), | |
('vendor_id', 'H', 0), | |
('device_type', 'H', 0), | |
('product_code', 'H', 0), | |
('revision_major', 'B', 0), | |
('revision_minor', 'B', 0), | |
('status', 'H', 0), | |
('serial_no', 'I', 0), | |
('product_name_length', 'B', 0), | |
('product_name', '0s', ''), | |
('state', 'B', 0)) | |
def unpack(self, buf): | |
# product name can be a string upto 32 chars, but python does not | |
# support variable string length, so we have to write a little | |
# work-a-round and first unpack it check the length and calculate it | |
# again | |
tmp = dpkt.Packet.unpack(self, buf) | |
self.__hdr_fmt__ = "<H16sHHHBBHIB" + str(self.product_name_length) + "sB" | |
self.__hdr_len__ = struct.calcsize(self.__hdr_fmt__) | |
dpkt.Packet.unpack(self, buf) | |
class SendRRPacket(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('interface_handle', 'I', 0), | |
('timeout', 'H', 10)) | |
class SocketAddressInfo(dpkt.Packet): | |
__byte_order__ = '>' # big endian | |
__hdr__ = (('sin_family', 'H', 0), | |
('sin_port', 'H', 0), | |
('sin_addr', 'I', 0), | |
('sin_zero', '8s', '')) | |
class UdpSendDataPacket(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('count', 'H', 2), | |
('type_id_seq_addr', 'H', 0x8002), | |
('len_seq_addr', 'H', 8), | |
('conn_id', 'I', 0), | |
('seq_num', 'I', 0), | |
('type_id_conn_data', 'H', 0x00b1), | |
('len_conn_data', 'H', 12), | |
('seq_count', 'H', 0)) | |
class UdpRecvDataPacket(dpkt.Packet): | |
__byte_order__ = '<' | |
__hdr__ = (('count', 'H', 2), | |
('type_id_seq_addr', 'H', 0x8002), | |
('len_seq_addr', 'H', 8), | |
('conn_id', 'I', 0), | |
('seq_num', 'I', 0), | |
('type_id_conn_data', 'H', 0x00b1), | |
('length', 'H', 12), | |
('unknown', 'H', 0)) # TODO check for what the first two bytes of data are | |
class EthernetIOThread(threading.Thread): | |
def __init__(self, typ, enip=None, conn=None): | |
self.typ = typ | |
self.enip = enip | |
self.conn = conn | |
threading.Thread.__init__(self) | |
def run(self): | |
if self.typ == 1: | |
self.enip.listenUDP() | |
elif self.typ == 2: | |
self.conn.prodThread() | |
class EtherNetIP(object): | |
ENIP_IO_TYPE_INPUT = 0 | |
ENIP_IO_TYPE_OUTPUT = 1 | |
def __init__(self, ip="127.0.0.1"): | |
self.assembly = {} | |
self.explicit = [] | |
self.udpsock = None | |
self.udpthread = None | |
self.io_state = 0 | |
self.ip = ip | |
def registerAssembly(self, iotype, size, inst, conn): | |
if inst in self.assembly: | |
print("Reg assembly failed for iotype=", iotype) | |
return None | |
bits = [] | |
for i in range(size*8): | |
bits.append(0) | |
self.assembly[inst] = (conn, iotype, bits) | |
if conn != None: | |
if iotype == EtherNetIP.ENIP_IO_TYPE_INPUT: | |
conn.mapIn(bits) | |
elif iotype == EtherNetIP.ENIP_IO_TYPE_OUTPUT: | |
conn.mapOut(bits) | |
return bits | |
def startIO(self): | |
if self.io_state == 0: | |
self.udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
self.udpsock.bind(("0.0.0.0", ENIP_UDP_PORT)) | |
self.udpthread = EthernetIOThread(1,self) | |
self.io_state = 1 | |
self.udpthread.start() | |
def stopIO(self): | |
if self.io_state == 1: | |
self.io_state = 0 | |
self.udpsock.close() | |
def listenUDP(self): | |
while 1 == self.io_state: | |
inp, out, err = select.select([self.udpsock], [], [], 2) | |
if len(inp) != 0: | |
try: | |
buf, addr = self.udpsock.recvfrom(1024) | |
except OSError: | |
# If we close the socket asynchronously, the recv will | |
# fail | |
if self.io_state == 0: | |
return | |
raise | |
addr = addr[0] | |
pkt = UdpRecvDataPacket(buf) | |
for inst in self.assembly: | |
conn = self.assembly[inst][0] | |
iotype = self.assembly[inst][1] | |
bits = self.assembly[inst][2] | |
# update i/o | |
if conn.ipaddr == addr and iotype == EtherNetIP.ENIP_IO_TYPE_INPUT and pkt.conn_id == conn.toconnid: | |
i = 0 | |
for byte in pkt.data: | |
for s in range(8): | |
if byte & (1 << s): | |
bits[i] = True | |
else: | |
bits[i] = False | |
i += 1 | |
def explicit_conn(self, ipaddr=None): | |
if ipaddr is None: | |
ipaddr = self.ip | |
exp = EtherNetIPExpConnection(ipaddr) | |
self.explicit.append(exp) | |
return exp | |
def listIDUDP(self, ipaddr=None, timeout=5): | |
udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
udpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
context = random.randint(1, 4026531839) | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTIDENTITY, | |
sender_context=context.to_bytes(8, byteorder='big')) | |
if ipaddr is None: | |
ipaddr = self.ip | |
udpsock.sendto(pkt.pack(),(ipaddr, ENIP_TCP_PORT)) | |
inp, out, err = select.select([udpsock], [], [], timeout) | |
if len(inp) != 0: | |
data = udpsock.recv(1024) | |
pkt = EncapsulationPacket() | |
pkt.unpack(data) | |
if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTIDENTITY: | |
csd = CommandSpecificData(pkt.data) | |
if csd.type_id == CommandSpecificData.TYPE_ID_LIST_IDENT_RESPONSE: | |
lid = ListIdentifyReply(csd.data) | |
return lid | |
return None | |
class EtherNetIPSocket(object): | |
def __init__(self, ip): | |
self.ipaddr = ip | |
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
self.sock.connect((self.ipaddr, ENIP_TCP_PORT)) | |
self.conn_serial_num = 0 | |
def delete(self): | |
self.sock.close() | |
def mkReqPath(self, clas, inst, attr): | |
if clas > 255: | |
clas_data = struct.pack("BBH", 0x21, 0, 0x300) | |
else: | |
clas_data = struct.pack("BB", 0x20, clas) | |
inst_data = struct.pack("BB", 0x24, inst) | |
attr_data = b'' | |
if attr != None: | |
attr_data = struct.pack("BB", 0x30, attr) | |
data = bytes([(int((len(clas_data) + len(inst_data) + len(attr_data))/2))]) | |
data += clas_data | |
data += inst_data | |
data += attr_data | |
return data | |
def scanNetwork(self, broadcastAddress="255.255.255.0", timeout=10): | |
import time | |
listOfNodes = [] | |
udpsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
#udpsock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) | |
udpsock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) | |
context = random.randint(1, 4026531839) | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTIDENTITY, | |
sender_context=context.to_bytes(8, byteorder='big')) | |
udpsock.sendto(pkt.pack(),(broadcastAddress, ENIP_TCP_PORT)) | |
tStart = time.time() | |
while time.time() < (tStart+timeout): | |
timeout = tStart + timeout - time.time() | |
inp, out, err = select.select([udpsock], [], [], timeout) | |
if len(inp) != 0: | |
data = udpsock.recv(1024) | |
pkt = EncapsulationPacket() | |
pkt.unpack(data) | |
if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTIDENTITY: | |
csd = CommandSpecificData(pkt.data) | |
if csd.type_id == CommandSpecificData.TYPE_ID_LIST_IDENT_RESPONSE: | |
lid = ListIdentifyReply(csd.data) | |
listOfNodes.append(lid) | |
return listOfNodes | |
def listID(self): | |
context = random.randint(1, 4026531839) | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTIDENTITY, | |
sender_context=context.to_bytes(8, byteorder='big')) | |
self.sock.send(pkt.pack()) | |
inp, out, err = select.select([self.sock], [], [], 10) | |
if len(inp) != 0: | |
data = self.sock.recv(1024) | |
pkt = EncapsulationPacket() | |
pkt.unpack(data) | |
if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTIDENTITY: | |
csd = CommandSpecificData(pkt.data) | |
if csd.type_id == CommandSpecificData.TYPE_ID_LIST_IDENT_RESPONSE: | |
lid = ListIdentifyReply(csd.data) | |
return lid | |
return None | |
def listServices(self): | |
context = random.randint(1,4026531839) | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_LISTSERVICES, | |
sender_context=context.to_bytes(8, byteorder='big')) | |
self.sock.send(pkt.pack()) | |
inp, out, err = select.select([self.sock], [], [], 10) | |
if len(inp) != 0: | |
data = self.sock.recv(1024) | |
pkt = EncapsulationPacket() | |
pkt.unpack(data) | |
if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_LISTSERVICES: | |
csd = CommandSpecificData(pkt.data) | |
if csd.type_id == CommandSpecificData.TYPE_ID_LISTSERVICES_RESPONSE: | |
lsr = ListServicesReply(csd.data) | |
return lsr | |
return None | |
class EtherNetIPSession(EtherNetIPSocket): | |
def __init__(self, ipaddr): | |
EtherNetIPSocket.__init__(self,ipaddr) | |
self.session = 0 | |
def delete(self): | |
self.session = 0 | |
def registerSession(self): | |
context = random.randint(1,4026531839) | |
csd = RegisterSessionPacket(protocol_version=1, option_flag=0) | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_REGISTERSESSION,\ | |
length=len(csd), sender_context=context.to_bytes(8, byteorder='big'), data=csd) | |
self.sock.send(pkt.pack()) | |
inp, out, err = select.select([self.sock], [], [], 10) | |
if len(inp) != 0: | |
data = self.sock.recv(1024) | |
pkt = EncapsulationPacket() | |
pkt.unpack(data) | |
if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and pkt.command == EncapsulationPacket.ENCAP_CMD_REGISTERSESSION: | |
self.session = pkt.session | |
return 0 | |
return None | |
def unregisterSession(self): | |
context = random.randint(1,4026531839) | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_UNREGISTERSESSION,\ | |
length=0, session=self.session, sender_context=context.to_bytes(8, byteorder='big'), data=b'') | |
self.sock.send(pkt.pack()) | |
self.session=0 | |
def sendEncap(self, command, data): | |
context = random.randint(1,4026531839) | |
pkt = EncapsulationPacket(command=command, length=len(data), sender_context=context.to_bytes(8, byteorder='big'), data=data) | |
return self.sock.send(pkt.pack()) | |
def unconnSend(self, service, data, context=0, chk=0, chkdata="\x00"): | |
sz = len(data) + 17 | |
# add service field | |
dsz = len(data) + 1 | |
cpf2 = \ | |
UnconnectedDataItem(type_id=CommandSpecificData.TYPE_ID_UNCONNECTED_MESSAGE, \ | |
length=dsz, data=data, \ | |
service=(service|UnconnectedDataItem.UNCONN_DATA_ITEM_SERVICE_REQUEST)) | |
cpf = CommandSpecificData(type_id=CommandSpecificData.TYPE_ID_NULL, \ | |
item_count=2, length=0, data=cpf2) | |
srr = SendRRPacket(interface_handle=0, timeout=10, data=cpf); | |
pkt = EncapsulationPacket(command=EncapsulationPacket.ENCAP_CMD_SENDRRDATA, \ | |
length=len(srr),session=self.session, sender_context=context.to_bytes(8, byteorder='big'), data=srr) | |
self.sock.send(pkt.pack()) | |
inp, out, err = select.select([self.sock], [], [], 10) | |
if len(inp) != 0: | |
data = self.sock.recv(4096) | |
if len(data) > 0: | |
pkt = EncapsulationPacket() | |
pkt.unpack(data) | |
if pkt.status == EncapsulationPacket.ENCAP_STATUS_SUCCESS and \ | |
pkt.command == EncapsulationPacket.ENCAP_CMD_SENDRRDATA: | |
srr = SendRRPacket(pkt.data) | |
csd = CommandSpecificData(srr.data) | |
cpf = UnconnectedDataItemResp(csd.data) | |
ret = [cpf.status, cpf.data] | |
if chk != 0: | |
rsppkt = self.unconnSendValidRsp(service, chkdata, context) | |
if str(rsppkt) != str(pkt): | |
print("Packets differ") | |
assert(0) | |
return ret | |