3.6 ICMPv6 报文接收和发送

3.6 ICMPv6 报文接收和发送

1. ICMPv6 数据包接收流程

image-20240331153407136

当有IP报头协议是58的数据包到来之后,会调用icmpv6_rcv()进行处理。数据包的处理流程在上面的流程图中说明的非常清楚,下面就来看一下代码是怎么实现的。

static int icmpv6_rcv(struct sk_buff *skb)
{
	struct net *net = dev_net(skb->dev);
	struct net_device *dev = icmp6_dev(skb);
	struct inet6_dev *idev = __in6_dev_get(dev);
	const struct in6_addr *saddr, *daddr;
	struct icmp6hdr *hdr;
	u8 type;
	bool success = false;
	
    // 1. 如果有开启ipsec安全检查,将会在这里进行处理
	if (!xfrm6_policy_check(NULL, XFRM_POLICY_IN, skb)) {
		...
	}
	
    // 2. 增加 ICMP6_MIB_INMSGS 计数器
	__ICMP6_INC_STATS(dev_net(dev), idev, ICMP6_MIB_INMSGS);

	saddr = &ipv6_hdr(skb)->saddr;
	daddr = &ipv6_hdr(skb)->daddr;
	
    // 3. 对报文的完整性进行检查
	if (skb_checksum_validate(skb, IPPROTO_ICMPV6, ip6_compute_pseudo)) {
		net_dbg_ratelimited("ICMPv6 checksum failed [%pI6c > %pI6c]\n",
				    saddr, daddr);
		goto csum_error;
	}
	
    // 4. data指针往下偏移 icmp头部结构体的大小,确保icmp头部完整。如果没有足够icmp头部大小,则报错。
	if (!pskb_pull(skb, sizeof(*hdr)))
		goto discard_it;
	
    // 5. 从skb报文中拿到icmpv6的头部信息
	hdr = icmp6_hdr(skb);
	
    // 6. 获取cimpv6报文的类型
	type = hdr->icmp6_type;
	
    // 7. 对对应的报文类型的计数器+1
	ICMP6MSGIN_INC_STATS(dev_net(dev), idev, type);
	
    // 8. 根据报文的类型,进行不同的处理。
	switch (type) {
    // ICMPv6 echo请求报文
	case ICMPV6_ECHO_REQUEST:
        // 8.1 如果没有开启忽略全部echo报文,则对请求作出回应
		if (!net->ipv6.sysctl.icmpv6_echo_ignore_all)
			icmpv6_echo_reply(skb);
		break;
	// ICMPv6 echo 回复报文
	case ICMPV6_ECHO_REPLY:
		success = ping_rcv(skb);
		break;
	// ICMPv6 数据包过大 错误
	case ICMPV6_PKT_TOOBIG:
		// 首先检查数据块区域包含的数据块长度是否不短于ICMP报头的长度,
		// 然后调用 icmpv6_notify 对错误情况进行处理 
		if (!pskb_may_pull(skb, sizeof(struct ipv6hdr)))
			goto discard_it;
		hdr = icmp6_hdr(skb);
	case ICMPV6_DEST_UNREACH:
	case ICMPV6_TIME_EXCEED:
	case ICMPV6_PARAMPROB:
        // 以上这些均为错误消息,使用icmpv6_notify对错误消息进行处理
		icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
		break;
	// 所有邻居发现类消息都有 ndisc_rcv 进行处理
	case NDISC_ROUTER_SOLICITATION:
	case NDISC_ROUTER_ADVERTISEMENT:
	case NDISC_NEIGHBOUR_SOLICITATION: // 相当于ARP请求 
	case NDISC_NEIGHBOUR_ADVERTISEMENT:// 相当于ARP应答
	case NDISC_REDIRECT:
		ndisc_rcv(skb);
		break;
	//组播侦听者查询处理
	case ICMPV6_MGM_QUERY:
		igmp6_event_query(skb);
		break;
	// 组播侦听者报告处理
	case ICMPV6_MGM_REPORT:
		igmp6_event_report(skb);
		break;
	// 以下消息都由 icmpv6_notify 进行处理

	case ICMPV6_MGM_REDUCTION:    //退出组播组时发送消息
	case ICMPV6_NI_QUERY: //结点信息查询
	case ICMPV6_NI_REPLY: //结点信息应答
	case ICMPV6_MLD2_REPORT: //MLDv2组播侦听者报告数据包
	case ICMPV6_DHAAD_REQUEST: //ICMP归属代理地址发现请求消息
	case ICMPV6_DHAAD_REPLY: //ICMP归属代理地址发现应答消息
	case ICMPV6_MOBILE_PREFIX_SOL:
	case ICMPV6_MOBILE_PREFIX_ADV:
		break;

	default:
		// 如果是信息消息就退出,否则执行错误处理函数
		if (type & ICMPV6_INFOMSG_MASK)
			break;

		net_dbg_ratelimited("icmpv6: msg of unknown type [%pI6c > %pI6c]\n",
				    saddr, daddr);

		/*
		 * error of unknown type.
		 * must pass to upper level
		 */
		// 处理上面特殊处理的类型,默认都执行这个错误处理函数
		icmpv6_notify(skb, type, hdr->icmp6_code, hdr->icmp6_mtu);
	}

	/* until the v6 path can be better sorted assume failure and
	 * preserve the status quo behaviour for the rest of the paths to here
	 */
	if (success)
		consume_skb(skb);
	else
		kfree_skb(skb);

	return 0;
    
// 最后是错误报文计数器累加
csum_error:
	__ICMP6_INC_STATS(dev_net(dev), idev, ICMP6_MIB_CSUMERRORS);
discard_it:
	__ICMP6_INC_STATS(dev_net(dev), idev, ICMP6_MIB_INERRORS);
drop_no_count:
	kfree_skb(skb);
	return 0;
}

