ICMP报文主要用于在网络系统中传递控制信息,如网络不可达,回显(ping),时间请求,掩码请求等。ICMP报文被IP层或者更高层(TCP/UDP)使用,一些用户进程也可以监听ICMP报文.
8位类型码(type),8位代码(code),type决定大的分类,code进一步细分ICMP报文种类,
协议规定,ICMP数据部分必须包含出错协议数据包的IP报头+8Byte. 这样是为了能准把icmp报文传递给上层协议和进程
最常见的是:目的不可达, 回显应答,每种类型的ICMP报文,又可以根据code字段进一步细分。
在af_inet初始化时,会注册协议为IPPROTO_ICMP的处理函数
static const struct net_protocol icmp_protocol = {
.handler = icmp_rcv,
.err_handler = icmp_err,
.no_policy = 1,
.netns_ok = 1,
};
IP层通过判断protocol字段,调用icmp_rcv处理icmp报文,具体细分到每种icmp消息,又会有不同的处理函数,所有支持的icmp消息都定义在数组icmp_pointers中
static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
/*回显应答处理函数*/
[ICMP_ECHOREPLY] = {
.handler = ping_rcv,
},
/*1-2是虚构的类型 */
[1] = {
.handler = icmp_discard,
.error = 1,
},
[2] = {
.handler = icmp_discard,
.error = 1,
},
/*目的不可达差错报文处理 */
[ICMP_DEST_UNREACH] = {
.handler = icmp_unreach,
.error = 1,
},
/*源端关闭*/
[ICMP_SOURCE_QUENCH] = {
.handler = icmp_unreach,
.error = 1,
},
/*重定向 */
[ICMP_REDIRECT] = {
.handler = icmp_redirect,
.error = 1,
},
[6] = {
.handler = icmp_discard,
.error = 1,
},
[7] = {
.handler = icmp_discard,
.error = 1,
},
/*回显 */
[ICMP_ECHO] = {
.handler = icmp_echo,
},
[9] = {
.handler = icmp_discard,
.error = 1,
},
[10] = {
.handler = icmp_discard,
.error = 1,
},
[ICMP_TIME_EXCEEDED] = {
.handler = icmp_unreach,
.error = 1,
},
[ICMP_PARAMETERPROB] = {
.handler = icmp_unreach,
.error = 1,
},
[ICMP_TIMESTAMP] = {
.handler = icmp_timestamp,
},
[ICMP_TIMESTAMPREPLY] = {
.handler = icmp_discard,
},
[ICMP_INFO_REQUEST] = {
.handler = icmp_discard,
},
[ICMP_INFO_REPLY] = {
.handler = icmp_discard,
},
/*地址掩码 */
[ICMP_ADDRESS] = {
.handler = icmp_discard,
},
[ICMP_ADDRESSREPLY] = {
.handler = icmp_discard,
},
};
这里以目的不可达来分析
static void 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);
/*获取icmp报头 */
icmph = icmp_hdr(skb);
iph = (const struct iphdr *)skb->data;
if (icmph->type == ICMP_DEST_UNREACH) {
switch (icmph->code & 15) {
/*这几种类型的ICMP不需要进行处理 */
case ICMP_NET_UNREACH:
case ICMP_HOST_UNREACH:
case ICMP_PROT_UNREACH:
case ICMP_PORT_UNREACH:
break;
case ICMP_FRAG_NEEDED:/*需要分片 */
if (ipv4_config.no_pmtu_disc) {
LIMIT_NETDEBUG(KERN_INFO pr_fmt("%pI4: fragmentation needed and DF set\n"),
&iph->daddr);
} else {
info = ntohs(icmph->un.frag.mtu);
}
break;
case ICMP_SR_FAILED:
LIMIT_NETDEBUG(KERN_INFO pr_fmt("%pI4: Source Route Failed\n"),
&iph->daddr);
break;
default:
break;
}
if (icmph->code > NR_ICMP_UNREACH)
goto out;
} else if (icmph->type == ICMP_PARAMETERPROB)/*参数出错 */
info = ntohl(icmph->un.gateway) >> 24;
if (!net->ipv4.sysctl_icmp_ignore_bogus_error_responses &&
inet_addr_type(net, 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;
}
/* 进一步处理数据包*/
icmp_socket_deliver(skb, info);
out:
return;
out_err:
ICMP_INC_STATS_BH(net, ICMP_MIB_INERRORS);
goto out;
}
static void icmp_socket_deliver(struct sk_buff *skb, u32 info)
{
const struct iphdr *iph = (const struct iphdr *) skb->data;
const struct net_protocol *ipprot;
int protocol = iph->protocol;
/*把icmp数据传递给ram socket,因为raw socket可能实现上层协议 */
raw_icmp_error(skb, protocol, info);
rcu_read_lock();
ipprot = rcu_dereference(inet_protos[protocol]);
/*传递给上层协议如:udp和tcp */
if (ipprot && ipprot->err_handler)
ipprot->err_handler(skb, info);
rcu_read_unlock();
}
icmp的输出由函数icmp_send函数处理,但网络数据包处理出错时,由上层协议调用,
void icmp_send(struct sk_buff *skb_in, int type, int code, __be32 info)
传递参数有type和code信息,处理完后,调用icmp_route_lookup把数据包发送到对端网络设备