relay, tds: route outbound through transport

- 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.
This commit is contained in:
psycep
2026-04-22 09:54:09 -05:00
parent 7a1914a077
commit efccf92228
4 changed files with 30 additions and 27 deletions
+10 -5
View File
@@ -15,6 +15,7 @@
package relay
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
@@ -24,8 +25,10 @@ import (
"net/http"
"regexp"
"strings"
"time"
"gopacket/internal/build"
"gopacket/pkg/transport"
)
// HTTPRelayClient implements ProtocolClient for relaying NTLM auth to HTTP/HTTPS targets.
@@ -73,20 +76,22 @@ func (c *HTTPRelayClient) SetPath(path string) {
func (c *HTTPRelayClient) InitConnection() error {
// Use a custom transport that keeps connections alive for the NTLM handshake.
// The entire 3-leg NTLM exchange must happen on the same TCP connection.
transport := &http.Transport{
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DisableKeepAlives: false,
// Force single connection reuse for NTLM auth
MaxIdleConnsPerHost: 1,
DialContext: (&net.Dialer{
Timeout: 10 * 1e9, // 10 seconds
}).DialContext,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
return transport.DialContext(ctx, network, addr)
},
}
c.httpClient = &http.Client{
Transport: transport,
Transport: tr,
// Don't follow redirects — we need to see the raw 401/302 responses
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
+3
View File
@@ -447,6 +447,9 @@ func (s *SOCKSServer) handleConnection(conn net.Conn) {
func (s *SOCKSServer) handleDNSPassthrough(clientConn net.Conn, host string, port int) {
target := fmt.Sprintf("%s:%d", host, port)
// Intentionally uses net.DialTimeout, not transport.DialTimeout: this is
// the outbound leg of our own SOCKS5 server, so routing it through the
// operator's -proxy would double-tunnel.
remotConn, err := net.DialTimeout("tcp", target, 10*time.Second)
if err != nil {
if build.Debug {
+10 -5
View File
@@ -15,6 +15,7 @@
package relay
import (
"context"
"crypto/tls"
"encoding/base64"
"fmt"
@@ -24,8 +25,10 @@ import (
"net/http"
"regexp"
"strings"
"time"
"gopacket/internal/build"
"gopacket/pkg/transport"
)
// WinRMRelayClient implements ProtocolClient for relaying NTLM auth to WinRM targets.
@@ -50,20 +53,22 @@ func NewWinRMRelayClient(addr string, useTLS bool) *WinRMRelayClient {
// InitConnection creates the HTTP client with connection reuse for NTLM handshake.
// Implements ProtocolClient.
func (c *WinRMRelayClient) InitConnection() error {
transport := &http.Transport{
tr := &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
DisableKeepAlives: false,
// Force single connection reuse for NTLM auth
MaxIdleConnsPerHost: 1,
DialContext: (&net.Dialer{
Timeout: 10 * 1e9, // 10 seconds
}).DialContext,
DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) {
ctx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
return transport.DialContext(ctx, network, addr)
},
}
c.httpClient = &http.Client{
Transport: transport,
Transport: tr,
CheckRedirect: func(req *http.Request, via []*http.Request) error {
return http.ErrUseLastResponse
},
+7 -17
View File
@@ -16,9 +16,10 @@ package tds
import (
"fmt"
"net"
"strings"
"time"
"gopacket/pkg/transport"
)
// SQLRInstance represents a discovered SQL Server instance
@@ -31,15 +32,10 @@ type SQLRInstance struct {
NamedPipe string
}
// GetInstances queries the SQL Server Browser service for instances
// GetInstances queries the SQL Server Browser service for instances.
// Fails under -proxy because SQL Browser is UDP and cannot be tunneled.
func GetInstances(server string, timeout time.Duration) ([]SQLRInstance, error) {
// Create UDP connection
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", server, SQLRPort))
if err != nil {
return nil, err
}
conn, err := net.DialUDP("udp", nil, addr)
conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort))
if err != nil {
return nil, err
}
@@ -66,15 +62,9 @@ func GetInstances(server string, timeout time.Duration) ([]SQLRInstance, error)
return parseInstanceResponse(buf[:n])
}
// GetInstancePort queries for a specific instance's port
// GetInstancePort queries for a specific instance's port. Fails under -proxy (UDP).
func GetInstancePort(server, instance string, timeout time.Duration) (int, error) {
// Create UDP connection
addr, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", server, SQLRPort))
if err != nil {
return 0, err
}
conn, err := net.DialUDP("udp", nil, addr)
conn, err := transport.DialUDP(fmt.Sprintf("%s:%d", server, SQLRPort))
if err != nil {
return 0, err
}