是否可通过软件模拟NIC网卡?Python+Scapy实现ARP响应后ARP表未更新
用户提问
介绍
我正尝试通过Python软件模拟NIC(网卡),计划通过响应ARP和ICMP报文实现该功能,请问该方案是否可行?
运行环境
我使用的是VMware中桥接网卡模式的Kali 2021.3系统,Python版本为3.9.7,Scapy版本为2.4.5。
eth0接口配置如下:
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500 inet 192.168.176.139 netmask 255.255.255.0 broadcast 192.168.176.255 inet6 fe80::20c:29ff:fee3:84e prefixlen 64 scopeid 0x20<link> ether 00:0c:29:e3:08:4e txqueuelen 1000 (Ethernet) RX packets 14614 bytes 19043293 (18.1 MiB) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 2504 bytes 259386 (253.3 KiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
已尝试操作
首先我尝试编写代码简单响应ARP请求,代码如下:
from scapy.all import * # Your network broadcast address broadcastNet = "192.168.176.255" macDict = { "192.168.176.182" : "60:01:94:98:97:c6", "192.168.176.183" : "68:c6:3a:a7:d3:40"} # Use MAC address of this machine as source. If not eth0, change this: myMAC = get_if_hwaddr('eth0') def handle_arp(packet): who_has = 1 is_at = 2 if packet[ARP].op == who_has: print(packet.summary()) if packet.pdst in macDict: print("Sending ARP response for " + packet.pdst) reply = ARP(op=is_at, hwsrc=macDict[packet.pdst], psrc=packet.pdst, hwdst="ff:ff:ff:ff:ff:ff", pdst=broadcastNet) go = Ether(dst="ff:ff:ff:ff:ff:ff", src=myMAC) / reply print(go.summary()) sendp(go) return def handle_icmp(packet): print(packet) return # Sniff for packets. sniff(filter="arp",prn=handle_arp)
我执行ping -c 1 192.168.176.183命令触发ARP请求,通过Wireshark抓包确认ARP应答已成功发送。
但查看本地ARP表时,对应IP的条目显示为incomplete,ARP表内容如下:
arp -a ? (192.168.176.183) at <incomplete> on eth0 ? (192.168.176.2) at 00:50:56:e6:c2:47 [ether] on eth0 ? (192.168.176.254) at 00:50:56:ea:77:59 [ether] on eth0
随后我修改脚本中的macDict配置,将虚拟IP对应的MAC地址改为eth0自身的MAC地址,修改后配置如下:
macDict = { "192.168.176.182" : "00:0c:29:e3:08:4e", "192.168.176.183" : "00:0c:29:e3:08:4e"}
但修改后ARP表仍未更新。
最后我尝试创建虚拟链路接口,执行的命令和接口输出如下:
sudo ip link add link eth0 address 56:8A:C0:DD:EE:FA eth0.1 type dummy sudo ifconfig eth0.1 up sudo ifconfig eth0.1 eth0.1: flags=195<UP,BROADCAST,RUNNING,NOARP> mtu 1500 inet6 fe80::548a:c0ff:fedd:eefa prefixlen 64 scopeid 0x20<link> ether 56:8a:c0:dd:ee:fa txqueuelen 1000 (Ethernet) RX packets 0 bytes 0 (0.0 B) RX errors 0 dropped 0 overruns 0 frame 0 TX packets 6 bytes 420 (420.0 B) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
该方案最终也未生效。
回答
该方案完全可行,问题是ARP报文构造不规范、内核参数拦截两个原因导致的,修复方法如下:
根因说明
- ARP响应构造错误:你构造的ARP应答为广播报文,按照ARP协议规范,ARP请求的响应必须单播回请求发起方,广播形式的ARP应答会被操作系统判定为无效报文,直接忽略,不会更新ARP表。
- 内核过滤拦截:Linux内核默认开启反向路径过滤(rp_filter),如果收到的ARP应答对应的IP不属于本机任何已配置的接口网段,内核会直接丢弃该应答,不会写入ARP表。
修复后的代码
from scapy.all import * macDict = { "192.168.176.182": "00:0c:29:e3:08:4e", "192.168.176.183": "00:0c:29:e3:08:4e" } my_mac = get_if_hwaddr('eth0') def handle_arp(packet): ARP_REQ = 1 ARP_RESP = 2 if packet[ARP].op == ARP_REQ: target_ip = packet[ARP].pdst if target_ip in macDict: # 构造单播ARP响应返回请求方 arp_resp = ARP( op=ARP_RESP, hwsrc=macDict[target_ip], psrc=target_ip, hwdst=packet[ARP].hwsrc, pdst=packet[ARP].psrc ) ether = Ether(dst=packet[Ether].src, src=my_mac) / arp_resp sendp(ether, iface='eth0', verbose=0) def handle_icmp(packet): if ICMP in packet and packet[ICMP].type == 8: # 匹配ICMP echo请求 target_ip = packet[IP].dst if target_ip in macDict: # 构造ICMP echo响应 icmp_resp = IP( src=target_ip, dst=packet[IP].src ) / ICMP( type=0, id=packet[ICMP].id, seq=packet[ICMP].seq ) / packet[Raw].load send(icmp_resp, iface='eth0', verbose=0) # 同时监听ARP和ICMP报文 sniff(filter="arp or icmp", prn=lambda p: handle_arp(p) if ARP in p else handle_icmp(p), iface='eth0')
前置配置
执行脚本前先调整内核参数,关闭拦截规则:
sudo sysctl -w net.ipv4.conf.all.rp_filter=0 sudo sysctl -w net.ipv4.conf.eth0.rp_filter=0 sudo sysctl -w net.ipv4.conf.all.arp_accept=1
配置完成后运行脚本,再ping对应虚拟IP即可看到ARP表正常更新,ICMP也能正常得到响应。
内容的提问来源于stack exchange,提问作者itasahobby
相关产品推荐
相关产品推荐

