diff options
| author | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 14:09:58 -0300 |
|---|---|---|
| committer | Nsable-sh <nsable-sh@noreply.git.bitflash.network> | 2026-08-13 14:09:58 -0300 |
| commit | 1bf78c53404d83638177889590ce199c87af63bc (patch) | |
| tree | e06544e9e7516203bc4309460438a0486b0328e4 /src/tor.cpp | |
| parent | 20243de8fcf6dc30d7f0b1dbbc2f9db22683ae24 (diff) | |
net: stop managed Tor through control port
Diffstat (limited to 'src/tor.cpp')
| -rw-r--r-- | src/tor.cpp | 249 |
1 files changed, 243 insertions, 6 deletions
diff --git a/src/tor.cpp b/src/tor.cpp index 180cd2b..e47ee44 100644 --- a/src/tor.cpp +++ b/src/tor.cpp | |||
| @@ -17,6 +17,7 @@ static CCriticalSection cs_managedTor; | |||
| 17 | static bool g_fManagedTor = false; | 17 | static bool g_fManagedTor = false; |
| 18 | static bool g_fManagedTorStopping = false; | 18 | static bool g_fManagedTorStopping = false; |
| 19 | static unsigned short g_managedTorSocksPort = 0; | 19 | static unsigned short g_managedTorSocksPort = 0; |
| 20 | static unsigned short g_managedTorControlPort = 0; | ||
| 20 | static unsigned short g_managedTorP2PPort = 0; | 21 | static unsigned short g_managedTorP2PPort = 0; |
| 21 | static std::string g_managedTorPath; | 22 | static std::string g_managedTorPath; |
| 22 | static std::string g_managedTorDataDir; | 23 | static std::string g_managedTorDataDir; |
| @@ -108,8 +109,16 @@ static bool EnsureDir(const std::string& path, std::string& errOut) | |||
| 108 | errOut = strprintf("%s exists and is not a directory", path.c_str()); | 109 | errOut = strprintf("%s exists and is not a directory", path.c_str()); |
| 109 | return false; | 110 | return false; |
| 110 | } | 111 | } |
| 111 | if (mkdir(path.c_str(), 0700) == 0 || errno == EEXIST) | 112 | if (mkdir(path.c_str(), 0700) == 0) |
| 112 | return true; | 113 | return true; |
| 114 | if (errno == EEXIST) | ||
| 115 | { | ||
| 116 | if (stat(path.c_str(), &st) == 0 && S_ISDIR(st.st_mode)) | ||
| 117 | { | ||
| 118 | chmod(path.c_str(), 0700); | ||
| 119 | return true; | ||
| 120 | } | ||
| 121 | } | ||
| 113 | errOut = strprintf("could not create %s: %s", path.c_str(), strerror(errno)); | 122 | errOut = strprintf("could not create %s: %s", path.c_str(), strerror(errno)); |
| 114 | return false; | 123 | return false; |
| 115 | #endif | 124 | #endif |
| @@ -154,6 +163,205 @@ static bool WriteTextFile(const std::string& path, const std::string& data, | |||
| 154 | return true; | 163 | return true; |
| 155 | } | 164 | } |
| 156 | 165 | ||
| 166 | static bool ReadBinaryFile(const std::string& path, std::vector<unsigned char>& data, | ||
| 167 | std::string& errOut) | ||
| 168 | { | ||
| 169 | data.clear(); | ||
| 170 | FILE* f = fopen(path.c_str(), "rb"); | ||
| 171 | if (!f) | ||
| 172 | { | ||
| 173 | errOut = strprintf("could not read %s", path.c_str()); | ||
| 174 | return false; | ||
| 175 | } | ||
| 176 | unsigned char buf[256]; | ||
| 177 | for (;;) | ||
| 178 | { | ||
| 179 | size_t n = fread(buf, 1, sizeof(buf), f); | ||
| 180 | if (n > 0) | ||
| 181 | data.insert(data.end(), buf, buf + n); | ||
| 182 | if (n < sizeof(buf)) | ||
| 183 | { | ||
| 184 | if (ferror(f)) | ||
| 185 | { | ||
| 186 | fclose(f); | ||
| 187 | errOut = strprintf("could not finish reading %s", path.c_str()); | ||
| 188 | return false; | ||
| 189 | } | ||
| 190 | break; | ||
| 191 | } | ||
| 192 | if (data.size() > 1024) | ||
| 193 | { | ||
| 194 | fclose(f); | ||
| 195 | errOut = strprintf("%s is unexpectedly large", path.c_str()); | ||
| 196 | return false; | ||
| 197 | } | ||
| 198 | } | ||
| 199 | fclose(f); | ||
| 200 | if (data.empty()) | ||
| 201 | { | ||
| 202 | errOut = strprintf("%s is empty", path.c_str()); | ||
| 203 | return false; | ||
| 204 | } | ||
| 205 | return true; | ||
| 206 | } | ||
| 207 | |||
| 208 | static void SetSocketTimeouts(SOCKET s, int millis) | ||
| 209 | { | ||
| 210 | #ifdef _WIN32 | ||
| 211 | DWORD timeout = millis; | ||
| 212 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, (const char*)&timeout, sizeof(timeout)); | ||
| 213 | setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, (const char*)&timeout, sizeof(timeout)); | ||
| 214 | #else | ||
| 215 | struct timeval tv; | ||
| 216 | tv.tv_sec = millis / 1000; | ||
| 217 | tv.tv_usec = (millis % 1000) * 1000; | ||
| 218 | setsockopt(s, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); | ||
| 219 | setsockopt(s, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv)); | ||
| 220 | #endif | ||
| 221 | } | ||
| 222 | |||
| 223 | static bool SendAll(SOCKET s, const std::string& data) | ||
| 224 | { | ||
| 225 | const char* p = data.data(); | ||
| 226 | int left = (int)data.size(); | ||
| 227 | while (left > 0) | ||
| 228 | { | ||
| 229 | int n = send(s, p, left, 0); | ||
| 230 | if (n <= 0) | ||
| 231 | return false; | ||
| 232 | p += n; | ||
| 233 | left -= n; | ||
| 234 | } | ||
| 235 | return true; | ||
| 236 | } | ||
| 237 | |||
| 238 | static bool RecvControlLine(SOCKET s, std::string& line) | ||
| 239 | { | ||
| 240 | line.clear(); | ||
| 241 | while (line.size() < 4096) | ||
| 242 | { | ||
| 243 | char ch = 0; | ||
| 244 | int n = recv(s, &ch, 1, 0); | ||
| 245 | if (n <= 0) | ||
| 246 | return false; | ||
| 247 | if (ch == '\n') | ||
| 248 | return true; | ||
| 249 | if (ch != '\r') | ||
| 250 | line += ch; | ||
| 251 | } | ||
| 252 | return false; | ||
| 253 | } | ||
| 254 | |||
| 255 | static SOCKET ConnectLoopback(unsigned short port, std::string& errOut) | ||
| 256 | { | ||
| 257 | #ifdef _WIN32 | ||
| 258 | WSADATA wsadata; | ||
| 259 | int ret = WSAStartup(MAKEWORD(2,2), &wsadata); | ||
| 260 | if (ret != NO_ERROR) | ||
| 261 | { | ||
| 262 | errOut = strprintf("could not start Winsock for Tor control shutdown (WSAStartup returned %d)", ret); | ||
| 263 | return INVALID_SOCKET; | ||
| 264 | } | ||
| 265 | #endif | ||
| 266 | SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); | ||
| 267 | if (s == INVALID_SOCKET) | ||
| 268 | { | ||
| 269 | errOut = "could not create Tor control socket"; | ||
| 270 | #ifdef _WIN32 | ||
| 271 | WSACleanup(); | ||
| 272 | #endif | ||
| 273 | return INVALID_SOCKET; | ||
| 274 | } | ||
| 275 | SetSocketTimeouts(s, 3000); | ||
| 276 | struct sockaddr_in addr; | ||
| 277 | memset(&addr, 0, sizeof(addr)); | ||
| 278 | addr.sin_family = AF_INET; | ||
| 279 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); | ||
| 280 | addr.sin_port = htons(port); | ||
| 281 | if (connect(s, (struct sockaddr*)&addr, sizeof(addr)) != 0) | ||
| 282 | { | ||
| 283 | closesocket(s); | ||
| 284 | errOut = strprintf("could not connect to Tor control port 127.0.0.1:%u", (unsigned)port); | ||
| 285 | #ifdef _WIN32 | ||
| 286 | WSACleanup(); | ||
| 287 | #endif | ||
| 288 | return INVALID_SOCKET; | ||
| 289 | } | ||
| 290 | return s; | ||
| 291 | } | ||
| 292 | |||
| 293 | static void CloseLoopback(SOCKET s) | ||
| 294 | { | ||
| 295 | if (s != INVALID_SOCKET) | ||
| 296 | closesocket(s); | ||
| 297 | #ifdef _WIN32 | ||
| 298 | WSACleanup(); | ||
| 299 | #endif | ||
| 300 | } | ||
| 301 | |||
| 302 | static bool ReadPositiveTorControlReply(SOCKET s, std::string& errOut) | ||
| 303 | { | ||
| 304 | std::string line; | ||
| 305 | if (!RecvControlLine(s, line)) | ||
| 306 | { | ||
| 307 | errOut = "Tor control port closed before replying"; | ||
| 308 | return false; | ||
| 309 | } | ||
| 310 | if (line.size() >= 3 && line.substr(0, 3) == "250") | ||
| 311 | return true; | ||
| 312 | errOut = strprintf("Tor control command failed: %s", line.c_str()); | ||
| 313 | return false; | ||
| 314 | } | ||
| 315 | |||
| 316 | static bool RequestManagedTorShutdown(std::string& errOut) | ||
| 317 | { | ||
| 318 | std::string dataDir; | ||
| 319 | unsigned short controlPort = 0; | ||
| 320 | CRITICAL_BLOCK(cs_managedTor) | ||
| 321 | { | ||
| 322 | dataDir = g_managedTorDataDir; | ||
| 323 | controlPort = g_managedTorControlPort; | ||
| 324 | } | ||
| 325 | if (controlPort == 0 || dataDir.empty()) | ||
| 326 | { | ||
| 327 | errOut = "Tor control port is not available"; | ||
| 328 | return false; | ||
| 329 | } | ||
| 330 | |||
| 331 | std::vector<unsigned char> cookie; | ||
| 332 | if (!ReadBinaryFile(PathJoin(dataDir, "control_auth_cookie"), cookie, errOut)) | ||
| 333 | return false; | ||
| 334 | |||
| 335 | SOCKET s = ConnectLoopback(controlPort, errOut); | ||
| 336 | if (s == INVALID_SOCKET) | ||
| 337 | return false; | ||
| 338 | |||
| 339 | bool ok = false; | ||
| 340 | std::string auth = "AUTHENTICATE " + HexStr(cookie.begin(), cookie.end(), false) + "\r\n"; | ||
| 341 | if (!SendAll(s, auth) || !ReadPositiveTorControlReply(s, errOut)) | ||
| 342 | goto done; | ||
| 343 | |||
| 344 | if (!SendAll(s, "SIGNAL SHUTDOWN\r\n")) | ||
| 345 | { | ||
| 346 | errOut = "could not send Tor shutdown signal"; | ||
| 347 | goto done; | ||
| 348 | } | ||
| 349 | |||
| 350 | { | ||
| 351 | std::string reply; | ||
| 352 | if (RecvControlLine(s, reply) && !(reply.size() >= 3 && reply.substr(0, 3) == "250")) | ||
| 353 | { | ||
| 354 | errOut = strprintf("Tor rejected shutdown signal: %s", reply.c_str()); | ||
| 355 | goto done; | ||
| 356 | } | ||
| 357 | } | ||
| 358 | ok = true; | ||
| 359 | |||
| 360 | done: | ||
| 361 | CloseLoopback(s); | ||
| 362 | return ok; | ||
| 363 | } | ||
| 364 | |||
| 157 | static bool ReadOneLine(const std::string& path, std::string& out) | 365 | static bool ReadOneLine(const std::string& path, std::string& out) |
| 158 | { | 366 | { |
| 159 | out.clear(); | 367 | out.clear(); |
| @@ -295,6 +503,7 @@ static std::string ResolveTorPath(const std::string& torPathOpt) | |||
| 295 | std::string BtfBuildManagedTorrcForTest(const std::string& dataDir, | 503 | std::string BtfBuildManagedTorrcForTest(const std::string& dataDir, |
| 296 | const std::string& hiddenServiceDir, | 504 | const std::string& hiddenServiceDir, |
| 297 | unsigned short socksPort, | 505 | unsigned short socksPort, |
| 506 | unsigned short controlPort, | ||
| 298 | unsigned short p2pPort) | 507 | unsigned short p2pPort) |
| 299 | { | 508 | { |
| 300 | std::string torData = NormalizeTorrcPath(dataDir); | 509 | std::string torData = NormalizeTorrcPath(dataDir); |
| @@ -304,6 +513,8 @@ std::string BtfBuildManagedTorrcForTest(const std::string& dataDir, | |||
| 304 | s += "DataDirectory " + QuoteTorrcPath(torData) + "\n"; | 513 | s += "DataDirectory " + QuoteTorrcPath(torData) + "\n"; |
| 305 | s += strprintf("SocksPort 127.0.0.1:%u IsolateSOCKSAuth IsolateClientAddr IsolateDestAddr IsolateDestPort\n", | 514 | s += strprintf("SocksPort 127.0.0.1:%u IsolateSOCKSAuth IsolateClientAddr IsolateDestAddr IsolateDestPort\n", |
| 306 | (unsigned)socksPort); | 515 | (unsigned)socksPort); |
| 516 | s += strprintf("ControlPort 127.0.0.1:%u\n", (unsigned)controlPort); | ||
| 517 | s += "CookieAuthentication 1\n"; | ||
| 307 | s += "HiddenServiceDir " + QuoteTorrcPath(hsDir) + "\n"; | 518 | s += "HiddenServiceDir " + QuoteTorrcPath(hsDir) + "\n"; |
| 308 | s += "HiddenServiceVersion 3\n"; | 519 | s += "HiddenServiceVersion 3\n"; |
| 309 | s += strprintf("HiddenServicePort %u 127.0.0.1:%u\n", | 520 | s += strprintf("HiddenServicePort %u 127.0.0.1:%u\n", |
| @@ -503,9 +714,13 @@ bool BtfStartManagedTor(const std::string& torPathOpt, std::string& errOut) | |||
| 503 | unsigned short socksPort = 0; | 714 | unsigned short socksPort = 0; |
| 504 | if (!PickLocalPort(socksPort, errOut)) | 715 | if (!PickLocalPort(socksPort, errOut)) |
| 505 | return false; | 716 | return false; |
| 717 | unsigned short controlPort = 0; | ||
| 718 | if (!PickLocalPort(controlPort, errOut)) | ||
| 719 | return false; | ||
| 506 | 720 | ||
| 507 | unsigned short p2pPort = ntohs(nListenPort); | 721 | unsigned short p2pPort = ntohs(nListenPort); |
| 508 | std::string torrc = BtfBuildManagedTorrcForTest(dataDir, hsDir, socksPort, p2pPort); | 722 | std::string torrc = BtfBuildManagedTorrcForTest(dataDir, hsDir, socksPort, |
| 723 | controlPort, p2pPort); | ||
| 509 | if (!WriteTextFile(torrcPath, torrc, errOut)) | 724 | if (!WriteTextFile(torrcPath, torrc, errOut)) |
| 510 | return false; | 725 | return false; |
| 511 | 726 | ||
| @@ -519,6 +734,7 @@ bool BtfStartManagedTor(const std::string& torPathOpt, std::string& errOut) | |||
| 519 | g_managedTorDataDir = dataDir; | 734 | g_managedTorDataDir = dataDir; |
| 520 | g_managedTorHiddenServiceDir = hsDir; | 735 | g_managedTorHiddenServiceDir = hsDir; |
| 521 | g_managedTorSocksPort = socksPort; | 736 | g_managedTorSocksPort = socksPort; |
| 737 | g_managedTorControlPort = controlPort; | ||
| 522 | g_managedTorP2PPort = p2pPort; | 738 | g_managedTorP2PPort = p2pPort; |
| 523 | g_managedTorOnion.clear(); | 739 | g_managedTorOnion.clear(); |
| 524 | g_managedTorStatus = strprintf("starting %s, SOCKS5 127.0.0.1:%u", | 740 | g_managedTorStatus = strprintf("starting %s, SOCKS5 127.0.0.1:%u", |
| @@ -582,8 +798,15 @@ void BtfStopManagedTor() | |||
| 582 | #ifdef _WIN32 | 798 | #ifdef _WIN32 |
| 583 | if (g_managedTorProcess.hProcess) | 799 | if (g_managedTorProcess.hProcess) |
| 584 | { | 800 | { |
| 585 | TerminateProcess(g_managedTorProcess.hProcess, 0); | 801 | std::string shutdownErr; |
| 586 | WaitForSingleObject(g_managedTorProcess.hProcess, 5000); | 802 | bool fGraceful = RequestManagedTorShutdown(shutdownErr); |
| 803 | DWORD waitResult = WaitForSingleObject(g_managedTorProcess.hProcess, | ||
| 804 | fGraceful ? 10000 : 1000); | ||
| 805 | if (waitResult != WAIT_OBJECT_0) | ||
| 806 | { | ||
| 807 | TerminateProcess(g_managedTorProcess.hProcess, 0); | ||
| 808 | WaitForSingleObject(g_managedTorProcess.hProcess, 5000); | ||
| 809 | } | ||
| 587 | CloseHandle(g_managedTorProcess.hProcess); | 810 | CloseHandle(g_managedTorProcess.hProcess); |
| 588 | CloseHandle(g_managedTorProcess.hThread); | 811 | CloseHandle(g_managedTorProcess.hThread); |
| 589 | memset(&g_managedTorProcess, 0, sizeof(g_managedTorProcess)); | 812 | memset(&g_managedTorProcess, 0, sizeof(g_managedTorProcess)); |
| @@ -591,15 +814,26 @@ void BtfStopManagedTor() | |||
| 591 | #else | 814 | #else |
| 592 | if (g_managedTorPid > 0) | 815 | if (g_managedTorPid > 0) |
| 593 | { | 816 | { |
| 594 | kill(g_managedTorPid, SIGTERM); | 817 | std::string shutdownErr; |
| 595 | for (int i = 0; i < 50; i++) | 818 | if (!RequestManagedTorShutdown(shutdownErr)) |
| 819 | kill(g_managedTorPid, SIGTERM); | ||
| 820 | bool fExited = false; | ||
| 821 | for (int i = 0; i < 100; i++) | ||
| 596 | { | 822 | { |
| 597 | int status = 0; | 823 | int status = 0; |
| 598 | pid_t r = waitpid(g_managedTorPid, &status, WNOHANG); | 824 | pid_t r = waitpid(g_managedTorPid, &status, WNOHANG); |
| 599 | if (r == g_managedTorPid) | 825 | if (r == g_managedTorPid) |
| 826 | { | ||
| 827 | fExited = true; | ||
| 600 | break; | 828 | break; |
| 829 | } | ||
| 601 | Sleep(100); | 830 | Sleep(100); |
| 602 | } | 831 | } |
| 832 | if (!fExited) | ||
| 833 | { | ||
| 834 | kill(g_managedTorPid, SIGKILL); | ||
| 835 | waitpid(g_managedTorPid, NULL, 0); | ||
| 836 | } | ||
| 603 | g_managedTorPid = -1; | 837 | g_managedTorPid = -1; |
| 604 | } | 838 | } |
| 605 | #endif | 839 | #endif |
| @@ -607,6 +841,9 @@ void BtfStopManagedTor() | |||
| 607 | { | 841 | { |
| 608 | g_managedTorStatus = "stopped"; | 842 | g_managedTorStatus = "stopped"; |
| 609 | g_managedTorOnion.clear(); | 843 | g_managedTorOnion.clear(); |
| 844 | g_managedTorSocksPort = 0; | ||
| 845 | g_managedTorControlPort = 0; | ||
| 846 | g_managedTorP2PPort = 0; | ||
| 610 | } | 847 | } |
| 611 | } | 848 | } |
| 612 | 849 | ||