TCP(SOCK_STREAM)和UDP套接口(SOCK_DGRAM)可以满足大部分需求,但要获取底层协议内容就需要原始套接字。相比前两者,SOCK_RAW具有如下优点:
1)使用原始套接字可以读写ICMP及ICMP6,如ping程序就是使用原始套接字发送ICMP应答请求。
2)使用原始套接字可以读写特殊的IP数据包,内核不处理这些数据包的IP协议字段,而出错的诊断将依靠字段的含义。
3)利用原始套接口设置IP_HDRINCL套接口选项可以构造自己的IP头部。
创建
int sockfd=socket(AF_INET, SOCK_RAW, protocol);
AF_INET:使用IPv4协议
SOCK_RAW:原始套接字
protocol:协议名
建立原始套接口后,可以通过它向网络中写自己的IP数据包,为了防止非法用户破坏网络,规定只有超级用户才有创建原始套接口的权限。
输入
内核如何将接收到的分组发送给原始套接字,遵循如下原则:
》接收到的TCP和UDP分组绝不会传递给任何原始套接子。
》当内核处理完ICMP消息之后,绝大部分ICMP分组将传递给原始套接口。
》当内核处理完IGMP消息后,所有IGMP分组将传递给某个原始套接口。
》所有带有内核不能识别的协议字段的IP数据包都将传递给某个原始套接口,而内核只是检验IP头部中的某些字段。
如果数据包以分片形式到达,则该分组将在所有分片到达并重组后才传给该套接字。
输出
如果套接字没有使用函数connect进行链接,则可以调用函数sendto或sendmsg,他们可以指定目的IP地址。如果已经连接,可以调用write/writev或send。
如果没有设置IP_HDRINCL选项,则内核写的数据起始地址指IP头部之后的第一个字节,此时内核构造IP头部,并且内核将IP头部的协议段设为用户在调用socket()时指定的protocol参数。
如果设置IP_HDRINCL选项,则内核写的数据起始地址指向IP头部的第一个字节,用户所提供的数据的大小值必须包括头部的字节数,此时进程构造出以下两项以外的整个IP头部:IP标识字段可以设为0,要求内核设置该值,IP头部的校验由内核来计算和存储。类似TCP协议超出外出接口的分组,内核将其分片。
匹配
当内核准备好一个待传输的数据包之后,内核将对所有的进程的原始套接口进行寻找,每个匹配的套接字都将收到一个该IP数据包的复制。匹配的过程分为3个测试步骤,只有3个测试步骤都满足时,数据包才会传递给该套接字。
》在创建套接字时,如果protocol不为0,则接收到的数据包的协议字段应该与之匹配。
》如果在原始套接口上绑定一个本地IP地址,那么接收到的数据包的地址应该与之匹配。
》如果此原始套接字通过调用connect()指定一个对方IP地址,那么接收到的源IP地址应该与之相匹配。
技巧:如果一个原始套接子创建参数protocol设置为0,并且没有调用connect()和bind(),那么对于内核传递给原始套接子的每个原始数据包,该套接口都会收到一个复制数据包。
示例
网上找到一个判断网络是否连通的程序,其通过发送ICMP包来实现。
参考:
1. linux环境下C编程指南