用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.</

  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
由于题目中要求构造TCP头部,因此需要使用原始套接字发送数据包。以下是一个简单的Python程序,它构造了一个包含IP头部、UDP头部TCP头部TCP数据包,并使用原始套接字将其发送到目标主机。 ``` import socket import struct # 构造IP头部 source_ip = '192.168.1.100' dest_ip = '192.168.1.200' ip_ver = 4 ip_ihl = 5 ip_dscp = 0 ip_ecn = 0 ip_ttl = 255 ip_proto = socket.IPPROTO_UDP ip_id = 54321 ip_frag_offset = 0 ip_header = struct.pack('!BBHHHBBH4s4s', (ip_ver << 4) + ip_ihl, ip_dscp << 2 + ip_ecn, ip_ihl + 20, ip_id, ip_frag_offset << 13, ip_ttl, ip_proto, 0, source_ip, dest_ip) # 构造UDP头部 udp_src_port = 1234 udp_dest_port = 5678 udp_len = 8 + 20 + 20 # UDP头部长度 + IP头部长度 + TCP头部长度 udp_checksum = 0 udp_header = struct.pack('!HHHH', udp_src_port, udp_dest_port, udp_len, udp_checksum) # 构造TCP头部 tcp_src_port = 1234 tcp_dest_port = 80 tcp_seq = 0 tcp_ack_seq = 0 tcp_doff = 5 # 数据偏移量,即TCP头部长度 tcp_flags = 0x02 # SYN标志位 tcp_window = socket.htons(5840) tcp_checksum = 0 tcp_urg_ptr = 0 tcp_offset_res = (tcp_doff << 4) + 0 tcp_header = struct.pack('!HHLLBBHHH', tcp_src_port, tcp_dest_port, tcp_seq, tcp_ack_seq, tcp_offset_res, tcp_flags, tcp_window, tcp_checksum, tcp_urg_ptr) # 构造TCP数据包 packet = ip_header + udp_header + tcp_header # 发送数据包 s = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW) s.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1) s.sendto(packet, (dest_ip, 0)) ``` 请注意,由于这是一个构造TCP数据包的示例,因此代码中只设置了SYN标志位,其他标志位应根据需要进行设置。此外,由于使用了原始套接字,因此需要以root权限运行此程序。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值