使用原始套接字发送了一个 SYN 数据包后,对方发送了 SYN,ACK 数据包,但系统仍然会自动发送 RST 数据包。这通常是因为操作系统内核在处理 TCP 连接时的行为。
原因分析
内核处理 TCP 连接:
即使你使用了原始套接字来发送和接收数据包,操作系统内核仍然会处理 TCP 连接的状态。如果你没有在内核中建立一个相应的 TCP 连接,内核会认为这是一个未授权的连接尝试,并自动发送 RST 数据包来重置连接。未建立的连接:
当你使用原始套接字发送 SYN 数据包时,内核不会自动为这个连接建立状态。因此,当对端发送 SYN,ACK 数据包时,内核不知道这个连接,会认为这是一个无效的连接尝试,并发送 RST 数据包。
解决方法
使用原始套接字完整处理 TCP 三握手:
你需要在你的应用程序中完整地处理 TCP 三握手过程,包括发送 SYN、接收 SYN,ACK、发送 ACK。这样可以确保内核不会干扰你的连接。TCP握手的手动处理:
你可以通过设置原始套接字的IP_HDRINCL
选项来自己处理 TCP 连接,包括构建完整的 IP 头。这通常通过setsockopt
函数来实现。
IP_HDRINCL
选项确实告诉内核不要自动处理 IP 头,而是由应用程序自己构建完整的 IP 头。这个选项主要用于需要对 IP 数据包进行精细控制的应用程序,例如网络测试工具、自定义协议实现等。
然而,IP_HDRINCL
选项并不会禁用系统内核自动处理所有的网络层功能,包括对错误情况的处理。具体来说,它不会直接阻止内核在检测到某些错误情况时自动发送 RST 数据包。
例如,如果应用程序发送了一个不完整的 TCP 数据包(比如缺少某些必要的 TCP 选项或标志),内核仍然可能会在接收到对端发送的错误响应时自动发送 RST 数据包,以终止连接。
要阻止内核自动发送 RST 数据包,可能需要更深层次的系统调优或网络协议栈的修改,而不仅仅是依赖 IP_HDRINCL
选项。这通常涉及更复杂的网络编程和系统级配置,可能需要对操作系统的网络栈进行定制或使用特定的工具来管理网络行为。
总结来说,IP_HDRINCL
选项主要用于让应用程序完全控制 IP 头的构建,但它并不会完全阻止内核在检测到错误时的自动响应行为,包括发送 RST 数据包。
示例代码
以下是一个示例,展示了如何使用原始套接字发送 SYN 数据包并处理完整的 TCP 三握手过程:
#include <iostream>
#include <cstring>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <netinet/tcp.h>
#include <netinet/if_ether.h>
#include <linux/if_ether.h>
#include <linux/ip.h>
#include <linux/tcp.h>
#include <cstdlib>
#define BUFFER_SIZE 65536
// Helper function to create a TCP packet
void createTCPPacket(char* packet, const char* srcIP, const char* dstIP, uint16_t srcPort, uint14_t dstPort, uint32_t seq, uint32_t ack, uint8_t flags) {
struct iphdr *iph = (struct iphdr *)packet;
struct tcphdr *tcph = (struct tcphdr *)(packet + sizeof(struct iphdr));
// IP header
iph->ihl = 5;
iph->version = 4;
iph->tos = 0;
iph->tot_len = sizeof(struct iphdr) + sizeof(struct tcphdr);
iph->id = htons(54321);
iph->frag_off = 0;
iph->ttl = 64;
iph->protocol = IPPROTO_TCP;
iph->check = 0;
iph->saddr = inet_addr(srcIP);
iph->daddr = inet_addr(dstIP);
// TCP header
tcph->source = htons(srcPort);
tcph->dest = htons(dstPort);
tcph->seq = htonl(seq);
tcph->ack_seq = htonl(ack);
tcph->doff = 5;
tcph->fin = 0;
tcph->syn = (flags & 0x02) ? 1 : 0;
tcph->rst = (flags & 0x04) ? 1 : 0;
tcph->psh = (flags & 0x08) ? 1 : 0;
tcph->ack = (flags & 0x10) ? 1 : 0;
tcph->urg = (flags & 0x20) ? 1 : 0;
tcph->window = htons(5840);
tcph->check = 0;
tcph->urg_ptr = 0;
// Calculate checksum
tcph->check = htons(compute_tcp_checksum(iph, tcph));
}
// Helper function to calculate TCP checksum
uint16_t compute_tcp_checksum(struct iphdr *iph, struct tcphdr *tcph) {
struct pseudohdr {
uint32_t src_addr;
uint32_t dst_addr;
uint8_t zero;
uint8_t protocol;
uint16_t tcp_len;
} pseudohdr;
pseudohdr.src_addr = iph->saddr;
pseudohdr.dst_addr = iph->daddr;
pseudohdr.zero = 0;
pseudohdr.protocol = IPPROTO_TCP;
pseudohdr.tcp_len = htons(sizeof(struct tcphdr));
char *buf = (char *)malloc(sizeof(struct pseudohdr) + sizeof(struct tcphdr));
memcpy(buf, &pseudohdr, sizeof(struct pseudohdr));
memcpy(buf + sizeof(struct pseudohdr), tcph, sizeof(struct tcphdr));
return checksum((uint16_t *)buf, sizeof(struct pseudohdr) + sizeof(struct tcphdr));
}
// Helper function to calculate checksum
uint16_t checksum(uint16_t *addr, int len) {
uint32_t sum = 0;
while (len > 1) {
sum += *addr++;
len -= 2;
}
if (len == 1) {
sum += *(uint8_t *)addr;
}
while (sum > 0xFFFF) {
sum = (sum & 0xFFFF) + (sum >> 16);
}
return ~sum;
}
// Send a TCP packet
void sendTCPpacket(int sock, const char* packet, int packetSize, const char* dstIP, uint16_t dstPort) {
struct sockaddr_in dest_addr;
dest_addr.sin_family = AF_INET;
dest_addr.sin_port = htons(dstPort);
inet_pton(AF_INET, dstIP, &dest_addr.sin_addr);
if (sendto(sock, packet, packetSize, 0, (struct sockaddr*)&dest_addr, sizeof(dest_addr)) == -1) {
perror("sendto");
}
}
// Receive packets
void receivePackets(int sock) {
char buffer[BUFFER_SIZE];
while (true) {
int data_size = recvfrom(sock, buffer, BUFFER_SIZE, 0, NULL, 0);
if (data_size == -1) {
perror("recvfrom");
continue;
}
struct iphdr *iph = (struct iphdr *)buffer;
struct tcphdr *tcph = (struct tcphdr *)(buffer + sizeof(struct iphdr));
if (iph->protocol == IPPROTO_TCP) {
std::cout << "Received TCP packet: " << inet_ntoa(*(struct in_addr *)&iph->saddr) << ":" << ntohs(tcph->source) << " -> " << inet_ntoa(*(struct in_addr *)&iph->daddr) << ":" << ntohs(tcph->dest) << std::endl;
if (tcph->syn && tcph->ack) {
std::cout << "Received SYN,ACK" << std::endl;
// Handle SYN,ACK and send ACK
char ackPacket[BUFFER_SIZE];
createTCPPacket(ackPacket, "192.168.1.100", "192.168.1.1", 12345, 80, htonl(ntohl(tcph->ack_seq) + 1), tcph->seq + 1, 0x10);
sendTCPpacket(sock, ackPacket, sizeof(struct iphdr) + sizeof(struct tcphdr), "192.168.1.1", 80);
}
}
}
}
int main() {
int sock = socket(AF_INET, SOCK_RAW, IPPROTO_TCP);
if (sock == -1) {
perror("socket");
return 1;
}
// Enable sending of custom packets
int one = 1;
if (setsockopt(sock, IPPROTO_IP, IP_HDRINCL, &one, sizeof(one)) == -1) {
perror("setsockopt");
return 1;
}
// Create a SYN packet
char packet[BUFFER_SIZE];
createTCPPacket(packet, "192.168.1.100", "192.168.1.1", 12345, 80, 0, 0, 0x02);
// Send the SYN packet
sendTCPpacket(sock, packet, sizeof(struct iphdr) + sizeof(struct tcphdr), "192.168.1.1", 80);
// Receive packets
receivePackets(sock);
close(sock);
return 0;
}