回复
| - __netif_receive_skb_list
| - __netif_receive_skb_list_core
| - __netif_receive_skb_core
| - __netif_receive_skb_list_ptype
|- arp_rcv
实际是通过对应的协议去找到对应的处理函数,然后调用处理函数。
比如 ipv4, 这里是 arp_rcv;
ipv6, 这里是 ipv6, 那么往下
|- ipv6_list_rcv
| - ip6_sublist_rcv
| - ip6_list_rcv_finish
| - ip6_sublist_rcv_finish
| - dst_input
| - ip6_input
| - ip6_input_finish
| - ip6_protocol_deliver_rcu
| - icmpv6_rcv
| - ndisc_rcv
arp_rcv
ipv4 的 arp 处理函数
arp_rcv
| - arp_process
arp_rcv
arp 报文检查, 长度和网卡的 mac 地址长度一致,protocol address 长度为 4, ipv4 地址长度为 4
arp = arp_hdr(skb);
if (arp->ar_hln != dev->addr_len || arp->ar_pln != 4)
goto freeskb;
arp_process
也是先检查, 如 arp_op 必须是 ARPOP_REPLY 或者 ARPOP_REQUEST
提取出 arp 报文的信息, 如报文截图
arp_ptr = (unsigned char *)(arp + 1);
sha = arp_ptr; // sedner mac address
arp_ptr += dev->addr_len;
memcpy(&sip, arp_ptr, 4); // sender ip address
arp_ptr += 4;
...
tha = arp_ptr; // target mac address
arp_ptr += dev->addr_len; // target ip address
memcpy(&tip, arp_ptr, 4);
if (ipv4_is_multicast(tip) || // 多播地址或者回环地址直接丢弃
(!IN_DEV_ROUTE_LOCALNET(in_dev) && ipv4_is_loopback(tip)))
goto out_free_skb;
if (sip == tip && IN_DEV_ORCONF(in_dev, DROP_GRATUITOUS_ARP)) // 如果 sip == tip 且系统参数 drop_gratuitous_arp 为真丢弃
goto out_free_skb; // sip == tip 通常为免费 arp.
...
if (sip == 0) {
if (arp->ar_op == htons(ARPOP_REQUEST) &&
inet_addr_type_dev_table(net, dev, tip) == RTN_LOCAL &&
!arp_ignore(in_dev, sip, tip)) // arp_op 为请求且 tip 在本地且不忽略, 回复 dev 的 mac 地址
arp_send_dst(ARPOP_REPLY, ETH_P_ARP, sip, dev, tip,
sha, dev->dev_addr, sha, reply_dst);
goto out_consume_skb;
}
if (arp->ar_op == htons(ARPOP_REQUEST) && // arp 请求
ip_route_input_noref(skb, tip, sip, 0, dev) == 0) {
rt = skb_rtable(skb);
addr_type = rt->rt_type;
if (addr_type == RTN_LOCAL) { // 本地地址
int dont_send;
dont_send = arp_ignore(in_dev, sip, tip); // 是否忽略, 通过收到 arp 请求网卡的 arp_ignore 判断
if (!dont_send && IN_DEV_ARPFILTER(in_dev)) // 网卡的 arp_filter 是否开启, arp_filter 通过 sip 和 tip 查询路由,
dont_send = arp_filter(sip, tip, dev); // 如果路由网卡不是收 arp 的网卡, dont_send 为真.
if (!dont_send) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev); // 通过 sha 和 sip 创建和更新 neigh, 保证回复对象的 neigh 状态为 NUD_STALE
if (n) {
arp_send_dst(ARPOP_REPLY, ETH_P_ARP, // 回复
sip, dev, tip, sha,
dev->dev_addr, sha,
reply_dst);
neigh_release(n); // 引用计数为 0 时释放 neigh
}
}
goto out_consume_skb;
} else if (IN_DEV_FORWARD(in_dev)) { // 不是本地地址,看网卡是否开了 forwarding
if (addr_type == RTN_UNICAST && // 单播地址且 proxy_arp 为真, 记录收到的 arp 请求者的信息并回复
(arp_fwd_proxy(in_dev, dev, rt) || // 很常用的场景: 容器网络中 veth pair host 侧开启 proxy_arp
arp_fwd_pvlan(in_dev, dev, rt, sip, tip) || // 当收到容器内发的 arp 的请求, 都会回复 host 侧的 mac 地址
(rt->dst.dev != dev &&
pneigh_lookup(&arp_tbl, net, &tip, dev, 0)))) {
n = neigh_event_ns(&arp_tbl, sha, &sip, dev);
if (n)
neigh_release(n);
if (NEIGH_CB(skb)->flags & LOCALLY_ENQUEUED ||
skb->pkt_type == PACKET_HOST ||
NEIGH_VAR(in_dev->arp_parms, PROXY_DELAY) == 0) {
arp_send_dst(ARPOP_REPLY, ETH_P_ARP,
sip, dev, tip, sha,
dev->dev_addr, sha,
reply_dst);
} else {
pneigh_enqueue(&arp_tbl,
in_dev->arp_parms, skb);
goto out_free_dst;
}
goto out_consume_skb;
}
}
}
/* Update our ARP tables */
n = __neigh_lookup(&arp_tbl, &sip, dev, 0);
addr_type = -1;
if (n || IN_DEV_ARP_ACCEPT(in_dev)) { // arp_accept 参数
is_garp = arp_is_garp(net, dev, &addr_type, arp->ar_op, // 是否是 gratuitous arp, 通过 sendip == tip, 且 arp_op 为 reply 判断
sip, tip, sha, tha);
}
...
if (n) { // 更新 neigh 状态
int state = NUD_REACHABLE;
int override;
override = time_after(jiffies,
n->updated +
NEIGH_VAR(n->parms, LOCKTIME)) ||
is_garp;
if (arp->ar_op != htons(ARPOP_REPLY) ||
skb->pkt_type != PACKET_HOST)
state = NUD_STALE; // 广播 arp 更新为 NUD_STALE, 其他更新为 NUD_REACHABLE
neigh_update(n, sha, state,
override ? NEIGH_UPDATE_F_OVERRIDE : 0, 0);
neigh_release(n);
}
ndisc_rcv
ipv6 的 ndisc 处理函数
- ns Neighbor Solicitation, 邻居请求
- na Neighbor Advertisement, 邻居通告
从 icmpv6_rcv 开始, 判断 icmp6_type, NS 时, ndisc_rcv -> ndisc_recv_ns 处理, NA 时, ndisc -> ndisc_recv_rs 处理
ndisc_recv_ns
收到 ns 的报文的处理
int dad = ipv6_addr_any(saddr); // ip 地址全 0 需要 dad 检查
...
if (ipv6_addr_is_multicast(&msg->target)) { // 请求的地址是多播地址, 不处理
ND_PRINTK(2, warn, "NS: multicast target address\n");
return;
}
if (dad && !ipv6_addr_is_solict_mult(daddr)) { // dad 检查, 不是 solicited multicast 地址不处理
ND_PRINTK(2, warn, "NS: bad DAD packet (wrong destination)\n"); // 或者说 DAD 类型 ns 必须指向特定的请求节点多播地址。
return; // FF02::1:FF00:0
}
...
inc = ipv6_addr_is_multicast(daddr); // 报文目的地址是多播地址
ifp = ipv6_get_ifaddr(dev_net(dev), &msg->target, dev, 1); // 请求的地址是否在该网卡上
if (ifp) {
have_ifp:
if (ifp->flags & (IFA_F_TENTATIVE|IFA_F_OPTIMISTIC)) { // 找到地址, 而且状态是 dad 检查等待时间
if (dad) // 而且收到的是 dad, 那么本地的 dad 检查直接失败
...
idev = ifp->idev; // 得到 idev, ipv6 的设备信息
else {
if (netif_is_l3_slave(dev)) { // 当网卡是 slave 时, 检查 master 是否有请求地址, 有则认为找到
...
}
}
if (dad) { // 回复 dad 检查
ndisc_send_na(dev, &in6addr_linklocal_allnodes, &msg->target,
!!is_router, false, (ifp != NULL), true);
goto out;
}
neigh = __neigh_lookup(&nd_tbl, saddr, dev, // 请求源 ip 的 neigh, 和 arp 一样, 保证回复对象的 neigh 状态为 NUD_STALE
!inc || lladdr || !dev->addr_len);
if (neigh)
ndisc_update(dev, neigh, lladdr, NUD_STALE,
NEIGH_UPDATE_F_WEAK_OVERRIDE|
NEIGH_UPDATE_F_OVERRIDE,
NDISC_NEIGHBOUR_SOLICITATION, &ndopts);
if (neigh || !dev->header_ops) { // 回复 na 报文
ndisc_send_na(dev, saddr, &msg->target, !!is_router,
true, (ifp != NULL && inc), inc);
if (neigh)
neigh_release(neigh);
}
ndisc_send_na
*msg = (struct nd_msg) { // 设置 na 回复 flag
.icmph = {
.icmp6_type = NDISC_NEIGHBOUR_ADVERTISEMENT,
.icmp6_router = router,
.icmp6_solicited = solicited,
.icmp6_override = override,
},
.target = *solicited_addr, // 回复的 mac 地址
};
if (inc_opt)
ndisc_fill_addr_option(skb, ND_OPT_TARGET_LL_ADDR, // 填充 na 报文 option
dev->dev_addr,
NDISC_NEIGHBOUR_ADVERTISEMENT);
ndisc_send_skb(skb, daddr, src_addr); // 回包
小结
- arp_filter, 网卡参数
0: 收到 arp 请求时不检查
1: 收到 arp 请求时检查, 当一个网卡收到 arp 请求时, 且请求的地址是本地地址, 那么通过请求的地址和本地地址查询路由表, 如果路由表的网卡不是收到请求的网卡, 那么丢弃这个 arp 请求.
当多张网卡在一个广播域里时,会同时收到 arp 请求,如果多张同时回复自己的 mac 地址, 将会混乱, 所以这种场景下需要开启 arp_filter, 只让 ip 所在网卡回复.
- proxy_arp, 网卡参数, arp 代理
在容器网络环境中常见, veth-pair
比如网络命名空间路由情况
$ ip r
default via 169.254.1.1 dev eth0
169.254.1.1 dev eth0 scope link
当 host 侧收到 arp 请求时, 就会回复 169.254.1.1 的 mac 地址为自己的 mac 地址.
-
arp_accept, 网卡参数, 是否接受 ip 没在 arp 表中的 garp.
-
neigh_event_ns(&arp_tbl, sha, &sip, dev)
可以看到回复前会调用 neigh_event_ns, 通过 sender mac 和 sender ip 创建和更新 neigh, 保证回复对象的 neigh 状态为 NUD_STALE -
DAD Duplicate Address Detection IPV6 中的重复地址检测
获取到新地址后, 启动 DAD 流程, 使用全 0 地址发送 IPv6 地址的邻居请求(NS)消息, 等待 NA 回复, 如果定时器到了未收到回复, 则认为地址可用, 否则认为地址冲突, 不能使用.