什么是ICMP?
IMCP主要用作发送有关网络层(L3)错误和控制消息的机制,让你能够通过发送ICMP消息来获取有关通信环境重问题的反馈。这些消息提供了错误处理和诊断功能。
ICMPv4
ICMP消息分为两类:错误消息和信息消息(查询消息)。
1、linux工具
ping,打开一个原始套接字并发送一条ICMP_ECHO消息,进而收到以ICMP_REPLY消息的方式返回的响应。
traceroute,一个用于确定主机和给定IP目标间路径的工具。它通过将IP包头中表示跳数的字段“存活时间TTL”设置为不同值来完成工作的。数据包的TTL变成0后,转发设备将发回一条ICMP_TIME_EXCEED消息,从而知道中间设备的ip。
2、ICMPv4报头
ICMPv4报头由类型(8位)、代码(8位)、校验和(16位)和32位的可变部分(其内容取决于ICMPv4的类型和代码)组成。后面的有效载荷,其中包含原始数据包的IPv4报头和部分有效载荷。
ICMPv4类型具体如下表
类型 | 正式名称 | E/I | 用途/注释 |
0 | 回显应答 | I | 回显(ping)应答,返回数据 |
3 | 目的不可到达 | E | 不可到达的主机/协议 |
4 | 源端抑制 | E | 表示拥塞(弃用) |
5 | 重定向 | E | 表示应该被使用的可选路由器 |
8 | 回显 | I | 回显(ping)请求(数据可选) |
9 | 路由器通告 | I | 指示路由器地址/优先级 |
10 | 路由器请求 | I | 请求路由器通告 |
11 | 超时 | E | 资源耗尽(例如IPv4TTL) |
12 | 参数问题 | E | 有问题的数据包或者头部 |
3、ICMPv4的初始化
引导阶段net/ipv4/icmp.c的inet_init()中注册icmp协议以及初始化icmp.
static const struct net_protocol icmp_protocol = {
.handler = icmp_rcv,
.err_handler = icmp_err,
.no_policy = 1,
.netns_ok = 1,
};
static int __init inet_init(void)
{
...
if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)//注册icmp协议
pr_crit("%s: Cannot add ICMP protocol\n", __func__);
if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
pr_crit("%s: Cannot add UDP protocol\n", __func__);
if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
pr_crit("%s: Cannot add TCP protocol\n", __func__);
#ifdef CONFIG_IP_MULTICAST
if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
pr_crit("%s: Cannot add IGMP protocol\n", __func__);
#endif
...
ping_init();
/*
* Set the ICMP layer up
*/
if (icmp_init() < 0)//初始化icmp
panic("Failed to create the ICMP control socket.\n");
...
}
ICMPv4初始化,icmp_sk_init函数为调用inet_ctl_sock_create每个CPU简历一个套接字,传输内核产生的ICMP消息。访问当前套接字可以使用icmp_sk(struct net *net)。
static int __net_init icmp_sk_init(struct net *net)
{
int i, err;
net->ipv4.icmp_sk = alloc_percpu(struct sock *);
if (!net->ipv4.icmp_sk)
return -ENOMEM;
for_each_possible_cpu(i) {
struct sock *sk;
err = inet_ctl_sock_create(&sk, PF_INET,
SOCK_RAW, IPPROTO_ICMP, net);
if (err < 0)
goto fail;
*per_cpu_ptr(net->ipv4.icmp_sk, i) = sk;
/* Enough space for 2 64K ICMP packets, including
* sk_buff/skb_shared_info struct overhead.
*/
sk->sk_sndbuf = 2 * SKB_TRUESIZE(64 * 1024);
/*
* Speedup sock_wfree()
*/
sock_set_flag(sk, SOCK_USE_WRITE_QUEUE);
inet_sk(sk)->pmtudisc = IP_PMTUDISC_DONT;
}
...
}
static struct pernet_operations __net_initdata icmp_sk_ops = {
.init = icmp_sk_init,
.exit = icmp_sk_exit,
};
int __init icmp_init(void)
{
return register_pernet_subsys(&icmp_sk_ops);
}
4、struct icmp_control
每种ICMP类型都有一个icmp_control数据结构实例。
handler是由icmp_rcv调用的函数,用于处理具体的ICMP报文。
error是当ICMP类型被分类为错误时此标记被设定。
static const struct icmp_control icmp_pointers[NR_ICMP_TYPES + 1] = {
[ICMP_ECHOREPLY] = {
.handler = ping_rcv,
},
[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,
},
};
4、接受ICMPv4信息
方法ip_local_deliver_finish()处理目的地位当前机器的数据包。收到ICMP数据包后,这个方法便将
其交给已经注册的ICMPv4即icmp_rcv()
static int ip_local_deliver_finish(struct net *net, struct sock *sk, struct sk_buff *skb)
{
...
resubmit:
raw = raw_local_deliver(skb, protocol);
ipprot = rcu_dereference(inet_protos[protocol]);
if (ipprot) {
int ret;
if (!ipprot->no_policy) {
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
kfree_skb(skb);
goto out;
}
nf_reset(skb);
}
ret = ipprot->handler(skb);
...
}
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;
if (!xfrm4_policy_check(NULL, XFRM_POLICY_IN, skb)) {
struct sec_path *sp = skb_sec_path(skb);
int nh;
if (!(sp && sp->xvec[sp->len - 1]->props.flags &
XFRM_STATE_ICMP))
goto drop;
if (!pskb_may_pull(skb, sizeof(*icmph) + sizeof(struct iphdr)))
goto drop;
nh = skb_network_offset(skb);
skb_set_network_header(skb, sizeof(*icmph));
if (!xfrm4_policy_check_reverse(NULL, XFRM_POLICY_IN, skb))
goto drop;
skb_set_network_header(skb, nh);
}
__ICMP_INC_STATS(net, ICMP_MIB_INMSGS);//inMesgs SNMP技术器加1
if (skb_checksum_simple_validate(skb))//核实校验和是否正确
goto csum_error;
if (!pskb_pull(skb, sizeof(*icmph)))
goto error;
icmph = icmp_hdr(skb);
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)//检查icmp类型
goto error;
/*
* Parse the ICMP message
*/
if (rt->rt_flags & (RTCF_BROADCAST | RTCF_MULTICAST)) {//核实广播组播回应请求是否被允许
/*
* RFC 1122: 3.2.2.6 An ICMP_ECHO to broadcast MAY be
* silently ignored (we let user decide with a sysctl).
* RFC 1122: 3.2.2.8 An ICMP_TIMESTAMP MAY be silently
* discarded if to broadcast/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);//按照icmp类型来处理
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;
}
如果想禁止对ping进行应答,可按如下方法进行处理:
echo 1 > /proc/sys/net/ipv4/icmp_echo_ignore_all