3.3 ICMPv4 数据包的接收和发送

3.3 ICMPv4 数据包的接收和发送

1. 接收ICMPv4 数据包

因为ICPM报文属于IP报文。所以ICMP将通过 ip_input()最终来到ip_local_deliver_finish()这是个处理本地ip数据包的入口函数。

static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
    ...
    // 在IP报文头部获取协议类型
    int protocol = ip_hdr(skb)->protocol;
	...
    // 尝试寻找原始套接字处理
    raw = raw_local_deliver(skb, protocol);
    ...
    // 根据ip模块初始化注册好的协议,找到相应的对象
    ipprot = rcu_dereference(inet_protos[protocol]);

    if (ipprot) {
        // 执行协议对应的处理函数,对于ICMPv4来说,是icmp_rcv()
        ret = ipprot->handler(skb);
    }			
	...
	return 0;
}

通过在inet_protos[]中匹配到 icmp 的注册对象,找到icmp_rcv()并执行。

该函数做了2个工作:

  1. 对数据包进行合法性校验
  2. 根据icmp报文类型进行处理
int icmp_rcv(struct sk_buff *skb)
{
	struct icmphdr *icmph;
	struct rtable *rt = skb_rtable(skb);
	struct net *net = dev_net(rt->dst.dev);
	bool success;
    
	// xfrm4为ipsec的安全校验,这边先过。
	if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
		...
	}

	// ICMP的进栈数据包计数原子+1
	// net->mib->icmp_statistics.mibs[ICMP_MIB_INMSGS] += 1;
	__ICMP_INC_STATS(net, ICMP_MIB_INMSGS);

	// 验证报文完整性
	if (skb_checksum_simple_validate(skb))
		goto csum_error;

	// data指针往下偏移 icmp头部结构体的大小,确保icmp头部完整。如果没有足够icmp头部大小,则报错。
	if (!pskb_pull(skb, sizeof(*icmph)))
		goto error;

	// 从skb中 返回icmp头部指针。       这个就是在ip头部之后。
	icmph = icmp_hdr(skb);

	// 从icmp 头部中获取icmp报文的类型,并且对这类报文的计数器+1
	ICMPMSGIN_INC_STATS(net, icmph->type);
	/*
	 *	18 is the highest 'known' ICMP type. Anything else is a mystery
	 *
	 *	RFC 1122: 3.2.2  Unknown ICMP messages types MUST be silently
	 *		  discarded.
	 */
	// 类型校验,如果类型超过最大值,则报错
	if (icmph->type > NR_ICMP_TYPES)
		goto error;

	/*
	 *	Parse the ICMP message
	 */
	// 对广播和多播的处理
	// 只处理 ICMP_ECHO、ICMP_TIMESTAMP、ICMP_ADDRESS、ICMP_ADDRESSREPLY类型的多播数据包
	// 如果开启了忽略echo广播包,那么也丢弃ICMP_ECHO、ICMP_TIMESTAMP类型的广播包
	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {
		if ((icmph->type == ICMP_ECHO ||
		     icmph->type == ICMP_TIMESTAMP) &&
		    net->ipv4.sysctl_icmp_echo_ignore_broadcasts) {
			goto error;
		}
		if (icmph->type != ICMP_ECHO && icmph->type != ICMP_TIMESTAMP &&
		    icmph->type != ICMP_ADDRESS &&
		    icmph->type != ICMP_ADDRESSREPLY) {
			goto error;
		}
	}

	// 根据报文类型处理
	success = icmp_pointers[icmph->type].handler(skb);

	if (success) {
		consume_skb(skb);
		return NET_RX_SUCCESS;
	}

drop:
	kfree_skb(skb);
	return NET_RX_DROP;
csum_error:
	__ICMP_INC_STATS(net, ICMP_MIB_CSUMERRORS);
error:
	__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
	goto drop;
}

