You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

是否可通过软件模拟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报文构造不规范、内核参数拦截两个原因导致的,修复方法如下:

根因说明

  1. ARP响应构造错误:你构造的ARP应答为广播报文,按照ARP协议规范,ARP请求的响应必须单播回请求发起方,广播形式的ARP应答会被操作系统判定为无效报文,直接忽略,不会更新ARP表。
  2. 内核过滤拦截: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.27 22:45:05