测试目的
验证TCP在 TIME-WAIT 状态下,对于重新传输的FIN段,是否能够发送FIN/ACK响应,并重新启动2*MSL超时计时器。
UT测试方法
Precondition :
DUT在TCP-closed状态下收到ACK回复RST
Step to reproduce:
Step1,使DUT进入Time-WAIT状态
Step2,等待1倍MSL时间,发送FIN报文
Step3,等待1.5倍MSL时间,再次发送FIN报文
Expected results:
At step2,DUT能够发送ACK
At step3,DUT能够发送ACK
非UT验证方式
Precondition:
一台Ubuntu电脑,T1转RJ45转换器,Ubuntu电脑可与DUT正常通信
Step to reproduce:
1、在Ubuntu系统配置vlan2网卡,ip=172.16.2.97
打开Ubuntu系统的wireshark软件,持续监控报文
2、编译EthUnitTest源码,执行如下
在Ubuntu系统中使用命令gcc -o server main.cpp -lpthread编译EthUnitTest源码
在Ubuntu系统中运行server
3、在DUT系统执行如下
4、当DUT执行完close接口,发送FIN后,立即在Ubuntu端输入如下命令
bash
sudo iptables -A INPUT -s 172.16.2.99 -d 172.16.2.97 -p tcp --tcp-flags ACK ACK -j DROP

以此过滤DUT发给Ubuntu的ACK报文,这样当Ubuntu收到FIN+ACK后,调用close接口回复FIN+ACK时,DUT回复的ACK报文就会被过滤,导致Ubuntu测试程序持续重传FIN+ACK,DUT测试程序会保持TIME_WAIT状态。
5、在Ubuntu终端按Ctrl+C退出程序,查看wireshark程序中交互的端口号和seq值、ack值,然后执行如下

报文解析

报文显示DUT在2MSL后收到Tester发送的FIN+ACK报文后仍然回复ACK,说明2MSL定时器被重置,满足TC8规范。
也可以通过netstat工具监控连接状态,从而发现DUT的测试程序在2MSL后仍保持TIME_WAIT状态,也说明2MSL定时器被重置,满足TC8规范。