系统只处理 ICMP_ECHOICMP_TIMESTAMPICMP_ADDRESSICMP_ADDRESSREPLY类型的广播多播数据包。
并且还要根据net->ipv4.sysctl_icmp_echo_ignore_broadcasts的值来判断是否丢弃ICMP_ECHOICMP_TIMESTAMP

ICMPv4的不同报文类型都注册在了icmp_pointers[icmph->type]数组中。见章节 3.2 ICMPv4 报文和报文类型。下文将对每一个处理函数展开。

2. ICMPv4报文处理

下面根据查询函数和差错函数分别展开介绍。

2.1 ping请求和应答

  • 处理 ping 请求 icmp_echo()
  • 处理 ping 应答 ping_rcv()

当收到一个来自远方的 ping 请求报文(Type=8)时,使用icmp_echo()进行处理。步骤如下:

  1. 将 ICMPv4 的 type 改成ICMP_ECHOREPLY.
  2. 使用icmp_reply()将数据包发送出去。
static bool icmp_echo(struct sk_buff *skb)
{
	struct net *net;

	net = dev_net(skb_dst(skb)->dev);
	if (!net->ipv4.sysctl_icmp_echo_ignore_all) {
		struct icmp_bxm icmp_param;

		icmp_param.data.icmph = *icmp_hdr(skb);
		icmp_param.data.icmph.type = ICMP_ECHOREPLY;
		icmp_param.skb = skb;
		icmp_param.offset = 0;
		icmp_param.data_len = skb->len;
		icmp_param.head_len = sizeof(struct icmphdr);
		icmp_reply(&icmp_param, skb);
	}
	/* should there be an ICMP stat for ignored echos? */
	return true;
}

当发送者收到了一个 ping 的回应ICMP_ECHOREPLY之后,它将使用ping_rcv()进行处理。步骤如下:

bool ping_rcv(struct sk_buff *skb)
{
	struct sock *sk;
	struct net *net = dev_net(skb->dev);
	struct icmphdr *icmph = icmp_hdr(skb);
	bool rc = false;
	...
     
    // 将skb的data指针指回icmp的头部
	skb_push(skb, skb->data - (u8 *)icmph);
	// 寻找指定的网络命名空间种寻找与接收的icmp同款id的套接字,如果没有找到对应的套接字服务,则不做处理。
	sk = ping_lookup(net, skb, ntohs(icmph->un.echo.id));
	if (sk) {
        // 如果找到了套接字,进行原子克隆报文,
		struct sk_buff *skb2 = skb_clone(skb, GFP_ATOMIC);
		...
        //并将克隆的报文加入找到的套接字的接收队列中。发送给感兴趣的应用端
		if (skb2 && !ping_queue_rcv_skb(sk, skb2))
			rc = true;
		sock_put(sk);
	}

	if (!rc)
		pr_debug("no socket, dropping\n");

	return rc;
}

ping_rcv() 最终会将报文发送给感兴趣的应用程序。

2.2 时间戳请求与应答

  • 处理时间戳请求:icmp_timestamp()
  • 处理时间戳应答:icmp_discard()

当收到一个时间戳请求ICMP_TIMESTAMP时,我们需要使用icmp_timestamp()进行处理。ICMPv4 时间戳请求允许系统向另一个系统查询当前时间。返回值是从午夜0点开始的计数的毫秒数(UTC)。现在获取时间有更通用的方式(SNTP),所以这边就当学习了解以下。处理步骤如下:

  • 填写当前UTC时间戳

  • 把报文类型改成ICMP_TIMESTAMPREPLY

  • 使用icmp_reply()进行发送

