tcpfwd-doc

tcpfwd --- TCP 端口转发 (nostdlib ARM)

2026/7/2

基本信息

项目
源文件 tcpfwd.c (248 行)
编译产物 tcpfwd (1864 bytes)
目标平台 ZTE UZ901 热点 (ARMv7, Linux 3.4.110)
工具链 arm-linux-gnueabihf-gcc -static -nostdlib -Os -s
工作目录 /tmp/ (RAM) 或 /mnt/userdata/ (JFFS2)

架构

复制代码
 ┌──────────┐     TCP     ┌──────────┐     TCP     ┌──────────┐
 │  Client  │ ←────────→ │  tcpfwd  │ ←────────→ │  Target   │
 │ (browser)│   listen    │  (hotspot)│   connect  │ (server)  │
 └──────────┘    port     └──────────┘    ip:port  └──────────┘

单线程、同步阻塞 poll 模型。每个连接串行处理(接受 → 转发 → 关闭 → 下一个)。

源码结构

1. 数据类型 (17-28行)

c 复制代码
struct sockaddr_in { short sin_family; ushort sin_port; uint sin_addr; char pad[8]; };
struct pollfd { int fd; short events; short revents; };

2. Syscall 包装层 (30-56行)

ARM EABI 纯内联汇编,r7 传 syscall 号,r0-r4 传参数。

c 复制代码
static long s5(long n, long a0, long a1, long a2, long a3, long a4) {
    register long r0 asm("r0") = a0;
    register long r1 asm("r1") = a1;
    register long r2 asm("r2") = a2;
    register long r3 asm("r3") = a3;
    register long r4 asm("r4") = a4;
    register long r7 asm("r7") = n;
    asm volatile("svc #0" : "+r"(r0) : "r"(r1), "r"(r2), "r"(r3), "r"(r4), "r"(r7) : "memory", "cc");
    return r0;
}

简化宏:

  • s3(n,a0,a1,a2) → 3 参数 syscall
  • s2(n,a0,a1) → 2 参数 syscall
  • s1(n,a0) → 1 参数 syscall

Syscall 映射

编号 说明
exit(n) 1 退出
read(f,b,l) 3 读文件/套接字
write(f,b,l) 4 写文件/套接字
open(p,f,m) 5 打开文件
close(fd) 6 关闭 fd
poll(f,n,t) 168 IO 多路复用
socket(d,t,p) 281 创建套接字
bind(f,a,l) 282 绑定端口
connect(f,a,l) 283 连接目标
listen(f,b) 284 监听
accept(f,a,l) 285 接受连接
setsockopt(f,l,o,v,s) 294 设置选项

3. 工具函数 (58-93行)

  • writestr(fd, s) --- 写字符串到 fd(自动计算长度)
  • htons(x) --- 主机序转网络序(大小端交换)
  • parse_ip(out, pp) --- 解析 IP 字符串 "1.2.3.4" → u32
  • parse_port(pp) --- 解析端口字符串 "8080" → int
  • skip_spaces(pp) --- 跳过空白字符

4. 转发核心 proxy_loop (95-142行)

c 复制代码
static void proxy_loop(int cfd, int tfd) {
    char buf[4096];
    while (1) {
        struct pollfd pfds[2];      // 监视两个 fd
        pfds[0] = {cfd, POLLIN};
        pfds[1] = {tfd, POLLIN};

        int r = poll(pfds, 2, -1); // 阻塞等待
        if (r <= 0) break;

        // 检查错误事件
        if (pfds[0].revents & (POLLERR|POLLHUP|POLLNVAL)) break;
        if (pfds[1].revents & (POLLERR|POLLHUP|POLLNVAL)) break;

        // client → target
        if (pfds[0].revents & POLLIN) {
            r = read(cfd, buf, sizeof(buf));
            if (r <= 0) break;
            // 全部写出(处理短写)
            char *bp = buf; int left = r;
            while (left > 0) {
                int w = write(tfd, bp, left);
                if (w <= 0) break;
                bp += w; left -= w;
            }
            if (left > 0) break;
        }

        // target → client (同上)
        if (pfds[1].revents & POLLIN) { ... }

        // 一方关闭后 break 并 close 两个 fd
    }
    close(cfd); close(tfd);
}

