$ ip link show 1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 3: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 link/none
defparse_ip_header(packet): # IPv4 头:版本/头长/服务/总长/标识/标志/TTL/协议/校验/源IP/目的IP iflen(packet) < 20: returnNone version_ihl = packet[0] protocol = packet[9] src_ip = ".".join(str(b) for b in packet[12:16]) dst_ip = ".".join(str(b) for b in packet[16:20]) return protocol, src_ip, dst_ip
if __name__ == "__main__": tun = create_tun("tun1") # 需要 root 权限 # ip addr add 10.9.0.1/24 dev tun1 && ip link set tun1 up print("Listening on tun1...") whileTrue: packet = tun.read(4096) result = parse_ip_header(packet) if result: proto, src, dst = result proto_name = {1: "ICMP", 6: "TCP", 17: "UDP"}.get(proto, str(proto)) print(f"{src} -> {dst} [{proto_name}] {len(packet)} bytes")
运行后,执行 ping 10.9.0.2,可以看到 ICMP
包被程序捕获并打印出来。
踩坑记录
在搭一个简单的点对点隧道测试时,遇到过一个很迷惑的问题:两端 TUN
程序都在运行,ping 却不通,但 tcpdump 在 TUN
接口上能看到包。
排查很久才发现:写入 TUN 接口的 IP 包,源 MAC 地址没有对应的 ARP
条目,内核在回包时不知道往哪里发。因为 TUN 是三层设备,不处理
ARP,而我没有在路由表里加正确的静态路由。
解决方案:在两端分别加上对方的路由条目:
1 2 3 4 5
# 节点 A(10.8.0.1)到节点 B(10.8.0.2) ip route add 10.8.0.2/32 dev tun0
# 节点 B(10.8.0.2)到节点 A(10.8.0.1) ip route add 10.8.0.1/32 dev tun0
如果用 TAP 则不需要,因为 TAP 是二层设备,会处理 ARP 广播,自动学习
MAC 地址。这是 TUN 和 TAP 最实际的区别之一。