Skip to content

transport: add SOCKS5 -proxy flag with UDP guard and test coverage#10

Merged
psycep merged 7 commits intomainfrom
proxy-support
Apr 22, 2026
Merged

transport: add SOCKS5 -proxy flag with UDP guard and test coverage#10
psycep merged 7 commits intomainfrom
proxy-support

Conversation

@psycep
Copy link
Copy Markdown
Collaborator

@psycep psycep commented Apr 22, 2026

Summary

Adds a -proxy socks flag to every gopacket tool, alongside the existing LD_PRELOAD / proxychains path. Operators no longer need libc hooking to route traffic; proxychains still works unchanged for users who prefer it (and the two can be chained).

  • New transport plumbing (pkg/transport): split into a router (tcp.go) plus platform-specific direct dialers (direct_libc.go for cgo+Unix, direct_portable.go for the portable path that unblocks CGO_ENABLED=0 and Windows builds in a follow-up). New proxy.go exposes Configure, DialContext, DialUDP, IsProxyConfigured, ProxyURL, and ErrUDPUnderProxy.
  • UDP under -proxy is refused, not silently bypassed. Surfacing a clear error via ErrUDPUnderProxy avoids leaking the operator's real source IP through UDP when the operator explicitly asked for proxying.
  • flags.RegisterProxyFlag helper so the handful of tools that hand-roll their flag setup (instead of calling flags.Parse) can get -proxy in two lines.
  • Call-site migrations across pkg/relay, pkg/tds, and the tools that reached out via raw net.Dial* or net.Resolver. Included an intentional carve-out in pkg/relay/socks.go (our own SOCKS5 server's outbound leg) to avoid double-tunneling the operator's proxy through the operator's proxy.
  • Tests: hand-rolled in-process SOCKS5 (CONNECT, no-auth) plus 13 cases covering scheme acceptance/rejection, ALL_PROXY env fallback, double-call panic, UDP rejection, credential redaction, direct-path behavior, and an end-to-end round-trip.
  • Docs: new "Proxy Support" section in README with proxychains and -proxy subsections; a new KNOWN_ISSUES.md entry documenting the UDP-under-proxy limitation with per-tool workarounds.

Relationship to #5

Thanks to @5amu for starting this conversation in #5. That PR solves a narrower slice of the same problem (Windows-only build via a _windows.go fallback). This PR covers the same ground and adds more: the Windows unblock (with a stricter !cgo || windows build tag so CGO_ENABLED=0 Linux also works), plus the explicit -proxy CLI flag, UDP handling, chaining support, and call-site migrations. Happy to credit the file-split idea from #5. Windows cross-compile verification and stubs for the 3 libpcap-dependent tools will land in a separate follow-up PR to keep this one focused on the proxy surface.

Smoke-tested

Against a live GOAD lab running in GCP, over an IAP SSH tunnel with SOCKS5 on :1080. Both -proxy socks5h://... and proxychains4 were exercised against kingslanding (sevenkingdoms.local) and winterfell (north.sevenkingdoms.local). Tools confirmed working end-to-end: lookupsid, samrdump, GetNPUsers, DumpNTLMInfo, rpcmap, cross-domain SID lookup. mssqlinstance surfaces ErrUDPUnderProxy cleanly as designed. No-proxy path verified against 127.0.0.1 to confirm libc connect() still runs unchanged (error strings match pre-refactor verbatim).

Not in scope

  • Native Windows builds and CGO_ENABLED=0 Linux builds. The direct_portable.go scaffolding here makes them possible; the actual cross-compile verification, Windows stubs for sniff / sniffer / split, and the README Platform Support rewrite land in a follow-up.

Test plan

  • go build ./... clean
  • go vet ./... clean
  • go test ./pkg/transport/ passes (13/13)
  • Live lab smoke-test across 6 tools via -proxy and proxychains
  • No-proxy regression check against 127.0.0.1 confirms libc path active
  • License audit: all new files carry Apache 2.0 + Google LLC 2026 header; no new dependencies; NOTICE unchanged

psycep added 6 commits April 22, 2026 09:54
Splits pkg/transport into a router (tcp.go) plus two platform-specific
direct dialers: direct_libc.go preserves the existing cgo libc connect()
path on Unix (so proxychains can still hook it), and direct_portable.go
provides a pure-Go fallback for CGO_ENABLED=0 and Windows builds.

Adds proxy.go with:
- Configure(Options) / ConfigureProxy to wire a SOCKS5 URL (socks5 or
  socks5h), with ALL_PROXY / all_proxy env fallback read directly via
  os.Getenv (not proxy.FromEnvironment, whose sync.Once cache is not
  test-friendly).
- DialContext routing through the configured proxy, or directDial.
- DialUDP returning ErrUDPUnderProxy when proxied, rather than silently
  leaking UDP packets past the SOCKS5.
- IsProxyConfigured and ProxyURL for callers that need to short-circuit
  features that can't be tunneled.
- libcForwarder so the TCP leg to the SOCKS5 proxy itself still goes
  through libc, enabling proxychains -> gopacket -> -proxy chaining.

Tests:
- export_test.go exposes ResetForTest for test isolation (Configure
  panics on double-call, so each test resets package state).
- socks5_server_test.go hand-rolls a minimal in-process SOCKS5 server
  (CONNECT + no-auth, ~130 LOC, no new deps).
- proxy_test.go covers invalid scheme rejection, socks5/socks5h accept,
  ALL_PROXY env fallback, double-call panic, default state,
  DialUDP/DialContext UDP rejection, credential redaction, direct-path
  behavior when unconfigured, and an end-to-end round-trip through the
  in-process SOCKS5.
Introduces three exports in pkg/flags:
- ProxyFlagUsage: the shared -proxy usage string, so tools that hand-roll
  their flag setup render identical help text to Parse().
- ConfigureProxy(url): calls transport.Configure and exits on error.
  Centralizes the "fail loud on bad -proxy" behavior.
- RegisterProxyFlag(): registers -proxy on the default flag.CommandLine
  and returns a finalizer to call after flag.Parse(). Two-line
  integration for tools that don't use flags.Parse().

Parse() now uses these internally. It also calls ConfigureProxy and
applies Debug/Timestamp unconditionally rather than returning early when
NArg == 0; tools that take all their config via flags (listeners, etc.)
were previously losing -proxy because the early return skipped Configure.
- pkg/relay/http_client.go, winrm_client.go: swap the http.Transport
  DialContext from &net.Dialer{Timeout: 10s}.DialContext to a closure
  that calls transport.DialContext. NTLM relay now respects -proxy.
- pkg/relay/socks.go: add a comment on the HTTP DNS passthrough path
  explaining why it intentionally uses net.DialTimeout rather than
  transport. That path is the outbound leg of our own SOCKS5 server,
  so routing it through -proxy would double-tunnel the operator's
  proxy through the operator's proxy.
- pkg/tds/sqlr.go: SQL Server Browser discovery (UDP 1434) now uses
  transport.DialUDP, which surfaces ErrUDPUnderProxy when a proxy is
  configured rather than silently bypassing it.
Four tools that already used flags.Parse (and therefore already had
-proxy registered transparently) still reached out via net.Dial* or
ran DNS resolution past the proxy. Migrate the network calls:

- tools/GetADComputers: custom net.Resolver now dials via
  transport.DialContext, so UDP DNS through the DC surfaces
  ErrUDPUnderProxy cleanly rather than bypassing -proxy.
- tools/changepasswd: kpasswd TCP dial now uses transport.DialTimeout.
- tools/smbexec: getLocalIP() short-circuits when
  transport.IsProxyConfigured(). The "dial UDP to find the local
  source address" trick has no meaning when traffic flows through a
  SOCKS5 proxy.
- tools/raiseChild: the DNS forest-FQDN fallback (net.LookupHost) is
  skipped under -proxy with a message pointing the user to -parent-dc,
  since net.LookupHost goes to the OS resolver and would leak DNS.
Four tools register their flags directly via the stdlib flag package
rather than going through flags.Parse, so they didn't inherit -proxy
and transport.Configure was never called (silent no-op for the user).

Wires each to flags.RegisterProxyFlag, a two-line integration that
registers -proxy and returns a finalizer to call after flag.Parse():

- tools/CheckLDAPStatus
- tools/DumpNTLMInfo
- tools/rpcmap (also adds the pkg/flags import)
- tools/mssqlinstance (also adds the pkg/flags import; -proxy will
  immediately error with ErrUDPUnderProxy since the tool's whole job
  is a UDP SQL Browser probe, but the error is surfaced clearly
  instead of silently bypassing)
- README.md: expand "Proxychains Support" into "Proxy Support" with
  two subsections (proxychains via LD_PRELOAD, and the new -proxy
  SOCKS5 flag). Adds -proxy to the Common Flags table, adds a quick
  example, and updates the final Notes line so "all tools work
  through proxychains" also covers -proxy.
- KNOWN_ISSUES.md: add entry #8 "UDP Features Disabled Under -proxy"
  with a table mapping affected tools to concrete workarounds
  (supply -port directly, pass -dc-host / -dc-ip / -target-ip,
  supply -parent-dc). Renumbers "Remaining Gaps" to #9.
Bumps the version string across pkg/flags and all tool banners to
v0.1.1-beta. Marks the first feature release after the initial beta
(adds the -proxy socks flag and UDP-under-proxy guard landing in
this PR).

Note: the version string is currently duplicated 34 times across 27
tool binaries and flags.go. A follow-up PR should refactor every
tool to call flags.Banner() instead of hardcoding the literal, so
future bumps are one-line changes.
@psycep psycep merged commit 8f997a5 into main Apr 22, 2026
2 checks passed
@psycep psycep deleted the proxy-support branch April 22, 2026 15:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant