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:
Diffstat (limited to 'src/proxy.cpp')
-rw-r--r--src/proxy.cpp254
1 files changed, 254 insertions, 0 deletions
diff --git a/src/proxy.cpp b/src/proxy.cpp
new file mode 100644
index 0000000..1e62ff9
--- /dev/null
+++ b/src/proxy.cpp
@@ -0,0 +1,254 @@
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
8static CCriticalSection cs_socks5;
9static bool g_fSocks5Proxy = false;
10static std::string g_socks5Host;
11static unsigned short g_socks5Port = 0;
12
13static void SetSocketTimeout(SOCKET s, int nTimeoutSecs)
14{
15 if (nTimeoutSecs <= 0)
16 return;
17#ifdef _WIN32
18 DWORD tv = (DWORD)nTimeoutSecs * 1000;
19 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof(tv));
20 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&tv, sizeof(tv));
21#else
22 struct timeval tv;
23 tv.tv_sec = nTimeoutSecs;
24 tv.tv_usec = 0;
25 setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
26 setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
27#endif
28}
29
30static bool ReadN(SOCKET s, void* buf, int n)
31{
32 char* p = (char*)buf;
33 int off = 0;
34 while (off < n)
35 {
36 int r = recv(s, p + off, n - off, 0);
37 if (r <= 0)
38 return false;
39 off += r;
40 }
41 return true;
42}
43
44static bool WriteN(SOCKET s, const void* buf, int n)
45{
46 const char* p = (const char*)buf;
47 int off = 0;
48 while (off < n)
49 {
50 int r = send(s, p + off, n - off, 0);
51 if (r <= 0)
52 return false;
53 off += r;
54 }
55 return true;
56}
57
58static bool ValidProxyHostChar(char c)
59{
60 return isalnum((unsigned char)c) || c == '.' || c == '-' || c == '_';
61}
62
63bool BtfParseSocks5Proxy(const std::string& spec, std::string& hostOut,
64 unsigned short& portOut, std::string& errOut)
65{
66 hostOut.clear();
67 portOut = 0;
68 errOut.clear();
69
70 size_t colon = spec.rfind(':');
71 if (colon == std::string::npos || colon == 0 || colon + 1 >= spec.size())
72 {
73 errOut = "expected HOST:PORT";
74 return false;
75 }
76
77 std::string host = spec.substr(0, colon);
78 std::string port = spec.substr(colon + 1);
79 if (host.size() > 255)
80 {
81 errOut = "host is too long";
82 return false;
83 }
84 for (size_t i = 0; i < host.size(); i++)
85 {
86 if (!ValidProxyHostChar(host[i]))
87 {
88 errOut = "host contains unsupported characters";
89 return false;
90 }
91 }
92 for (size_t i = 0; i < port.size(); i++)
93 {
94 if (!isdigit((unsigned char)port[i]))
95 {
96 errOut = "port is not numeric";
97 return false;
98 }
99 }
100 int nPort = atoi(port.c_str());
101 if (nPort <= 0 || nPort > 65535)
102 {
103 errOut = "port is out of range";
104 return false;
105 }
106
107 hostOut = host;
108 portOut = (unsigned short)nPort;
109 return true;
110}
111
112bool BtfSetSocks5Proxy(const std::string& spec, std::string& errOut)
113{
114 std::string host;
115 unsigned short port = 0;
116 if (!BtfParseSocks5Proxy(spec, host, port, errOut))
117 return false;
118 CRITICAL_BLOCK(cs_socks5)
119 {
120 g_socks5Host = host;
121 g_socks5Port = port;
122 g_fSocks5Proxy = true;
123 }
124 return true;
125}
126
127void BtfClearSocks5Proxy()
128{
129 CRITICAL_BLOCK(cs_socks5)
130 {
131 g_fSocks5Proxy = false;
132 g_socks5Host.clear();
133 g_socks5Port = 0;
134 }
135}
136
137bool BtfSocks5ProxyEnabled()
138{
139 CRITICAL_BLOCK(cs_socks5)
140 return g_fSocks5Proxy;
141 return false;
142}
143
144std::string BtfSocks5ProxyName()
145{
146 CRITICAL_BLOCK(cs_socks5)
147 {
148 if (!g_fSocks5Proxy)
149 return "";
150 return strprintf("%s:%u", g_socks5Host.c_str(), (unsigned)g_socks5Port);
151 }
152 return "";
153}
154
155static SOCKET ConnectDirect(const std::string& host, unsigned short port,
156 int nSockSite, int nTimeoutSecs)
157{
158 struct addrinfo hints, *res = NULL, *rp = NULL;
159 memset(&hints, 0, sizeof(hints));
160 hints.ai_family = AF_UNSPEC;
161 hints.ai_socktype = SOCK_STREAM;
162 char portstr[16];
163 sprintf(portstr, "%u", (unsigned)port);
164 if (getaddrinfo(host.c_str(), portstr, &hints, &res) != 0 || !res)
165 return INVALID_SOCKET;
166
167 SOCKET s = INVALID_SOCKET;
168 for (rp = res; rp != NULL; rp = rp->ai_next)
169 {
170 s = BtfSocketTag(socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol), nSockSite);
171 if (s == INVALID_SOCKET)
172 continue;
173 SetSocketTimeout(s, nTimeoutSecs);
174 if (connect(s, rp->ai_addr, (int)rp->ai_addrlen) == 0)
175 break;
176 BtfCloseSocket(s);
177 s = INVALID_SOCKET;
178 }
179 freeaddrinfo(res);
180 return s;
181}
182
183static bool Socks5Connect(SOCKET s, const std::string& destHost,
184 unsigned short destPort)
185{
186 if (destHost.empty() || destHost.size() > 255)
187 return false;
188
189 unsigned char hello[3] = { 0x05, 0x01, 0x00 }; // SOCKS5, one method, no auth
190 if (!WriteN(s, hello, sizeof(hello)))
191 return false;
192 unsigned char choice[2] = { 0, 0 };
193 if (!ReadN(s, choice, sizeof(choice)) || choice[0] != 0x05 || choice[1] != 0x00)
194 return false;
195
196 std::vector<unsigned char> req;
197 req.reserve(7 + destHost.size());
198 req.push_back(0x05); // version
199 req.push_back(0x01); // CONNECT
200 req.push_back(0x00); // reserved
201 req.push_back(0x03); // domain name; proxy resolves, avoiding local DNS leak
202 req.push_back((unsigned char)destHost.size());
203 req.insert(req.end(), destHost.begin(), destHost.end());
204 req.push_back((unsigned char)(destPort >> 8));
205 req.push_back((unsigned char)(destPort & 0xff));
206 if (!WriteN(s, &req[0], (int)req.size()))
207 return false;
208
209 unsigned char rep[4] = {0,0,0,0};
210 if (!ReadN(s, rep, sizeof(rep)) || rep[0] != 0x05 || rep[1] != 0x00)
211 return false;
212 int nAddr = 0;
213 if (rep[3] == 0x01) nAddr = 4;
214 else if (rep[3] == 0x04) nAddr = 16;
215 else if (rep[3] == 0x03)
216 {
217 unsigned char len = 0;
218 if (!ReadN(s, &len, 1))
219 return false;
220 nAddr = len;
221 }
222 else
223 return false;
224
225 std::vector<unsigned char> discard(nAddr + 2);
226 return ReadN(s, &discard[0], (int)discard.size());
227}
228
229SOCKET BtfConnectSocket(const std::string& destHost, unsigned short destPort,
230 int nSockSite, int nTimeoutSecs)
231{
232 std::string proxyHost;
233 unsigned short proxyPort = 0;
234 bool fProxy = false;
235 CRITICAL_BLOCK(cs_socks5)
236 {
237 fProxy = g_fSocks5Proxy;
238 proxyHost = g_socks5Host;
239 proxyPort = g_socks5Port;
240 }
241
242 if (!fProxy)
243 return ConnectDirect(destHost, destPort, nSockSite, nTimeoutSecs);
244
245 SOCKET s = ConnectDirect(proxyHost, proxyPort, nSockSite, nTimeoutSecs);
246 if (s == INVALID_SOCKET)
247 return INVALID_SOCKET;
248 if (!Socks5Connect(s, destHost, destPort))
249 {
250 BtfCloseSocket(s);
251 return INVALID_SOCKET;
252 }
253 return s;
254}