1.原始套接字的特点
原始套接字(SOCK_RAW)可以用来自行组装IP数据包,然后将数据包发送到其他终端。也就是说原始套接字是基于IP数据包的编程
(SOCK_PACKET是基于数据链路层的编程)。另外,必须在管理员权限下才能使用原始套接字。
原始套接口提供了普通TCP和UDP socket不能提供的3个能力:
(1)进程使用raw socket 可以读写ICMP、IGMP等分组。这个能力还使得使用ICMP或IGMP构造的应用程序能够完全作为用户进程处理,而不必往内核中添加额外代码。
(2)大多数内核只处理IPv4数据报中一个名为协议的8位字段的值为1(ICMP)、2(IGMP)、6(TCP)、17(UDP)四种情况。然而该字段的值还有许多其他值。进程使用raw socket 就可以读写那些内核不处理的IPv4数据报了。因此,可以使用原始套接字定义用户自己的协议格式。
(3)通过使用raw socket ,进程可以使用IP_HDRINCL套接口选项自行构造IP头部。这个能力可用于构造特定类型的TCP或UDP分组等。
下面用程序举例获取IP数据报首部和tcp数据报一些数据
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <unistd.h>
-
#include <string.h>
-
#include <sys/socket.h>
-
#include <netinet/in.h>
-
#include <arpa/inet.h>
-
#include <linux/if_ether.h>
-
#include <linux/ip.h>
-
#include <linux/udp.h>
-
#include <linux/tcp.h>
-
#include <linux/icmp.h>
-
-
main()
-
{
-
int fd;
-
char buf[2000];//2000可以根据MTU设置
-
struct iphdr *iph;
-
struct tcphdr *tcph;
-
struct icmphdr *icmph;
-
-
int r;
-
int isip=1;
-
struct in_addr ddr;
-
fd=socket(AF_INET,SOCK_RAW,IPPROTO_TCP);//过滤规则,原始套接字的创建
-
//SOCK_RAW:原生数据层编程,可以扩展到链路层
-
setsockopt(fd,IPPROTO_IP,IP_HDRINCL,&isip,sizeof(int));//包含IP 头
-
-
while(1)
-
{
-
r=read(fd,buf,2000);
-
iph=(struct iphdr*)buf;
-
ddr.s_addr=iph->saddr;
-
printf("捕获到ip协议报:%d:%s\n",r,inet_ntoa(ddr));
-
printf("ihl=%d\t",iph->ihl);
-
printf("version=%d\n",iph->version);
-
tcph=(struct tcphdr*)(buf+20);//IP首部移动20字节就是TCP数据报了
-
printf("source=%u\t",tcph->source);
-
printf("dest=%u\t",tcph->dest);
-
printf("seq=%u\n",tcph->seq);
-
printf("ack_seq=%u\n",tcph->ack_seq);
-
if(r<=0) break;
-
}
-
-
}
2. bind 和 connect 函数说明
原始套接字直接使用IP协议的套接字,所以是非面向连接的,使用sendto和recvfrom函数。在这个套接字上能够调用connect和bind函数(一般不这么用),分别执行绑定对方和本地地址。
bind函数:调用bind函数后,发送数据包的源IP地址将是bind函数指定的地址。该函数仅仅设置本地地址,因为原始套接口不存在端口的概念。如是不调用bind,则内核将以发接口的主IP地址填充。假如配置了IP_HDRINCL,那么必须手工填充每个发送数据包的源IP地址。
connetc函数:调用connect函数后,能够用write和send发送数据包。调用该函数仅仅设置远地地址,同样因为原始套接口不存在端口号的概念。内核将用这个绑定的地址填充IP数据包的目的IP地址。
3. 原始套接字的输出
如果IP_HDRINCL套接字选项未开启,那么由进程让内核发送的数据的起始位置指的是IP首部之后的第一个字节,因为内核将构造IP首部并把它置于来自进程的数据之前。内核把所有构造IPv4首部的协议字段设置成来之sock调用的第三个参数。
如果IP_HDRINCL套接字选项已开启,那么由进程让内核发送的数据的起始位置指的是IP首部的第一个字节。进程调用输出函数写出的数据量必须包括IP首部的大小。整个IP首部都是由进程构造,不过IP的标识字段可以设置为0,从而告知内核设置该值;IP首部校验和字段总是由内核计算并存储;IPv4的选项字段也是可选的。
另外,内核会对超出外出接口MTU的原始分组进行分片。
4. 原始套接字的输入
首先要考虑内核将哪些接收到的IP数据报传递到原始套接字?这要遵循下面的规则:
(1)接收到的UDP或者TCP分组绝不传递到任何原始套接字,如果一个进程想要读取含有UDP分组或TCP分组的IP数据报,它就必须在数据链路层读取这些分组(即使用IPPROTO_IP选项读取整个IP包)。
(2)大多数ICMP分组在内核处理完其中的ICMP消息后传递到原始套接字。IGMP亦是如此。
(3)内核把不认识其协议字段的所有IP数据报传递给原始套接字。内核把这些分组执行的唯一处理是针对某些IP首部字段的最小验证:IP版本,IPv4校验和,首部长度,以及目的地址。
(4)如果某个数据报以片段的形式到达,那么在它的所有片段均到达且重组出该数据报之前,不传递任何片段分组给原始套接字。
当内核有一个需要传递到原始套接字的数据报时,它将检查所有进程上的所有原始套接字,以寻找所有匹配的套接字。每个匹配的套接字将被传递送以该IP数据报的一个副本。内核对每个原始套接字均执行以下3个测试,只有这三个测试均为真,内核才把接收到的数据报发送给这个套接字。
(1)如果创建这个原始套接字时指定了非0的协议参数(socket的第三个参数),那么接收到的数据报协议字段必须匹配该值。
(2)如果这个套接字已由bind调用绑定了某个IP地址,那么接收到的数据报的目的地址必须匹配这个绑定地址。
(3)若该套接字调用了connect,那么接收到的数据报的源地址必须匹配这个已连接地址。
注意,如果一个原始套接字是以0值协议参数传递的,并且没有调用bind,connect,那么该套接字将接收可由内核传递到原始套接字的每个原始数据报的一个副本。
另外,无论何时往一个原始IPv4套接字上递送一个IP数据报,传递到该套接字所在进程的都是包括IP首部在内的完整数据报。
阅读(3850) | 评论(1) | 转发(4) |