static bool icmp_timestamp(struct sk_buff *skb)
{
	struct icmp_bxm icmp_param;

	/*
	 *	Fill in the current time as ms since midnight UT:
	 */
	icmp_param.data.times[1] = inet_current_timestamp(); //接收时间戳
	icmp_param.data.times[2] = icmp_param.data.times[1];

	icmp_param.data.icmph = *icmp_hdr(skb);
	icmp_param.data.icmph.type = ICMP_TIMESTAMPREPLY;
	icmp_param.data.icmph.code = 0;
	icmp_param.skb = skb;
	icmp_param.offset = 0;
	icmp_param.data_len = 0;
	icmp_param.head_len = sizeof(struct icmphdr) + 12;
	icmp_reply(&icmp_param, skb);
	return true;
	...
}

接收方发送包含3个时间戳。[0]是发送方发送的时间戳[1]是接收方接收的时间戳[2]是接收方发送的时间。实际上,[1]和[2]往往是一样的。

在收到ICMP_TIMESTAMPREPLY的消息之后,以前是有处理的,现在因为有了更新的时间同步服务。所以现在收到这个信息之后,执行icmp_discard()不对报文做处理。

2.3 地址掩码请求和回应

ICMP_ADDRESSICMP_ADDRESSREPLY以前都有icmp_address()icmp_address_reply()进行回复和应答。但是现在有更好的代替,比如DHCP,所以现在也都使用icmp_discard()不对报文进行处理。

以下为差错报文:

2.4 消息不可达信息的回应与请求

2.4.1 消息不可达信息的处理

消息不可达的消息类型有很多:ICMP_DEST_UNREACHICMP_SOURCE_QUENCHICMP_TIME_EXCEEDEDICMP_PARAMETERPROB他们都使用icmp_unreach()处理。

核心逻辑是:根据icmp有效载荷中的值,调用传输层的错误处理函数进行处理

static bool icmp_unreach(struct sk_buff *skb)
{
	const struct iphdr *iph;
	struct icmphdr *icmph;
	struct net *net;
	u32 info = 0;

	// 获取网络空间
	net = dev_net(skb_dst(skb)->dev);

	if (!pskb_may_pull(skb, sizeof(struct iphdr)))
		goto out_err;

	// 获取ICMP头部
	icmph = icmp_hdr(skb);
	iph = (const struct iphdr *)skb->data;

	// 判断IP首部是否完整
	if (iph->ihl < 5) /* Mangled header, drop. */
		goto out_err;

	// 仅处理ICMP_DEST_UNREACH、ICMP_PARAMETERPROB、ICMP_TIME_EXCEEDED类型的报文
	/* 在ICMP_DEST_UNREACH中,仅处理
			ICMP_FRAG_NEEDED 需要分片但设置了不分片bit
			ICMP_SR_FAILED 源站选路失败
			*/
	switch (icmph->type) {
	case ICMP_DEST_UNREACH:
		switch (icmph->code & 15) {
		case ICMP_NET_UNREACH:
		case ICMP_HOST_UNREACH:
		case ICMP_PROT_UNREACH:
		case ICMP_PORT_UNREACH:
			break; //不处理
		case ICMP_FRAG_NEEDED:
			switch (net->ipv4.sysctl_ip_no_pmtu_disc) {
			default:
				net_dbg_ratelimited(
					"%pI4: fragmentation needed and DF set\n",
					&iph->daddr);
				break;
			case 2:
				goto out;
			case 3:
				if (!icmp_tag_validation(iph->protocol))
					goto out;
				/* fall through */
			case 0:
				info = ntohs(icmph->un.frag.mtu);
			}
			break;
		case ICMP_SR_FAILED:
			net_dbg_ratelimited("%pI4: Source Route Failed\n",
					    &iph->daddr);
			break;
		default:
			break;
		}
		if (icmph->code > NR_ICMP_UNREACH)
			goto out;
		break;
	case ICMP_PARAMETERPROB:
		info = ntohl(icmph->un.gateway) >> 24;
		break;
	case ICMP_TIME_EXCEEDED:
		// 超时计数器+1
		__ICMP_INC_STATS(net, ICMP_MIB_INTIMEEXCDS);
		// 如果是分片超时,则不处理
		if (icmph->code == ICMP_EXC_FRAGTIME)
			goto out;
		break;
	}

	// 对于目的地址是广播的icmp数据包,且需要忽略时,则打印错误消息,并忽略处理
	if (!net->ipv4.sysctl_icmp_ignore_bogus_error_responses &&
	    inet_addr_type_dev_table(net, skb->dev, iph->daddr) ==
		    RTN_BROADCAST) {
		net_warn_ratelimited(
			"%pI4 sent an invalid ICMP type %u, code %u error to a broadcast: %pI4 on %s\n",
			&ip_hdr(skb)->saddr, icmph->type, icmph->code,
			&iph->daddr, skb->dev->name);
		goto out;
	}

	// 核心:调用4层协议进行错误处理
	icmp_socket_deliver(skb, info);

out:
	return true;
out_err:
	__ICMP_INC_STATS(net, ICMP_MIB_INERRORS);
	return false;
}

