2026/4/16 23:33:03
网站建设
项目流程
成都网站建设成都,自建服务器做网站,网站推广要点,德阳高端网站建设日常上网离不开DNS#xff08;域名系统#xff09;#xff0c;但很少有人注意到#xff1a;DNS默认用UDP协议传输#xff0c;可一旦查询结果超过512字节#xff08;比如包含大量IP的CDN域名#xff09;#xff0c;客户端就会自动切换到TCP 53端口重新请求。传统的TCP D…日常上网离不开DNS域名系统但很少有人注意到DNS默认用UDP协议传输可一旦查询结果超过512字节比如包含大量IP的CDN域名客户端就会自动切换到TCP 53端口重新请求。传统的TCP DNS服务需要维护每一个连接的状态比如三次握手、四次挥手的上下文高并发时会消耗大量服务器资源而“无状态TCP”技术能绕开连接维护用更轻量的方式处理TCP DNS请求甚至能实现DNS请求的转发或劫持——这就是我们今天要拆解的核心技术思路。一、先搞懂两个核心概念有状态VS无状态TCP要理解这个方案先分清“有状态TCP”和“无状态TCP”的区别用大白话讲就是有状态TCP像我们和朋友打电话从拨号三次握手到挂电话四次挥手全程要记住“通话状态”——比如对方说了什么、当前聊到哪一步。服务器会记录每个连接的SEQ/ACK序号、窗口大小、连接阶段优点是可靠但高并发时会占满内存和CPU。无状态TCP像街边发传单不管谁来拿发完就忘不记任何人的信息。服务器收到每个TCP报文后只根据当前报文的内容比如源IP、端口、SEQ序号生成响应不用记住之前的交互优点是极致轻量化缺点是需要手动“伪造”TCP报文对协议细节要求极高。另外要补充TCP DNS和UDP DNS有个关键区别——TCP DNS的报文开头会加2个字节的“长度字段”告诉对方后面的DNS数据有多长而UDP DNS没有这个字段无状态方案的核心之一就是处理好这个长度字段的“加/减”。二、无状态TCP实现DNS代理的核心原理整个流程就像“手工模拟TCP服务器”全程绕开操作系统的有状态TCP协议栈手动处理每一个数据包拆解成5个关键步骤1. 网卡抓包只盯TCP 53端口的报文首先用libpcap库tcpdump、Wireshark的底层核心监听指定网卡的所有数据包通过过滤规则只保留“目的端口是53的TCP报文”——相当于“守在网卡门口”只捡和TCP DNS相关的数据包其他报文直接忽略。2. 解析报文剥洋葱式拆出核心数据拿到数据包后像剥洋葱一样逐层解析先剥14字节的以太网头不管只取后面的IP报文再解析IP头获取源IP、目的IP、IP头长度、协议类型确认是TCP最后解析TCP头获取源端口、目的端口、SEQ/ACK序号、报文标志位SYN/FIN/ACK、TCP头长度以及后面的DNS数据。这里要注意IP头和TCP头的长度都不是固定的因为有可选字段必须用协议宏比如IP_HL、TH_OFF计算实际长度否则会解析错数据位置。3. 无状态响应伪造TCP报文“骗”客户端客户端发TCP报文时会带不同的标志位服务器要针对性伪造响应全程不记录任何状态收到SYN报文客户端想建立连接直接伪造SYNACK报文发回去。关键是交换源/目的IP和端口SEQ序号用当前时间生成不用记ACK序号是客户端SEQ1还要加MSS最大分段大小选项告诉客户端单次能传1220字节最后算好IP和TCP校验和。收到FIN报文客户端想关闭连接伪造ACK报文回应同样交换地址/端口计算正确的SEQ/ACK发完就忘。收到带数据的报文DNS查询先伪造一个ACK报文回应防止客户端超时重传再处理后面的DNS数据。4. 转发请求把TCP DNS转成UDP发给后端TCP DNS报文开头有2字节长度字段UDP DNS不需要所以先剥掉这2字节然后通过普通UDP Socket发给后端的真实DNS服务器比如8.8.8.8——这么做是因为公共DNS的UDP端口更稳定、处理更快还不用和后端建立TCP连接进一步简化逻辑。5. 封装响应把UDP结果转回TCP发给客户端收到后端DNS的UDP响应后先加回2字节的长度字段还原成TCP DNS格式如果响应太大就分成512字节的小块符合TCP的MSS限制为每个小块伪造TCP报文带ACK标志最后一块还要加FIN标志表示数据发完计算好校验和后发送给客户端。三、设计思路为什么要这么做这个方案的设计核心是“极致轻量化”所有逻辑都围绕“不维护连接状态”展开不用连接表省资源传统TCP服务器要为每个连接存SEQ/ACK、窗口大小等信息无状态方案只处理当前报文哪怕十万个客户端同时请求内存占用也不会暴涨适合嵌入式设备、低配置服务器。Raw Socket绕开系统协议栈普通SocketTCP/UDP由操作系统处理协议头而Raw Socket能让程序直接操作IP/TCP头手动伪造所有字段——这是实现无状态的关键相当于“跳过操作系统自己做TCP协议栈”。UDP转发简化逻辑后端用UDP而非TCP不用和后端维护连接进一步降低复杂度只需要处理“TCP转UDP”“UDP转TCP”的格式转换核心是2字节长度字段。分片发送防丢包把大响应分成512字节块符合TCP的最大分段大小避免报文被IP分片提高传输可靠性。四、涉及的核心技术领域知识点总结这个方案看似是“DNS代理”实则覆盖了网络编程的多个核心领域是学习底层协议的绝佳案例1. libpcap抓包技术所有抓包工具的底层核心能直接从网卡获取原始数据包支持自定义过滤规则比如只抓TCP 53端口是实现“报文监听”的基础。2. Raw Socket编程网络编程的高级用法普通Socket只处理应用层数据Raw Socket能直接操作IP/TCP层需要手动构造协议头、计算校验和——适合做协议分析、报文伪造、网络测试。3. TCP/IP协议栈细节IP头要关注版本头长度、总长度、DF标志不分片、TTL、协议类型6TCP、校验和TCP头核心是SEQ/ACK序号无状态下用时间生成SEQACK是客户端SEQ1、标志位SYN/FIN/ACK、MSS选项、校验和TCP伪头计算TCP校验和必须包含伪头IP源/目的地址、协议号、TCP长度否则报文会被客户端丢弃。4. DNS协议格式关键是区分TCP和UDP的差异TCP DNS开头加2字节长度字段UDP没有DNS查询/响应的核心格式一致只是标志位不同。5. 校验和计算IP和TCP校验和都用“反码求和”先把数据按16位分组求和再对结果取反计算时要先把校验和字段设为0最后填回结果。五、实现流程示意图SYN建立连接FIN关闭连接有数据DNS查询libpcap监听网卡过滤出TCP 53端口报文解析以太网/IP/TCP头判断TCP标志位伪造SYNACK报文发送伪造ACK报文发送伪造ACK报文回应防重传剥掉TCP DNS的2字节长度字段通过UDP转发给后端DNS接收后端UDP DNS响应添加2字节长度字段分512字节块为每个块伪造TCP报文ACK标志最后一块添加FIN标志发送结束单次报文处理无状态不记录...structip_hdr{u_char ip_vhl;#defineIP_HL(ip)(((ip)-ip_vhl)0x0f)#defineIP_V(ip)(((ip)-ip_vhl)4)u_char ip_tos;u_short ip_len;u_short ip_id;u_short ip_off;#defineIP_RF0x8000#defineIP_DF0x4000#defineIP_MF0x2000#defineIP_OFFMASK0x1fffu_char ip_ttl;u_char ip_p;u_short ip_sum;structin_addrip_src,ip_dst;};typedefu_int32_t tcp_seq;structtcp_hdr{u_short th_sport;u_short th_dport;tcp_seq th_seq;tcp_seq th_ack;u_char th_offx2;#defineTH_OFF(th)(((th)-th_offx20xf0)4)u_char th_flags;#defineTH_FIN0x01#defineTH_SYN0x02#defineTH_RST0x04#defineTH_PUSH0x08#defineTH_ACK0x10#defineTH_URG0x20#defineTH_ECE0x40#defineTH_CWR0x80#defineTH_FLGS(TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)u_short th_win;u_short th_sum;u_short th_urg;};structtcp_option{u_char opt_type;u_char opt_len;u_short opt_val;};...voidgot_packet(u_char*args,conststructpcap_pkthdr*header,constu_char*packet){...ip(structip_hdr*)(packetSIZE_ETHERNET);size_ipIP_HL(ip)*4;if(size_ip20){printf( * Invalid IP header length: %u bytes\n,size_ip);return;}if(ip-ip_p!IPPROTO_TCP)return;tcp(structtcp_hdr*)(packetSIZE_ETHERNETsize_ip);size_tcpTH_OFF(tcp)*4;if(size_tcp20){printf( * Invalid TCP header length: %u bytes\n,size_tcp);return;}if(ntohs(tcp-th_dport)!PORT)return;ip_payload(u_char*)(packetSIZE_ETHERNET);payload(u_char*)(packetSIZE_ETHERNETsize_ipsize_tcp);size_payloadntohs(ip-ip_len)-(size_ipsize_tcp);if(tcp-th_flagsTH_SYN){send_tcp_synack(ip_payload);return;}if(tcp-th_flagsTH_FIN){send_tcp_ack(ip_payload);return;}if(size_payload0){bcopy(payload,cmd_buffer,size_payload);cmd_buffer[size_payload]\0;server_request(ip_payload,cmd_buffer,size_payload);}return;}intmain(intargc,char**argv){...sprintf(filter_exp,dst port %d and dst host %s,PORT,THIS_HOST);if(argc2){devargv[1];}elseif(argc2){fprintf(stderr,error: unrecognized command-line options\n\n);exit(EXIT_FAILURE);}else{devpcap_lookupdev(errbuff);if(devNULL){fprintf(stderr,Couldnt find default device: %s\n,errbuff);exit(EXIT_FAILURE);}}if(pcap_lookupnet(dev,net,mask,errbuff)-1){fprintf(stderr,Couldnt get netmask for device %s: %s\n,dev,errbuff);net0;mask0;}in(structin_addr*)net;printf(Device: %s Network: %s Mask: %x\n,dev,inet_ntoa(*in),ntohl(mask));printf(Filter expression: %s\n,filter_exp);handlepcap_open_live(dev,SNAP_LEN,1,1000,errbuff);if(handleNULL){fprintf(stderr,Couldnt open device %s: %s\n,dev,errbuff);exit(EXIT_FAILURE);}if(pcap_compile(handle,fp,filter_exp,0,net)-1){fprintf(stderr,Couldnt parse filter %s: %s\n,filter_exp,pcap_geterr(handle));exit(EXIT_FAILURE);}if(pcap_setfilter(handle,fp)-1){fprintf(stderr,Couldnt install filter %s: %s\n,filter_exp,pcap_geterr(handle));exit(EXIT_FAILURE);}open_raw_socket();pcap_loop(handle,-1,got_packet,NULL);pcap_close(handle);return(0);}If you need the complete source code, please add the WeChat number (c17865354792)执行以下命令启动程序指定要监听的网卡比如eth0根据你的机器调整sudo./test_dns eth0此时程序会一直运行监听eth0网卡上发往本机TCP 53端口的所有报文。六、测试验证核心步骤我们需要验证两个核心点①无状态TCP的SYNACK响应是否正常 ②DNS查询是否能通过这个程序转发并返回结果。测试1验证TCP 53端口的无状态响应用ncnetcat工具模拟TCP DNS客户端发起连接请求# 新开一个终端执行以下命令替换成你的测试机IPnc-v192.168.1.10053如果程序正常工作nc会显示Connection to 192.168.1.100 53 port [tcp/domain] succeeded!这说明程序伪造的SYNACK报文生效了——因为系统本身没有监听TCP 53端口是程序手动返回的SYNACK模拟了TCP连接建立。测试2验证DNS查询转发功能用dig命令DNS查询工具指定TCP协议向测试机发起DNS查询# 关键参数测试机IP 域名 tcp强制用TCP查询dig192.168.1.100 www.baidu.com tcp如果程序正常工作dig会返回百度的DNS解析结果和直接查询8.8.8.8的结果一致如果返回超时说明转发环节有问题比如后端DNS不可达、代码里的长度字段处理错误。测试3抓包验证进阶看底层报文用tcpdump抓包直观看到无状态TCP的报文交互# 新开终端抓eth0网卡上的TCP 53端口报文sudotcpdump -i eth0 -nn port53and tcp然后再执行dig 测试机IP www.baidu.com tcp你会看到客户端发SYN报文 → 程序返回SYNACK伪造的客户端发带DNS查询的TCP报文 → 程序返回ACK伪造的程序向8.8.8.8发UDP DNS查询 → 8.8.8.8返回UDP响应程序把UDP响应封装成TCP报文加2字节长度字段发给客户端最后程序发FINACK关闭连接无状态不用等客户端的FIN。总结无状态TCP实现DNS代理的核心是“手动替代操作系统的TCP协议栈”——从抓包、解析、伪造响应到转发请求、封装响应全程不保存任何连接状态。这种方案的优势是极致轻量化适合资源受限的场景但缺点也很明显需要对TCP/IP协议细节了如指掌且不支持TCP的高级特性比如窗口缩放属于“极简版”TCP实现。从学习角度看这个方案把抓包、原始套接字、协议解析、报文伪造等知识点串联起来能帮我们跳出“调用API”的表层深入理解TCP/IP协议的本质——毕竟无状态TCP的实现就是把操作系统帮我们做的事手动重新做了一遍。Welcome to follow WeChat official account【程序猿编码】