测试源码
cpp
/**
* 多模式网络工具 - main.cpp 与 main_tester.cpp 合并版
*
* 用法: sudo ./EthUnitTest <local_ip> <local_port> <remote_ip> <remote_port>
* 示例: sudo ./EthUnitTest 172.16.2.97 55000 172.16.2.98 55001
*
* 交互命令:
* 1 - listen_on_server (listen on local address, support raw sending)
* 2 - connect_to_server (send out-of-window SYN after 10s)
* 3 - connect_sendPSH_client (send invalid-ACK data segment after 10s)
* 4 - raw packet sender (no connection, send FIN/PSH/SA/SYN/ACK)
* 5 - time_wait_send_raw (establish connection, then send FA/ACK/SA/SYN/SR)
* 6 - last_ack_test_server (bind local, recv FIN, close -> LAST_ACK)
* 7 - last_ack_test_client (connect remote, support close/FA/ACK/SA)
* 0 - quit
*
* 编译: gcc -o EthUnitTest main2.cpp -lpthread
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <pthread.h>
#include <signal.h>
#include <time.h>
#include <stdint.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/tcp.h>
#include <arpa/inet.h>
static char local_ip[64];
static int local_port;
static char remote_ip[64];
static int remote_port;
/* 计算校验和 */
static unsigned short checksum(unsigned short *buf, int len) {
unsigned long sum = 0;
while (len > 1) {
sum += *buf++;
len -= 2;
}
if (len == 1)
sum += *(unsigned char *)buf;
sum = (sum >> 16) + (sum & 0xFFFF);
sum += (sum >> 16);
return (unsigned short)(~sum);
}
/* 发送原始 TCP 报文(自定义标志、seq、ack、数据) */
static void send_raw_tcp(const char *src_ip, int src_port,
const char *dst_ip, int dst_port,
uint32_t seq, uint32_t ack,
uint8_t flags, const unsigned char *payload, int payload_len) {
int raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW);
if (raw_sock < 0) {
perror("socket raw");
return;
}
// IP 头
struct iphdr iph;
memset(&iph, 0, sizeof(iph));
iph.version = 4;
iph.ihl = 5;
iph.tot_len = htons(sizeof(iph) + sizeof(struct tcphdr) + payload_len);
iph.id = htons(rand() % 65535);
iph.ttl = 64;
iph.protocol = IPPROTO_TCP;
inet_pton(AF_INET, src_ip, &iph.saddr);
inet_pton(AF_INET, dst_ip, &iph.daddr);
iph.check = 0;
iph.check = checksum((unsigned short *)&iph, sizeof(iph));
// TCP 头
struct tcphdr tcph;
memset(&tcph, 0, sizeof(tcph));
tcph.source = htons(src_port);
tcph.dest = htons(dst_port);
tcph.seq = htonl(seq);
tcph.ack_seq = htonl(ack);
tcph.doff = 5; // 20字节头
tcph.fin = (flags & 0x01) ? 1 : 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(65535);
tcph.check = 0;
// 伪首部
struct pseudo_header {
uint32_t src_addr;
uint32_t dst_addr;
uint8_t zero;
uint8_t protocol;
uint16_t tcp_len;
} ps;
ps.src_addr = iph.saddr;
ps.dst_addr = iph.daddr;
ps.zero = 0;
ps.protocol = IPPROTO_TCP;
ps.tcp_len = htons(sizeof(tcph) + payload_len);
int total_ps_len = sizeof(ps) + sizeof(tcph) + payload_len;
unsigned char *ps_buf = (unsigned char *)malloc(total_ps_len);
memcpy(ps_buf, &ps, sizeof(ps));
memcpy(ps_buf + sizeof(ps), &tcph, sizeof(tcph));
if (payload && payload_len > 0)
memcpy(ps_buf + sizeof(ps) + sizeof(tcph), payload, payload_len);
tcph.check = checksum((unsigned short *)ps_buf, total_ps_len);
free(ps_buf);
// 组装完整报文
int packet_len = sizeof(iph) + sizeof(tcph) + payload_len;
unsigned char *packet = (unsigned char *)malloc(packet_len);
memcpy(packet, &iph, sizeof(iph));
memcpy(packet + sizeof(iph), &tcph, sizeof(tcph));
if (payload && payload_len > 0)
memcpy(packet + sizeof(iph) + sizeof(tcph), payload, payload_len);
struct sockaddr_in dest;
dest.sin_family = AF_INET;
inet_pton(AF_INET, dst_ip, &dest.sin_addr);
if (sendto(raw_sock, packet, packet_len, 0,
(struct sockaddr *)&dest, sizeof(dest)) < 0) {
perror("sendto");
} else {
char flags_str[10] = {0};
if (flags & 0x01) strcat(flags_str, "FIN");
if (flags & 0x02) strcat(flags_str, "SYN");
if (flags & 0x04) strcat(flags_str, "RST");
if (flags & 0x08) strcat(flags_str, "PSH");
if (flags & 0x10) strcat(flags_str, "ACK");
printf("[Raw] Sent %s: %s:%d -> %s:%d, seq=%u, ack=%u, len=%d\n",
flags_str, src_ip, src_port, dst_ip, dst_port, seq, ack, payload_len);
}
free(packet);
close(raw_sock);
}
/* PSH2 命令实现:输入是否循环(1 循环 / 0 单次)与数据长度 len;循环时再输入循环次数,
按 len 构造数据,以当前 seq/ack 无间隔连续发送 PSH+ACK 报文,每次发送后 seq 前进 len */
static void send_psh2(const char *src_ip, int src_port,
const char *dst_ip, int dst_port,
uint32_t *seq, uint32_t ack) {
int loop_mode, data_len, loop_cnt = 1, i;
char line[32];
printf("Enter loop mode (1: loop, 0: send once): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
return;
}
if (sscanf(line, "%d", &loop_mode) != 1 || (loop_mode != 0 && loop_mode != 1)) {
printf("Invalid loop mode. Valid: 0 or 1.\n");
return;
}
printf("Enter data len (e.g., 100): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
return;
}
if (sscanf(line, "%d", &data_len) != 1 || data_len <= 0 || data_len > 65495) {
printf("Invalid data len. Valid range: 1 ~ 65495.\n");
return;
}
if (loop_mode == 1) {
printf("Enter loop count (e.g., 10): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
return;
}
if (sscanf(line, "%d", &loop_cnt) != 1 || loop_cnt <= 0) {
printf("Invalid loop count. Must be a positive number.\n");
return;
}
}
unsigned char *payload = (unsigned char *)malloc(data_len);
if (!payload) {
printf("malloc failed.\n");
return;
}
memset(payload, 'A', data_len);
if (loop_mode == 1)
printf("[PSH2] Sending %d times: seq=%u, ack=%u, data_len=%d\n", loop_cnt, *seq, ack, data_len);
else
printf("[PSH2] Sending once: seq=%u, ack=%u, data_len=%d\n", *seq, ack, data_len);
for (i = 0; i < loop_cnt; i++) {
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, *seq, ack, 0x18, payload, data_len);
*seq += data_len;
}
free(payload);
}
/* 函数1:监听模式,建立连接后可通过命令发送原始 TCP 报文 */
static void listen_on_server(const char *ip, int port) {
int listen_fd, reuse = 1;
struct sockaddr_in addr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) { perror("socket"); return; }
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) { perror("inet_pton"); close(listen_fd); return; }
if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); close(listen_fd); return; }
if (listen(listen_fd, 128) < 0) { perror("listen"); close(listen_fd); return; }
printf("[Server] Listening on %s:%d\n", ip, port);
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) { perror("accept"); close(listen_fd); return; }
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
int client_port = ntohs(client_addr.sin_port);
printf("[Server] New connection from %s:%d (fd=%d)\n", client_ip, client_port, client_fd);
struct sockaddr_in local_addr;
socklen_t local_len = sizeof(local_addr);
char local_ip_str[INET_ADDRSTRLEN];
int local_port_actual = port;
if (getsockname(client_fd, (struct sockaddr*)&local_addr, &local_len) == 0) {
inet_ntop(AF_INET, &local_addr.sin_addr, local_ip_str, sizeof(local_ip_str));
local_port_actual = ntohs(local_addr.sin_port);
} else {
strncpy(local_ip_str, ip, sizeof(local_ip_str) - 1);
local_ip_str[sizeof(local_ip_str) - 1] = '\0';
}
printf("\n=== Listen Raw Sender Mode (Established Connection) ===\n");
printf("Commands:\n");
printf(" FIN - send FIN packet\n");
printf(" SA - send SYN+ACK packet\n");
printf(" ACK - send ACK packet\n");
printf(" RST - send RST packet\n");
printf(" PSH - send PSH+ACK packet (manually enter seq & ack)\n");
printf(" PSH2 - send PSH+ACK data segments (enter loop mode 1/0 & data len)\n");
printf(" quit - close connection and return to main menu\n");
uint32_t seq = rand();
uint32_t ack = rand();
char cmd[16];
while (1) {
printf("listen> ");
fflush(stdout);
if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;
cmd[strcspn(cmd, "\n")] = '\0';
if (strcmp(cmd, "quit") == 0) {
printf("Closing connection.\n");
break;
} else if (strcmp(cmd, "FIN") == 0) {
send_raw_tcp(local_ip_str, local_port_actual, client_ip, client_port, seq, ack, 0x01, NULL, 0);
seq++;
} else if (strcmp(cmd, "SA") == 0) {
send_raw_tcp(local_ip_str, local_port_actual, client_ip, client_port, seq, ack, 0x12, NULL, 0);
seq++;
} else if (strcmp(cmd, "ACK") == 0) {
send_raw_tcp(local_ip_str, local_port_actual, client_ip, client_port, seq, ack, 0x10, NULL, 0);
} else if (strcmp(cmd, "RST") == 0) {
send_raw_tcp(local_ip_str, local_port_actual, client_ip, client_port, seq, ack, 0x04, NULL, 0);
} else if (strcmp(cmd, "PSH") == 0) {
uint32_t user_seq, user_ack;
char line[64];
printf("Enter seq and ack (e.g., 12345 67890): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
continue;
}
if (sscanf(line, "%u %u", &user_seq, &user_ack) != 2) {
printf("Invalid input. Expected two numbers.\n");
continue;
}
unsigned char payload[] = "ABCDE";
int payload_len = sizeof(payload);
printf("[Server] Sending PSH+ACK with seq=%u, ack=%u\n", user_seq, user_ack);
send_raw_tcp(local_ip_str, local_port_actual, client_ip, client_port, user_seq, user_ack, 0x18, payload, payload_len);
} else if (strcmp(cmd, "PSH2") == 0) {
send_psh2(local_ip_str, local_port_actual, client_ip, client_port, &seq, ack);
} else {
printf("Unknown command. Available: FIN, SA, ACK, RST, PSH, PSH2, quit\n");
}
}
close(client_fd);
close(listen_fd);
}
/* 函数2:连接后支持发送窗口外 SYN 或 invalid-ACK 数据段,PSH 命令支持手动输入 seq/ack */
static void connect_to_server(const char *ip, int port) {
int sockfd;
struct sockaddr_in addr, local_addr;
socklen_t addr_len = sizeof(local_addr);
char src_ip_str[INET_ADDRSTRLEN] = {0};
int src_port = 0;
int have_local_addr = 0;
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) { perror("socket"); return; }
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(port);
if (inet_pton(AF_INET, ip, &addr.sin_addr) <= 0) { perror("inet_pton"); close(sockfd); return; }
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("connect"); close(sockfd); return; }
if (getsockname(sockfd, (struct sockaddr*)&local_addr, &addr_len) == 0) {
inet_ntop(AF_INET, &local_addr.sin_addr, src_ip_str, sizeof(src_ip_str));
src_port = ntohs(local_addr.sin_port);
printf("[Client] Connected to %s:%d (local %s:%d)\n", ip, port, src_ip_str, src_port);
have_local_addr = 1;
} else {
printf("[Client] Connected to %s:%d\n", ip, port);
}
if (!have_local_addr) {
strncpy(src_ip_str, local_ip, sizeof(src_ip_str) - 1);
src_ip_str[sizeof(src_ip_str) - 1] = '\0';
src_port = local_port;
}
printf("\n=== Connected Client Mode ===\n");
printf("Commands:\n");
printf(" OSYN - send out-of-window SYN (seq=0x7fffffff)\n");
printf(" PSH - send invalid-ACK data segment (PSH+ACK) with manually entered seq & ack\n");
printf(" PSH2 - send PSH+ACK data segments (enter loop mode 1/0 & data len)\n");
printf(" FA - send FIN+ACK packet\n");
printf(" ACK - send ACK packet\n");
printf(" SA - send SYN+ACK packet\n");
printf(" SYN - send SYN packet\n");
printf(" SR - send SYN+RST packet\n");
printf(" quit - close connection and return to main menu\n");
uint32_t seq = rand();
uint32_t ack = rand();
char cmd[16];
char buf[1024];
while (1) {
ssize_t n = recv(sockfd, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT);
if (n == 0) {
printf("[Client] Remote closed connection\n");
break;
} else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
perror("recv");
break;
}
printf("client> ");
fflush(stdout);
if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;
cmd[strcspn(cmd, "\n")] = '\0';
if (strcmp(cmd, "quit") == 0) {
printf("Closing connection.\n");
break;
} else if (strcmp(cmd, "OSYN") == 0) {
uint32_t out_of_window_seq = 0x7fffffff;
printf("[Client] Sending SYN with seq=%u (out of window)\n", out_of_window_seq);
send_raw_tcp(src_ip_str, src_port, ip, port, out_of_window_seq, 0, 0x02, NULL, 0);
} else if (strcmp(cmd, "PSH") == 0) {
uint32_t snd_nxt, ack_val;
char line[64];
printf("Enter seq and ack (e.g., 12345 67890): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
continue;
}
if (sscanf(line, "%u %u", &snd_nxt, &ack_val) != 2) {
printf("Invalid input. Expected two numbers.\n");
continue;
}
unsigned char payload[] = "ABCDE";
int payload_len = sizeof(payload);
printf("[Client] Sending PSH with seq=%u, ack=%u\n", snd_nxt, ack_val);
send_raw_tcp(src_ip_str, src_port, ip, port, snd_nxt, ack_val, 0x18, payload, payload_len);
} else if (strcmp(cmd, "PSH2") == 0) {
send_psh2(src_ip_str, src_port, ip, port, &seq, ack);
} else if (strcmp(cmd, "FA") == 0) {
send_raw_tcp(src_ip_str, src_port, ip, port, seq, ack, 0x11, NULL, 0);
seq++;
} else if (strcmp(cmd, "ACK") == 0) {
send_raw_tcp(src_ip_str, src_port, ip, port, seq, ack, 0x10, NULL, 0);
} else if (strcmp(cmd, "SA") == 0) {
send_raw_tcp(src_ip_str, src_port, ip, port, seq, ack, 0x12, NULL, 0);
seq++;
} else if (strcmp(cmd, "SYN") == 0) {
send_raw_tcp(src_ip_str, src_port, ip, port, seq, ack, 0x02, NULL, 0);
seq++;
} else if (strcmp(cmd, "SR") == 0) {
send_raw_tcp(src_ip_str, src_port, ip, port, seq, ack, 0x06, NULL, 0);
seq++;
} else {
printf("Unknown command. Available: OSYN, PSH, PSH2, FA, ACK, SA, SYN, SR, quit\n");
}
}
close(sockfd);
}
/* 函数3:直接发送原始报文(不建立连接),支持用户交互 */
static void send_raw_mode(const char *src_ip, int src_port,
const char *dst_ip, int dst_port) {
printf("\n=== Raw Packet Sender Mode (No Connection) ===\n");
printf("Source: %s:%d, Destination: %s:%d\n", src_ip, src_port, dst_ip, dst_port);
printf("Commands:\n");
printf(" FIN - send FIN packet (manually enter seq & ack)\n");
printf(" FA - send FIN+ACK packet (manually enter seq & ack)\n");
printf(" PSH - send PSH+ACK packet (manually enter seq & ack, payload \"ABCDE\")\n");
printf(" PSH2 - send PSH+ACK data segments (enter loop mode 1/0 & data len)\n");
printf(" SA - send SYN+ACK packet\n");
printf(" SYN - send SYN packet\n");
printf(" ACK - send ACK packet\n");
printf(" quit - return to main menu\n");
uint32_t seq = rand();
uint32_t ack = 0;
char cmd[16];
while (1) {
printf("raw> ");
fflush(stdout);
if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;
cmd[strcspn(cmd, "\n")] = '\0';
if (strcmp(cmd, "quit") == 0) {
printf("Exiting raw mode.\n");
break;
} else if (strcmp(cmd, "FIN") == 0) {
uint32_t user_seq, user_ack;
char line[64];
printf("Enter seq and ack (e.g., 12345 67890): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
continue;
}
if (sscanf(line, "%u %u", &user_seq, &user_ack) != 2) {
printf("Invalid input. Expected two numbers.\n");
continue;
}
printf("[Raw] Sending FIN with seq=%u, ack=%u\n", user_seq, user_ack);
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, user_seq, user_ack, 0x01, NULL, 0);
} else if (strcmp(cmd, "FA") == 0) {
uint32_t user_seq, user_ack;
char line[64];
printf("Enter seq and ack (e.g., 12345 67890): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
continue;
}
if (sscanf(line, "%u %u", &user_seq, &user_ack) != 2) {
printf("Invalid input. Expected two numbers.\n");
continue;
}
printf("[Raw] Sending FIN+ACK with seq=%u, ack=%u\n", user_seq, user_ack);
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, user_seq, user_ack, 0x11, NULL, 0);
} else if (strcmp(cmd, "PSH") == 0) {
uint32_t user_seq, user_ack;
char line[64];
printf("Enter seq and ack (e.g., 12345 67890): ");
fflush(stdout);
if (fgets(line, sizeof(line), stdin) == NULL) {
printf("Input error.\n");
continue;
}
if (sscanf(line, "%u %u", &user_seq, &user_ack) != 2) {
printf("Invalid input. Expected two numbers.\n");
continue;
}
unsigned char payload[] = "ABCDE";
int payload_len = sizeof(payload);
printf("[Raw] Sending PSH+ACK with seq=%u, ack=%u, payload \"ABCDE\"\n", user_seq, user_ack);
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, user_seq, user_ack, 0x18, payload, payload_len);
} else if (strcmp(cmd, "PSH2") == 0) {
send_psh2(src_ip, src_port, dst_ip, dst_port, &seq, ack);
} else if (strcmp(cmd, "SA") == 0) {
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, seq, ack, 0x12, NULL, 0);
seq++;
} else if (strcmp(cmd, "SYN") == 0) {
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, seq, ack, 0x02, NULL, 0);
seq++;
} else if (strcmp(cmd, "ACK") == 0) {
send_raw_tcp(src_ip, src_port, dst_ip, dst_port, seq, ack, 0x10, NULL, 0);
// ACK 不消耗序列号
} else {
printf("Unknown command. Available: FIN, FA, PSH, PSH2, SA, SYN, ACK, quit\n");
}
}
}
/* 函数4:bind local_ip:local_port,建立连接后收到对端 FIN,调用 close 进入 LAST_ACK 状态 */
static void last_ack_test_server(const char *bind_ip, int bind_port) {
int listen_fd, reuse = 1;
struct sockaddr_in addr;
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0) { perror("socket"); return; }
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(bind_port);
if (inet_pton(AF_INET, bind_ip, &addr.sin_addr) <= 0) { perror("inet_pton"); close(listen_fd); return; }
if (bind(listen_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) { perror("bind"); close(listen_fd); return; }
if (listen(listen_fd, 128) < 0) { perror("listen"); close(listen_fd); return; }
printf("[Server] Listening on %s:%d\n", bind_ip, bind_port);
struct sockaddr_in client_addr;
socklen_t client_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_len);
if (client_fd < 0) { perror("accept"); close(listen_fd); return; }
char client_ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &client_addr.sin_addr, client_ip, sizeof(client_ip));
int client_port = ntohs(client_addr.sin_port);
printf("[Server] Accepted connection from %s:%d\n", client_ip, client_port);
printf("[Server] Waiting for peer to send FIN...\n");
char buf[1024];
while (1) {
ssize_t n = recv(client_fd, buf, sizeof(buf), MSG_PEEK | MSG_DONTWAIT);
if (n == 0) {
printf("[Server] Peer sent FIN, calling close() 10 seconds later to enter LAST_ACK...\n");
sleep(10);
close(client_fd);
printf("[Server] Entered LAST_ACK state, keeping for 6000000 seconds...\n");
sleep(6000000);
break;
} else if (n < 0 && errno != EAGAIN && errno != EWOULDBLOCK) {
perror("recv");
break;
}
sleep(1);
}
close(listen_fd);
}
/* 函数7:last_ack_test_client: 连接后,支持 close(正常关闭但不退出),支持 FA/ACK/SA 原始报文 */
static void last_ack_test_client(const char *dst_ip, int dst_port) {
int sockfd;
struct sockaddr_in addr, local_addr;
socklen_t addr_len = sizeof(local_addr);
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd < 0) { perror("socket"); return; }
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(dst_port);
if (inet_pton(AF_INET, dst_ip, &addr.sin_addr) <= 0) { perror("inet_pton"); close(sockfd); return; }
if (connect(sockfd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
perror("connect");
close(sockfd);
return;
}
if (getsockname(sockfd, (struct sockaddr*)&local_addr, &addr_len) == 0) {
char src_ip_str[INET_ADDRSTRLEN];
inet_ntop(AF_INET, &local_addr.sin_addr, src_ip_str, sizeof(src_ip_str));
int src_port = ntohs(local_addr.sin_port);
printf("[Client] Connected to %s:%d (local %s:%d)\n", dst_ip, dst_port, src_ip_str, src_port);
printf("\n=== Last ACK Test Client ===\n");
printf("Commands:\n");
printf(" close - call close() to shutdown connection normally (but stay in this mode)\n");
printf(" FA - send FIN+ACK packet (raw)\n");
printf(" ACK - send ACK packet (raw)\n");
printf(" SA - send SYN+ACK packet (raw)\n");
printf(" quit - close connection and return to main menu\n");
uint32_t seq = rand();
uint32_t ack = rand();
char cmd[16];
int conn_closed = 0;
while (1) {
printf("lastack> ");
fflush(stdout);
if (fgets(cmd, sizeof(cmd), stdin) == NULL) break;
cmd[strcspn(cmd, "\n")] = '\0';
if (strcmp(cmd, "quit") == 0) {
if (!conn_closed) {
close(sockfd);
}
printf("Exiting last_ack_test_client.\n");
break;
} else if (strcmp(cmd, "close") == 0) {
if (!conn_closed) {
printf("Calling close() - sending FIN normally.\n");
close(sockfd);
conn_closed = 1;
printf("Connection closed by close(). You may still send raw packets (FA/ACK/SA).\n");
} else {
printf("Connection already closed.\n");
}
} else if (strcmp(cmd, "FA") == 0) {
send_raw_tcp(src_ip_str, src_port, dst_ip, dst_port, seq, ack, 0x11, NULL, 0);
seq++;
} else if (strcmp(cmd, "ACK") == 0) {
send_raw_tcp(src_ip_str, src_port, dst_ip, dst_port, seq, ack, 0x10, NULL, 0);
} else if (strcmp(cmd, "SA") == 0) {
send_raw_tcp(src_ip_str, src_port, dst_ip, dst_port, seq, ack, 0x12, NULL, 0);
seq++;
} else {
printf("Unknown command. Available: close, FA, ACK, SA, quit\n");
}
}
} else {
printf("[Client] Cannot get local address, raw sending unavailable.\n");
close(sockfd);
}
}
int main(int argc, char *argv[]) {
if (argc != 5) {
fprintf(stderr, "Usage: %s <local_ip> <local_port> <remote_ip> <remote_port>\n", argv[0]);
exit(EXIT_FAILURE);
}
strncpy(local_ip, argv[1], sizeof(local_ip)-1);
local_ip[sizeof(local_ip)-1] = '\0';
local_port = atoi(argv[2]);
strncpy(remote_ip, argv[3], sizeof(remote_ip)-1);
remote_ip[sizeof(remote_ip)-1] = '\0';
remote_port = atoi(argv[4]);
signal(SIGPIPE, SIG_IGN);
srand(time(NULL));
printf("=== Multi-mode Network Tool (Merged) ===\n");
printf("Local: %s:%d\n", local_ip, local_port);
printf("Remote: %s:%d\n", remote_ip, remote_port);
printf("Select mode by number:\n");
printf(" 1 - listen_on_server (listen on local address, support raw sending)\n");
printf(" 2 - connect_to_server (interactive: send out-of-window SYN or invalid-ACK segment or send FA/ACK/SA/SYN/SR)\n");
printf(" 3 - raw packet sender (no connection, send FIN/PSH/SA/SYN/ACK)\n");
printf(" 4 - last_ack_test_server (bind local, recv FIN, close -> LAST_ACK)\n");
printf(" 5 - last_ack_test_client (connect remote, support close/FA/ACK/SA)\n");
printf(" 0 - quit\n");
int choice;
while (1) {
printf("> ");
fflush(stdout);
if (scanf("%d", &choice) != 1) {
int c;
while ((c = getchar()) != '\n' && c != EOF);
printf("Invalid input. Please enter a number.\n");
continue;
}
// 清空输入缓冲区
int c;
while ((c = getchar()) != '\n' && c != EOF);
if (choice == 0) {
printf("Exiting.\n");
break;
} else if (choice == 1) {
printf("Starting listen_on_server...\n");
listen_on_server(local_ip, local_port);
} else if (choice == 2) {
printf("Starting connect_to_server...\n");
connect_to_server(remote_ip, remote_port);
} else if (choice == 3) {
send_raw_mode(local_ip, local_port, remote_ip, remote_port);
} else if (choice == 4) {
printf("Starting last_ack_test_server...\n");
last_ack_test_server(local_ip, local_port);
} else if (choice == 5) {
printf("Starting last_ack_test_client...\n");
last_ack_test_client(remote_ip, remote_port);
} else {
printf("Invalid choice. Please enter 1, 2, 3, 4, 5, or 0.\n");
}
}
return 0;
}