2.4.2 消息不可达信息的发送情况

image-20240325170537678

类型:消息不可达携带的code字段从0-15 如上图所示。下面将挑选一些常用的进行展开。

  • code2 协议不可达 ICMP_PROT_UNREACH
    当收到一条ip协议不存在(或者无效,无监听)时,将向发送方回复一条 ICMP_DEST_UNREACH/ICMP_PROT_UNREACH消息。

    static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
    {
        ...
            if (!raw) {
                if (xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
                    __IP_INC_STATS(net, IPSTATS_MIB_INUNKNOWNPROTOS);
                    icmp_send(skb, ICMP_DEST_UNREACH,
                              ICMP_PROT_UNREACH, 0); // 协议不可达。
                }
                kfree_skb(skb);
            } 
        ...
    }
    
  • code3 端口不可达 ICMP_PORT_UNREACH
    当接收 UDPv4数据包时,将查找匹配的UDP套接字。如果没有找到匹配的套接字,将检查校验和是否正确。
    如果校验和错误,则丢弃报文。如果校验和正确,则更新报文统计信息,并且发送一条 端口不可达 消息。

    int __udp4_lib_rcv(struct sk_buff *skb, struct udp_table *udptable,
    		   int proto)
    {
    	...
        // 查找匹配的套接字
    	sk = __udp4_lib_lookup_skb(skb, uh->source, uh->dest, udptable);
    	if (sk) //如果有找到目标,则处理并返回。
    		return udp_unicast_rcv_skb(sk, skb, uh);
    
    	...
    
    	/* No socket. Drop packet silently, if checksum is wrong */
    	if (udp_lib_checksum_complete(skb))
    		goto csum_error;
    	// 未找到端口,更新统计,并发送ICMP_PORT_UNREACH报文。
    	__UDP_INC_STATS(net, UDP_MIB_NOPORTS, proto == IPPROTO_UDPLITE);
    	icmp_send(skb, ICMP_DEST_UNREACH, ICMP_PORT_UNREACH, 0);
    ...
    }
    
  • code4 分片需求 ICMP_FRAG_NEEDED
    转发数据包时,如果长度超过了外出链路的MTU,并且IPV4的报头中没有设置分片位(DF),将把数据包丢弃。并且向发送方发送一条code为ICMP_FRAG_NEEDED的不可达消息。

    int ip_forward(struct sk_buff *skb)
    {
    	...
    	mtu = ip_dst_mtu_maybe_forward(&rt->dst, true);
    	if (ip_exceeds_mtu(skb, mtu)) {
    		IP_INC_STATS(net, IPSTATS_MIB_FRAGFAILS);
    		icmp_send(skb, ICMP_DEST_UNREACH, ICMP_FRAG_NEEDED,
    			  htonl(mtu));
    		goto drop;
    	}
        ...
    }
    
  • code5 源站选路失败 ICMP_SR_FAILED
    在转发数据包时,如果指定了严格路由选择(strict routing)网关(gatewaying),将丢弃数据包,并发回一条code为ICMP_SR_FAILED的不可达消息。

    int ip_forward(struct sk_buff *skb)
    {
    	struct ip_options *opt	= &(IPCB(skb)->opt);
    	...
    	if (opt->is_strictroute && rt->rt_uses_gateway)
    		goto sr_failed;
    	...
    sr_failed:
    	 icmp_send(skb, ICMP_DEST_UNREACH, ICMP_SR_FAILED, 0);
    	 goto drop;
        ...
    }
    

