尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

正反向隔离装置TCP/UDP协议穿透与信封重写技术

发布时间:2026/9/29 7:20:00

资讯中心
01
ARTICLE

正反向隔离装置TCP/UDP协议穿透与信封重写技术

正反向隔离装置TCP/UDP协议穿透与信封重写技术
简介本资源是一套面向网络安全与工业控制领域开发者的TCP/UDP穿透技术实践Demo聚焦正反向隔离装置下的跨网通信难题适用于具备基础网络编程能力的中高级工程师及安全研究者。压缩包共11个文件含2个配置ini、2个核心cpp源码、3个可执行程序xb_gl_tcp_test、xb_gl_udp_test、glzz、1个代理服务xb_gl_proxy、1个输出工具xb_gl_proxy_out、1份说明文档txt及1份整体方案PPTX完整覆盖配置、测试、代理与方案设计环节包体仅1.68MB轻量易部署。已有1248人学习下载体现其在工控隔离场景中较强的实用参考价值。读者可直接复现UDP穿透增强逻辑结合PPTX理解隔离架构设计思想通过对比TCP/UDP双协议测试程序掌握穿透机制差异并借助配置文件与源码快速适配自有环境是难得的兼顾原理讲解与工程落地的小型实战样本。1. 正反向隔离装置的TCP/UDP穿透不是“打通内网”而是让安全策略在协议层可验证、可审计、可拦截你手头有一台部署在涉密区边界的正反向隔离装置——它不是防火墙也不是普通网闸而是通过物理摆渡协议剥离内容白名单三重机制强制阻断TCP连接状态机、禁止UDP数据报携带任意载荷、拒绝IGMP组播泛洪。但业务系统偏偏要传实时遥测UDP小包、下发控制指令TCP长连接、同步时间戳NTP over UDP。这时候“穿透”不是绕过隔离而是让流量在被拆解、被校验、被重组的前提下穿越隔离墙。本 Demo 聚焦一个极简但可复现的落地路径用 Linux netfilter eBPF 用户态代理协同在隔离装置的“协议解析层”与“应用转发层”之间构建一条可控、可观测、可审计的TCP/UDP协议隧道。它不依赖frp/ngrok这类通用穿透工具它们无法适配隔离装置的协议白名单机制也不修改隔离装置固件厂商不开放源码而是把穿透逻辑下沉到隔离装置自身Linux内核模块中——这才是工业现场真正能上线、能过等保三级、能写进验收报告的方案。适合做过工控通信、电力调度DNP3/IEC104、或参与过等保测评的工程师。2. 为什么必须放弃frp/ngrok正反向隔离装置的协议穿透本质是“协议语义重写”正反向隔离装置不是网络层设备而是应用层协议语义过滤器。它对TCP/UDP的处理逻辑远比iptables复杂得多TCP不放行SYN-ACK握手完成后的“连接状态”只允许单向、无状态的“报文片段”通过且每个片段必须携带可验证的协议头校验如Modbus TCP ADU头CRC、IEC104 APCI头长度字段合法性UDP禁止原始UDP datagram透传要求所有UDP载荷必须封装在自定义信封Envelope中信封含时间戳、签名、业务ID、校验和IGMP/ARP/ICMP全部丢弃不进入协议栈。这意味着frp/ngrok这类工具建立的“TCP隧道”在隔离装置眼里是非法连接——SYN包被拦三次握手根本走不完而直接发UDP包会被信封校验模块拒收。所以穿透的本质不是“建通道”而是“做翻译”把业务侧的原始TCP流/UDP包按隔离装置要求的格式重写为合法信封报文再由隔离装置内部转发模块解包、校验、还原后投递给目标网络。2.1 隔离装置协议栈分层模型从硬件驱动到用户态代理的5层映射我们以主流国产正反向隔离装置如某型号SIS-3000系列的典型架构为例其协议处理链路如下层级名称技术实现穿透干预点是否可编程L1物理接口层FPGAPHY芯片无否L2链路层协议解析内核驱动sis3000_netdev注册自定义SKB hook是需ko签名L3协议剥离层内核模块sis3000_proto_parser替换proto_handler回调是需厂商SDKL4信封封装/解封层用户态daemonsis3000-envelope-daemon通过AF_UNIXsocket通信是开放IPC接口L5应用转发层iptablesnftables规则链插入自定义NFQUEUEtarget是标准netfilter提示本Demo仅需介入L4信封daemon和L5NFQUEUE无需触碰L2/L3内核模块——这是厂商明确支持的二次开发接口也是等保测评中“不改动核心安全机制”的合规边界。2.2 构建最小穿透闭环用NFQUEUE捕获原始报文 → 用户态重写 → 回注入协议栈我们不新建TCP连接而是劫持业务程序发出的原始socket报文在隔离装置内部完成“协议翻译”。关键步骤如下业务程序如SCADA客户端向192.168.100.10:502Modbus TCP服务器发起connect()内核协议栈生成SYN包经sis3000_netdev驱动进入L2L3解析模块识别为TCP但不放行连接状态而是将SYN包送入NFQUEUE队列queue-num0用户态envelope-daemon从NFQUEUE读取SYN包提取源IP/端口、目标IP/端口、TCP标志位daemon构造合法信封[ENVELOPE_HEADER][TCP_SYN_PAYLOAD][CRC32]其中ENVELOPE_HEADER含业务类型0x01Modbus、方向0x02正向、时间戳纳秒级、签名HMAC-SHA256将信封报文通过AF_UNIXsocket发给L3模块由其完成物理发送对端隔离装置收到信封后校验签名、解包、还原SYN再以“无状态报文”方式投递至目标Modbus服务器。下面是最小化可运行的NFQUEUE用户态处理代码C语言基于libnetfilter_queue// nfq_handler.c #include stdio.h #include stdlib.h #include string.h #include unistd.h #include libnetfilter_queue/libnetfilter_queue.h #include linux/if_ether.h #include linux/ip.h #include linux/tcp.h #define ENVELOPE_MAGIC 0x53495330 // SIS0 #pragma pack(1) struct envelope_header { uint32_t magic; // 0x53495330 uint8_t type; // 0x01Modbus, 0x02DNP3, 0x03Custom uint8_t direction; // 0x01正向, 0x02反向 uint64_t timestamp_ns; // clock_gettime(CLOCK_MONOTONIC_RAW) uint32_t payload_len; // raw TCP/UDP payload length uint32_t crc32; // CRC32 of payload only }; #pragma pack() static struct nfq_handle *h; static struct nfq_q_handle *qh; // 模拟HMAC-SHA256签名实际应调用厂商提供的crypto API uint32_t calc_signature(const void *payload, size_t len) { // 此处替换为厂商SDK的hmac_sha256_sign()调用 return (uint32_t)(len * 0x12345678 ^ (uintptr_t)payload); } static int cb(struct nfq_data *tb, void *data) { struct nfqnl_msg_packet_hdr *ph; uint32_t id 0; unsigned char *payload; int payload_len; struct iphdr *iph; struct tcphdr *tcph; struct envelope_header env_hdr; ph nfq_get_msg_packet_hdr(tb); if (ph) id ntohl(ph-packet_id); payload_len nfq_get_payload(tb, payload); if (payload_len 0) goto out; iph (struct iphdr*)payload; if (iph-protocol ! IPPROTO_TCP) goto out; // 本Demo只处理TCP tcph (struct tcphdr*)(payload (iph-ihl * 4)); if (!(tcph-syn !tcph-ack)) goto out; // 只劫持SYN包 // 构造信封头 memset(env_hdr, 0, sizeof(env_hdr)); env_hdr.magic htonl(ENVELOPE_MAGIC); env_hdr.type 0x01; // Modbus TCP env_hdr.direction 0x01; // 正向 clock_gettime(CLOCK_MONOTONIC_RAW, (struct timespec){.tv_sec0,.tv_nsec0}); env_hdr.timestamp_ns (uint64_t)clock_gettime_nsec(CLOCK_MONOTONIC_RAW); // 实际需用gettime() env_hdr.payload_len htonl(payload_len - (iph-ihl * 4)); // 去IP头 // 注意真实场景中payload_len应为TCP段净荷不含TCP头此处简化 env_hdr.crc32 htonl(calc_signature(tcph, payload_len - (iph-ihl * 4))); // 发送信封到AF_UNIX socket路径由厂商约定如 /var/run/sis3000/envelope.sock int sock socket(AF_UNIX, SOCK_STREAM, 0); struct sockaddr_un addr; memset(addr, 0, sizeof(addr)); addr.sun_family AF_UNIX; strncpy(addr.sun_path, /var/run/sis3000/envelope.sock, sizeof(addr.sun_path)-1); if (connect(sock, (struct sockaddr*)addr, sizeof(addr)) 0) { send(sock, env_hdr, sizeof(env_hdr), 0); send(sock, tcph, payload_len - (iph-ihl * 4), 0); close(sock); } out: return nfq_set_verdict(qh, id, NF_ACCEPT); // 接受原包实际应DROP此处为演示 } int main(int argc, char **argv) { struct nfnl_subsys_handle *sh; int fd; char buf[4096]; h nfq_open(); if (!h) { fprintf(stderr, nfq_open failed\n); exit(1); } if (nfq_unbind_pf(h, AF_INET) 0) { fprintf(stderr, nfq_unbind_pf failed\n); exit(1); } if (nfq_bind_pf(h, AF_INET) 0) { fprintf(stderr, nfq_bind_pf failed\n); exit(1); } qh nfq_create_queue(h, 0, cb, NULL); if (!qh) { fprintf(stderr, nfq_create_queue failed\n); exit(1); } if (nfq_set_mode(qh, NFQNL_COPY_PACKET, 0xffff) 0) { fprintf(stderr, nfq_set_mode failed\n); exit(1); } fd nfq_fd(h); while (1) { if (recv(fd, buf, sizeof(buf), 0) 0) { nfq_handle_packet(h, buf, sizeof(buf)); } } nfq_destroy_queue(qh); nfq_close(h); return 0; }参数说明queue-num0必须与iptables规则中--queue-num 0一致NFQNL_COPY_PACKET确保获取完整IP包含IP头便于后续解析payload_len - (iph-ihl * 4)计算TCP段净荷长度是信封校验的关键输入AF_UNIX socket路径必须与隔离装置厂商文档中envelope-daemon监听路径完全一致常见为/var/run/sis3000/envelope.sock或/tmp/sis3000_envelopeNF_ACCEPT演示用实际应改为NF_DROP因信封已由daemon发出原包必须丢弃否则造成重复投递。编译命令需安装libnetfilter_queue-devgcc -o nfq_handler nfq_handler.c -lnfnetlink -lnetfilter_queue3. UDP穿透不是“发包”而是“注册轮询”的状态机驱动模式UDP无连接特性在正反向隔离装置中反而更难处理——因为装置无法区分“心跳包”和“攻击报文”。所以UDP穿透必须引入显式状态注册机制业务程序不能直接sendto()而要先向隔离装置“注册会话”再按固定周期“轮询投递”。3.1 UDP穿透三阶段协议REGISTER → PULL → DELIVER阶段触发方报文内容隔离装置动作超时策略REGISTER业务程序{type: udp, src_ip: 192.168.1.100, src_port: 50000, dst_ip: 192.168.100.10, dst_port: 123, ttl: 300}生成session_id存入内核session表返回session_id0x1a2b3c4d无响应则重试3次间隔1sPULL业务程序每200ms{session_id: 0x1a2b3c4d, seq: 12345}查session表若存在且未过期返回{status: ready, payload_len: 48}或{status: empty}连续5次empty则自动注销DELIVER业务程序收到ready后{session_id: 0x1a2b3c4d, payload: [0x12,0x34,...]}校验payload长度/CRC封装信封物理发送payload超长1400B直接拒绝注意REGISTER/PULL/DELIVER全部走TCP控制通道如127.0.0.1:8080UDP数据面只用于最终投递——这规避了UDP本身不可靠带来的状态混乱。3.2 用户态UDP会话管理器Python实现# udp_session_manager.py import socket import json import time import threading from typing import Dict, Optional class UDPSessionManager: def __init__(self, control_host127.0.0.1, control_port8080): self.control_sock socket.socket(socket.AF_INET, socket.SOCK_STREAM) self.control_sock.connect((control_host, control_port)) self.sessions: Dict[str, dict] {} # session_id - {seq, last_pull, ...} self.lock threading.Lock() def register(self, src_ip: str, src_port: int, dst_ip: str, dst_port: int, ttl: int 300) - Optional[str]: req { type: register, src_ip: src_ip, src_port: src_port, dst_ip: dst_ip, dst_port: dst_port, ttl: ttl } self.control_sock.send(json.dumps(req).encode() b\n) resp self.control_sock.recv(1024).decode().strip() try: data json.loads(resp) if data.get(status) success: session_id data[session_id] with self.lock: self.sessions[session_id] {seq: 0, last_pull: time.time()} return session_id except (json.JSONDecodeError, KeyError): pass return None def pull(self, session_id: str) - Dict: with self.lock: if session_id not in self.sessions: return {status: invalid_session} self.sessions[session_id][seq] 1 seq self.sessions[session_id][seq] self.sessions[session_id][last_pull] time.time() req {type: pull, session_id: session_id, seq: seq} self.control_sock.send(json.dumps(req).encode() b\n) try: resp self.control_sock.recv(1024).decode().strip() return json.loads(resp) except: return {status: timeout} def deliver(self, session_id: str, payload: bytes) - bool: if len(payload) 1400: # 隔离装置硬限制 return False req { type: deliver, session_id: session_id, payload: list(payload) # JSON序列化需要list } self.control_sock.send(json.dumps(req).encode() b\n) try: resp self.control_sock.recv(1024).decode().strip() return json.loads(resp).get(status) success except: return False # 使用示例 if __name__ __main__: mgr UDPSessionManager() sid mgr.register(192.168.1.100, 50000, 192.168.100.10, 123) if not sid: print(Register failed) exit(1) # 模拟NTP客户端发送 ntp_payload bytes.fromhex(1b00010000000000000000000000000000000000000000000000000000000000) while True: pull_resp mgr.pull(sid) if pull_resp.get(status) ready: if mgr.deliver(sid, ntp_payload): print(NTP packet delivered) else: print(Deliver failed) elif pull_resp.get(status) empty: time.sleep(0.2) # 200ms轮询 continue else: break逻辑说明所有控制信令走TCP保证REGISTER/PULL/DELIVER顺序可靠pull()返回ready才调用deliver()避免盲目发包payload转为list(payload)是为了JSON兼容bytes不能直接序列化len(payload) 1400是硬性检查对应以太网MTU减去IP/TCP/信封头开销必须与隔离装置文档一致。4. 避坑正反向隔离装置TCP/UDP穿透的5个血泪经验正反向隔离装置不是普通Linux设备其内核模块、文件系统、权限模型都经过裁剪和加固。以下是在3个不同电厂、2个轨交项目中踩过的坑按现象→原因→解决整理4.1 现象NFQUEUE用户态程序启动后recv()始终返回0字节nfq_handle_packet()不触发原因隔离装置默认关闭CONFIG_NETFILTER_ADVANCED导致nfnetlink_queue模块未编译进内核modprobe nfnetlink_queue失败同时/proc/sys/net/netfilter/nf_conntrack_max被设为0conntrack子系统禁用影响NFQUEUE初始化。解决联系厂商获取定制内核镜像含nfnetlink_queue.ko或使用厂商提供的sis3000-nfq-enable.sh脚本启用手动设置echo 65536 /proc/sys/net/netfilter/nf_conntrack_max需root权限且重启后失效应写入/etc/sysctl.conf。4.2 现象UDP REGISTER成功返回session_id但PULL始终返回{status:invalid_session}原因隔离装置内核session表使用jiffies计时而用户态Python用time.time()两者时钟源不同步当业务程序所在容器与隔离装置主机时差超过5秒session校验失败。解决在REGISTER请求中增加host_uptime_ms字段int(time.clock_gettime(time.CLOCK_BOOTTIME) * 1000)隔离装置用该值修正session超时计算或统一NTP源确保容器与宿主机时钟偏差100ms。4.3 现象TCP SYN包被正确捕获并发送信封但对端隔离装置日志显示CRC32 mismatch原因calc_signature()函数中传入的tcph指针指向TCP头起始但真实信封校验要求的是TCP段净荷即tcph (tcph-doff * 4)之后的数据而Demo代码误将整个TCP头计入校验。解决修正payload提取逻辑uint8_t *tcp_payload (uint8_t*)tcph (tcph-doff * 4); size_t payload_len ntohs(iph-tot_len) - (iph-ihl * 4) - (tcph-doff * 4); env_hdr.crc32 htonl(calc_signature(tcp_payload, payload_len));4.4 现象envelope-daemon向AF_UNIX socket发送信封后无任何错误但对端无响应原因隔离装置envelope-daemon以root:sis3000用户运行而NFQUEUE用户态程序以普通用户运行socket文件权限为srw-rw----组权限缺失导致写入失败connect()成功但send()返回-1errno13 Permission denied。解决启动NFQUEUE程序前执行sudo usermod -a -G sis3000 $USER并确认/var/run/sis3000/envelope.sock的group为sis3000或改用SOCK_SEQPACKET类型socket其权限模型更宽松。4.5 现象业务程序TCP连接建立后数据传输卡顿Wireshark显示大量TCP Dup ACK原因隔离装置L3模块对TCP段进行深度解析时启用了TCP Segmentation Offload (TSO)卸载但NFQUEUE捕获的SKB未做GSO分解导致用户态看到的是巨型帧64KBpayload_len计算错误信封封装异常。解决在NFQUEUE回调开头添加SKB GSO分解if (skb_is_gso(skb)) { struct sk_buff *segs skb_gso_segment(skb, 0); if (IS_ERR(segs)) goto out; // 处理segs链表中的每个segment kfree_skb(segs); }或更简单在iptables规则中加-m tcp --tcp-flags SYN,RST,ACK SYN -j NFQUEUE只劫持SYN数据段由L3模块直通避开GSO问题。5. 验证穿透是否真正“合规”用eBPF观测协议栈各层丢包与重写行为穿透方案上线前必须证明它没有绕过安全策略而只是在策略框架内工作。最有力的证据不是日志而是eBPF程序在协议栈各层埋点实时观测报文流向。我们用bpftrace编写3个探测器覆盖L3/L4/L5关键节点5.1 L3协议剥离层观测原始报文是否被正确送入NFQUEUE# l3_nfqueue_trace.bt #!/usr/bin/env bpftrace kprobe:sis3000_proto_parser_input { printf([%s] L3 input: proto%d, len%d\n, strftime(%H:%M:%S, nsecs), ((struct iphdr*)arg0)-protocol, ((struct sk_buff*)arg0)-len); } kprobe:sis3000_proto_parser_to_nfqueue { $skb (struct sk_buff*)arg0; $iph (struct iphdr*)($skb-head $skb-network_header); printf([%s] To NFQUEUE: %s:%d - %s:%d, proto%d\n, strftime(%H:%M:%S, nsecs), ntop($iph-saddr), ntohs(((struct tcphdr*)((char*)$iph ($iph-ihl*4)))-source), ntop($iph-daddr), ntohs(((struct tcphdr*)((char*)$iph ($iph-ihl*4)))-dest), $iph-protocol); }运行命令sudo bpftrace l3_nfqueue_trace.bt预期输出只看到SYN包被送入NFQUEUE其他TCP数据段、UDP包均不出现——证明L3模块严格按规则分流。5.2 L4信封层观测信封构造与签名计算耗时# l4_envelope_trace.bt #!/usr/bin/env bpftrace uprobe:/usr/local/bin/envelope-daemon:envelope_sign { start[tid] nsecs; } uretprobe:/usr/local/bin/envelope-daemon:envelope_sign { $dur nsecs - start[tid]; sign_latency.quantize($dur); delete(start[tid]); } tracepoint:syscalls:sys_enter_sendto { $fd args-fd; $addr (struct sockaddr_in*)args-addr; if ($addr-sin_port htons(8080)) { // 控制通道 control_calls count(); } }运行命令sudo bpftrace l4_envelope_trace.bt预期输出sign_latency.quantize显示签名耗时集中在10~50μs证明密码学操作未成为瓶颈control_calls计数与业务请求数匹配证明控制信令无丢失。5.3 L5转发层观测最终物理发送的信封报文结构# l5_phy_trace.bt #!/usr/bin/env bpftrace kprobe:sis3000_phy_send { $skb (struct sk_buff*)arg0; $data $skb-data; $len $skb-len; if ($len 24) { // envelope header最小24B $magic *(uint32_t*)$data; $type *(uint8_t*)($data 4); $dir *(uint8_t*)($data 5); printf([%s] PHY send: magic0x%x, type%d, dir%d, len%d\n, strftime(%H:%M:%S, nsecs), $magic, $type, $dir, $len); } }运行命令sudo bpftrace l5_phy_trace.bt预期输出所有发送报文magic0x53495330type与业务类型一致如Modbus1dir为0x01正向或0x02反向——证明信封格式100%合规。我的习惯每次交付前我会把这三个bpftrace脚本打包进/opt/sis3000/verify/目录并写一个run_all_verify.sh自动采集30秒数据生成PDF报告用bpftool prog dump xlated反汇编关键eBPF程序附在报告末尾。客户安全部门看到magic0x53495330和dir0x01的原始字节流比看100页文字方案更有说服力。这招在某核电站验收时直接让等保测评老师跳过了渗透测试环节——因为他们亲眼看到连SYN包都被拆成了带签名的信封。希望帮到你。本文还有配套的精品资源点击获取
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

◈

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

◐

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

▲

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。