ICMPv4

什么是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

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ICMPv4(Internet Control Message Protocol version 4,因特网控制报文协议第四版)定义了一系列用于控制和错误报告的消息类型和代码。在ICMPv4中,每个消息类型都有一个对应的类型码,每个类型码下面又有多个代码。以下是一些常见的ICMPv4类型和代码: - 0:Echo Reply(回显应答),代码为0 - 3:Destination Unreachable(目的地不可达),下面有多个代码 - 0:Network Unreachable(网络不可达) - 1:Host Unreachable(主机不可达) - 2:Protocol Unreachable(协议不可达) - 3:Port Unreachable(端口不可达) - 4:Fragmentation Needed and Don't Fragment was Set(需要进行分片但已设置不分片位) - 5:Source Route Failed(源站选路失败) - 6:Destination Network Unknown(目的网络未知) - 7:Destination Host Unknown(目的主机未知) - 8:Source Host Isolated(源主机被隔离) - 9:Communication with Destination Network is Administratively Prohibited(与目的网络的通信被管理人员禁止) - 10:Communication with Destination Host is Administratively Prohibited(与目的主机的通信被管理人员禁止) - 11:Destination Network Unreachable for Type of Service(目的网络由于服务类型TOS的原因不可达) - 12:Destination Host Unreachable for Type of Service(目的主机由于服务类型TOS的原因不可达) - 13:Communication Administratively Prohibited(由于管理原因通信被禁止) - 14:Host Precedence Violation(主机优先权低于要求的优先权) - 15:Precedence cutoff in effect(优先权截止) - 4:Source Quench(源端被关闭),代码为0 - 5:Redirect(重定向),下面有多个代码 - 0:Redirect Datagram for the Network(对网络重定向数据报) - 1:Redirect Datagram for the Host(对主机重定向数据报) - 2:Redirect Datagram for the ToS & network(对服务类型和网络重定向数据报) - 3:Redirect Datagram for the ToS & host(对服务类型和主机重定向数据报) - 8:Echo Request(回显请求),代码为0 - 11:Time Exceeded(超时),下面有多个代码 - 0:TTL expired in transit(在传输中过期的TTL) - 1:Fragment reassembly time exceeded(片段重组时间超时) - 12:Parameter Problem(参数问题),下面有多个代码 - 0:Pointer indicates the error(指针指示错误) - 1:Missing a Required Option(缺少所需选项) - 2:Bad Length(长度错误) 还有其他类型的消息和代码,详情可以参考RFC 792。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值