发包
发包通过 ip_finish_output2 和 ip6_finish_output2 函数
ipv4
ip_finish_output2
查找 neigh, 使用 neigh 的 output 发包
neigh = ip_neigh_for_gw(rt, skb, &is_v6gw);
if (!IS_ERR(neigh)) {
int res;
...
res = neigh_output(neigh, skb, is_v6gw);
rcu_read_unlock_bh();
return res;
}
ip_neigh_for_gw 里 ip_neigh_gw4 先查找, 没有则 __neigh_create 创建
ipv6
ip6_finish_output2
查找 neigh, 如果没有, 创建, 使用 neigh 的 output 发包
neigh = __ipv6_neigh_lookup_noref(dst->dev, nexthop);
if (unlikely(!neigh))
neigh = __neigh_create(&nd_tbl, nexthop, dst->dev, false);
if (!IS_ERR(neigh)) {
...
ret = neigh_output(neigh, skb, false);
...
return ret;
}
neigh 创建
__neigh_create
对应 neigh_table 构造, 分为 ipv4 的 arp_constructor 和 ipv6 的 ndisc_constructor
/* Protocol specific setup. */
if (tbl->constructor && (error = tbl->constructor(n)) < 0) {
rc = ERR_PTR(error);
goto out_neigh_release;
}
ipv4
arp_constructor
neigh->ha 设置为 dev 的广播地址, dev->broadcast 通过 eth_broadcast_addr 在 ether_setup 时进行设置 memset(addr, 0xff, ETH_ALEN)。
} else if (neigh->type == RTN_BROADCAST ||
(dev->flags & IFF_POINTOPOINT)) {
neigh->nud_state = NUD_NOARP;
memcpy(neigh->ha, dev->broadcast, dev->addr_len);
}
neigh->ha ff:ff:ff:ff:ff:ff, 通过 ip a 也能看到网卡的广播地址
ipv6
ndisc_constructor
if (is_multicast) {
neigh->nud_state = NUD_NOARP;
ndisc_mc_map(addr, neigh->ha, dev, 1);
addr: 目的 ip, 多播包
int ndisc_mc_map(const struct in6_addr *addr, char *buf, struct net_device *dev, int dir)
{
switch (dev->type) {
case ARPHRD_ETHER:
case ARPHRD_IEEE802: /* Not sure. Check it later. --ANK */
case ARPHRD_FDDI:
ipv6_eth_mc_map(addr, buf);
neigh->ha 设置多播包的 mac 地址, ipv6_eth_mc_map
neigh_output
找到 neigh 或创建完 neigh 后, 使用 neigh 的 output 发包
如果有 hh_cache 且 neigh 状态可用, 使用 hh_cache 中的 data 发数据包, 否则调用 neigh->output 发 arp 请求包。
关注发送 arp 请求包的 neigh->output
static inline int neigh_output(struct neighbour *n, struct sk_buff *skb,
bool skip_cache)
{
const struct hh_cache *hh = &n->hh;
if ((n->nud_state & NUD_CONNECTED) && hh->hh_len && !skip_cache)
return neigh_hh_output(hh, skb);
else
return n->output(n, skb);
}
ipv4/ipv6
neigh_resolve_output
|- neigh_event_send
|- __neigh_event_send
|- neigh_probe
| - neigh->ops->solicit
if (!neigh_event_send(neigh, skb)) # neigh 请求
...
if (dev->header_ops->cache && !READ_ONCE(neigh->hh.hh_len))
neigh_hh_init(neigh); ## 有结果初始化 hh_cache,方便后续使用
do {
__skb_pull(skb, skb_network_offset(skb));
seq = read_seqbegin(&neigh->ha_lock);
err = dev_hard_header(skb, dev, ntohs(skb->protocol), ## 设置 mac eth.c eth_header, 使用 dev 的 mac 做源 mac
neigh->ha, NULL, skb->len); 使用 neigh->ha 做目的 mac
} while (read_seqretry(&neigh->ha_lock, seq));
dev_queue_xmit(skb);
__neigh_event_send 设置 timer, 如果状态是 INCOMPLETE, 将 skb 加到 neigh arp 队列中, 等到 neigh 状态可用后, 从队列取出发送
__skb_queue_tail(&neigh->arp_queue, skb);
neigh->arp_queue_len_bytes += skb->truesize;
...
if (immediate_probe)
neigh_probe(neigh);
请求
neigh->ops->solicit
ipv4
arp_solicit
| - arp_send_dst
| - arp_create
| - dev_hard_header
| - eth_header
| - skb_dst_set
| - arp_xmit
| - dev_queue_xmit
saddr = ip_hdr(skb)->saddr;
arp_send_dst(ARPOP_REQUEST, ETH_P_ARP, target, dev, saddr,
dst_hw, dev->dev_addr, NULL, dst);
skb_dst_set
skb = alloc_skb(arp_hdr_len(dev) + hlen + tlen, GFP_ATOMIC); ## 分配 skb
skb->protocol = htons(ETH_P_ARP);
if (!src_hw)
src_hw = dev->dev_addr; ## 源 mac 设备的 mac
if (!dest_hw)
dest_hw = dev->broadcast; ## 目的 mac 为 dev 的广播地址
if (dev_hard_header(skb, dev, ptype, dest_hw, src_hw, skb->len) < 0) ## 使用 eth 的 create eth_header 填充 mac 头
goto out;
填充 arp 请求的协议内容
arp->ar_hrd = htons(dev->type); ## 以太类型
arp->ar_pro = htons(ETH_P_IP); ## IPv4
arp->ar_hln = dev->addr_len; ## mac 地址长度
arp->ar_pln = 4; ## ip 地址长度
arp->ar_op = htons(type); ## ARP 请求 type 为 ARPOP_REQUEST 1
arp_ptr = (unsigned char *)(arp + 1);
memcpy(arp_ptr, src_hw, dev->addr_len); ## 源 mac
arp_ptr += dev->addr_len;
memcpy(arp_ptr, &src_ip, 4); ## 源 ip
arp_ptr += 4;
if (target_hw)
memcpy(arp_ptr, target_hw, dev->addr_len);
else
memset(arp_ptr, 0, dev->addr_len); ## 全 0
arp_ptr += dev->addr_len;
}
memcpy(arp_ptr, &dest_ip, 4); ## 请求 ip
ipv6
ndisc_solicit
| - addrconf_addr_solict_mult
| - ndisc_send_ns
addrconf_addr_solict_mult(target, &mcaddr); ## target 为请求的地址,
ndisc_send_ns(dev, target, &mcaddr, saddr, 0);
addrconf_addr_solict_mult 设置 mcaddr 地址, 为 ff02::1:ff00:<s6_addr32[3]>, 最后一段为请求地址的最后 32 位
注意, ns 的多播包 目的 ip 是 mcaddr 地址
ipv6_addr_set(solicited,
htonl(0xFF020000), 0,
htonl(0x1),
htonl(0xFF000000) | addr->s6_addr32[3]);
ndisc_send_ns
| - ndisc_send_skb
| - ip6_nd_hdr
| - ip6_flow_hdr
|- dst_output
| - ip6_output
| - ip6_finish_output2
| - neigh_output
skb = ndisc_alloc_skb(dev, sizeof(*msg) + optlen);
*msg = (struct nd_msg) { ## nd_msg icmp_type 为 NDISC_NEIGHBOUR_SOLICITATION 135
.icmph = { solicit 请求的 neigh 地址
.icmp6_type = NDISC_NEIGHBOUR_SOLICITATION,
},
.target = *solicit,
};
ndisc_fill_addr_option(skb, ND_OPT_SOURCE_LL_ADDR, ## 主要将 NDISC_NEIGHBOUR_SOLICITATION 填充进 icmpv6 type 中
dev->dev_addr,
NDISC_NEIGHBOUR_SOLICITATION);
ndisc_send_skb(skb, daddr, src_addr);
ndisc_send_skb 填充报文
icmp6h->icmp6_cksum = csum_ipv6_magic(saddr, daddr, skb->len, ## 计算校验和
IPPROTO_ICMPV6,
csum_partial(icmp6h,
skb->len, 0));
ip6_nd_hdr(skb, saddr, daddr, inet6_sk(sk)->hop_limit, skb->len); ## ipv6 头部填充
ip6_nd_hdr 填充 ipv6 头部
hdr = ipv6_hdr(skb);
ip6_flow_hdr(hdr, tclass, 0); ## 设置 ip 层的 version 为 6, 填充 tc 和 flow label(0)
hdr->payload_len = htons(len); ## 设置 payload_len, 为 icmpv6 的长度
hdr->nexthdr = IPPROTO_ICMPV6; ## 设置 nexthdr 为 icmpv6
hdr->hop_limit = hop_limit; ## 设置 hop_limit
hdr->saddr = *saddr; ## 设置源地址
hdr->daddr = *daddr; ## 设置目的地址, 该目的地址为上面的 solicit 地址 ff02::1:ff00:<s6_addr32[3]>
和 ipv4 不同, 又重新回到 ip6_finish_output2
再次查找 neigh, 这次已经存在, 且有可能收到回复, 填充了 neigh->hh_cache
再通过 neigh_resolve_output 的 dev_queue_xmit 发送
目的 mac 地址
ipv4
dev 的广播
net/ipv4/arp.c
struct sk_buff *arp_create(
...
if (!dest_hw)
dest_hw = dev->broadcast;
ipv6
根据目的 ip 获取目的 mac
include/net/if_inet6.h
static inline void ipv6_eth_mc_map(const struct in6_addr *addr, char *buf)
{
/*
* +-------+-------+-------+-------+-------+-------+
* | 33 | 33 | DST13 | DST14 | DST15 | DST16 |
* +-------+-------+-------+-------+-------+-------+
*/`
buf[0]= 0x33;
buf[1]= 0x33;
memcpy(buf + 2, &addr->s6_addr32[3], sizeof(__u32));
}
开始是 33:33, 后面是 ipv6 地址的后 32 位
如 ff02::1:ff00:1 -> 33:33:ff:00:00:01
ff02::1:ff00:2 -> 33:33:ff:00:00:02