用Raw socket自己构造数据包头部

本文详细介绍了如何使用Raw Socket创建原始套接字,包括创建、发送和接收数据的过程。通过设置IP_HDRINCL套接字选项,允许应用程序自定义IP首部。文章还深入探讨了IP和TCP头部的结构,以及构造IP首部时需要注意的细节。
摘要由CSDN通过智能技术生成

        一、原始套接字的创建

        只有超级用户才能创建原始套接字。

int sockFd;
sockFd = socket(AF_INET, SOCK_RAW, protocol);

        其中第3个参数protocol是形如IPPROTO_xxx的某个常值,在<netinet/in.h>头文件中定义,经常不为0。

        原始套接字不存在端口号的概念。在原始套接字上调用bind()比较少见,这么做仅仅是设置本地地址。在原始套接字上调用connect()也比较少见,这么做仅仅是设置外地地址。


        二、通过原始套接字发送数据

        通过raw socket发送数据时,内核会自动对超出外出接口MTU的原始分组执行分片。

        普通输出通过sendto()或sendmsg()并指定目的IP地址完成。

        如果我们想通过一个原始套接字自己构造IP首部应该怎么做呢?最早原始套接字是不能实现这个目的的,后来某个大牛给了支持traceroute而给出了一个内核补丁,该补丁要求应用进程在调用socket()创建原始套接字时必须指定protocol参数值为IPPROTO_RAW(值为255),它是一个保留值,从不允许作为IP首部中的协议字段出现。时至今日,我们还可以看到很多这样的代码:

if(-1 == (sockFd = socket(AF_INET, SOCK_RAW, 255)))

        这种方法需要自行填写IP头部和TCP头部,并自行计算校验和,细节见后文。

        不过,后来又有了新的方法,4.3BSD Reno引入了IP_HDRINCL套接字选项,进程可以使用该选项自行构造IPv4首部:

const int on = 1;
if(setsocketopt(sockFd, IPPROTO_IP, IP_HDRINCL, &on, sizeof(on)) < 0){
    //error handler;
}

        如果IP_HDRINCL选项未开启,则由内核自动构造IP首部并把它置于来自进程的数据之前,进程让内核发送的数据起始地址指的是IP首部之后的第一个字节。

        如果IP_HDRINCL选项开启,则进程让内核发送数据的起始地址指的是IP首部的第一个字节,进程调用输出函数写出的数据量必须包括IP首部的大小,整个IP首部由进程构造,不过(a)IPv4标识字段可置为0,表示进程让内核来设置该值(b)IPv4首部校验和字段总是由内核计算并存储(c)IPv4选项字段是可选的。

        至于IPv6,不存在IP_HDRINCL这样的选项,大概也不存在可以自行构造首部的内核补丁。IPv6首部的几乎所有字段和所有扩展首部都可以通过套接字选项或辅助数据由应用进程指定或获取。如果一定应用进程一定要读入或写出完整的IPv6数据包,就必须使用链路层访问。


        三、通过原始套接字接收数据

        如果某个IP datagram以片段形式到达,内核会重组好完整的IP datagram之后再传递给raw socket.

        内核向原始套接字交付IP datagram的规则:

        1. 内核接收到的UDP分组和TCP分组绝对不会交付给任何原始套接字。如果一个进程想要读取含有TCP/UDP分组的完整IP数据包,就必须在数据链路层读取这些分组。

        2. 大多数ICMP分组在内核处理完其中的ICMP消息后传递到原始套接字。源自Berkeley的实现只有回射请求、时间戳请求和地址掩码请求完全由内核处理,此外的所有ICMP分组都会传递给raw socket.</

在LwIP(Lightweight IP)这个小型实时操作系统(RTOS)的网络库中,raw sockets允许开发者直接操作IP层的数据包,包括分片和重组。以下是基本步骤: 1. **打开raw socket**: 首先,你需要创建一个RAW socket并绑定到一个特定的端口和IP地址,以便接收或发送原始数据包。 ```c struct ip_addr addr; sys_sethostbyname("your_ip_address", &addr); err_t err = netif_new_raw_socket(socket_num, IPPROTO_RAW, AF_INET, &addr, NULL); ``` 2. **处理数据包**: 当收到数据包时,它会作为一个缓冲区传递给你。你可以检查IP头部的标识符(ID)和片段偏移字段(Fragment Offset)来判断是否需要分片或重组。如果遇到标志位表明它是第一部分,那么可能是重组;如果是最后一部分,则可能是需要发送出去。 ```c struct pbuf *p = netif_recv_pbuf(socket_num); // 获取接收到的PBUF if (p != NULL && IP_PFRAG(p->payload)) { // 分片处理... } else if (IP_DF(p->payload)) { // 这是一个完整的数据包,可以直接发送或者处理 } ``` 3. **分片与重组**: 如果是分片,通常会将数据包分解成小块,并设置正确的标志。对于重组,当收到所有碎片后,可以组合成完整的数据包并发送给应用程序或继续上层协议处理。 ```c if (IP_PFRAG(p->payload)) { struct etharp_hdr *eth = (struct etharp_hdr *) p->payload; for (unsigned i = 0; i < IP_FRAG_SIZE(iph->frag_off); i++) { // 处理每个片段... } } // 对于重组,可以遍历所有已接收的片段,找到正确的顺序后重新构建pbuf ``` 4. **发送数据包**: 发送时,如果你需要手动分片数据包,可以在构造PBUF时设置适当的标志。 ```c struct ip_hdr *iph = pbuf_header(p, sizeof(struct ip_hdr)); iph->version = IP_VERSION; iph->ihl = IP_HLEN; iph->tos = ...; iph->len = htons(data_len + sizeof(struct ip_hdr)); iph->id = htons(rand()); iph->fragment_offset = htons(IP_DF); // 设置不分片标志 netif_send_pbuf(socket_num, p); ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值