简介
这边用br虚拟网卡以及命名空间来搭建实验环境,对网卡启动过程中ipv6相关变化进行抓包分析。
实验环境
搭建如下的实验环境,先启动net1空间的网卡跟br0,但此时不启动net2空间的网卡。对br0抓包或者ns1_linux也可,然后再启动net2空间网卡,以此来分析网卡启动过程中ipv6的相关过程。

环境搭建
先创建br0
brctl addbr br0
ip addr add 123.123.0.1/24 dev br0
ip link set br0 up

生成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

查看两个网卡信息
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:~#

将ns1_linux网卡接入br0网卡
ip link set dev ns1_linux master br0

生成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

查看两个网卡信息
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:~#

将ns1_linux网卡接入br0网卡
ip link set dev ns2_linux master br0

查看网桥信息
brctl show

netns内通信

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:~#

实验开启
在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

开始对br0抓包,对ns_linux1抓包也是一样的
sudo tcpdump -i br0 -w br0.pcap &
sudo tcpdump -i ns1_linux -w ns1_linux.pcap &

up网络空间net2里的两张网卡
ip link set dev ns2_linux up
ip netns exec net2 ip link set dev ns2_virt up

再查看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


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

抓包文件链接

内核初始化
ipv6对应内核中的INET6协议族,初始化代码在net/ipv6/af_inet6.c 中的inet6_init函数。想要分析ipv6相关抓住两个,一个是inet6_init函数,一个是net/ipv6/addrconf.c的static struct ipv6_devconf ipv6_devconf结构体。
这里简单分析下下面要用到内核相关初始化过程,针对addrconf_init函数

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_work跟dad_work函数
-
注册网络内核通知链,这个内核的通知链机制后续可以专门讲讲,这里注册了
ipv6_dev_notf。该结构体如下static struct notifier_block ipv6_dev_notf = { .notifier_call = addrconf_notify, .priority = ADDRCONF_NOTIFY_PRIORITY, };其中函数
addrconf_notify就是当有什么网络事件通知的时候,就会调用到这个函数里面,这个函数比较重要,经常可以看到如下打印,就是这个函数里面的,addrconf_notify函数代码有点类似于内核的phy状态机处理的那种意思。
代码中还有
addrconf_notify->addrconf_dev_config->addrconf_addr_gen,此处涉及ipv6的linklocal地址生成 -
还有一个重要的a
ddrconf_verify函数最后调用到addrconf_verify_rtnl函数,里面配置了个延时工作队列,与ipv6地址的life_time跟prefferd_time以及tempaddr相关
step1 生成链路本地地址
生成linklocal地址

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

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

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

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

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


step2 生成多播地址


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

被请求节点多播地址用来顺应IPv6中的多播而生,替代IPv4用ARP广播来做地址解析。当别的设备想要知道某个IP对应的MAC地址,在IPv4中则是用ARP广播来做,在IPv6则是往IP的请求节点多播地址发送NS报文。
说白点就是,我自己生成地址的时候就已经生成了一个被请求节点多播地址,那自然我也会侦听这个地址。如果其他成员往这个组播地址发包,那我也会收到这些数据包,并返回MAC地址(比如NA报文)给对方,这就完成了地址解析。
step3 多播成员报告
其实就是加入自己被请求节点多播地址的多播组。对应的报文如下,这里使用MLD协议中的多播成员报告报文,ICMPv6报文的type为143,具体的MLD协议可以看此参考链接。跟IPv4的组播协议IGMP中的成员报告差不多。MLDv1(RFC2710),源自 IGMPv2。MLDv2(RFC3810),源自 IGMPv3。


-
成员报告是单向的,不会受到回应包
-
只要生成多播地址,就要进行成员报告,这是多播的工作机制 (这部分到时候可以看看内核的代码实现)
-
以
frame4为例,这里的目的地址为ff02::16,这个特殊地址指代链路上具有MLDv2能力的路由器
step4 DAD
DAD即是Duplicate Address Detection重复地址检测的缩写,每生成一个单播地址,包括linklocal地址,都会进行一次DAD。
-
何时进行
DAD?在生成单播地址并完成发送一次MLDv2成员报告后,就会随机延时一小段时间进行检测 -
内核的
/proc/sys/net/ipv6/conf/all/dad_transmits为dad检测次数,若设置为0表示不进行检测
-
工作原理则是发送一个
NS报文,请求解析地址是自己的地址,并等待回应,若超时未得到NA报文的回应,则认为地址可用
从下面的报文分析中可以看到先发了一次MLDv2报文后,发送了NS请求报文

内核DAD相关代码分析可以见此链接
step5 无状态配置
一般DAD通过过,主机都会发送RS消息尝试来发现路由器,一般RS消息就发三次,避免网络拥塞。这个前面一些文章都已经了解过了,就不重复讲了。这里可以看到在发送RS报文。

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