iptables实现的IPMAC绑定对ARP报文无效,以下使用arptables规则控制ARP报文的IPMAC绑定。在主机192.168.1.127上配置以下规则:
# arptables -A INPUT -s 192.168.1.201 --source-mac ! 00:0C:29:38:40:6B -j DROP
#
# arptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
-j DROP -i any -o any -s 192.168.1.201 ! --src-mac 00:0c:29:38:40:6b , pcnt=4 -- bcnt=184
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
在主机192.168.1.201(MAC:00:0c:29:38:40:19,与以上配置的source-mac不相同)上使用arping测试到以上主机1.127的连通性,发送了4个报文,没有收到回应。在主机1.127上查看arptables计数,显示丢弃了4个报文,两者相吻合。
# arping 192.168.1.127
ARPING 192.168.1.127 from 192.168.1.201 eth0
^CSent 4 probes (4 broadcast(s))
Received 0 response(s)
将主机1.201的MAC地址修改为:00:0c:29:38:40:6b,其到主机1.127的arp可通。arptables显示没有匹配DROP规则的计数。
# arptables -L -v
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
-j DROP -i any -o any -s 192.168.1.201 ! --src-mac 00:0c:29:38:40:6b , pcnt=0 -- bcnt=0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
另外,关键字–source-mac和–destination-mac还可以指定内置的字符串(Unicast/Multicast/Broadcast),如下拒绝主机1.201的ARP广播流量。
# arptables -A INPUT -s 192.168.1.201 --destination-mac Broadcast -j DROP
#
# arptables -L -v --line-number
Chain INPUT (policy ACCEPT 0 packets, 0 bytes)
1 -j DROP -i any -o any -s 192.168.1.201 --dst-mac ff:ff:ff:ff:ff:ff , pcnt=0 -- bcnt=0
Chain OUTPUT (policy ACCEPT 0 packets, 0 bytes)
arp报文匹配
如下arp规则处理函数arpt_do_table,其中由arp_packet_match执行报文合规则的匹配,最后一个参数为配置的规则信息。
unsigned int arpt_do_table(struct sk_buff *skb,
const struct nf_hook_state *state, struct xt_table *table)
{
const struct arphdr *arp;
struct arpt_entry *e, **jumpstack;
e = get_entry(table_base, private->hook_entry[hook]);
acpar.state = state;
acpar.hotdrop = false;
arp = arp_hdr(skb);
do {
const struct xt_entry_target *t;
struct xt_counters *counter;
if (!arp_packet_match(arp, skb->dev, indev, outdev, &e->arp)) {
e = arpt_next_entry(e);
continue;
}
首先,由ARP报文中获取发送端的硬件地址(src_devaddr),和IP地址(src_ipaddr);以及target目的端的硬件地址(tgt_devaddr)和IP地址(tgt_ipaddr),接下来,比较报文中获取的硬件地址和arptables规则(arpinfo)中配置的硬件地址是否匹配,不匹配返回0。
static inline int arp_packet_match(const struct arphdr *arphdr,
struct net_device *dev,
const char *indev, const char *outdev,
const struct arpt_arp *arpinfo)
{
const char *arpptr = (char *)(arphdr + 1);
const char *src_devaddr, *tgt_devaddr;
...
src_devaddr = arpptr;
arpptr += dev->addr_len;
memcpy(&src_ipaddr, arpptr, sizeof(u32));
arpptr += sizeof(u32);
tgt_devaddr = arpptr;
arpptr += dev->addr_len;
memcpy(&tgt_ipaddr, arpptr, sizeof(u32));
if (NF_INVF(arpinfo, ARPT_INV_SRCDEVADDR,
arp_devaddr_compare(&arpinfo->src_devaddr, src_devaddr,
dev->addr_len)) ||
NF_INVF(arpinfo, ARPT_INV_TGTDEVADDR,
arp_devaddr_compare(&arpinfo->tgt_devaddr, tgt_devaddr,
dev->addr_len)))
return 0;
其次,比较报文中的IP地址与规则配置的地址是否匹配,不匹配返回0。完全匹配的情况返回1,执行规则中指定的动作,以上为DROP,丢弃报文。
if (NF_INVF(arpinfo, ARPT_INV_SRCIP,
(src_ipaddr & arpinfo->smsk.s_addr) != arpinfo->src.s_addr) ||
NF_INVF(arpinfo, ARPT_INV_TGTIP,
(tgt_ipaddr & arpinfo->tmsk.s_addr) != arpinfo->tgt.s_addr))
return 0;
/* Look for ifname matches. */
ret = ifname_compare(indev, arpinfo->iniface, arpinfo->iniface_mask);
if (NF_INVF(arpinfo, ARPT_INV_VIA_IN, ret != 0))
return 0;
ret = ifname_compare(outdev, arpinfo->outiface, arpinfo->outiface_mask);
if (NF_INVF(arpinfo, ARPT_INV_VIA_OUT, ret != 0))
return 0;
return 1;
内核hook点
NF_ARP_IN位于arp协议的入口函数arp_rcv中,如果没有规则将报文丢弃,最后交由arp_process进行处理。
static int arp_rcv(struct sk_buff *skb, struct net_device *dev,
struct packet_type *pt, struct net_device *orig_dev)
{
const struct arphdr *arp;
/* do not tweak dropwatch on an ARP we will ignore */
if (dev->flags & IFF_NOARP ||
skb->pkt_type == PACKET_OTHERHOST ||
skb->pkt_type == PACKET_LOOPBACK)
goto consumeskb;
...
memset(NEIGH_CB(skb), 0, sizeof(struct neighbour_cb));
return NF_HOOK(NFPROTO_ARP, NF_ARP_IN,
dev_net(dev), NULL, skb, dev, NULL,
arp_process);
内核版本 5.10