邻居发现代码分析一 -- 请求

发包

发包通过 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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值