网卡启动ipv6相关分析

清明

Posted by 婷 on April 4, 2024 本文总阅读量

简介

这边用br虚拟网卡以及命名空间来搭建实验环境,对网卡启动过程中ipv6相关变化进行抓包分析。

实验环境

搭建如下的实验环境,先启动net1空间的网卡跟br0,但此时不启动net2空间的网卡。对br0抓包或者ns1_linux也可,然后再启动net2空间网卡,以此来分析网卡启动过程中ipv6的相关过程。

123

环境搭建

先创建br0

brctl addbr br0
ip addr add 123.123.0.1/24 dev br0
ip link set br0 up

image-20240302231157181

生成ns1_linux网卡跟ns1_virt网卡,创建net1空间,将ns1_virt网卡放入net1空间,并up两个网卡

ip netns add net1
ip link add ns1_linux type veth peer name ns1_virt
ip link set ns1_virt netns net1
ip netns exec net1 ip addr add 123.123.0.100/24 dev ns1_virt
ip link set dev ns1_linux up
ip netns exec net1 ip link set dev ns1_virt up

image-20240302230634817

查看两个网卡信息

root@linaro-alip:~# ip link show ns1_linux
4: ns1_linux@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether d2:fa:16:2e:e2:35 brd ff:ff:ff:ff:ff:ff link-netns net1
root@linaro-alip:~# ip netns exec net1 ip link show ns1_virt
3: ns1_virt@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 22:b0:a7:3d:74:dd brd ff:ff:ff:ff:ff:ff link-netnsid 0
root@linaro-alip:~#

image-20240302230801107

ns1_linux网卡接入br0网卡

ip link set dev ns1_linux master br0

image-20240302231230996

生成ns2_linux网卡跟ns2_virt网卡,创建net12空间,将ns2_virt网卡放入net2空间,此时不要up两个网卡!!!

ip netns add net2
ip link add ns2_linux type veth peer name ns2_virt
ip link set ns2_virt netns net2
ip netns exec net2 ip addr add 123.123.0.200/24 dev ns2_virt

image-20240302231406002

查看两个网卡信息

root@linaro-alip:~#
root@linaro-alip:~# ip link show ns2_linux
7: ns2_linux@if6: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 9a:e9:16:20:6a:99 brd ff:ff:ff:ff:ff:ff link-netns net2
root@linaro-alip:~# ip netns exec net2 ip link show ns2_virt
6: ns2_virt@if7: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT group default qlen 1000
    link/ether 8a:51:58:47:a7:3f brd ff:ff:ff:ff:ff:ff link-netnsid 0
root@linaro-alip:~#

image-20240302231436759

ns1_linux网卡接入br0网卡

ip link set dev ns2_linux master br0

image-20240302231513958

查看网桥信息

brctl show

image-20240302231636735

netns内通信

image-20240302231555725

root@linaro-alip:~# ip netns exec net1 ip -6 addr show
3: ns1_virt@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::20b0:a7ff:fe3d:74dd/64 scope link
       valid_lft forever preferred_lft forever
root@linaro-alip:~# ip -6 addr show br0
5: br0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 state UP qlen 1000
    inet6 fe80::90d9:4fff:fe37:f40b/64 scope link
       valid_lft forever preferred_lft forever
root@linaro-alip:~#
root@linaro-alip:~#
root@linaro-alip:~# ping6 fe80::20b0:a7ff:fe3d:74dd%5
PING fe80::20b0:a7ff:fe3d:74dd%5(fe80::20b0:a7ff:fe3d:74dd%br0) 56 data bytes
64 bytes from fe80::20b0:a7ff:fe3d:74dd%br0: icmp_seq=1 ttl=64 time=0.570 ms
64 bytes from fe80::20b0:a7ff:fe3d:74dd%br0: icmp_seq=2 ttl=64 time=0.405 ms
64 bytes from fe80::20b0:a7ff:fe3d:74dd%br0: icmp_seq=3 ttl=64 time=0.443 ms
64 bytes from fe80::20b0:a7ff:fe3d:74dd%br0: icmp_seq=4 ttl=64 time=0.427 ms
^C
--- fe80::20b0:a7ff:fe3d:74dd%5 ping statistics ---
4 packets transmitted, 4 received, 0% packet loss, time 42ms
rtt min/avg/max/mdev = 0.405/0.461/0.570/0.065 ms
root@linaro-alip:~#

image-20240302231853133

实验开启

net2里启动网卡之前,先对net2里的网络环境进行查看

# 查看网卡状态 
ip netns exec net2 ip link 
 
# 查看ipv6地址 
ip netns exec net2 ip -6 addr 
 
# 查看ipv6路由 
ip netns exec net2 ip -6 route 
 
# 查看ipv6多播地址 
ip netns exec net2 ip -6 maddr 

image-20240302232105913

开始对br0抓包,对ns_linux1抓包也是一样的

sudo tcpdump -i br0 -w br0.pcap &
sudo tcpdump -i ns1_linux -w ns1_linux.pcap &

image-20240302232349066

up网络空间net2里的两张网卡

ip link set dev ns2_linux up
ip netns exec net2 ip link set dev ns2_virt up

image-20240302232443376

再查看net2,可以看到其ipv6地址已经有了

ip netns exec net2 ifconfig ns2_virt

# 查看网卡状态 
ip netns exec net2 ip link 
 
# 查看ipv6地址 
ip netns exec net2 ip -6 addr 
 
# 查看ipv6路由 
ip netns exec net2 ip -6 route 
 
# 查看ipv6多播地址 
ip netns exec net2 ip -6 maddr 

image-20240302232554334

image-20240302232712512

