Skip to content
API Documentation
BastionGuard™
bank_filter.hpp
Go to the documentation of this file.
1/*
2 * BastionGuard™
3 * Copyright (C) 2025–2026 Calogero Scarnà
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, version 3.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 *
17 * BastionGuard™ is a trademark of Calogero Scarnà.
18 * The BastionGuard™ name and branding are not licensed under the GPL.
19 */
20
21#pragma once
22
23#include <string>
24#include <string_view>
25#include <unordered_set>
26#include <fstream>
27#include <sstream>
28#include <iostream>
29#include <cstdlib>
30#include <cstdint>
31#include <cstring>
32#include <filesystem>
33#include <mutex>
34#include <shared_mutex>
35#include <cstdio>
36#include <algorithm>
37#include <cctype>
38
39#include <arpa/inet.h> // inet_pton per riconoscere indirizzi LAN/loopback
40
41#include <glib/gi18n.h>
42
43
44static std::unordered_set<std::string> extract_list(const std::string& json) {
45 std::unordered_set<std::string> result;
46
47 // Trova "list" : [
48 auto list_pos = json.find("\"list\"");
49 if (list_pos == std::string::npos) return result;
50
51 auto bracket = json.find('[', list_pos);
52 if (bracket == std::string::npos) return result;
53
54
55 std::size_t depth = 1;
56 std::size_t pos = bracket + 1;
57 bool in_string = false;
58
59 while (pos < json.size() && depth > 0) {
60 char c = json[pos];
61 if (in_string) {
62 if (c == '\\') ++pos;
63 else if (c == '"') in_string = false;
64 } else {
65 if (c == '"') in_string = true;
66 else if (c == '[') ++depth;
67 else if (c == ']') --depth;
68 }
69 ++pos;
70 }
71
72 if (depth != 0) return result;
73
74 std::size_t i = bracket + 1;
75 const std::size_t end = pos - 1;
76
77 while (i < end) {
78 auto q1 = json.find('"', i);
79 if (q1 == std::string::npos || q1 >= end) break;
80
81 auto q2 = q1 + 1;
82
83 while (q2 < end) {
84 if (json[q2] == '\\') {
85 q2 += 2;
86 continue;
87 }
88 if (json[q2] == '"') break;
89 ++q2;
90 }
91
92 if (q2 >= end) break;
93
94 std::string entry = json.substr(q1 + 1, q2 - q1 - 1);
95 if (!entry.empty()) result.insert(std::move(entry));
96
97 i = q2 + 1;
98 }
99
100 return result;
101}
102
103static std::string read_file(const std::filesystem::path& p) {
104 std::ifstream f(p);
105 if (!f) return {};
106
107 std::ostringstream ss;
108 ss << f.rdbuf();
109 return ss.str();
110}
111
112
114public:
115 void load() {
116 const char* home = std::getenv("HOME");
117 if (!home) {
118 std::cerr << _("[filter] HOME variable not set\n");
119 return;
120 }
121
122 namespace fs = std::filesystem;
123 fs::path data_base = fs::path(home) / ".local/share/BastionGuard";
124 fs::path config_base = fs::path(home) / ".config/BastionGuard";
125
126 std::unordered_set<std::string> new_banks;
127 std::unordered_set<std::string> new_payments;
128 std::unordered_set<std::string> new_trusted;
129 std::unordered_set<std::string> new_exclusions;
130 std::unordered_set<std::string> new_user_banks;
131 std::unordered_set<std::string> new_proxy_bypass;
132 std::vector<CidrEntry> new_proxy_bypass_cidrs;
133
134 load_file(data_base / "banks.json", new_banks, _("banks"));
135 load_file(config_base / "payments.json", new_payments, _("payments"));
136 load_file(config_base / "exclusions.json", new_exclusions, _("exclusions"));
137 load_file(config_base / "whitelist.json", new_user_banks, _("user-banks"));
138 load_file(config_base / "proxy_bypass.json", new_proxy_bypass, _("proxy-bypass"));
139
140 // Separiamo i CIDR (es. "93.55.84.0/24") dal set di stringhe esatte.
141 // Il set string-based serve solo per match esatto/suffisso; i CIDR
142 // vanno gestiti col bit-mask in match_cidr().
143 {
144 std::vector<std::string> to_strip;
145 for (const auto& entry : new_proxy_bypass) {
146 if (entry.find('/') == std::string::npos) continue;
147 CidrEntry ce;
148 if (parse_cidr(entry, ce)) {
149 new_proxy_bypass_cidrs.push_back(ce);
150 to_strip.push_back(entry);
151 }
152 }
153 for (const auto& s : to_strip) new_proxy_bypass.erase(s);
154 }
155
156 for (const auto& d : get_builtin_exclusions())
157 new_exclusions.insert(d);
158
159 {
160 std::unique_lock lock(mutex_);
161 banks_ = std::move(new_banks);
162 payments_ = std::move(new_payments);
163 trusted_ = std::move(new_trusted);
164 exclusions_ = std::move(new_exclusions);
165 user_banks_ = std::move(new_user_banks);
166 proxy_bypass_ = std::move(new_proxy_bypass);
167 proxy_bypass_cidrs_ = std::move(new_proxy_bypass_cidrs);
168
169 char msg[256];
170 std::snprintf(msg, sizeof(msg),
171 _("[filter] loaded %zu bank, %zu user-bank, %zu payment, %zu exclusion, %zu proxy-bypass (+%zu CIDR)\n"),
172 banks_.size(), user_banks_.size(), payments_.size(),
173 exclusions_.size(), proxy_bypass_.size(), proxy_bypass_cidrs_.size());
174 std::cerr << msg;
175 }
176 }
177
178 bool is_proxy_bypass(const std::string& host) const {
179 std::shared_lock lock(mutex_);
180 // 1) match esatto/suffisso DNS (incluso IP letterale "93.55.84.43")
181 if (match(host, proxy_bypass_)) return true;
182 // 2) match CIDR (host deve essere un IP letterale che cade in un range)
183 return match_cidr(host, proxy_bypass_cidrs_);
184 }
185
186 // -----------------------------------------------------------------------
187 // is_local_network
188 //
189 // True per qualunque host che non deve passare dal proxy/MITM CEF:
190 // - hostname locali: "localhost", *.local (mDNS), *.lan,
191 // *.internal, *.intranet, *.home.arpa
192 // - hostname brevi (senza punti) tipici di reti aziendali
193 // - IPv4 letterali in:
194 // 127.0.0.0/8 loopback
195 // 10.0.0.0/8 RFC1918 privato
196 // 172.16.0.0/12 RFC1918 privato
197 // 192.168.0.0/16 RFC1918 privato
198 // 169.254.0.0/16 link-local
199 // 100.64.0.0/10 CGNAT (RFC6598, spesso usato dai router)
200 // - IPv6 letterali:
201 // ::1 loopback
202 // fe80::/10 link-local
203 // fc00::/7 unique local (ULA)
204 //
205 // Non richiede stato → è un metodo statico, può essere chiamato anche
206 // senza istanziare DomainFilter.
207 // -----------------------------------------------------------------------
208 static bool is_local_network(const std::string& host_raw) {
209 if (host_raw.empty()) return false;
210
211 // Strip eventuali bracket IPv6: [::1] → ::1
212 std::string h = host_raw;
213 if (h.size() >= 2 && h.front() == '[' && h.back() == ']')
214 h = h.substr(1, h.size() - 2);
215
216 // Lowercase per il match hostname (DNS è case-insensitive)
217 std::string h_lower = h;
218 std::transform(h_lower.begin(), h_lower.end(), h_lower.begin(),
219 [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
220
221 // 1) Hostname speciali
222 if (h_lower == "localhost") return true;
223 if (h_lower == "ip6-localhost") return true;
224 if (h_lower == "ip6-loopback") return true;
225
226 // 2) TLD locali standard (mDNS/zeroconf, RFC6762, RFC8375, RFC2606)
227 static const char* local_suffixes[] = {
228 ".local", ".localhost", ".lan", ".intranet",
229 ".internal", ".private", ".corp", ".home", ".home.arpa",
230 nullptr
231 };
232 for (int i = 0; local_suffixes[i]; ++i) {
233 const std::string suf = local_suffixes[i];
234 if (h_lower.size() >= suf.size() &&
235 h_lower.compare(h_lower.size() - suf.size(), suf.size(), suf) == 0) {
236 return true;
237 }
238 }
239
240 // 3) IPv4 letterale → check per range
241 {
242 in_addr v4{};
243 if (::inet_pton(AF_INET, h_lower.c_str(), &v4) == 1) {
244 const std::uint32_t a = ntohl(v4.s_addr);
245 const std::uint8_t b1 = (a >> 24) & 0xFF;
246 const std::uint8_t b2 = (a >> 16) & 0xFF;
247
248 // 127.0.0.0/8 loopback
249 if (b1 == 127) return true;
250 // 10.0.0.0/8 RFC1918
251 if (b1 == 10) return true;
252 // 172.16.0.0/12 RFC1918
253 if (b1 == 172 && (b2 >= 16 && b2 <= 31)) return true;
254 // 192.168.0.0/16 RFC1918
255 if (b1 == 192 && b2 == 168) return true;
256 // 169.254.0.0/16 link-local
257 if (b1 == 169 && b2 == 254) return true;
258 // 100.64.0.0/10 CGNAT (RFC6598)
259 if (b1 == 100 && (b2 & 0xC0) == 0x40) return true;
260 // 0.0.0.0/8 "this network"
261 if (b1 == 0) return true;
262
263 return false; // IPv4 pubblico
264 }
265 }
266
267 // 4) IPv6 letterale → check per range
268 {
269 in6_addr v6{};
270 if (::inet_pton(AF_INET6, h_lower.c_str(), &v6) == 1) {
271 // ::1 loopback
272 static const std::uint8_t loopback[16] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1};
273 if (std::memcmp(v6.s6_addr, loopback, 16) == 0) return true;
274
275 // fe80::/10 link-local: i primi 10 bit = 1111 1110 10
276 if (v6.s6_addr[0] == 0xFE && (v6.s6_addr[1] & 0xC0) == 0x80) return true;
277
278 // fc00::/7 unique local: i primi 7 bit = 1111 110
279 if ((v6.s6_addr[0] & 0xFE) == 0xFC) return true;
280
281 // ::ffff:0:0/96 IPv4-mapped → estraiamo l'IPv4 dentro e ricorriamo
282 static const std::uint8_t v4mapped_prefix[12] =
283 {0,0,0,0,0,0,0,0,0,0,0xFF,0xFF};
284 if (std::memcmp(v6.s6_addr, v4mapped_prefix, 12) == 0) {
285 char buf[INET_ADDRSTRLEN];
286 in_addr v4inner{};
287 std::memcpy(&v4inner.s_addr, v6.s6_addr + 12, 4);
288 if (::inet_ntop(AF_INET, &v4inner, buf, sizeof(buf)))
289 return is_local_network(buf);
290 }
291
292 return false; // IPv6 pubblico
293 }
294 }
295
296 // 5) Hostname senza punti = nome NetBIOS / LAN tipico (es. "router",
297 // "nas", "printer") — lo trattiamo come locale per sicurezza.
298 if (h_lower.find('.') == std::string::npos) return true;
299
300 return false;
301 }
302
303 bool is_secure(const std::string& host) const {
304 if (is_local_network(host)) return false;
305 std::shared_lock lock(mutex_);
306
307 if (match(host, proxy_bypass_)) return false;
308 if (match(host, exclusions_)) return false;
309 if (match(host, banks_) || match(host, user_banks_) || match(host, payments_))
310 return true;
311 // Provider di pagamento noti: intercetta anche senza payments.json,
312 // così Stripe/PayPal/ecc. si aprono in sandbox e non nel browser.
313 return match(host, get_builtin_payment_providers());
314 }
315
316 bool is_trusted(const std::string& host) const {
317 std::shared_lock lock(mutex_);
318 return match(host, trusted_);
319 }
320
321 bool is_bank(const std::string& host) const {
322 if (is_local_network(host)) return false;
323 std::shared_lock lock(mutex_);
324 if (match(host, proxy_bypass_)) return false;
325 if (match(host, exclusions_)) return false;
326 return match(host, banks_) || match(host, user_banks_);
327 }
328
329 bool is_payment(const std::string& host) const {
330 if (is_local_network(host)) return false;
331 std::shared_lock lock(mutex_);
332 if (match(host, proxy_bypass_)) return false;
333 if (match(host, exclusions_)) return false;
334 if (match(host, payments_)) return true;
335 return match(host, get_builtin_payment_providers());
336 }
337
338private:
339 mutable std::shared_mutex mutex_;
340
341 std::unordered_set<std::string> banks_;
342 std::unordered_set<std::string> user_banks_;
343 std::unordered_set<std::string> payments_;
344 std::unordered_set<std::string> trusted_;
345 std::unordered_set<std::string> exclusions_;
346 std::unordered_set<std::string> proxy_bypass_;
347
348 // ----- CIDR di bypass: parsati da proxy_bypass.json (es. "93.55.84.0/24") -----
349 struct CidrEntry {
350 bool is_v6 = false;
351 std::uint8_t addr[16] = {0}; // IPv4 nei primi 4 byte se !is_v6
352 int prefix = 0; // 0..32 per v4, 0..128 per v6
353 };
354 std::vector<CidrEntry> proxy_bypass_cidrs_;
355
356 // Provider di pagamento "navigabili" (pagine di checkout/login/dashboard)
357 // che vanno SEMPRE intercettati e aperti in sandbox, anche se l'utente non
358 // li ha messi in payments.json. NON includere qui gli endpoint tecnici
359 // (js.*, api.*, hooks.*, cdn.*): quelli stanno nelle esclusioni perché
360 // sono iframe/API caricati dentro le pagine dei merchant e intercettarli
361 // romperebbe il checkout. Le esclusioni hanno comunque priorità (vengono
362 // controllate prima), quindi js.stripe.com resta passante mentre
363 // checkout.stripe.com viene aperto in sandbox.
364 static const std::unordered_set<std::string>& get_builtin_payment_providers() {
365 static const std::unordered_set<std::string> pp = {
366 "stripe.com",
367 "paypal.com",
368 "paypalobjects.com",
369 "adyen.com",
370 "checkout.com",
371 "klarna.com",
372 "worldpay.com",
373 "braintreegateway.com",
374 "nexi.it",
375 "nexigroup.com",
376 "sisalpay.it",
377 "satispay.com",
378 "sumup.com",
379 "square.com",
380 "squareup.com",
381 "mollie.com",
382 "gocardless.com",
383 "wise.com",
384 "revolut.com",
385 "skrill.com",
386 "neteller.com",
387 "paysafecard.com",
388 "amazonpay.com",
389 "wallet.google.com",
390 "pay.google.com",
391 };
392 return pp;
393 }
394
395 static const std::unordered_set<std::string>& get_builtin_exclusions() {
396 static const std::unordered_set<std::string> ex = {
397 "js.stripe.com",
398 "api.stripe.com",
399 "m.stripe.com",
400 "r.stripe.com",
401 "b.stripe.com",
402 "q.stripe.com",
403 "hooks.stripe.com",
404 "cdn.stripe.com",
405 "errors.stripe.com",
406 "stripe.network",
407 "api.paypal.com",
408 "t.paypal.com",
409 "c.paypal.com",
410 "pics.paypal.com",
411 "history.paypal.com",
412 "stats.paypal.com",
413 "api-m.paypal.com",
414 "api-m.sandbox.paypal.com",
415 "www.sandbox.paypal.com",
416 "api.sandbox.paypal.com",
417 "payments.amazon.com",
418 "static.payments.amazon.com",
419 "widgets.amazon.com",
420 "fls-eu.amazon.com",
421 "google.com/pay",
422 "tpay.google.com",
423 "pay-sandbox.google.com",
424 "apple-pay-gateway.apple.com",
425 "apple-pay-gateway-cert.apple.com",
426 "cn-apple-pay-gateway.apple.com",
427 "live.adyen.com",
428 "checkoutshopper-live.adyen.com",
429 "checkoutshopper-test.adyen.com",
430 "ca-live.adyen.com",
431 "analytics.adyen.com",
432 "js.klarna.com",
433 "api.klarna.com",
434 "x.klarnacdn.net",
435 "klarnaservices.com",
436 "osm.klarnaservices.com",
437 "api.checkout.com",
438 "cdn.checkout.com",
439 "events.checkout.com",
440 "api.worldpay.com",
441 "access.worldpay.com",
442 "cdn.worldpay.com",
443 "api.braintreegateway.com",
444 "assets.braintreegateway.com",
445 "payments.braintree-api.com",
446 "client-analytics.braintreegateway.com",
447 "api.braintreepayments.com",
448 "api.nexi.it",
449 "xpay.nexigroup.com",
450 "ecommerce.nexi.it",
451 "api.sisalpay.it",
452 "facebook.com",
453 "www.facebook.com",
454 "m.facebook.com",
455 "graph.facebook.com",
456 "connect.facebook.net",
457 "staticxx.facebook.com",
458 "static.xx.fbcdn.net",
459 "accounts.facebook.com",
460 "web.facebook.com",
461 "business.facebook.com",
462 "developers.facebook.com",
463 "l.facebook.com",
464 "fbcdn.net",
465 "akamaihd.net",
466 "instagram.com",
467 "www.instagram.com",
468 "cdninstagram.com",
469 "whatsapp.com",
470 "web.whatsapp.com",
471 "api.whatsapp.com",
472 "messenger.com",
473 "www.messenger.com",
474 "accounts.google.com",
475 "oauth2.googleapis.com",
476 "apis.google.com",
477 "recaptcha.google.com",
478 "www.recaptcha.net",
479 "recaptcha.net",
480 "www.gstatic.com",
481 "gstatic.com",
482 "google.com",
483 "www.google.com",
484 "google.it",
485 "www.google.it",
486 "id.google.com",
487 "myaccount.google.com",
488 "signin.google.com",
489 "login.microsoftonline.com",
490 "login.live.com",
491 "login.windows.net",
492 "account.microsoft.com",
493 "microsoftonline.com",
494 "appleid.apple.com",
495 "idmsa.apple.com",
496 "gsa.apple.com",
497 "challenges.cloudflare.com",
498 "cloudflare.com",
499 "twitter.com",
500 "x.com",
501 "api.twitter.com",
502 "t.co",
503 };
504
505 return ex;
506 }
507
508 static void load_file(const std::filesystem::path& path,
509 std::unordered_set<std::string>& target,
510 const char* label)
511 {
512 target.clear();
513
514 std::string json = read_file(path);
515 if (json.empty()) {
516 char msg[512];
517 std::snprintf(msg, sizeof(msg),
518 _("[filter] cannot read %s list: %s\n"),
519 label, path.c_str());
520 std::cerr << msg;
521 return;
522 }
523
524 target = extract_list(json);
525 }
526
527 static bool match(const std::string& host,
528 const std::unordered_set<std::string>& set)
529 {
530 if (set.count(host)) return true;
531 if (set.count("." + host)) return true;
532 std::string h = host;
533
534 while (true) {
535 auto dot = h.find('.');
536 if (dot == std::string::npos) break;
537
538 std::string with_dot = h.substr(dot);
539 std::string without = h.substr(dot + 1);
540
541 if (without.find('.') != std::string::npos) {
542 if (set.count(with_dot)) return true;
543 if (set.count(without)) return true;
544 }
545
546 h = without;
547 }
548
549 return false;
550 }
551
552 // -----------------------------------------------------------------------
553 // parse_cidr / match_cidr
554 //
555 // Supporto per voci CIDR nella proxy_bypass list.
556 // Es.: "93.55.84.0/24" matcha 93.55.84.0..93.55.84.255.
557 // "2a01:4f8::/29" matcha tutto il blocco /29.
558 //
559 // parse_cidr ritorna true se la stringa è un CIDR valido e popola 'out'.
560 // match_cidr controlla se 'host' (deve essere un IP letterale) cade
561 // in uno dei CIDR del vector.
562 // -----------------------------------------------------------------------
563 static bool parse_cidr(const std::string& s, CidrEntry& out) {
564 auto slash = s.find('/');
565 if (slash == std::string::npos) return false;
566 const std::string addr = s.substr(0, slash);
567 const std::string mask = s.substr(slash + 1);
568 if (addr.empty() || mask.empty()) return false;
569
570 // mask numerica
571 for (char c : mask)
572 if (!std::isdigit(static_cast<unsigned char>(c))) return false;
573 const int m = std::atoi(mask.c_str());
574
575 in_addr v4{};
576 if (::inet_pton(AF_INET, addr.c_str(), &v4) == 1) {
577 if (m < 0 || m > 32) return false;
578 out.is_v6 = false;
579 std::memset(out.addr, 0, 16);
580 std::memcpy(out.addr, &v4.s_addr, 4);
581 out.prefix = m;
582 return true;
583 }
584 in6_addr v6{};
585 if (::inet_pton(AF_INET6, addr.c_str(), &v6) == 1) {
586 if (m < 0 || m > 128) return false;
587 out.is_v6 = true;
588 std::memcpy(out.addr, v6.s6_addr, 16);
589 out.prefix = m;
590 return true;
591 }
592 return false;
593 }
594
595 static bool match_cidr(const std::string& host_raw,
596 const std::vector<CidrEntry>& cidrs)
597 {
598 if (cidrs.empty() || host_raw.empty()) return false;
599
600 // Strip bracket "[::1]" → "::1"
601 std::string h = host_raw;
602 if (h.size() >= 2 && h.front() == '[' && h.back() == ']')
603 h = h.substr(1, h.size() - 2);
604
605 std::uint8_t hbytes[16] = {0};
606 bool host_is_v6 = false;
607
608 in_addr v4{};
609 in6_addr v6{};
610 if (::inet_pton(AF_INET, h.c_str(), &v4) == 1) {
611 std::memcpy(hbytes, &v4.s_addr, 4);
612 host_is_v6 = false;
613 } else if (::inet_pton(AF_INET6, h.c_str(), &v6) == 1) {
614 std::memcpy(hbytes, v6.s6_addr, 16);
615 host_is_v6 = true;
616 } else {
617 // Hostname (non IP): non possiamo confrontare con CIDR senza DNS.
618 return false;
619 }
620
621 for (const auto& c : cidrs) {
622 if (c.is_v6 != host_is_v6) continue;
623 const int total_bits = c.is_v6 ? 128 : 32;
624 int rem = c.prefix;
625 bool ok = true;
626 for (int i = 0; i < (total_bits / 8) && rem > 0; ++i) {
627 const std::uint8_t mask =
628 (rem >= 8) ? 0xFF : static_cast<std::uint8_t>(0xFF << (8 - rem));
629 if ((hbytes[i] & mask) != (c.addr[i] & mask)) {
630 ok = false;
631 break;
632 }
633 rem -= 8;
634 }
635 if (ok) return true;
636 }
637 return false;
638 }
639};
640
642 static DomainFilter f;
643 return f;
644}
nlohmann::json json
Definition Backend.cpp:2908
#define _(s)
DomainFilter & domain_filter()
static std::unordered_set< std::string > extract_list(const std::string &json)
static std::string read_file(const std::filesystem::path &p)
#define AF_INET
Definition bsc_ebpf.c:56
#define AF_INET6
Definition bsc_ebpf.c:57
bool is_trusted(const std::string &host) const
bool is_secure(const std::string &host) const
bool is_bank(const std::string &host) const
std::shared_mutex mutex_
static const std::unordered_set< std::string > & get_builtin_payment_providers()
static bool match_cidr(const std::string &host_raw, const std::vector< CidrEntry > &cidrs)
static bool is_local_network(const std::string &host_raw)
static const std::unordered_set< std::string > & get_builtin_exclusions()
std::unordered_set< std::string > user_banks_
std::vector< CidrEntry > proxy_bypass_cidrs_
std::unordered_set< std::string > payments_
std::unordered_set< std::string > trusted_
std::unordered_set< std::string > banks_
static bool match(const std::string &host, const std::unordered_set< std::string > &set)
std::unordered_set< std::string > proxy_bypass_
bool is_payment(const std::string &host) const
std::unordered_set< std::string > exclusions_
static bool parse_cidr(const std::string &s, CidrEntry &out)
static void load_file(const std::filesystem::path &path, std::unordered_set< std::string > &target, const char *label)
bool is_proxy_bypass(const std::string &host) const
std::uint8_t addr[16]