1. Ethernet/IP的整体框架
Ethernet/IP 协议作为 CIP 的一种实现方式,其核心在于将高层的 CIP 数据封装到以太网的标准帧中进行传输。这种设计允许工业设备充分利用现有成熟的以太网基础设施来完成复杂的数据交互任务1。
2. 以太网帧中的CIP数据封装
在一个典型的 Ethernet/IP 报文中,最外层遵循 IEEE 802.3 定义的传统以太网 II 帧格式。这包含了目的 MAC 地址、源 MAC 地址以及 EtherType 字段等内容。对于大多数 Ethernet/IP 应用来说,EtherType 被设置为 0x0800
表明后续负载是一个 IPv4 数据包;然而,在某些情况下也可能直接指定特殊的值如 0x88A4
来标识这是未经 IP 层处理的纯 CIP 流量4。
-
当前者情况发生时(即存在 IP 头部),紧接着的是标准 TCP 或 UDP 协议头部之后才是真正的应用层有效载荷区域——这里便是我们的主角:CIP Message Router 请求或者响应消息所在位置。
-
如果后者被选用了,则意味着整个事务完全绕过了更高层次上的寻址机制而直奔主题去了!
3. 具体字段解析
(a)UDP无连接显式消息(CoE)
Field Name | Length(Bytes) | Description |
---|---|---|
Command Specific Data | Variable | Contains additional parameters depending on command type |
Session Handle | 4 | Identifies the session between client/server |
Status | 4 | Indicates success/failure of operation |
Sender Context | 8 | Optional field used by sender |
Interface Handshake Protocol Version | 2 | Specifies version number |
Option Flags | 2 | Reserved |
Length | 2 | Total length of encapsulated data |
Encapsulation Commands | 2 | Defines what action should be taken |
Data | Variable | Actual payload containing either request or reply |
以上表格描述了一个典型UDP CoE PDU的基本组成部分及其含义解释
(b)TCP有连接隐式I/O流(IoC)
相比之下,TCP IoC 更加关注周期性的实时性要求较高的IO信号交换过程,因此它的PDU相对简单得多:
+-------------------+
| Common Packet Format|
+-------------------+
| Service Code |
+-------------------+
| Request Path Size |
+-------------------+
| Request Path |
+-------------------+
| Additional Data |
+-------------------+
此处,"Common Packet Format"再次扮演起承上启下的角色,它由固定长度的部分构成,用于指示接下来的服务类型编码等等信息
import socket
from struct import pack, unpack
def create_ethernet_ip_request(command_code, session_handle, status, context_bytes=b'\x00'*8, interface_version=1, option_flags=0):
"""
构造一个基本的Ethernet/IP请求头
参数:
command_code (int): 封装命令代码.
session_handle (int): 会话句柄数值.
status (int): 状态字节通常设为零表示正常操作.
context_bytes (bytes): 可选上下文字节数组,默认填充八个零字节.
interface_version (int): 接口握手协议版本号,默认值为1.
option_flags (int): 额外标志位,默认清零.
返回:
bytes: 组合后的完整请求二进制字符串.
"""
# 打包各个字段成连续字节序列
packed_data = b''.join([
pack('!H', command_code), # ! 表示网络序(Half-word unsigned short)
pack('!L', session_handle),
pack('!L', status),
context_bytes,
pack('!H', interface_version),
pack('!H', option_flags)
])
return packed_data
if __name__ == "__main__":
req_header = create_ethernet_ip_request(0x6F, 123456789, 0)
print(req_header.hex())
上述Python脚本演示了如何手动创建并发送一条简单的Ethernet/IP指令的方法之一2