2.5 网关发出的重定向

ICMP_REDIRCT由网关和路由发出,告诉主机,有存在更优的传输路径。在主机收到这个报文之后,决定是否使用更优解的报文路径,但是要注意网络安全,注意欺骗。

static bool icmp_redirect(struct sk_buff *skb)
{
	if (skb->len < sizeof(struct iphdr)) {
		__ICMP_INC_STATS(dev_net(skb->dev), ICMP_MIB_INERRORS);
		return false;
	}

	if (!pskb_may_pull(skb, sizeof(struct iphdr))) {
		/* there aught to be a stat */
		return false;
	}
	// 将数据包交给原始套接字处理。
	icmp_socket_deliver(skb, icmp_hdr(skb)->un.gateway);
	return true;
}

2.6 时间戳超时信息

image-20240325173318465

  • code0 TTL超时 ICMP_EXC_TTL
    在ip_forward()中,数据包的TTL都会被减去。如果TTL的值变成了0.就表明应该将数据包丢弃掉。并发送ICMP_EXC_TTL消息。

    int ip_forward(struct sk_buff *skb)
    {
    	...
    	if (ip_hdr(skb)->ttl <= 1)
    		goto too_many_hops;
    	...
    	/* Decrease ttl after skb cow done */
    	ip_decrease_ttl(iph);
    
    
    too_many_hops:
    	/* Tell the sender its packet died... */
    	__IP_INC_STATS(net, IPSTATS_MIB_INHDRERRORS);
    	icmp_send(skb, ICMP_TIME_EXCEEDED, ICMP_EXC_TTL, 0);
    }
    
  • cdoe1 分片超时 ICMP_EXC_FRAGTIME
    ip_expire()中,如果分片超时,将发送ICMP_EXC_FRAGTIME的消息。

    static void ip_expire(struct timer_list *t)
    {
    	...
    	icmp_send(head, ICMP_TIME_EXCEEDED, ICMP_EXC_FRAGTIME, 0);
    	...
    }
    

3. ICMPv4报文发送

在上文中涉及到了2种数据包发送的接口icmp_reply()icmp_send()

  • icmp_reply() 用于发送ICMP_ECHOICMP_TIMESTAMP的响应。

  • icmp_send() 用于发送当前机器在特定条件下主动发送ICMPv4报文。

    这两个方法最终都调用icmp_push_reply()来发送报文。

  • icmp_reply()icmp_send() 都支持速率限制,他们调用 icmpv4_xrlim_allow()来进行判断。

发送ICMP报文,涉及到一个结构体 struct icmp_bxm

struct icmp_bxm {
	struct sk_buff *skb;
	int offset;
	int data_len;

	struct {
		struct icmphdr icmph;
		__be32 times[3];
	} data;
	int head_len;
	struct ip_options_data replyopts;
};
  • skb 对于icmp_reply()来说,skb为请求数据包;在 icmp_echo()icmp_timestamp()中,用它来创建icmp_param对象。对于icmp_send()来说,skb是发送的数据包。
  • offsetheader指针和data指针之间的偏移量
  • data_lenICMPv4 数据包的有效载荷的长度。
  • struct icmphdr ICMPv4 报头
  • times[3] 存放3个时间戳
  • head_lenICMPv4报头长度。
  • replyopts IP选项。支持高级功能,如严格路由选择、宽松路由选择、记录路由选择、时间戳等。

