Repository navigation
Dev - #244
Merged
Merged
Dev#244
Conversation
ProxyBridge.exe no longer requires administrator. A LocalSystem service (ProxyBridgeSvc.exe, registered once by the installer) owns ProxyBridgeCore.dll and WinDivert; the GUI drives it over two named pipes (request/response + event stream), the same model Proxifier uses. - add src/service: service host and wire protocol - add gui/api/pb_ipc.h: pipe-backed PBApi; PB_Load -> PB_LoadDirect - manifest: requireAdministrator -> asInvoker - no service (portable copy): relaunch elevated and load the core directly - Run at Startup: per-user HKCU Run entry instead of elevated logon task - installer: create/start/remove the service, drop legacy logon task - compile.ps1: build ProxyBridgeSvc.exe - ignore MSVC .exp/.lib/.obj intermediates Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…session failures Per-source-port decision cache and connection tracking were only cleared on an outbound FIN/RST. Connections that end without one (peer reset, connect timeout, app killed) left stale state, and when Windows reused the ephemeral port a new connection was either sent DIRECT (stale bit) or redirected to the previous connection's destination (stale tracking entry). A fresh SYN now clears both, for IPv4 and IPv6. The capture loop retried immediately and logged on every non-INVALID_HANDLE receive error. It now exits quietly on shutdown errors, rate-limits logging, backs off on persistent failure and reports when capture is effectively dead. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… DIRECT bypass, #205) Two lookup defects made fresh connections look unowned, so they were sent DIRECT without being logged or rule-matched: - The TCP table lists closed connections in TIME_WAIT with PID 0. When Windows reuses that ephemeral port both rows exist and the lookup could take the TIME_WAIT row. Rows with no owner are now skipped (IPv4 and IPv6). - The size-query-then-fetch pattern failed with ERROR_INSUFFICIENT_BUFFER whenever the table grew in between (busy machine). Fetch now adds headroom and retries (TCP/UDP, IPv4/IPv6). Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ion into DIRECT (#205) - A new outbound TCP connection can be absent from the owner-PID table when its SYN arrives. Retry the lookup briefly (3 x ~0.5 ms) before treating it as ownerless. The capture layer only sees traffic to/from this host, so a persistent miss is rare. - When the process cannot be opened (access denied / protected) fall back to the basename from a toolhelp snapshot so name-based rules still match. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ST app on handshake failure "SOCKS5 IPv6: CONNECT failed (reply=-1)" gave no destination and a meaningless code; -1 actually means the proxy closed the connection or never answered. Failures now name the destination and the reason (RFC 1928 REP names for real replies), and identical lines within 5 s are collapsed to a "repeated N more times" note. The relay completes the app's TCP handshake before talking to the proxy, so on a failed upstream handshake it now resets (RST) the app socket instead of a silent close, so the app fails or retries immediately. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…ssion #190) The GUI is system-DPI aware but used fixed 16 px fonts and a fixed 1200x820 window, so on high-DPI screens (e.g. 3072x1920 14") the log text and window were tiny and nothing scaled. - fonts and main-window layout metrics scale with the system DPI - default window size scales with DPI, capped at 90% of the work area - Settings > Increase/Decrease/Reset Text Size, plus Ctrl+mouse wheel and Ctrl +/-/0 on the log boxes and search fields; 60-300%, persisted per profile as "FontZoom" - strings added for English and Chinese Dialogs keep their fixed-pixel layout. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…e-port collision with relay upstream) The relay's upstream sockets to the proxy draw ephemeral ports from the same pool as the apps, and the packet loop checked per-port state (DIRECT bit / tracked entry) before looking at who the packet was for. After the port range had cycled for hours, a leftover entry for an earlier app flow could match the relay's own connection to the proxy and redirect it back into the relay, so proxied apps hung while the log still looked normal. Outbound TCP whose destination is a configured proxy server (IPv4) is now passed through untouched before any per-port state is consulted.
…ost (Discord voice, slowdown) UDP "connections" are tracked by source port only, with a single destination, and once tracked a flow was never re-checked: - A socket that moves to another server (e.g. Discord migrating a voice session) kept sending to the first destination forever, so the call dropped and never recovered. - A UDP port reused by a different socket inherited the old flow's routing for as long as traffic kept the entry alive. - The relay's own UDP socket to the proxy's UDP relay draws its port from the same pool as the apps; a collision with a tracked app port redirected the relay's packets back into the relay and broke all proxied UDP until restart. Fixes: udp_flow_validate() retargets the entry when the destination changes and evicts it after 20 s idle so the rules are re-evaluated; relay <-> proxy UDP relay traffic is passed through untouched. DIRECT UDP flows had no decision cache, so every datagram re-ran the process lookup (OpenProcess per packet) on the single capture thread, which saturates it at high packet rates and slows all traffic. DIRECT decisions are now cached for 5 s per (src port, destination). Also: the PID cache stored a 64-bit tick count in a 32-bit field, which breaks its TTL math after ~49.7 days of uptime; widened to 64 bits.
With --verbose the log/connection callbacks printf() directly. The engine calls them on its single packet-capture thread, so a console that stops draining (QuickEdit selection, or stdout redirected to a pipe/file that nobody reads, as on a headless host) blocks that thread and with it all captured traffic. The callbacks now only enqueue into a bounded ring (drops when full, with a "dropped" note); a writer thread does the output. Also include <shellapi.h> for ShellExecuteA (its return value was being truncated on x64).
… of scanning the TCP table Every new outbound TCP connection triggered GetExtendedTcpTable on the single capture thread (twice: a sizing call and the fetch). The table includes every TIME_WAIT socket, so on a busy machine it has tens of thousands of rows and one fetch costs several ms (measured 3.6 ms at ~9.6k rows). A proxied connection evaluates three SYNs (app, relay->proxy, proxy->target), so the capture thread was the bottleneck: ~49 connections/s at 101% of one core, median latency 1.2-1.6 s and growing with the table. - pb_sockmap.c: a sniff-only WinDivert SOCKET-layer handle records the PID of every TCP connect() by local port before the SYN is sent; lookups hit it first (entries valid 8 s) and fall back to the table scan on a miss, so behaviour is never worse than before. - owner_table_fetch(): try the previous scratch buffer first and only size the table when it is too small (halves the cost of the remaining scans). Measured on the same machine with a SOCKS5 test proxy: 1500 proxied connections went from 30.5 s (p50 1240 ms) to 1.5 s (p50 60 ms); a 14k-connection churn holds a flat ~16 ms median with 100% of connections proxied.
…nection count (#213) The data-transfer stage ran two blocking threads (upload + download) and two 128 KB buffers per proxied connection, so a BitTorrent-style workload with ~1000 peers meant 2000+ threads, ~350 KB and ~9 handles per connection (issue #213: machine sluggish, 1000+ threads). Both sockets are now handed to a small pool of worker threads (2-8, by core count) driving overlapped WSARecv/WSASend through an I/O completion port. Semantics are unchanged: each direction copies recv -> send, and when either direction ends both sockets are shut down and closed once both have drained. Buffers and socket buffers shrink as the number of live relays grows (64 KB -> 16 KB -> 8 KB; 4 MB -> 1 MB -> 256 KB -> 64 KB socket buffers). ProxyBridge_Stop() aborts every live relay and joins the workers before returning, so the engine can be unloaded safely. If the pool cannot start, connections fall back to the previous blocking relay threads. Measured on the same machine (800 held connections with light traffic): threads 1608 -> 22 private memory 285 MB -> 42 MB handles 7530 -> 2002 3000 held connections: 18 threads, 61 MB. Bulk throughput 364/428 MB/s -> 413/500 MB/s (up/down); 384 MB of random data echoed through it verified byte-for-byte; a 1500-connection churn stayed 100% proxied.
…tions Shrinking per-connection buffers as the number of relays grew (64 KB -> 16 KB -> 8 KB) made fast transfers slow whenever many other connections were open: with 800 connections held, a single stream dropped to 164/294 MB/s up/down versus 344/492 MB/s with full-size buffers. An idle direction now waits with a zero-byte receive and owns no buffer. When data arrives it takes a 64 KB buffer from a shared pool, reads and sends, then goes back to waiting unless the stream is still busy (>= 16 KB per read), in which case it keeps the buffer. Socket buffers only shrink (4 MB -> 1 MB) above 1024 live relays. Measured through the proxy, 4 streams up/down MB/s while N other connections are held open: N=0 old relay 359/477 this 433/532 N=800 old relay 303/347 this 365/399 N=3000 old relay 105/94 this 179/189 Private memory with 3000 held connections: 12 MB (old relay ~1 GB projected, small-buffer pool 61 MB, always-large buffers 174 MB). Data integrity verified byte-for-byte at every load.
…tion A TCP FIN/RST used to delete the connection's tracking entry immediately. A client that sends data and closes at once can have its FIN seen before the relay has accept()ed the connection; the relay's lookup then fails and the connection - together with the client's data - is dropped silently. Under load this was easy to hit: with 3000 connections created over ~47 s and a 25 s hold, ~800 of the late connect-send-close connections never reached the proxy. The entry is now kept until both sides have sent their FIN (new fin_flags: 1 = app, 2 = relay), and a reset still removes it right away. Port reuse is already handled by clearing stale state on a fresh SYN. Measured: the same 3000-connection burst now proxies 3000/3000 (twice), a 16000-connection churn with 25% abnormal closes stays 100% proxied, data integrity unchanged. Also wrap WinDivertSend: every call in the packet loop ignored its result, so a failed re-injection dropped the packet silently. Failures are now counted and logged (first ten, then every 500th) with protocol, direction and flags.
Matches the macOS 4.1.0 release. Updates the GUI version resource (also fixes the numeric file version, which was still 4.0.11), the application manifest, the installer name and version info, the core and CLI version strings, and the installer filename in CONTRIBUTING.md.
…rks (firewall rule for the relay) ProxyBridge redirects a connection by turning the app's packet around so it arrives at the local relay as an *inbound* connection on whichever interface the app used. Windows Firewall only prompts to allow an app on the Private profile, and VPN adapters (WireGuard, OpenVPN) are classified Public. With a full-tunnel VPN the redirected SYN was therefore dropped: the Connections log looked normal, the app timed out in SYN-SENT, and the proxy never saw anything. Reproduced with a WireGuard (UniFi Endpoint) tunnel: 0 of N connections proxied; after adding an inbound allow rule, N of N. The same gap affects the service: the relay now lives in ProxyBridgeSvc.exe (a background service never gets the first-run firewall prompt), so a fresh install had no inbound rule for it at all. - On start, when elevated, the engine adds an inbound allow rule for its host executable on all network profiles (one rule per path, "ProxyBridge relay <hash>"); logged in the Activity tab, best effort. - The uninstaller removes the rules for ProxyBridgeSvc.exe, ProxyBridge.exe and ProxyBridge_CLI.exe. - Because the relay is now reachable on every profile, accept() verifies the peer: the redirected SYN always arrives from the tracked flow's ORIGINAL destination address (or a loopback address), so a connection from any other address is dropped. Previously a flow was claimed by source port alone, which another host could have exploited once the relay port was reachable. Verified over a WireGuard full tunnel with only the auto-created rule: 6/6 requests proxied, 800/800 burst connections, byte-verified bidirectional integrity, UDP server-switch and port reuse correct.
…/re-add (#203) The GUI saved the id returned by the engine's AddProxyConfig as the proxy's permanent id in the profile. That id is only valid for the current engine session (it restarts at 1 whenever the engine is reloaded, which the service does for every GUI connection), so ids drifted and collided: after deleting a proxy and adding another, a newly added proxy could get an id that an older proxy or an existing rule already used, and rules resolved to the wrong proxy. Proxy ids are now owned by the profile: PB_AllocConfigId() hands out stable ids from a counter that is persisted ("NextProxyConfigId") and never reused, and the existing stored-to-engine id mapping (ResolveNativeCfg) keeps translating them to the engine's session ids. On load, profiles written by the old code are repaired: zero or duplicate ids get fresh ones (the first holder keeps its id, so rules stay with it) and the counter is positioned above every id in use, including ids that only rules still reference. Tested with the real profile.c: the issue's delete/re-add scenario, save/reload persistence, and a broken profile with duplicate/zero ids.
…reads outliving the engine) ProxyBridgeSvc reloads the engine DLL for every GUI connection, so ProxyBridge_Stop() must leave no thread running inside the DLL: a thread that wakes up after the DLL is unloaded executes unmapped code and takes the whole service down. Windows logged "service terminated unexpectedly" (event 7034) four times today, each right after the GUI was closed; the GUI hid it by restarting the service on its next launch. In-process (GUI/CLI) the process exiting masked the same bug. The culprit was the cleanup thread: it did Sleep(30000) while Stop() waited only one second for it, then abandoned it. Fixes: - the cleanup thread sleeps in 100 ms slices and checks the stop flag - thread joins in Stop() wait up to 8 s instead of 1 s - the firewall-rule thread is tracked and joined - per-connection handshake threads are counted from creation to exit and Stop() waits for them (up to 35 s) before the engine can be unloaded - remove the unused ACCEPT_AND_DISPATCH macro, which would have bypassed that accounting Verified on an installed service: closing the GUI no longer kills it (same process id after 120 s, no new 7034 events).
…ecision (#206) Clearing the decision before testing whether it was direct made the packet fall through to a fresh rule lookup that cached DIRECT again, so teardown never evicted anything. Remember the direct state first, clear on FIN/RST, and send untracked FIN/RST untouched instead of looking up an owner that is usually already gone.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Pull Request
Required Pre-Submission Checklist
devbranch (NOTmaster)masterbranch is ONLY updated when a new release is createdmasterwill be REJECTED⛔ Critical Information
DO NOT submit a pull request directly to the
masterbranch. The master branch is only updated when a new release is created. No direct commits or merges are accepted to master - neither from the InterceptSuite team nor from contributors.All changes MUST be submitted to the
devbranch, which contains the latest code and commits.You MUST create a GitHub issue first before submitting a pull request. This allows the InterceptSuite team to:
Pull requests without an associated issue will be rejected.
Pull Request Details
Type of Change
Related GitHub Issue
Issue Link:
Platform(s) Affected
macOS Application Changes
Does this pull request include changes to the macOS application?
If YES, you MUST answer the following:
macOS Code Signing & Verification
Description of Changes
What does this pull request do?
How has this been tested?
Compilation & Dependencies
New dependencies (if any):
Screenshots/Logs (if applicable)
Additional Context
Final Checklist
devbranch