Skip to content

Commit 51b9a7e

Browse files
committed
Add CGNAT STUN port discovery with TCP proxy keepalive
- Independent CGNATEnabled toggle (default off, decoupled from UPnP) - UDP STUN discovers external port for uTP - Standalone TCP proxy maintains NAT mapping + forwards inbound connections - Fix STUN XOR-MAPPED-ADDRESS byte-order bug - WebUI settings: enable toggle, STUN server, probe interval - Default STUN server empty (user-configured) Verified: 364MB upload through CGNAT proxy port on pure IPv4 Full Cone NAT.
1 parent 52d3a96 commit 51b9a7e

9 files changed

Lines changed: 626 additions & 0 deletions

File tree

src/base/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ add_library(qbt_base STATIC
1010
bittorrent/bandwidthscheduler.h
1111
bittorrent/bencoderesumedatastorage.h
1212
bittorrent/cachestatus.h
13+
bittorrent/cgnatmanager.h
1314
bittorrent/categoryoptions.h
1415
bittorrent/common.h
1516
bittorrent/customstorage.h
@@ -138,6 +139,7 @@ add_library(qbt_base STATIC
138139
bittorrent/bandwidthscheduler.cpp
139140
bittorrent/bencoderesumedatastorage.cpp
140141
bittorrent/categoryoptions.cpp
142+
bittorrent/cgnatmanager.cpp
141143
bittorrent/customstorage.cpp
142144
bittorrent/dbresumedatastorage.cpp
143145
bittorrent/downloadpathoption.cpp
@@ -304,3 +306,8 @@ if (PLUGINS)
304306
plugins/pluginversion.h
305307
)
306308
endif()
309+
310+
# CGNAT TCP proxy helper (pure C, standalone)
311+
add_executable(cgnat_proxy bittorrent/cgnat_proxy.cpp)
312+
313+
set_target_properties(cgnat_proxy PROPERTIES AUTOMOC OFF)
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
// cgnat_proxy.cpp — standalone TCP proxy + keepalive for CGNAT
2+
// Usage: cgnat_proxy <listen_port> <backend_port> <port_check_url>
3+
// Outputs "PORT=<ext_port>\n" on startup, then proxies.
4+
5+
#include <stdio.h>
6+
#include <stdlib.h>
7+
#include <string.h>
8+
#include <unistd.h>
9+
#include <errno.h>
10+
#include <signal.h>
11+
#include <time.h>
12+
#include <sys/socket.h>
13+
#include <sys/select.h>
14+
#include <netinet/in.h>
15+
#include <netdb.h>
16+
#include <arpa/inet.h>
17+
18+
#ifndef SO_REUSEPORT
19+
#define SO_REUSEPORT 15
20+
#endif
21+
22+
static volatile int running = 1;
23+
static void sig_handler(int) { running = 0; }
24+
25+
static int discover_port(int bind_port, const char *url)
26+
{
27+
char host[256] = {0}, path[256] = "/";
28+
int port = 80;
29+
const char *p = url;
30+
if (strncmp(p, "http://", 7) == 0) p += 7;
31+
const char *slash = strchr(p, '/'), *colon = strchr(p, ':');
32+
if (colon && (!slash || colon < slash)) {
33+
memcpy(host, p, colon - p); port = atoi(colon + 1);
34+
} else if (slash) memcpy(host, p, slash - p);
35+
else strncpy(host, p, sizeof(host)-1);
36+
if (slash) strncpy(path, slash, sizeof(path)-1);
37+
38+
struct hostent *he = gethostbyname(host);
39+
if (!he) return -1;
40+
41+
int fd = socket(AF_INET, SOCK_STREAM, 0);
42+
if (fd < 0) return -1;
43+
int opt = 1;
44+
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
45+
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
46+
47+
struct sockaddr_in local = {0};
48+
local.sin_family = AF_INET; local.sin_port = htons(bind_port); local.sin_addr.s_addr = INADDR_ANY;
49+
if (bind(fd, (struct sockaddr *)&local, sizeof(local)) < 0) { close(fd); return -1; }
50+
51+
struct sockaddr_in remote = {0};
52+
remote.sin_family = AF_INET; remote.sin_port = htons(port);
53+
memcpy(&remote.sin_addr, he->h_addr_list[0], he->h_length);
54+
if (connect(fd, (struct sockaddr *)&remote, sizeof(remote)) < 0) { close(fd); return -1; }
55+
56+
char req[512];
57+
snprintf(req, sizeof(req), "GET %s HTTP/1.0\r\nHost: %s\r\nConnection: close\r\n\r\n", path, host);
58+
send(fd, req, strlen(req), 0);
59+
60+
char buf[4096]; int n = 0;
61+
fd_set fds; struct timeval tv = {3, 0};
62+
FD_ZERO(&fds); FD_SET(fd, &fds);
63+
if (select(fd+1, &fds, NULL, NULL, &tv) > 0) n = recv(fd, buf, sizeof(buf)-1, 0);
64+
close(fd);
65+
if (n <= 0) return -1;
66+
buf[n] = 0;
67+
68+
char *body = strstr(buf, "\r\n\r\n");
69+
if (!body) return -1;
70+
body += 4;
71+
while (*body == ' ' || *body == '\r' || *body == '\n') body++;
72+
return atoi(body);
73+
}
74+
75+
int main(int argc, char *argv[])
76+
{
77+
if (argc < 4) { fprintf(stderr, "Usage: %s <listen_port> <backend_port> <url>\n", argv[0]); return 1; }
78+
int lp = atoi(argv[1]), bp = atoi(argv[2]);
79+
const char *url = argv[3];
80+
signal(SIGTERM, sig_handler); signal(SIGINT, sig_handler); signal(SIGPIPE, SIG_IGN);
81+
82+
// Discover
83+
int ext = discover_port(lp, url);
84+
if (ext <= 0) { fprintf(stderr, "port discovery failed\n"); return 1; }
85+
printf("PORT=%d\n", ext); fflush(stdout);
86+
87+
// Listen socket
88+
int ls = socket(AF_INET, SOCK_STREAM, 0);
89+
int opt = 1;
90+
setsockopt(ls, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
91+
setsockopt(ls, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
92+
struct sockaddr_in la = {0};
93+
la.sin_family = AF_INET; la.sin_port = htons(lp); la.sin_addr.s_addr = INADDR_ANY;
94+
if (bind(ls, (struct sockaddr *)&la, sizeof(la)) < 0 || listen(ls, 32) < 0)
95+
{ perror("bind/listen"); return 1; }
96+
97+
// Keepalive
98+
int kf = -1; time_t last_ka = 0;
99+
char ka_host[256] = {0};
100+
{ const char *p = url; if (strncmp(p, "http://", 7) == 0) p += 7;
101+
const char *s = strchr(p, '/'); size_t n = s ? (size_t)(s-p) : strlen(p);
102+
memcpy(ka_host, p, n < 255 ? n : 255); }
103+
104+
auto open_ka = [&]() -> int {
105+
struct hostent *he = gethostbyname(ka_host);
106+
if (!he) return -1;
107+
int fd = socket(AF_INET, SOCK_STREAM, 0);
108+
if (fd < 0) return -1;
109+
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
110+
setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
111+
struct sockaddr_in l = {0};
112+
l.sin_family = AF_INET; l.sin_port = htons(lp); l.sin_addr.s_addr = INADDR_ANY;
113+
if (bind(fd, (struct sockaddr *)&l, sizeof(l)) < 0) { close(fd); return -1; }
114+
struct sockaddr_in r = {0};
115+
r.sin_family = AF_INET; r.sin_port = htons(80);
116+
memcpy(&r.sin_addr, he->h_addr_list[0], he->h_length);
117+
if (connect(fd, (struct sockaddr *)&r, sizeof(r)) < 0) { close(fd); return -1; }
118+
return fd;
119+
};
120+
121+
// Client slots
122+
struct { int cf, bf; } clients[64];
123+
memset(clients, -1, sizeof(clients));
124+
125+
while (running)
126+
{
127+
fd_set rfds; FD_ZERO(&rfds);
128+
FD_SET(ls, &rfds); int maxfd = ls;
129+
if (kf >= 0) { FD_SET(kf, &rfds); if (kf > maxfd) maxfd = kf; }
130+
for (int i = 0; i < 64; i++) {
131+
if (clients[i].cf >= 0) { FD_SET(clients[i].cf, &rfds); if (clients[i].cf > maxfd) maxfd = clients[i].cf; }
132+
if (clients[i].bf >= 0) { FD_SET(clients[i].bf, &rfds); if (clients[i].bf > maxfd) maxfd = clients[i].bf; }
133+
}
134+
struct timeval tv = {1, 0};
135+
if (select(maxfd+1, &rfds, NULL, NULL, &tv) < 0) break;
136+
137+
if (FD_ISSET(ls, &rfds)) {
138+
int cf = accept(ls, NULL, NULL);
139+
if (cf >= 0) {
140+
int bf = socket(AF_INET, SOCK_STREAM, 0);
141+
struct sockaddr_in be = {0};
142+
be.sin_family = AF_INET; be.sin_port = htons(bp); be.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
143+
if (bf < 0 || connect(bf, (struct sockaddr *)&be, sizeof(be)) < 0) {
144+
if (bf >= 0) close(bf); close(cf);
145+
} else {
146+
for (int i = 0; i < 64; i++) if (clients[i].cf < 0) { clients[i].cf = cf; clients[i].bf = bf; break; }
147+
}
148+
}
149+
}
150+
151+
for (int i = 0; i < 64; i++) {
152+
if (clients[i].cf >= 0 && FD_ISSET(clients[i].cf, &rfds)) {
153+
char buf[8192]; ssize_t n = recv(clients[i].cf, buf, sizeof(buf), 0);
154+
if (n <= 0) { close(clients[i].cf); close(clients[i].bf); clients[i].cf = -1; }
155+
else if (clients[i].bf >= 0) send(clients[i].bf, buf, n, MSG_NOSIGNAL);
156+
}
157+
if (clients[i].bf >= 0 && FD_ISSET(clients[i].bf, &rfds)) {
158+
char buf[8192]; ssize_t n = recv(clients[i].bf, buf, sizeof(buf), 0);
159+
if (n <= 0) { close(clients[i].cf); close(clients[i].bf); clients[i].cf = -1; }
160+
else if (clients[i].cf >= 0) send(clients[i].cf, buf, n, MSG_NOSIGNAL);
161+
}
162+
}
163+
164+
time_t now = time(NULL);
165+
if (now - last_ka > 25) {
166+
if (kf >= 0) {
167+
char head[256]; snprintf(head, sizeof(head), "HEAD / HTTP/1.0\r\nHost: %s\r\nConnection: keep-alive\r\n\r\n", ka_host);
168+
if (send(kf, head, strlen(head), MSG_NOSIGNAL) < 0) { close(kf); kf = -1; }
169+
}
170+
if (kf < 0) kf = open_ka();
171+
last_ka = now;
172+
}
173+
}
174+
175+
for (int i = 0; i < 64; i++) { if (clients[i].cf >= 0) { close(clients[i].cf); close(clients[i].bf); } }
176+
if (kf >= 0) close(kf); close(ls);
177+
return 0;
178+
}

0 commit comments

Comments
 (0)