3.1 icmp_reply()

icmp_reply的流程如下:

static void icmp_reply(struct icmp_bxm *icmp_param, struct sk_buff *skb)
{
	struct ipcm_cookie ipc;

	// 获取原始请求包的路由信息
	struct rtable *rt = skb_rtable(skb);
	struct net *net = dev_net(rt->dst.dev);
	struct flowi4 fl4;
	struct sock *sk;
	struct inet_sock *inet;
	__be32 daddr, saddr;

	// 根据skb的标记设置新的报文标记
	u32 mark = IP4_REPLY_MARK(net, skb->mark);
	int type = icmp_param->data.icmph.type;
	int code = icmp_param->data.icmph.code;

	if (ip_options_echo(net, &icmp_param->replyopts.opt.opt, skb))
		return;

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

	// 根据 type 和 code 字段,检查当前网络环境下是否允许发送这种类型的ICMP消息
	if (!icmpv4_global_allow(net, type, code))
		goto out_bh_enable;

	// 加锁并尝试获取一个用于发送ICMP回应的套接字
	sk = icmp_xmit_lock(net);
	if (!sk)
		goto out_bh_enable;
	inet = inet_sk(sk);

	// 清零ICMP报文校验和,并准备相关的IP选项和特殊地址信息。
	icmp_param->data.icmph.checksum = 0;

	// 使用原始请求包的源IP地址作为ICMP回复的目的地址,同时计算本地发送接口的源IP地址。
	ipcm_init(&ipc);
	inet->tos = ip_hdr(skb)->tos;
	sk->sk_mark = mark;
	daddr = ipc.addr = ip_hdr(skb)->saddr;
	saddr = fib_compute_spec_dst(skb);

	// 根据IP选项(如源路由记录SRR)更新目的地址。
	if (icmp_param->replyopts.opt.opt.optlen) {
		ipc.opt = &icmp_param->replyopts.opt;
		if (ipc.opt->opt.srr)
			daddr = icmp_param->replyopts.opt.opt.faddr;
	}
	// flowi4 用于描述IP路由查找的关键参数
	memset(&fl4, 0, sizeof(fl4));
	fl4.daddr = daddr;
	fl4.saddr = saddr;
	fl4.flowi4_mark = mark;
	fl4.flowi4_uid = sock_net_uid(net, NULL);
	fl4.flowi4_tos = RT_TOS(ip_hdr(skb)->tos);
	fl4.flowi4_proto = IPPROTO_ICMP;
	fl4.flowi4_oif = l3mdev_master_ifindex(skb->dev);
	security_skb_classify_flow(skb, flowi4_to_flowi(&fl4));
	rt = ip_route_output_key(net, &fl4);
	if (IS_ERR(rt))
		goto out_unlock;

	// 校验发送速率许可,如果允许发送,则调用icmp_push_reply进行发送
	if (icmpv4_xrlim_allow(net, rt, &fl4, type, code))
		icmp_push_reply(icmp_param, &fl4, &ipc, &rt);

	// 发送完成之后,释放路由表的引用
	ip_rt_put(rt);
out_unlock:
	icmp_xmit_unlock(sk);
out_bh_enable:
	local_bh_enable();
}

3.2 icmp_send()

icmp_send的核心逻辑如下:

  • 不能使用icmp_send() error 数据包的条件:

    • 对于源数据包时多播的数据包,不发送icmp error 报文。
    • 对于源数据包有分段时,仅针对首个报文发送icmp error报文。
    • 对于源数据包本身就是icmp error类型的报文,不发送icmp error报文。
  • 流程:

    • 查找路由
    • 当路由查找成功之后,调用icmp_push_reply()将数据发送出去。
