ICMP协议
ICMP(Internet Control Message Protocol,Internet控制报文协议)是一种面向无连接的协议,是TCP/IP协议族的一个子协议,属于网络层协议,用于在IP主机、路由器之间传递控制消息(网络通不通、主机是否可达、路由是否可用等)。当遇到IP数据无法访问目标、IP路由器无法按当前的传输速率转发数据包等情况时,会自动发送ICMP消息。这些控制消息虽然并不传输用户数据,但是对于用户数据的传递起着重要的作用。
ICMP类型的部分列表如下:
TYPE | CODE | Description | Query | Error |
---|---|---|---|---|
0 | 0 | Echo Reply——回显应答(Ping应答) | x | |
3 | 0 | Network Unreachable——网络不可达 | x | |
3 | 1 | Host Unreachable——主机不可达 | x | |
3 | 2 | Protocol Unreachable——协议不可达 | x | |
3 | 3 | Port Unreachable——端口不可达 | x | |
3 | 6 | Destination network unknown——目的网络未知 | x | |
3 | 7 | Destination host unknown——目的主机未知 | x | |
8 | 0 | Echo request——回显请求(Ping请求) | x |
原始套接字
原始套接字提供普通TCP和UDP套接字所不提供的能力。具体如下:
进程可以使用原始套接字读与写ICMPv4、ICMPv6和IGMPv4等分组。
ping程序使用原始套接字发送ICMP回射请求并接收ICMP回射应答。 #include <netinet/in.h> int sockfd; // 使用原始套接字创建ICMPv4套接字 sockfd = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
大多数内核仅仅处理ICMP、IGMP、TCP和UDP的数据报,有了原始套接字,进程可以读写内核不处理其协议字段的IPv4数据报。
使用原始套接字时,进程可使用
IP_HDRINCL
套接字选项自行构造IPv4首部。const int on = 1; if (setsockopt(sockfd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0) printf("出错");
原始套接字不存在端口号的概念。因此,在原始套接字上调用bind仅仅设置从这个原始套接字发送的所有数据报的源IP地址(未开启
IP_HDRINCL
选项的前提下)。在原始套接字上调用connect仅仅指定目的IP地址,调用connect之后可把sendto调用改为write或send调用。
原始套接字的输出规则
- 普通输出调用sendto或sendmsg并指定目的IP地址完成。如果成功调用connect后也可调用
write、writev或send。 - 未开启
IP_HDRINCL
选项时,内核将根据socket函数第三个参数构造IPv4首部并把它置于来自进程的数据之前。 - 开启
IP_HDRINCL
选项后,整个IPv4首部由进程构造,不过IPv4标识字段可置为0以让内核设置,而且IPv4首部校验和字段总是由内核计算并存储。另外,IPv4选项字段是可选的。 - 内核会对超出外出接口MTU的原始分组执行分片。
- 对于IPv4,进程必须负责IPv4首部之后数据报中包含的任何首部校验和的校验工作。
原始套接字的输入规则
- 接收到的UDP和TCP分组绝不传递到任何原始套接字。
- 源自Berkeley的实现把不是回射请求、时间戳请求或地址掩码请求(这三类ICMP消息有内核处理)的所有ICMP分组传递到原始套接字。
- 所有IGMP分组均传递到原始套接字。
- 内核不认识其协议字段的IP数据报传递到原始套接字。
- 内核不会传递分组中的单个或多个片段到原始套接字。
内核将IP数据报传递到原始套接字时,因没有端口号的概念,内核会检查所有进程上的所有原始套接字,进行如下3个测试以寻找所匹配的原始套接字。
- 接收到的数据报的协议字段是否匹配创建原始套接字时socket函数的第三个参数。
- 如果在原始套接字上调用bind已经绑定了某个本地IP地址,接收到的数据报的目的IP地址是否匹配。
- 如果此原始套接字已有connect调用指定了目的IP地址,接收到的数据报的源IP地址是否匹配。
注意,如果创建原始套接字socket函数的第三个参数为0值,且未调用过bind和connect,那么该原始套接字接收所有内核传递到原始套接字的数据报的一个副本。另外,内核传递到原始套接字的数据报一定是包括IPv4首部的完整数据报。
ping程序
此ping程序仅支持一个-v
命令行选项,以打印详尽输出。为了追求最简化,也仅支持IPv4。ping程序的操作非常简单,往某个IP地址发送一个ICMP回射请求,该节点则以一个ICMP回射应答响应。
#include <errno.h>
#include <netdb.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in_systm.h>
#include <netinet/ip.h>
#include <netinet/ip_icmp.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
/* C99中规定宏可以像函数一样带有可变参数 */
#define error_exit(format, ...) \
do { fprintf(stderr, format