报文的接收流程就在上面代码中表示,处理业务主要是集中在switch的选择中。对于不同报文的处理,放在下一节来看。后面先了解一下ICMPv6 报文的发送流程。

2. ICMPv6 数据包发送流程

在ICMPv6 中,使用icmpv6_send()icmpv6_echo_reply() 发送消息,其中 icmpv6_echo_reply只是回复消息用,主力还是icmpv6_send():发送一个ICMP消息作为对错误数据包的响应

void icmp6_send(struct sk_buff *skb, u8 type, u8 code, __u32 info,
		const struct in6_addr *force_saddr,
		const struct inet6_skb_parm *parm)
{
	struct inet6_dev *idev = NULL;
	struct ipv6hdr *hdr = ipv6_hdr(skb);
	struct sock *sk;
	struct net *net;
	struct ipv6_pinfo *np;
	const struct in6_addr *saddr = NULL;
	struct dst_entry *dst;
	struct icmp6hdr tmp_hdr;
	struct flowi6 fl6;
	struct icmpv6_msg msg;
	struct ipcm6_cookie ipc6;
	int iif = 0;
	int addr_type = 0;
	int len;
	u32 mark;

	if ((u8 *)hdr < skb->head ||
	    (skb_network_header(skb) + sizeof(*hdr)) > skb_tail_pointer(skb))
		return;

	if (!skb->dev)
		return;
    // 获取网络命名空间
	net = dev_net(skb->dev);
	mark = IP6_REPLY_MARK(net, skb->mark);
	/*
	 *	Make sure we respect the rules
	 *	i.e. RFC 1885 2.4(e)
	 *	Rule (e.1) is enforced by not using icmp6_send
	 *	in any code that processes icmp errors.
	 */
	addr_type = ipv6_addr_type(&hdr->daddr);
	// 将报文的目的地址,设置成源地址,并根据地址类型进行更新
    // 用于检查hdr的目的地址是否有效,并且是否合适发往指定的网络设备skb->dev. 返回值非零则有效。
	// 或者检查hdr的目的地址是否是有效的广播或者多播地址,并确认发送设备skb->dev是否可以使用这个地址。
	if (ipv6_chk_addr(net, &hdr->daddr, skb->dev, 0) ||
	    ipv6_chk_acast_addr_src(net, skb->dev, &hdr->daddr))
        // 将报文的目的地址设置为源地址
		saddr = &hdr->daddr;

	/*
	 *	Dest addr check
	 */
    // 当遇到多播地址或非直连数据包时,除非是遇到数据包过大、未知option、未解析的option,
	// 否则把saddr设置成NULL。
	if (addr_type & IPV6_ADDR_MULTICAST || skb->pkt_type != PACKET_HOST) {
		if (type != ICMPV6_PKT_TOOBIG &&
		    !(type == ICMPV6_PARAMPROB && code == ICMPV6_UNK_OPTION &&
		      (opt_unrec(skb, info))))
			return;

		saddr = NULL;
	}

    //根据报文的源地址,来设置报文接收接口iif
	addr_type = ipv6_addr_type(&hdr->saddr);
	/*
	 *	Source addr check
	 */

	if (__ipv6_addr_needs_scope_id(addr_type)) {
        // 如果需要范围标识,iif是传入数据包的入口索引
		iif = icmp6_iif(skb);
	} else {
    	// 如果不需要索引,从skb中寻转路由转发表dst
		// 并且调用l3mdev_master_ifindex 来获取入口索引iif
		dst = skb_dst(skb);
		iif = l3mdev_master_ifindex(dst ? dst->dev : skb->dev);
	}

	/*
	 *	Must not send error if the source does not uniquely
	 *	identify a single node (RFC2463 Section 2.4).
	 *	We check unspecified / multicast addresses here,
	 *	and anycast addresses will be checked later.
	 */
    // 仅发送单播数据包,如果是任意地址或者组播地址,则返回(任意地址无法识别到发送方)
	if ((addr_type == IPV6_ADDR_ANY) || (addr_type & IPV6_ADDR_MULTICAST)) {
		net_dbg_ratelimited(
			"icmp6_send: addr_any/mcast source [%pI6c > %pI6c]\n",
			&hdr->saddr, &hdr->daddr);
		return;
	}

	/*
	 *	Never answer to a ICMP packet.
	 */
    // 如果触发的消息是ICMPv6的错误消息,则不再发送。
	if (is_ineligible(skb)) {
		net_dbg_ratelimited(
			"icmp6_send: no reply to icmp error [%pI6c > %pI6c]\n",
			&hdr->saddr, &hdr->daddr);
		return;
	}

	/* Needed by both icmp_global_allow and icmpv6_xmit_lock */
	local_bh_disable();

	/* Check global sysctl_icmp_msgs_per_sec ratelimit */
	// 如果不是通过回环接口发送的数据包,进而判断icmpv6全局速率限制不通过,则解锁退出
	if (!(skb->dev->flags & IFF_LOOPBACK) && !icmpv6_global_allow(type))
		goto out_bh_enable;

	mip6_addr_swap(skb, parm);
	
    // 初始化fl6 对象,并设置报文协议为IPPROTO_ICMPV6
	memset(&fl6, 0, sizeof(fl6));
	fl6.flowi6_proto = IPPROTO_ICMPV6; //设置报文协议
	fl6.daddr = hdr->saddr; //设置报文的目的地址
	if (force_saddr)
		saddr = force_saddr;
	if (saddr)
		fl6.saddr = *saddr; //设置报文的源地址
	fl6.flowi6_mark = mark;
	fl6.flowi6_oif = iif; //设置报文的出口ID
	fl6.fl6_icmp_type = type;
	fl6.fl6_icmp_code = code;
	fl6.flowi6_uid = sock_net_uid(net, NULL);
	fl6.mp_hash = rt6_multipath_hash(net, &fl6, skb, NULL);
    // ipsec安全性检查
	security_skb_classify_flow(skb, flowi6_to_flowi(&fl6));
	
    // 获取sk套接字,并上锁
	sk = icmpv6_xmit_lock(net);
	if (!sk)
		goto out_bh_enable;

	sk->sk_mark = mark;
	np = inet6_sk(sk);
	
	// 数据跑流量控制
	if (!icmpv6_xrlim_allow(sk, type, &fl6))
		goto out;

	tmp_hdr.icmp6_type = type;
	tmp_hdr.icmp6_code = code;
	tmp_hdr.icmp6_cksum = 0;
	tmp_hdr.icmp6_pointer = htonl(info);
	
	// 如果还没有设置报文的出口ID
	if (!fl6.flowi6_oif && ipv6_addr_is_multicast(&fl6.daddr))
		fl6.flowi6_oif = np->mcast_oif; //如果是组播地址则使用哦组播出口
	else if (!fl6.flowi6_oif)
		fl6.flowi6_oif = np->ucast_oif; //如果是单播地址则使用单拨出口
	
    // 初始化报文
	ipcm6_init_sk(&ipc6, np);
    // 获取ipv6 转发表
	fl6.flowlabel = ip6_make_flowinfo(ipc6.tclass, fl6.flowlabel);
	// 路由查找
	dst = icmpv6_route_lookup(net, skb, sk, &fl6);
	if (IS_ERR(dst))
		goto out;
	// 设置报文hlimit
	ipc6.hlimit = ip6_sk_dst_hoplimit(np, &fl6, dst);
	
    // 初始化报文msg对象。
	msg.skb = skb;
	msg.offset = skb_network_offset(skb);
	msg.type = type;

	len = skb->len - msg.offset;
	len = min_t(unsigned int, len,
		    IPV6_MIN_MTU - sizeof(struct ipv6hdr) -
			    sizeof(struct icmp6hdr));
	if (len < 0) {
		net_dbg_ratelimited("icmp: len problem [%pI6c > %pI6c]\n",
				-    &hdr->saddr, &hdr->daddr);
		goto out_dst_release;
	}

	rcu_read_lock();
	idev = __in6_dev_get(skb->dev);
	
    // 尝试发送数据包看是否符合发送条件
	if (ip6_append_data(sk, icmpv6_getfrag, &msg,
			    len + sizeof(struct icmp6hdr),
			    sizeof(struct icmp6hdr), &ipc6, &fl6,
			    (struct rt6_info *)dst, MSG_DONTWAIT)) {
		ICMP6_INC_STATS(net, idev, ICMP6_MIB_OUT
                        ERRORS);
        // 发送失败,则使用flush丢弃缓存队列
		ip6_flush_pending_frames(sk);
	} else {
        // 如果成功,则用push将数据包压入发送队列
		icmpv6_push_pending_frames(sk, &fl6, &tmp_hdr,
					   len + sizeof(struct icmp6hdr));
	}

	rcu_read_unlock();
out_dst_release:
    // 释放路由表
	dst_release(dst);
out:
    // 最后释放套接字,关闭软中断
	icmpv6_xmit_unlock(sk);
out_bh_enable:
	local_bh_enable();
}
posted @ 2024-04-05 18:02  kmist  阅读(471)  评论(0编辑  收藏  举报