void __icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info,
		 const struct ip_options *opt)
{
	...
	iph = ip_hdr(skb_in);
	...
	// 不回复物理mac层组播、广播
	if (skb_in->pkt_type != PACKET_HOST)
		goto out;

	// 不回复网络组播、广播
	if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST))
		goto out;

	// 仅回复分段受段
	if (iph->frag_off & htons(IP_OFFSET))
		goto out;

	// 判断接收的数据包是不是一个ICMP的错误消息,如果是,则不对该数据包回复ICMP错误消息。
	if (icmp_pointers[type].error) {
		...
	}
	...
    // 关闭软中断,并为sosket加自旋锁,确保同一时刻是由1个icmp报文发送出去。
	sk = icmp_xmit_lock(net);
	if (!sk)
		goto out_bh_enable;

	// 设置发送报文的源ip地址 saddr
	saddr = iph->daddr;
	if (!(rt->rt_flags & RTCF_LOCAL)) {
		struct net_device *dev = NULL;

		rcu_read_lock();
		if (rt_is_input_route(rt) &&
		    net->ipv4.sysctl_icmp_errors_use_inbound_ifaddr)
			dev = dev_get_by_index_rcu(net, inet_iif(skb_in));

		if (dev)
			saddr = inet_select_addr(dev, 0, RT_SCOPE_LINK);
		else
			saddr = 0;
		rcu_read_unlock();
	}
	// 设置tos值
	tos = icmp_pointers[type].error ? ((iph->tos & IPTOS_TOS_MASK) |
					   IPTOS_PREC_INTERNETCONTROL) :
					  iph->tos;
	mark = IP4_REPLY_MARK(net, skb_in->mark);

	if (__ip_options_echo(net, &icmp_param.replyopts.opt.opt, skb_in, opt))
		goto out_unlock;

	// 设置icmp的头部信息
	icmp_param.data.icmph.type = type;
	icmp_param.data.icmph.code = code;
	icmp_param.data.icmph.un.gateway = info;
	icmp_param.data.icmph.checksum = 0;
	icmp_param.skb = skb_in;
	icmp_param.offset = skb_network_offset(skb_in);
	inet_sk(sk)->tos = tos;
	sk->sk_mark = mark;
	ipcm_init(&ipc);
	ipc.addr = iph->saddr;
	ipc.opt = &icmp_param.replyopts.opt;
	// 获取路由
	rt = icmp_route_lookup(net, &fl4, skb_in, iph, saddr, tos, mark, type,
			       code, &icmp_param);
	if (IS_ERR(rt))
		goto out_unlock;

	/* peer icmp_ratelimit */
    // 检查速率控制
	if (!icmpv4_xrlim_allow(net, rt, &fl4, type, code))
		goto ende;

	/* RFC says return as much as we can without exceeding 576 bytes. */

	room = dst_mtu(&rt->dst);
	if (room > 576)
		room = 576;
	room -= sizeof(struct iphdr) + icmp_param.replyopts.opt.opt.optlen;
	room -= sizeof(struct icmphdr);
	/* Guard against tiny mtu. We need to include at least one
	 * IP network header for this message to make any sense.
	 */
	if (room <= (int)sizeof(struct iphdr))
		goto ende;

	icmp_param.data_len = skb_in->len - icmp_param.offset;
	if (icmp_param.data_len > room)
		icmp_param.data_len = room;
	icmp_param.head_len = sizeof(struct icmphdr);

	/* if we don't have a source address at this point, fall back to the
	 * dummy address instead of sending out a packet with a source address
	 * of 0.0.0.0
	 */
	if (!fl4.saddr)
		fl4.saddr = htonl(INADDR_DUMMY);
	// 发送icmp报文
	icmp_push_reply(&icmp_param, &fl4, &ipc, &rt);
ende:
	ip_rt_put(rt);
out_unlock:
	icmp_xmit_unlock(sk);
out_bh_enable:
	local_bh_enable();
out:;
}
posted @ 2024-04-05 17:57  kmist  阅读(168)  评论(0编辑  收藏  举报