LIBTCPIP 技术探秘
一、项目概述与用途
LIBTCPIP 是一个基于 LwIP TCP/IP C netstack 2.13-RC2 实现的 TCP/IP loopback 子网络栈。
核心用途: 该项目的主要目的是创建一个用户态的 TCP/IP 网络栈,用于实现透明代理或 NAT 转换功能。它可以拦截和处理 IP 数据包,建立本地 TCP 连接来代理原始的 TCP 连接。
二、技术原理
2.1 核心设计
LIBTCPIP 采用双层网络栈架构:
- LwIP 层:使用轻量级 TCP/IP 协议栈处理数据包
- 本地 Socket 层:使用真实的 TCP Socket 与目标服务通信
项目配置为 NO_SYS 模式运行,仅支持 TCP 协议,禁用了 UDP、ICMP、ARP、DHCP 等其他协议。
2.2 工作原理
Loopback 模式: 当 IP 数据包进入系统时,LwIP 会处理这些数据包。对于 TCP 连接,系统会在本地环回地址(localhost)上创建真实的 TCP Socket 连接来代理流量。
三、核心结构
3.1 主要 API 接口
项目提供三个核心 C API:
-
libtcpip_loopback:初始化 loopback 网络栈,配置 IP、网关、掩码和输出回调函数 -
libtcpip_input:将 IP 数据包输入到网络栈进行处理 -
libtcpip_link:查询 NAT 映射信息,获取源/目标 IP 和端口
3.2 核心数据结构
netstack_tcp_socket:这是连接管理的核心结构,包含:
- LwIP 的
tcp_pcb指针 - Boost.Asio 的真实 TCP Socket
- 本地和远程 IP/端口信息
- 发送缓冲区队列(分为 LwIP 层和 Socket 层)
- NAT 端口映射标识符
3.3 管理映射表
使用两个哈希表管理 Socket 连接:
p2ss_:PCB 指针到 Socket 的映射n2ss_:NAT 端口到 Socket 的映射
四、实现细节
4.1 初始化流程
初始化时会:
- 验证 IP、网关、掩码参数的有效性
- 设置输出回调函数和网络参数
- 初始化 LwIP 协议栈
- 配置网络接口的 IP 地址和输出函数
- 创建监听所有端口的 TCP PCB
- 启动独立线程运行 Boost.Asio 事件循环
4.2 TCP 连接建立流程
当收到 TCP 连接请求时:
-
接受连接 :LwIP 的
netstack_tcp_doaccept被调用 -
创建 Socket 对象 :分配
netstack_tcp_socket结构,记录连接的四元组信息 -
建立映射:将 PCB 与 Socket 对象关联
-
打开本地连接:在 localhost 上创建真实的 TCP Socket
-
连接目标:异步连接到目标端点(localhost)
-
注册回调:设置接收、发送、错误和轮询回调函数
4.3 数据转发机制
LwIP → Socket 方向:
- LwIP 接收到数据时,通过
netstack_tcp_dorecv处理 - 数据被转发到真实的 Socket 通过
netstack_tunnel_send
Socket → LwIP 方向:
- 从 Socket 读取数据通过
netstack_tunnel_dorecv - 使用
netstack_tcp_send将数据写入 LwIP 的 TCP PCB
4.4 发送队列管理
系统维护两层发送队列:
-
LWIP 层队列:当 LwIP 的发送缓冲区满时,数据会排队等待
-
Socket 层队列:在真实 Socket 连接建立之前,数据会被缓存
当发送缓冲区有空间或连接建立时,队列中的数据会被逐一发送
4.5 NAT 端口映射
系统使用本地绑定端口作为 NAT 标识符,建立本地端口与原始连接的映射关系
通过 link 函数可以查询 NAT 映射,获取原始的源和目标地址信息
五、工作流程图
目标服务 本地 TCP Socket LwIP 协议栈 LIBTCPIP API 应用程序 目标服务 本地 TCP Socket LwIP 协议栈 LIBTCPIP API 应用程序 loop [数据转发] libtcpip_loopback(初始化) 初始化协议栈 libtcpip_input(IP 包) 处理数据包 TCP 连接请求 创建本地 Socket 连接 localhost 从 LwIP 接收数据 转发到目标 接收响应数据 回传到 LwIP output 回调 返回 IP 包
六、性能优化
-
零拷贝优化 :使用
TCP_WRITE_FLAG_COPY标志让 LwIP 处理数据复制 -
高优先级线程:事件循环线程设置为最高优先级(Windows 上为 THREAD_PRIORITY_TIME_CRITICAL,Linux 上为 SCHED_FIFO)
-
TCP Fast Open:尝试启用 TCP Fast Open 优化
-
禁用校验和验证:禁用所有协议的校验和检查以提高性能
-
配置大的 TCP 窗口:TCP 窗口大小设置为 32KB
Notes
应用场景:
- 透明代理:拦截 TCP 流量并转发到代理服务器
- VPN/隧道:实现用户态的网络隧道
- 流量分析:在用户态分析和修改 TCP 流量
- 网络模拟:模拟不同的网络条件进行测试
技术特点:
- 纯用户态实现,无需内核模块
- 基于成熟的 LwIP 协议栈
- 使用 Boost.Asio 实现异步 I/O
- 支持 Windows 和 Linux 平台
- 提供 C API 接口,易于集成
局限性:
- 仅支持 TCP 协议
- 需要配合其他组件(如 TUN/TAP)实现完整的网络代理功能
- 所有流量都通过 localhost 转发,适合本地代理场景
Citations
File: README.md (L1-2)
markdown
# libtcpip
TCP/IP loopback sub-netstack based on LwIP TCP/IP C netstack 2.13-RC2.
File: lwip/my/lwipopts.h (L36-60)
text
#define NO_SYS 1
#define LWIP_TIMERS 1
#define IP_DEFAULT_TTL 64
#define LWIP_ARP 0
#define ARP_QUEUEING 0
#define IP_FORWARD 0
#define LWIP_ICMP 0
#define LWIP_RAW 0
#define LWIP_DHCP 0
#define LWIP_AUTOIP 0
#define LWIP_SNMP 0
#define LWIP_IGMP 0
#define LWIP_DNS 0
#define LWIP_UDP 0
#define LWIP_UDPLITE 0
#define LWIP_TCP 1
#define LWIP_CALLBACK_API 1
#define LWIP_NETIF_API 0
#define LWIP_NETIF_LOOPBACK 0
#define LWIP_HAVE_LOOPIF 1
#define LWIP_HAVE_SLIPIF 0
#define LWIP_NETCONN 0
#define LWIP_SOCKET 0
#define PPP_SUPPORT 0
File: lwip/my/lwipopts.h (L65-70)
text
// disable checksum checks
#define CHECKSUM_CHECK_IP 0
#define CHECKSUM_CHECK_UDP 0
#define CHECKSUM_CHECK_TCP 0
#define CHECKSUM_CHECK_ICMP 0
#define CHECKSUM_CHECK_ICMP6 0
File: lwip/my/lwipopts.h (L85-86)
text
#define TCP_WND 32 * 1024
#define TCP_SND_BUF (TCP_WND)
File: netstack.cpp (L13-42)
cpp
struct netstack_tcp_socket {
typedef struct {
std::shared_ptr<char> p;
int sz;
} buffer_chunk;
typedef struct {
buffer_chunk buf;
std::function<void(struct tcp_pcb*)> cb;
} send_context;
typedef std::shared_ptr<send_context> send_context_ptr;
typedef enum {
ENETSTACK_TCP_SENT_LWIP,
ENETSTACK_TCP_SENT_SOCK,
ENETSTACK_TCP_SENT_MAX
} ENETSTACK_TCP_SENT_BUFS;
std::list<send_context_ptr> sents[ENETSTACK_TCP_SENT_MAX];
std::shared_ptr<boost::asio::ip::tcp::socket> socket;
bool open;
int pnat;
struct tcp_pcb* pcb;
ip_addr_t local_ip;
u16_t local_port;
ip_addr_t remote_ip;
u16_t remote_port;
u8_t buf[1400];
};
File: netstack.cpp (L54-56)
cpp
static Ptr2Socket p2ss_;
static Nat2Socket n2ss_;
static NetstackMutex lockobj_;
File: netstack.cpp (L146-189)
cpp
inline static err_t netstack_tcp_send(struct tcp_pcb* pcb, void* data, u16_t len, const std::function<void(struct tcp_pcb*)>& callback) noexcept {
if (!pcb) {
return ERR_ARG;
}
std::shared_ptr<netstack_tcp_socket> socket_ = netstack_tcp_getsocket(pcb->callback_arg);
if (!socket_) {
return ERR_ABRT;
}
if (!data || !len) {
return ERR_ARG;
}
static auto tcp_enqueue_ =
[](netstack_tcp_socket* socket_, struct tcp_pcb* pcb, void* data, u16_t len, const std::function<void(struct tcp_pcb*)>& callback) noexcept {
std::shared_ptr<char> chunk_ = std::shared_ptr<char>((char*)malloc(len), free);
memcpy(chunk_.get(), data, len);
netstack_tcp_socket::send_context_ptr context_ =
std::make_shared<netstack_tcp_socket::send_context>();
context_->buf.p = std::move(chunk_);
context_->buf.sz = len;
context_->cb = callback;
socket_->sents[netstack_tcp_socket::ENETSTACK_TCP_SENT_LWIP].push_back(std::move(context_));
return ERR_OK;
};
if (!socket_->sents[netstack_tcp_socket::ENETSTACK_TCP_SENT_LWIP].empty()) {
return tcp_enqueue_(socket_.get(), pcb, data, len, callback);
}
err_t err = tcp_write(pcb, data, len, TCP_WRITE_FLAG_COPY);
if (err == ERR_OK) {
tcp_output(pcb);
if (callback) {
callback(pcb);
}
return err;
}
else if (err == ERR_MEM) {
return tcp_enqueue_(socket_.get(), pcb, data, len, callback);
}
return err;
}
File: netstack.cpp (L278-298)
cpp
inline static err_t netstack_tcp_dorecv(void* arg, struct tcp_pcb* pcb, struct pbuf* p, err_t err) noexcept {
LWIP_UNUSED_ARG(arg);
if (p) {
std::shared_ptr<netstack_tcp_socket> socket = netstack_tcp_getsocket(pcb->callback_arg);
if (!socket) {
tcp_abort(pcb);
return ERR_ABRT;
}
for (struct pbuf* q = p; p; p = p->next) {
tcp_recved(pcb, q->len);
netstack_tunnel_send(socket, p->payload, p->len, true);
}
netstack_pbuf_free(p);
}
else if (err == ERR_OK) {
return netstack_tcp_closesocket(pcb);
}
return ERR_OK;
}
File: netstack.cpp (L300-335)
cpp
inline static err_t netstack_tcp_dosent(void* arg, struct tcp_pcb* pcb, u16_t len) noexcept {
LWIP_UNUSED_ARG(arg);
std::shared_ptr<netstack_tcp_socket> socket_ = netstack_tcp_getsocket(pcb->callback_arg);
if (socket_) {
std::list<netstack_tcp_socket::send_context_ptr>& sents = socket_->sents[netstack_tcp_socket::ENETSTACK_TCP_SENT_LWIP];
while (!sents.empty()) { // tcp_sndbuf
err_t err_;
do {
netstack_tcp_socket::send_context_ptr context_ = sents.front();
err_ = tcp_write(pcb,
context_->buf.p.get(),
context_->buf.sz, TCP_WRITE_FLAG_COPY);
if (err_ == ERR_OK) {
tcp_output(pcb);
if (context_->cb) {
context_->cb(pcb);
}
sents.pop_front();
}
} while (0);
if (err_) {
if (err_ == ERR_MEM) {
break;
}
netstack_tcp_closesocket(socket_);
return ERR_ABRT;
}
}
return ERR_OK;
}
else {
tcp_abort(pcb);
return ERR_ABRT;
}
}
File: netstack.cpp (L364-366)
cpp
inline static err_t netstack_tcp_doaccept(void* arg, struct tcp_pcb* pcb, err_t err) noexcept {
LWIP_UNUSED_ARG(arg);
LWIP_UNUSED_ARG(err);
File: netstack.cpp (L368-376)
cpp
std::shared_ptr<netstack_tcp_socket> socket_ = std::make_shared<netstack_tcp_socket>();
socket_->pcb = pcb;
socket_->pnat = 0;
socket_->open = false;
socket_->socket = std::make_shared<boost::asio::ip::tcp::socket>(context_);
socket_->local_ip = pcb->remote_ip;
socket_->local_port = pcb->remote_port;
socket_->remote_ip = pcb->local_ip;
socket_->remote_port = pcb->local_port;
File: netstack.cpp (L378-385)
cpp
void* callback_arg = netstack_tcp_linksocket(pcb, socket_);
if (callback_arg) {
netstack_tcp_arg(pcb, callback_arg);
}
else {
netstack_tcp_closesocket(socket_);
return ERR_ABRT;
}
File: netstack.cpp (L389-389)
cpp
netstack_tcp_event(pcb, netstack_tcp_dorecv, netstack_tcp_dosent, netstack_tcp_doerrf, netstack_tcp_dopoll);
File: netstack.cpp (L398-437)
cpp
inline static bool netstack_tunnel_send(const std::shared_ptr<netstack_tcp_socket>& socket_, void* data, int len, bool unsafe_) noexcept {
if (!socket_ || !data || len < 1) {
return false;
}
std::shared_ptr<boost::asio::ip::tcp::socket>& socket = socket_->socket;
if (!socket || !socket->is_open()) {
return false;
}
if (!socket_->open) {
std::shared_ptr<char> chunk_ = std::shared_ptr<char>((char*)malloc(len), free);
memcpy(chunk_.get(), data, len);
netstack_tcp_socket::send_context_ptr context_ =
std::make_shared<netstack_tcp_socket::send_context>();
context_->buf.p = std::move(chunk_);
context_->buf.sz = len;
socket_->sents[netstack_tcp_socket::ENETSTACK_TCP_SENT_SOCK].push_back(std::move(context_));
return true;
}
std::shared_ptr<char> chunk_;
if (unsafe_) {
chunk_ = std::shared_ptr<char>((char*)malloc(len), free);
memcpy(chunk_.get(), data, len);
}
else {
chunk_ = *(std::shared_ptr<char>*)data;
}
std::shared_ptr<netstack_tcp_socket> socket__ = socket_;
boost::asio::async_write(*socket, boost::asio::buffer(chunk_.get(), len), [socket__, chunk_](const boost::system::error_code& ec, size_t sz) noexcept {
if (ec) {
netstack_tcp_closesocket(socket__);
}
});
return true;
}
File: netstack.cpp (L439-462)
cpp
inline static bool netstack_tunnel_dorecv(const std::shared_ptr<netstack_tcp_socket>& socket_) noexcept {
if (!socket_) {
return false;
}
std::shared_ptr<boost::asio::ip::tcp::socket>& socket = socket_->socket;
if (!socket || !socket->is_open()) {
return false;
}
std::shared_ptr<netstack_tcp_socket> socket__ = socket_;
socket->async_read_some(boost::asio::buffer(socket_->buf, sizeof(socket_->buf)), [socket__](const boost::system::error_code& ec, size_t sz) {
int by = std::max<int>(-1, ec ? -1 : (int)sz);
if (by < 1) {
netstack_tcp_closesocket(socket__);
}
else {
netstack_tcp_send(socket__->pcb, socket__->buf, by, [socket__](struct tcp_pcb*) {
netstack_tunnel_dorecv(socket__);
});
}
});
return true;
}
File: netstack.cpp (L478-509)
cpp
inline static bool netstack_socket_connect(const std::shared_ptr<netstack_tcp_socket>& socket_, const boost::asio::ip::tcp::endpoint& remoteEP_) noexcept {
if (!socket_) {
return false;
}
std::shared_ptr<boost::asio::ip::tcp::socket> socket = socket_->socket;
if (!socket) {
return false;
}
socket->async_connect(remoteEP_, [socket_](const boost::system::error_code& ec) noexcept {
if (ec) {
netstack_tcp_closesocket(socket_);
return;
}
if (socket_->open) {
netstack_tcp_closesocket(socket_);
return;
}
else {
socket_->open = true;
}
bool ok = netstack_tunnel_dorecv(socket_) && nestack_tunnel_post_all_unsent(socket_);
if (!ok) {
netstack_tcp_closesocket(socket_);
return;
}
});
return true;
}
File: netstack.cpp (L511-552)
cpp
inline static bool netstack_tunnel_open(const std::shared_ptr<netstack_tcp_socket>& socket_, boost::asio::ip::tcp::endpoint& remoteEP_) noexcept {
std::shared_ptr<boost::asio::ip::tcp::socket>& socket = socket_->socket;
if (!socket || socket->is_open()) {
return false;
}
if (IP_IS_V4_VAL(socket_->remote_ip)) {
remoteEP_ = boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), netstack::Localhost);
}
else {
return false;
}
boost::system::error_code ec;
try {
socket->open(remoteEP_.protocol(), ec);
if (ec) {
return false;
}
socket->set_option(boost::asio::detail::socket_option::boolean<IPPROTO_TCP, TCP_FASTOPEN>(true), ec);
socket->bind(boost::asio::ip::tcp::endpoint(boost::asio::ip::address_v4::loopback(), 0), ec);
if (ec) {
return false;
}
boost::asio::ip::tcp::endpoint localEP = socket->local_endpoint(ec);
if (ec) {
return false;
}
socket_->pnat = netstack_tcp_linksocket(localEP.port(), socket_);
if (!socket_->pnat) {
return false;
}
return true;
}
catch (std::exception&) {
return false;
}
}
File: netstack.cpp (L571-579)
cpp
struct tcp_pcb* pcb = tcp_new();
tcp_bind(pcb, IP_ADDR_ANY, 0);
pcb = tcp_listen(pcb);
tcp_arg(pcb, NULL);
tcp_accept(pcb, netstack_tcp_doaccept);
pcb_ = pcb;
return pcb_ != NULL;
File: netstack.cpp (L628-628)
cpp
lwip_init();
File: netstack.cpp (L634-637)
cpp
ip4_addr_t ips[] = { netstack::IP, netstack::MASK, netstack::IP };
netif_set_ipaddr(netif, ips + 0);
netif_set_netmask(netif, ips + 1);
netif_set_gw(netif, ips + 2);
File: netstack.cpp (L645-657)
cpp
std::thread([]() noexcept {
#ifdef _WIN32
SetThreadPriority(GetCurrentProcess(), THREAD_PRIORITY_TIME_CRITICAL);
#else
/* ps -eo state,uid,pid,ppid,rtprio,time,comm */
struct sched_param param_;
param_.sched_priority = sched_get_priority_max(SCHED_FIFO); // SCHED_RR
pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m_);
#endif
boost::system::error_code ec_;
boost::asio::io_context::work work_(context_);
context_.run(ec_);
}).detach();
File: netstack.cpp (L692-719)
cpp
bool netstack::link(int nat, uint32_t& srcAddr, int& srcPort, uint32_t& dstAddr, int& dstPort) noexcept {
dstAddr = 0;
dstPort = 0;
srcAddr = 0;
srcPort = 0;
std::shared_ptr<netstack_tcp_socket> socket = netstack_tcp_getsocket(nat);
if (!socket) {
return false;
}
if (IP_IS_V4_VAL(socket->remote_ip)) {
dstAddr = ip_addr_get_ip4_u32(&socket->remote_ip);
dstPort = socket->remote_port;
}
else {
return false;
}
if (IP_IS_V4_VAL(socket->local_ip)) {
srcAddr = ip_addr_get_ip4_u32(&socket->local_ip);
srcPort = socket->local_port;
return true;
}
else {
return false;
}
}
File: libtcpip.h (L25-26)
text
LIBTCPIP_API
bool libtcpip_input(void* packet, int size) noexcept;
File: libtcpip.h (L28-29)
text
LIBTCPIP_API
bool libtcpip_link(int nat, uint32_t& srcAddr, int& srcPort, uint32_t& dstAddr, int& dstPort) noexcept;
File: libtcpip.h (L31-32)
text
LIBTCPIP_API
bool libtcpip_loopback(int localhost, uint32_t ip, uint32_t gw, uint32_t mask, LIBTCPIP_IPV4_OUTPUT outputfn) noexcept;
File: libtcpip.cpp (L6-16)
cpp
if (ip == INADDR_ANY || ip == INADDR_NONE) {
return false;
}
if (gw == INADDR_ANY || gw == INADDR_NONE) {
return false;
}
if (mask == INADDR_ANY || !outputfn) {
return false;
}
File: libtcpip.cpp (L18-22)
cpp
lwip::netstack::output = outputfn;
lwip::netstack::IP = ip;
lwip::netstack::GW = gw;
lwip::netstack::MASK = mask;
lwip::netstack::Localhost = localhost;