关键设计:

  • 单 4KB 缓冲区,两个方向共用(串行 poll,不会同时读写)
  • while (left > 0) 循环处理短写(write 可能未写完所有字节)
  • 任何方向 read ≤ 0 或 write ≤ 0 都断开整个连接
  • poll 超时 -1(无限等待)

5. 入口 _start (144-247行)

复制代码
_start
 ├── open("/tmp/tcpfwd.conf", O_RDONLY)
 ├── read 配置 (最大 512 字节)
 ├── parse: 跳过 # 注释和空行
 │   └── listen_port target_ip target_port
 ├── socket → bind → listen
 ├── writestr("tcpfwd: running\n")
 └── loop:
     ├── accept → 新 client fd
     ├── socket → connect target
     ├── proxy_loop(cfd, tfd)
     └── 断开后回到 accept

配置格式

/tmp/tcpfwd.conf:

复制代码
8080 192.168.1.100 3000
# 或带注释:
# 转发热点 8080 → 内网服务器 3000
  • 只解析第一行有效规则
  • 支持 # 行注释
  • 支持空行

编译

bash 复制代码
arm-linux-gnueabihf-gcc -static -nostdlib -Os -s \
    -o tcpfwd tcpfwd.c

或使用 WSL:

bash 复制代码
/opt/arm-linux-musleabihf-cross/bin/arm-linux-musleabihf-gcc \
    -static -nostdlib -Os -s -mfloat-abi=soft \
    -o tcpfwd tcpfwd.c

部署

bash 复制代码
adb push tcpfwd /tmp/
adb shell chmod +x /tmp/tcpfwd
echo "8080 192.168.1.100 3000" | adb shell tee /tmp/tcpfwd.conf
adb shell "/tmp/tcpfwd &"

限制

限制 说明
单连接串行 一次只能转发一个连接,完成后才 accept 下一个
无 fork/线程 无法并发处理多个连接
无超时 poll(-1) 永久阻塞,僵尸连接不回收
4KB 缓冲区 超过 4KB 报文需多次 poll 转发
单规则 config 只读第一条规则
config 512B 文件最大 512 字节
硬编码路径 config 只读 /tmp/tcpfwd.conf

已知 Bug

  1. config 路径混淆 : 头注释写 /mnt/userdata/tcpfwd.conf,代码写 /tmp/tcpfwd.conf
  2. 无 SO_REUSEADDR 效果: 设置了 reuse 但连接断开后有 TIME_WAIT 问题
  3. accept 空地址: 第二个参数传 0,无法知道客户端 IP
  4. gcc register asm : s5 使用 register asm("rX")-Os 下跨函数调用可能被优化出错(GCC 11 已知问题)

文档日期: 2026-07-02

相关文件: 已删除

相关推荐
牢姐与蒯2 小时前
Linux基本指令及知识点(二)
linux·运维·服务器
小玮看世界2 小时前
[Python]从“脏”数据到优雅实现:一个IoT滑动窗口最大值问题的测试驱动优化实录
linux·前端·python
BLi4ee3 小时前
WSL 文件资源管理器左侧 Linux / Ubuntu 入口恢复
linux·ubuntu
BLUcoding4 小时前
Ubuntu 开机自启 Kiosk 模式浏览器全屏展示网页配置指南
linux·运维·ubuntu
Kina_C6 小时前
Keepalived 配置管理进阶:使用子配置文件实现模块化与高可读性
linux·网络·keepalived
wsad05326 小时前
CentOS Stream 10 配置静态 IP 完整指南
linux·tcp/ip·centos
Ai拆代码的曹操6 小时前
K8s 实战:CrashLoopBackOff 状态下 kubectl logs 拿不到日志的三层排查方案
linux·docker·kubernetes
tedcloud1237 小时前
Kimi-K3 部署指南:大模型应用开发环境搭建实践
linux·运维·服务器·开源·音视频
深念Y7 小时前
ZTE_UZ901_NVRAM经验总结
linux·服务器·网络·嵌入式硬件·嵌入式·随身wifi
似水এ᭄往昔8 小时前
【Linux网络编程】--TCP_Socket
linux·网络·tcp/ip