资讯动态

从DOS源码学TCP半开连接:原理、复现与防御实验

发布时间:2026/10/29 20:14:34 来源:尧图企业网站定制
简介这份资源面向网络安全初学者与攻防技术研究者提供一套DOS拒绝服务攻击的源代码示例用于理解攻击原理与防御机制。压缩包共6个文件约11KB以cpp源文件为核心配合plg、opt、dsw、dsp、ncb等工程配置与编译辅助文件构成一个可直接在开发环境中打开分析的完整工程便于对照代码梳理SYN Flood、Ping Flood、UDP Flood等典型攻击的实现思路。已有515人学习下载说明其在网络安全入门与教学场景中具有一定参考价值。读者可从中获得攻击流程的代码级认知进而理解半开连接耗尽、ICMP请求洪泛等机制为设计防火墙过滤、限速策略、流量清洗等防护方案提供对照依据。资源强调仅供学习研究需在合法合规前提下使用适合作为课堂案例或自学材料帮助提升安全意识与防御分析能力。1. 从一份“DOS拒绝服务攻击源代码”说起它到底能帮你理解什么如果你在找一份能直接跑起来、对真实目标发起攻击的DOS源码这篇笔记帮不到你——我也不会往那个方向写。但如果你手里正好有一份标注为“DOS拒绝服务攻击源代码”的学习材料想搞清楚它内部到底在做什么、为什么几行代码就能把一台机器的连接表撑满、以及怎么在自己的实验环境里复现和观察这个过程那这份材料值得认真拆一遍。它本质上是一套教学性质的网络编程示例核心围绕TCP连接建立、半开连接、资源耗尽这几个概念展开适合已经会写socket、想往网络协议底层再走一步的开发者。下面我按“它由哪些模块组成 → 怎么在隔离环境里跑起来 → 核心代码逐段看 → 怎么改造成自己的实验工具”这条线讲全程只谈原理和防御视角下的复现。2. 项目结构与技术选型一个最小化的网络压力实验框架2.1 目录骨架与文件职责这类教学源码通常不会很大几百行到一两千行之间结构也比较朴素。我拿到手的这份大致是这样组织的dos-lab/ ├── src/ │ ├── main.c # 入口解析参数、启动工作线程 │ ├── packet.c # 报文构造与校验和计算 │ ├── packet.h │ ├── sender.c # 发送循环控制速率与并发 │ ├── sender.h │ └── util.c # 随机源端口、时间戳、日志 ├── conf/ │ └── lab.conf # 目标地址、端口、并发数、速率上限 ├── scripts/ │ └── run_lab.sh # 一键启动实验环境 ├── Makefile └── README.mdmain.c负责把配置读进来、初始化随机数种子、按配置拉起若干发送线程packet.c是真正干活的地方负责把IP头、TCP头一层层拼出来并算校验和sender.c管节奏决定每秒发多少、并发多少util.c是一些零碎工具函数。conf/lab.conf把目标地址、端口、并发、速率这些参数外置改实验条件不用重新编译这一点对反复做对照实验很关键。技术栈上这类项目基本是纯C加POSIX socket构建用Makefile没有第三方依赖。选C不是为了炫技而是因为要直接操作原始套接字、自己拼报文头高层语言反而绕。常见做法是用gcc -O2 -pthread编译链接-lpthread。如果你的环境是macOS原始套接字权限模型和Linux不一样建议直接在Linux虚拟机里做省得跟权限较劲。2.2 为什么用原始套接字而不是普通connect普通socket()加connect()走的是内核协议栈三次握手由内核替你完成你拿不到中间状态也没法控制源地址和序列号。这份源码要演示的是“连接建立到一半就丢弃”的效果必须自己构造TCP头、自己决定发不发最后一个ACK所以用的是SOCK_RAW。这意味着程序需要CAP_NET_RAW能力或者root权限也意味着它只能在你自己控制的实验网段里跑——这一点在README里通常会写但很多人不看。提示原始套接字程序在容器里跑经常因为缺少NET_RAW能力直接报Operation not permitted启动容器时加--cap-addNET_RAW或者干脆在虚拟机里跑。2.3 配置项与实验边界lab.conf里的字段一般长这样字段含义建议实验取值target_ip实验目标地址仅限本机回环或隔离网段内的靶机target_port目标端口靶机上自己起的测试服务端口threads发送线程数14先小后大rate_pps每秒报文数上限从100起步观察靶机表现duration持续时间秒1030够观察即可把速率和时长做成可配是为了让实验可控。真正做对照实验时你会希望一次只改一个变量先固定线程数调速率再固定速率调线程数看靶机上ss -s和netstat -an | grep SYN_RECV的计数怎么变。没有这个配置层每次改参数都要重编译实验节奏会被打断。3. 在隔离环境里跑起来从编译到观察现象3.1 搭建靶机与实验网络第一步永远是把实验边界划清楚。我的习惯是开两台虚拟机放在同一个仅主机host-only网络里一台当靶机、一台当实验机两边都不接外网。靶机上起一个最简单的TCP服务比如用Python起一个监听端口# 靶机侧起一个只接受连接、不做业务处理的测试服务 python3 -c import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind((0.0.0.0, 9000)) s.listen(128) print(lab target listening on 9000) while True: conn, addr s.accept() # 只接受不读不写方便观察连接堆积 这段代码的作用是提供一个“只建立连接、不消费数据”的靶点方便观察连接表的变化。listen(128)里的128是等待队列长度实验时把它调小一点比如8能更快看到队列被填满的现象。靶机侧同时开一个观察窗口跑watch -n 1 ss -s盯着SYN_RECV和ESTAB两个计数。3.2 编译与首次运行实验机上解压源码先看Makefile里的编译选项再执行# 实验机侧编译并做一次最小规模试跑 make clean make # 默认 -O2 -pthread产物在 ./bin/dos-lab sudo ./bin/dos-lab -c conf/lab.conf --dry-run # 先干跑只打印将要发送的报文数 sudo ./bin/dos-lab -c conf/lab.conf # 正式跑按配置的速率和时长--dry-run是我加的一个习惯很多教学源码没有这个开关但值得自己补上它只解析配置、打印目标地址、线程数、预计发送总量不真正发包。第一次跑一定先dry-run确认目标地址没写错——把实验流量打到隔壁工位的机器上是这类项目最经典的血泪教训。正式运行时实验机上用tcpdump -i any -nn tcp port 9000抓包靶机上同时看ss -s两边对照着看才能把“发了什么”和“靶机感受到了什么”对上。3.3 观察指标与判读跑起来之后重点看三个数靶机ss -s里的SYN_RECV是否持续上涨、netstat -an | grep -c SYN_RECV的绝对值、以及靶机业务端口是否还能正常响应新连接。如果SYN_RECV涨到几百就停住不动说明靶机的半开连接队列已经满了新来的SYN被丢弃——这就是这类源码想演示的核心现象。反过来如果计数一直很低先检查抓包有没有抓到报文、目标端口对不对、防火墙有没有把包拦掉。判读的时候别只看一个指标三个数一起看才能区分“包没发出去”和“发出去了但靶机没反应”。4. 核心模块拆解一个SYN报文是怎么被拼出来的4.1 报文构造与校验和packet.c里最关键的函数是构造TCP SYN报文。伪代码逻辑大致如下// packet.c构造一个带IP头和TCP头的SYN报文 int build_syn_packet(uint8_t *buf, const char *src_ip, const char *dst_ip, uint16_t src_port, uint16_t dst_port, uint32_t seq) { struct iphdr *ip (struct iphdr *)buf; struct tcphdr *tcp (struct tcphdr *)(buf sizeof(struct iphdr)); // IP头版本4、头长5、总长、协议号TCP、TTL ip-version 4; ip-ihl 5; ip-tot_len htons(sizeof(struct iphdr) sizeof(struct tcphdr)); ip-protocol IPPROTO_TCP; ip-ttl 64; ip-saddr inet_addr(src_ip); ip-daddr inet_addr(dst_ip); ip-check 0; ip-check checksum((uint16_t *)ip, sizeof(struct iphdr)); // TCP头源端口随机、目的端口固定、SYN置位、序列号随机 tcp-source htons(src_port); tcp-dest htons(dst_port); tcp-seq htonl(seq); tcp-doff 5; tcp-syn 1; tcp-window htons(65535); tcp-check 0; tcp-check tcp_checksum(ip, tcp); // 需要伪首部参与计算 return sizeof(struct iphdr) sizeof(struct tcphdr); }逐段看IP头里tot_len必须是网络字节序protocol填IPPROTO_TCPsaddr和daddr用inet_addr转成网络序check字段先清零再算算完填回去这是校验和计算的标准套路。TCP头里doff5表示头部20字节无选项syn1是这条报文的目的所在window给一个正常值避免被中间设备直接判为异常。tcp_checksum不能只算TCP头必须把IP头里的源地址、目的地址、协议号和TCP长度拼成伪首部一起算这是新手最容易翻车的地方——只算TCP头的话靶机收到包直接丢弃抓包能看到包发出去了但靶机毫无反应排查半天以为是网络问题。参数上src_port和seq都应该随机化源码里用util.c的随机函数生成。固定源端口和序列号会让报文特征过于明显也容易被靶机的连接跟踪表直接合并实验现象就不典型了。4.2 发送循环与速率控制sender.c负责把构造好的报文按节奏发出去// sender.c单线程发送循环按目标速率控制发包间隔 void *sender_thread(void *arg) { struct sender_ctx *ctx (struct sender_ctx *)arg; uint8_t pkt[64]; struct timespec next, now; long interval_ns 1000000000L / ctx-rate_pps; // 每包间隔纳秒 clock_gettime(CLOCK_MONOTONIC, next); while (!ctx-stop) { uint16_t sport rand_port(); // 每包换源端口 uint32_t seq rand_u32(); // 每包换序列号 int len build_syn_packet(pkt, ctx-src_ip, ctx-dst_ip, sport, ctx-dst_port, seq); sendto(ctx-sock, pkt, len, 0, (struct sockaddr *)ctx-dst, sizeof(ctx-dst)); next.tv_nsec interval_ns; // 累加而非sleep固定值 if (next.tv_nsec 1000000000L) { next.tv_sec 1; next.tv_nsec - 1000000000L; } clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, next, NULL); } return NULL; }这段代码有两个值得说的点。一是用clock_nanosleep的绝对时间模式TIMER_ABSTIME而不是相对sleep这样每次循环的误差不会累积长时间跑下来实际速率和配置速率基本吻合如果用相对sleep每轮多出来的调度延迟会一点点把速率拖慢。二是每包都换源端口和序列号这是让靶机连接表持续增长的关键——如果源端口固定靶机侧只会看到一个连接反复重传计数涨不上去。rate_pps是单线程速率总速率等于线程数乘以它。调参时先单线程跑确认单线程速率准确抓包数一下每秒报文数再往上加线程。多线程共享一个socket时要注意sendto本身是线程安全的但速率控制是各线程独立的总速率会有抖动做精确实验时用单线程更稳。4.3 配置解析与参数校验main.c里读配置的部分通常比较朴素但校验不能省// main.c读取配置并做基本校验防止把实验流量打到意外地址 int load_config(const char *path, struct lab_config *cfg) { FILE *fp fopen(path, r); if (!fp) return -1; char key[64], val[128]; while (fscanf(fp, %63s %127s, key, val) 2) { if (strcmp(key, target_ip) 0) strncpy(cfg-dst_ip, val, 63); else if (strcmp(key, target_port) 0) cfg-dst_port atoi(val); else if (strcmp(key, threads) 0) cfg-threads atoi(val); else if (strcmp(key, rate_pps) 0) cfg-rate_pps atoi(val); else if (strcmp(key, duration) 0) cfg-duration atoi(val); } fclose(fp); // 关键校验目标必须是回环或私有网段拒绝公网地址 struct in_addr a; inet_aton(cfg-dst_ip, a); uint32_t h ntohl(a.s_addr); int is_private ((h 24) 10) || ((h 20) 0xAC1) || // 172.16.0.0/12 ((h 16) 0xC0A8) || // 192.168.0.0/16 ((h 24) 127); // 127.0.0.0/8 if (!is_private) { fprintf(stderr, refuse: target %s is not in lab range\n, cfg-dst_ip); return -2; } return 0; }这段校验是我强烈建议保留甚至加强的部分。教学源码里经常没有这层检查配置写错一个数字流量就出了实验网段。加上私有网段白名单之后程序在解析阶段就会拒绝非实验地址等于给自己上了一道保险。threads和rate_pps也要做上限检查比如threads不超过8、rate_pps不超过10000防止手滑把参数写成天文数字。5. 避坑与排查这类实验最容易翻车的五个地方5.1 抓包有包、靶机无反应现象是实验机tcpdump能看到SYN报文发出去靶机ss -s的SYN_RECV纹丝不动。原因八成是TCP校验和算错了靶机协议栈校验失败直接静默丢弃。解决方法是把tcp_checksum单独拿出来做单元测试构造一个已知正确的报文比对算出来的校验和或者用Wireshark打开抓包文件看它有没有标“checksum incorrect”。伪首部漏算、字节序搞反、校验和字段没先清零是三个最常见的具体错误。5.2 程序启动就报权限错误现象是sendto返回-1errno是EPERM。原因是原始套接字需要CAP_NET_RAW普通用户没有。解决方法是sudo运行或者给二进制加能力sudo setcap cap_net_rawep ./bin/dos-lab。容器里跑的话启动参数加--cap-addNET_RAW。注意加完能力之后普通用户也能跑实验机上要控制好这个二进制的存放位置。5.3 速率怎么调都上不去现象是配置了每秒1000包抓包数出来只有两三百。原因通常是用了相对sleep导致误差累积或者单线程里构造报文和算校验和的开销本身就吃掉了时间。解决方法是改用clock_nanosleep绝对时间模式并且把校验和计算里能预计算的部分比如IP头里不变的部分提前算好缓存起来。如果还上不去说明单线程到瓶颈了加线程但加线程后总速率会有抖动做精确对照实验时还是单线程加预计算更可靠。5.4 靶机连接表涨到一定数量就不动了现象是SYN_RECV涨到某个值后稳定新连接进不来也出不去。这不是程序的问题是靶机内核的半开连接队列net.ipv4.tcp_max_syn_backlog和listen的backlog共同决定的队列满了之后新SYN被丢弃计数自然不涨。做实验时这恰恰是你要观察的“饱和点”记下这个数值然后调大靶机的tcp_max_syn_backlog再跑一遍对比饱和点的变化比单纯看计数上涨更有信息量。5.5 实验做完靶机网络异常现象是实验结束后靶机一段时间内新连接建立很慢。原因是大量半开连接占着连接跟踪表需要等超时回收。解决方法是实验结束后在靶机上清理ss -s确认SYN_RECV在下降必要时重启靶机的测试服务或者直接重启靶机虚拟机。这也是为什么实验一定要在隔离环境里做——真把这种状态带到生产网络上恢复起来就不是重启一台虚拟机的事了。6. 二次开发方向把它改成一个可观测的实验工具这份源码最有价值的改造方向不是让它“更强”而是让它“更可观测”。我一般会做三件事。第一加一个统计输出每秒钟打印实际发送包数、发送失败数、sendto的平均耗时跑完输出汇总。这比事后抓包数报文直观得多也方便判断速率控制准不准。第二把靶机侧的观察也纳入工具链写一个小脚本实验期间每秒采集靶机的ss -s输出存成CSV实验结束直接画一条SYN_RECV随时间变化的曲线饱和点一目了然。第三把配置校验再收紧一层除了私有网段白名单再加一个“必须显式传--i-own-this-lab确认参数”的开关防止误操作。改造时有个细节值得注意统计输出不要用printf直接打在发送线程里高频打印本身会拖慢发送速率把计数放在原子变量里单独起一个统计线程每秒读一次。这个坑我踩过加了打印之后速率掉了三成一开始还以为是网络问题查了半天才发现是日志拖的。验证改造是否成功方法很朴素同一组配置跑三次看三次的实际发送速率是否稳定在配置值附近偏差5%以内看靶机侧曲线的饱和点是否可复现。可复现改造就算成了。从那以后我每次动这类网络实验代码都强制先跑一遍--dry-run确认目标地址再单线程小速率试跑最后才放开参数——这个顺序帮我省掉了不止一次把流量打到意外地址的麻烦。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