upleb.uk

Public git repos — served from a NIP-34 GRASP relay at git.upleb.uk

summaryrefslogtreecommitdiff
path: root/src/proxy.cpp
diff options
context:
space:
mode:
authorBitflash <admin@bitflash.network>2026-08-12 10:18:14 -0300
committerBitflash <admin@bitflash.network>2026-08-12 10:18:14 -0300
commitc330b8e5e1c057049b5b361908c94f41344dff80 (patch)
tree44e70a657e4fa70205d9d318839ac68f175a1fb9 /src/proxy.cpp
parent026ec904b7bde4898fbb889b7758dbcb13bf860a (diff)
parent066379ebcb1dac720221fe1a1841fa6f59df21d9 (diff)
Merge pull request 'net: add optional SOCKS5 outbound proxy' (#27) from socks5-proxy into main
Diffstat (limited to 'src/proxy.cpp')
-rw-r--r--src/proxy.cpp329
1 files changed, 329 insertions, 0 deletions
diff --git a/src/proxy.cpp b/src/proxy.cpp
new file mode 100644
index 0000000..d7dc324
--- /dev/null
+++ b/src/proxy.cpp
@@ -0,0 +1,329 @@
1// Copyright (c) 2026 Bitflash developers
2// Distributed under the MIT/X11 software license.
3
4#include "headers_core.h"
5#include "proxy.h"
6#include "sockcount.h"
7
8#include <errno.h>
9
10static CCriticalSection cs_socks5;
11static bool g_fSocks5Proxy = false;
12static std::string g_socks5Host;
13static unsigned short g_socks5Port = 0;
14
15static void SetSocketTimeout(SOCKET s, int nTimeoutSecs)
16{
17 if (nTimeoutSecs <= 0)
18 return;
19#ifdef _WIN32
20 DWORD tv = (DWORD)nTimeoutSecs * 1000;
21 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
22 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
23#else
24 struct timeval tv;
25 tv.tv_sec = nTimeoutSecs;
26 tv.tv_usec = 0;
27 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
28 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
29#endif
30}
31
32static bool ReadN(SOCKET s, void* buf, int n)
33{
34 char* p = (char*)buf;
35 int off = 0;
36 while (off < n)
37 {
38 int r = recv(s, p + off, n - off, 0);
39 if (r <= 0)
40 return false;
41 off += r;
42 }
43 return true;
44}
45
46static bool WriteN(SOCKET s, const void* buf, int n)
47{
48 const char* p = (const char*)buf;
49 int off = 0;
50 while (off < n)
51 {
52 int r = send(s, p + off, n - off, 0);
53 if (r <= 0)
54 return false;
55 off += r;
56 }
57 return true;
58}
59
60static bool ValidProxyHostChar(char c)
61{
62 return isalnum((unsigned char)c) || c == '.' || c == '-' || c == '_';
63}
64
65static bool ValidProxyIpv6HostChar(char c)
66{
67 return isxdigit((unsigned char)c) || c == ':' || c == '.';
68}
69
70static bool ParsePort(const std::string& port, unsigned short& portOut,
71 std::string& errOut)
72{
73 if (port.empty())
74 {
75 errOut = "port is empty";
76 return false;
77 }
78 for (size_t i = 0; i < port.size(); i++)
79 {
80 if (!isdigit((unsigned char)port[i]))
81 {
82 errOut = "port is not numeric";
83 return false;
84 }
85 }
86
87 errno = 0;
88 char* end = NULL;
89 long nPort = strtol(port.c_str(), &end, 10);
90 if (errno == ERANGE || end == NULL || *end != '\0' || nPort <= 0 || nPort > 65535)
91 {
92 errOut = "port is out of range";
93 return false;
94 }
95 portOut = (unsigned short)nPort;
96 return true;
97}
98
99bool BtfParseSocks5Proxy(const std::string& spec, std::string& hostOut,
100 unsigned short& portOut, std::string& errOut)
101{
102 hostOut.clear();
103 portOut = 0;
104 errOut.clear();
105
106 std::string host;
107 std::string port;
108 bool fBracketedIpv6 = false;
109 if (!spec.empty() && spec[0] == '[')
110 {
111 size_t close = spec.find(']');
112 if (close == std::string::npos || close == 1 || close + 1 >= spec.size() || spec[close + 1] != ':')
113 {
114 errOut = "expected [IPv6]:PORT";
115 return false;
116 }
117 host = spec.substr(1, close - 1);
118 port = spec.substr(close + 2);
119 fBracketedIpv6 = true;
120 }
121 else
122 {
123 size_t colon = spec.rfind(':');
124 if (colon == std::string::npos || colon == 0 || colon + 1 >= spec.size())
125 {
126 errOut = "expected HOST:PORT";
127 return false;
128 }
129 host = spec.substr(0, colon);
130 port = spec.substr(colon + 1);
131 if (host.find(':') != std::string::npos)
132 {
133 errOut = "IPv6 proxy hosts must use [ADDR]:PORT";
134 return false;
135 }
136 }
137
138 if (host.size() > 255)
139 {
140 errOut = "host is too long";
141 return false;
142 }
143 if (fBracketedIpv6)
144 {
145 bool fHasColon = false;
146 for (size_t i = 0; i < host.size(); i++)
147 {
148 fHasColon = fHasColon || host[i] == ':';
149 if (!ValidProxyIpv6HostChar(host[i]))
150 {
151 errOut = "IPv6 proxy host contains unsupported characters";
152 return false;
153 }
154 }
155 if (!fHasColon)
156 {
157 errOut = "bracketed proxy host is not IPv6";
158 return false;
159 }
160 }
161 else
162 {
163 for (size_t i = 0; i < host.size(); i++)
164 {
165 if (!ValidProxyHostChar(host[i]))
166 {
167 errOut = "host contains unsupported characters";
168 return false;
169 }
170 }
171 }
172 if (!ParsePort(port, portOut, errOut))
173 return false;
174
175 hostOut = host;
176 return true;
177}
178
179bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut)
180{
181 std::string host;
182 unsigned short port = 0;
183 if (!BtfParseSocks5Proxy(spec, host, port, errOut))
184 return false;
185 CRITICAL_BLOCK(cs_socks5)
186 {
187 g_socks5Host = host;
188 g_socks5Port = port;
189 g_fSocks5Proxy = true;
190 }
191 return true;
192}
193
194void BtfClearSocks5Proxy()
195{
196 CRITICAL_BLOCK(cs_socks5)
197 {
198 g_fSocks5Proxy = false;
199 g_socks5Host.clear();
200 g_socks5Port = 0;
201 }
202}
203
204bool BtfSocks5ProxyEnabled()
205{
206 CRITICAL_BLOCK(cs_socks5)
207 return g_fSocks5Proxy;
208 return false;
209}
210
211std::string BtfSocks5ProxyName()
212{
213 CRITICAL_BLOCK(cs_socks5)
214 {
215 if (!g_fSocks5Proxy)
216 return "";
217 if (g_socks5Host.find(':') != std::string::npos)
218 return strprintf("[%s]:%u", g_socks5Host.c_str(), (unsigned)g_socks5Port);
219 return strprintf("%s:%u", g_socks5Host.c_str(), (unsigned)g_socks5Port);
220 }
221 return "";
222}
223
224static SOCKET ConnectDirect(const std::string& host, unsigned short port,
225 int nSockSite, int nTimeoutSecs)
226{
227 struct addrinfo hints, *res = NULL, *rp = NULL;
228 memset(&hints, 0, sizeof(hints));
229 hints.ai_family = AF_UNSPEC;
230 hints.ai_socktype = SOCK_STREAM;
231 char portstr[16];
232 sprintf(portstr, "%u", (unsigned)port);
233 if (getaddrinfo(host.c_str(), portstr, &hints, &res) != 0 || !res)
234 return INVALID_SOCKET;
235
236 SOCKET s = INVALID_SOCKET;
237 for (rp = res; rp != NULL; rp = rp->ai_next)
238 {
239 s = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), nSockSite);
240 if (s == INVALID_SOCKET)
241 continue;
242 SetSocketTimeout(s, nTimeoutSecs);
243 if (connect(s, rp->ai_addr, (int)rp->ai_addrlen) == 0)
244 break;
245 BtfCloseSocket(s);
246 s = INVALID_SOCKET;
247 }
248 freeaddrinfo(res);
249 return s;
250}
251
252static bool Socks5Connect(SOCKET s, const std::string& destHost,
253 unsigned short destPort)
254{
255 if (destHost.empty() || destHost.size() > 255)
256 return false;
257
258 unsigned char hello[3] = { 0x05, 0x01, 0x00 }; // SOCKS5, one method, no auth
259 if (!WriteN(s, hello, sizeof(hello)))
260 return false;
261 unsigned char choice[2] = { 0, 0 };
262 if (!ReadN(s, choice, sizeof(choice)) || choice[0] != 0x05 || choice[1] != 0x00)
263 return false;
264
265 std::vector<unsigned char> req;
266 req.reserve(7 + destHost.size());
267 req.push_back(0x05); // version
268 req.push_back(0x01); // CONNECT
269 req.push_back(0x00); // reserved
270 req.push_back(0x03); // domain name; proxy resolves, avoiding local DNS leak
271 req.push_back((unsigned char)destHost.size());
272 req.insert(req.end(), destHost.begin(), destHost.end());
273 req.push_back((unsigned char)(destPort >> 8));
274 req.push_back((unsigned char)(destPort & 0xff));
275 if (!WriteN(s, &req[0], (int)req.size()))
276 return false;
277
278 unsigned char rep[4] = {0,0,0,0};
279 if (!ReadN(s, rep, sizeof(rep)) || rep[0] != 0x05)
280 return false;
281 if (rep[1] != 0x00)
282 {
283 error("SOCKS5 CONNECT to %s:%u failed with reply 0x%02x\n",
284 destHost.c_str(), (unsigned)destPort, (unsigned)rep[1]);
285 return false;
286 }
287 int nAddr = 0;
288 if (rep[3] == 0x01) nAddr = 4;
289 else if (rep[3] == 0x04) nAddr = 16;
290 else if (rep[3] == 0x03)
291 {
292 unsigned char len = 0;
293 if (!ReadN(s, &len, 1))
294 return false;
295 nAddr = len;
296 }
297 else
298 return false;
299
300 std::vector<unsigned char> discard(nAddr + 2);
301 return ReadN(s, &discard[0], (int)discard.size());
302}
303
304SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
305 int nSockSite, int nTimeoutSecs)
306{
307 std::string proxyHost;
308 unsigned short proxyPort = 0;
309 bool fProxy = false;
310 CRITICAL_BLOCK(cs_socks5)
311 {
312 fProxy = g_fSocks5Proxy;
313 proxyHost = g_socks5Host;
314 proxyPort = g_socks5Port;
315 }
316
317 if (!fProxy)
318 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs);
319
320 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs);
321 if (s == INVALID_SOCKET)
322 return INVALID_SOCKET;
323 if (!Socks5Connect(s, destHost, destPort))
324 {
325 BtfCloseSocket(s);
326 return INVALID_SOCKET;
327 }
328 return s;
329}