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个工作:
- 对数据包进行合法性校验
- 根据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_ECHO
、ICMP_TIMESTAMP
、ICMP_ADDRESS
、ICMP_ADDRESSREPLY
类型的广播多播数据包。
并且还要根据net->ipv4.sysctl_icmp_echo_ignore_broadcasts
的值来判断是否丢弃ICMP_ECHO
和ICMP_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()
进行处理。步骤如下:
- 将 ICMPv4 的 type 改成
ICMP_ECHOREPLY
. - 使用
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_ADDRESS
和ICMP_ADDRESSREPLY
以前都有icmp_address()
和icmp_address_reply()
进行回复和应答。但是现在有更好的代替,比如DHCP
,所以现在也都使用icmp_discard()
不对报文进行处理。
以下为差错报文:
2.4 消息不可达信息的回应与请求
2.4.1 消息不可达信息的处理
消息不可达的消息类型有很多:ICMP_DEST_UNREACH
、ICMP_SOURCE_QUENCH
、ICMP_TIME_EXCEEDED
、ICMP_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 消息不可达信息的发送情况
类型:消息不可达
携带的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 时间戳超时信息
-
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_ECHO
和ICMP_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是发送的数据包。offset
header指针和data指针之间的偏移量data_len
ICMPv4 数据包的有效载荷的长度。struct icmphdr
ICMPv4 报头times[3]
存放3个时间戳head_len
ICMPv4报头长度。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:;
}