首页 > 其他分享 >3.6 ICMPv6 报文接收和发送

3.6 ICMPv6 报文接收和发送

时间:2024-04-05 18:12:54浏览次数:31  
标签:hdr 报文 dev fl6 3.6 ICMPv6 skb type

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();
}

标签:hdr,报文,dev,fl6,3.6,ICMPv6,skb,type
From: https://www.cnblogs.com/kmist/p/18116023

相关文章

  • 3.7 ICMPv6 数据包处理
    目录3.7ICMPv6数据包处理1.echo请求和应答2.错误报文处理3.发送错误报文3.1超时差错3.2目的地不可达差错3.3需要分段差错3.4参数异常差错3.7ICMPv6数据包处理1.echo请求和应答处理echo请求:icmpv6_echo_reply()处理echo回应:ping_rcv()staticvoidicmpv6_ec......
  • 3.2 ICMPv4 报文和报文类型
    目录3.2ICMPv4报文和报文类型1.ICMPv4报头2.ICMPv4报文类型3.附录3.2ICMPv4报文和报文类型1.ICMPv4报头ICMPv4是基于IP协议的。所以在ICMPv4的报文外面,还有一层IP报文格式。ICMPv4报头structicmphdr由类型(8位)、代码(8位)、校验和(16位)和可变部分(32位)......
  • 3.4 ICMPv6 初始化
    3.4ICMPv6初始化1.ICMPv6简述ICMPv6除了跟ICMPv4一样负责错误处理和诊断之外,还负责邻居发现(NeighbourDiscoveryND)和组播侦听者发现(MulticastListenerDiscoverMLD)。邻居发现(ND)--ARP(IPV4)组播侦听者发现(MLD)--IGMP(IPV4)但是这两个功能会放在后面去......
  • 如何查看java代码编写的soap请求报文头信息
    在浏览器中打开接口地址,点击浏览器中的插件查看接口点击具体方法之后可以看到请求报文头工具如下: ......
  • 常见面试算法题-报文解压缩
    ■ 题目描述为了提升数据传输的效率,会对传输的报文进行压缩处理。输入一个压缩后的报文,请返回它解压后的原始报文。压缩规则:n[str],表示方括号内部的str正好重复n次。注意n为正整数(0<n<=100),str只包含小写英文字母,不考虑异常情况。输入描述:输入压缩后的报文:1)不考......
  • 增量式修改报文校验和
    计算方法HC:旧检验和HC':新检验和m:16位修改前值m':16位修改后值RFC1624修改某个16位域校验和HC'=HC-~m-m'staticinlineunsignedshortcsum_incremental_update(unsignedshortold_csum,unsignedshortold_field,unsignedshortnew_field){__asm____volatile_......
  • S7Comm报文详解
    S7协议是西门子公司为其S7系列PLC(可编程逻辑控制器)通信而设计的一种专用协议。S7协议主要用于西门子PLC之间的通信,以及PLC与其他设备的通信。该协议支持多种通信方式,如MPI(多点接口)、PROFIBUS和IndustrialEthernet等。S7协议的报文结构相对复杂,可分为多个层次。1.简介对比OSI参......
  • macOS Ventura 13.6.6 (22G630) Boot ISO 原版可引导镜像下载
    macOSVentura13.6.6(22G630)BootISO原版可引导镜像下载3月26日凌晨,macOSSonoma14.4.1发布,同时带来了macOSVentru13.6.6安全更新。macOSVentura13.6及更新版本,如无特殊说明皆为安全更新,不再赘述。本站下载的macOS软件包,既可以拖拽到Applications(应用程序)......
  • macOS Ventura 13.6.6 (22G630) 正式版发布,ISO、IPSW、PKG 下载
    macOSVentura13.6.6(22G630)正式版发布,ISO、IPSW、PKG下载3月26日凌晨,macOSSonoma14.4.1发布,同时带来了macOSVentru13.6.6安全更新。macOSVentura13.6及更新版本,如无特殊说明皆为安全更新,不再赘述。请访问原文链接:https://sysin.org/blog/macOS-Ventura/,查看......
  • python3.6 使用调用栈储存上下文变量
    python3.6使用调用栈储存上下文变量从python3.7开始,新增contextvars模块,用于储存上下文变量.使用场景#python3.7fromcontextvarsimportContextVarimportasynciouser=ContextVar('var')asyncdefb():u=user.get()print(f'getname{u}')......