抓包分析

网卡启动过程中ipv6相关过程可以参照下图

image-20240310230132784

抓包文件链接

image-20240303230247641

内核初始化

ipv6对应内核中的INET6协议族,初始化代码在net/ipv6/af_inet6.c 中的inet6_init函数。想要分析ipv6相关抓住两个,一个是inet6_init函数,一个是net/ipv6/addrconf.cstatic struct ipv6_devconf ipv6_devconf结构体。

这里简单分析下下面要用到内核相关初始化过程,针对addrconf_init函数

image-20240310221513422

module_init(inet6_init)
	addrconf_init
    	addrconf_wq = create_workqueue("ipv6_addrconf");
		register_netdevice_notifier(&ipv6_dev_notf);
		addrconf_verify();
			mod_delayed_work(addrconf_wq, &addr_chk_work, 0);
			DECLARE_DELAYED_WORK(addr_chk_work, addrconf_verify_work);
			addrconf_verify_work()
                addrconf_verify_rtnl
  • 创建一个ipv6_addrconf内核工作队列,可以加入addrconf_verify_workdad_work函数

    image-20240303000325900

  • 注册网络内核通知链,这个内核的通知链机制后续可以专门讲讲,这里注册了ipv6_dev_notf。该结构体如下

    static struct notifier_block ipv6_dev_notf = {
    	.notifier_call = addrconf_notify,
    	.priority = ADDRCONF_NOTIFY_PRIORITY,
    };
    

    其中函数addrconf_notify就是当有什么网络事件通知的时候,就会调用到这个函数里面,这个函数比较重要,经常可以看到如下打印,就是这个函数里面的,addrconf_notify函数代码有点类似于内核的phy状态机处理的那种意思。

    image-20240305215931413

    代码中还有addrconf_notify->addrconf_dev_config->addrconf_addr_gen,此处涉及ipv6linklocal地址生成

  • 还有一个重要的addrconf_verify函数最后调用到addrconf_verify_rtnl函数,里面配置了个延时工作队列,与ipv6地址的life_timeprefferd_time以及tempaddr相关

step1 生成链路本地地址

生成linklocal地址

image-20240303152910652

当网卡有状态变化,不清楚是哪部分(可能是mac驱动?)调用了通知链

image-20240303223624302

call_netevent_notifiers调用到netevent_notif_chain这个全局链表中的注册的对应函数

image-20240303155228041

上面我们提到了addrconf_notify函数就是注册到这里面去的,所以调用到这个addrconf_notify函数中。这部分具体细节后续再出一篇博客研究研究

image-20240310224208515

addrconf_notify->addrconf_dev_config->addrconf_addr_genaddrconf_addr_gen有生成linklocal地址的代码

image-20240305230450779

我们这边是IN6_ADDR_GEN_MODE_EUI64模式,这个是由idev->cnf.addr_gen_mode决定的,这里的idev->cnf指向的结构体是net/ipv6/addrconf.cstatic struct ipv6_devconf ipv6_devconf结构体

image-20240305230746206

image-20240305230732071

step2 生成多播地址

image-20240305231330596

image-20240305230849274

每个IPv6单播地址的生成,都会生成一个被请求节点多播地址,英文是Solicited-Node multicast address。地址形式为

image-20240305231528222

被请求节点多播地址用来顺应IPv6中的多播而生,替代IPv4ARP广播来做地址解析。当别的设备想要知道某个IP对应的MAC地址,在IPv4中则是用ARP广播来做,在IPv6则是往IP的请求节点多播地址发送NS报文。

说白点就是,我自己生成地址的时候就已经生成了一个被请求节点多播地址,那自然我也会侦听这个地址。如果其他成员往这个组播地址发包,那我也会收到这些数据包,并返回MAC地址(比如NA报文)给对方,这就完成了地址解析。

step3 多播成员报告

其实就是加入自己被请求节点多播地址的多播组。对应的报文如下,这里使用MLD协议中的多播成员报告报文,ICMPv6报文的type143,具体的MLD协议可以看此参考链接。跟IPv4的组播协议IGMP中的成员报告差不多。MLDv1(RFC2710),源自 IGMPv2MLDv2(RFC3810),源自 IGMPv3

image-20240303230653525

image-20240307053006987

  • 成员报告是单向的,不会受到回应包

  • 只要生成多播地址,就要进行成员报告,这是多播的工作机制 (这部分到时候可以看看内核的代码实现)

  • frame4为例,这里的目的地址为ff02::16,这个特殊地址指代链路上具有MLDv2能力的路由器

    image-20240404111418329

step4 DAD

DAD即是Duplicate Address Detection重复地址检测的缩写,每生成一个单播地址,包括linklocal地址,都会进行一次DAD

  • 何时进行DAD?在生成单播地址并完成发送一次MLDv2成员报告后,就会随机延时一小段时间进行检测

  • 内核的/proc/sys/net/ipv6/conf/all/dad_transmitsdad检测次数,若设置为0表示不进行检测

    image-20240310203300207

  • 工作原理则是发送一个NS报文,请求解析地址是自己的地址,并等待回应,若超时未得到NA报文的回应,则认为地址可用

从下面的报文分析中可以看到先发了一次MLDv2报文后,发送了NS请求报文

image-20240310203120040

内核DAD相关代码分析可以见此链接

step5 无状态配置

一般DAD通过过,主机都会发送RS消息尝试来发现路由器,一般RS消息就发三次,避免网络拥塞。这个前面一些文章都已经了解过了,就不重复讲了。这里可以看到在发送RS报文。

image-20240309111235984

内核RS报文发送代码分析可以见此链接

参考链接