3.4 ICMPv6 初始化

3.4 ICMPv6 初始化

1. ICMPv6 简述

ICMPv6 除了跟 ICMPv4 一样负责错误处理诊断之外,还负责邻居发现( Neighbour Discovery ND )组播侦听者发现(Multicast Listener Discover MLD)

  • 邻居发现(ND) -- ARP(IPV4)
  • 组播侦听者发现(MLD) --IGMP(IPV4)

但是这两个功能会放在后面去解析,本章节还是学习 ICMPv6 和 ICMPv4 相同的部分。另外同ICMPv4 一样,不能将ICMPv6 构建成内核模块。原因见 ICMPv4 附录。

2. ICMPv6 初始化

首先就是 ICMPv6 在网络协议栈中的初始化。ICMPv6 同 ICMPv4 一样,附属于IP层三层网络协议,所以和TCPv6、UDPv6一样,在inet6_init()中完成初始化。

static int __init inet6_init(void)
{
    ...
	err = icmpv6_init();
	if (err)
		goto icmp_fail;
	...
}

inet6_init()中,调用 icmpv6_init()函数对ICMPv6 进行初始化。跟 ICMPv4 初始化有一点点的区别是,在 ICMPv4中,协议注册和内核初始化是分开的,而 ICMPv6 中,它们都在 icmpv6_init() 中进行处理。怎么说呢,毕竟是后者,进行代码整理和优化也是应该的。ICMPv6 很多地方都体现了编码优化的身影。

int __init icmpv6_init(void)
{
	int err;
    
	err = register_pernet_subsys(&icmpv6_sk_ops);
	if (err < 0)
		return err;

	err = -EAGAIN;
	if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0)
		goto fail;

	err = inet6_register_icmp_sender(icmp6_send);
	if (err)
		goto sender_reg_err;
	return 0;
	...
}

2.1 ICMPv6 内核模块初始化

在 ICMPv6 中,首先执行的是 ICMPv6 的内核套接字创建初始化

static struct pernet_operations icmpv6_sk_ops = {
	.init = icmpv6_sk_init,
	.exit = icmpv6_sk_exit,
};

int __init icmpv6_init(void)
{
	err = register_pernet_subsys(&icmpv6_sk_ops);
	if (err < 0)
		return err;
	...
}

最终ICMPv6 内核模块初始化函数注册在了icmpv6_sk_init()上。这个函数主要完成如下功能:

  • 为每个CPU创建 ICMPv6 原始套接字,并储存在数组中。

    static int __net_init icmpv6_sk_init(struct net *net)
    {
    	struct sock *sk;
    	int err, i, j;
    
    	// 申请足够空间
    	net->ipv6.icmp_sk =
    		kcalloc(nr_cpu_ids, sizeof(struct sock *), GFP_KERNEL);
    	if (!net->ipv6.icmp_sk)
    		return -ENOMEM;
    
    	// 遍历每个CPU,为每个CPU创建ICMPv6原始套接字
    	for_each_possible_cpu (i) {
    		err = inet_ctl_sock_create(&sk, PF_INET6, SOCK_RAW,
    					   IPPROTO_ICMPV6, net);
    		if (err < 0) {
    			pr_err("Failed to initialize the ICMP6 control socket (err %d)\n",
    			       err);
    			goto fail;
    		}
    		// 将套接字存放在ICMPv6 原始套接字的数组中
    		net->ipv6.icmp_sk[i] = sk;
    
    		/* Enough space for 2 64K ICMP packets, including
    		 * sk_buff struct overhead.
    		 */
    		sk->sk_sndbuf = 2 * SKB_TRUESIZE(64 * 1024);
    	}
    	return 0;
    
    fail:
    	// 创建失败则销毁所有ICMPv6 原始套接字,并释放数组空间
    	for (j = 0; j < i; j++)
    		inet_ctl_sock_destroy(net->ipv6.icmp_sk[j]);
    	kfree(net->ipv6.icmp_sk);
    	return err;
    }
    

    和ICMPv4 不同的是,ICMPv6 似乎并没有一些协议上参数的限制。比如限速等。

2.2 ICMPv6 收包处理函数初始化

ICMPv6 协议结构体:

struct inet6_protocol icmpv6_protocol = {
	.handler	=	icmpv6_rcv,
	.err_handler	=	icmpv6_err,
	.flags		=	INET6_PROTO_NOPOLICY|INET6_PROTO_FINAL,
};

int __init icmpv6_init(void)
{
    ...
	err = -EAGAIN;
	if (inet6_add_protocol(&icmpv6_protocol, IPPROTO_ICMPV6) < 0)
		goto fail;
	...
}
  • icmpv6_rcv icmpv6 的数据包处理函数,在处理IP报文时,协议字段为IPPROTO_ICMPV6(58)时,就是用icmpv6_rcv()来处理。
  • INET6_PROTO_NOPOLICY 标志着不使用ipsec进行安全策略检查。
  • INET6_PROTO_FINAL 标志着该协议在这一层是最终的协议封装,不会再有其他协议。以避免意外的协议嵌套。

2.3 注册 ICMPv6 数据包发送函数

static ip6_icmp_send_t __rcu *ip6_icmp_send;
int inet6_register_icmp_sender(ip6_icmp_send_t *fn)
{
	return (cmpxchg((ip6_icmp_send_t **)&ip6_icmp_send, NULL, fn) == NULL) ?
		0 : -EBUSY;
}

int __init icmpv6_init(void)
{
	...
	err = inet6_register_icmp_sender(icmp6_send);
	...
}

这个处理比较简单,仅仅是为 ICMPv6 的发送报文函数指针ip6_icmp_send注册发送函数为 icmp6_send(), 发送函数的实现将在后面展开。

初始化部分完成。下一节将介绍ICMPv6 报文格式和报文类型。

posted @ 2024-04-05 17:59  kmist  阅读(57)  评论(0编辑  收藏  举报