Skip to content

Commit 5ac8830

Browse files
added the notion of proxy mode, added support for
1 parent 72ffcba commit 5ac8830

File tree

2 files changed

+98
-27
lines changed

2 files changed

+98
-27
lines changed

cmd/gohpts/cli.go

Lines changed: 29 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"os"
77
"runtime"
8+
"slices"
89

910
gohpts "github.com/shadowy-pycoder/go-http-proxy-to-socks"
1011
"golang.org/x/term"
@@ -14,6 +15,7 @@ const (
1415
app string = "gohpts"
1516
addrSOCKS = "127.0.0.1:1080"
1617
addrHTTP = "127.0.0.1:8080"
18+
tproxyOS = "linux"
1719
)
1820
const usagePrefix string = `
1921
_____ _ _ _____ _______ _____
@@ -41,9 +43,17 @@ func root(args []string) error {
4143
flags.StringVar(&conf.CertFile, "c", "", "Path to certificate PEM encoded file")
4244
flags.StringVar(&conf.KeyFile, "k", "", "Path to private key PEM encoded file")
4345
flags.StringVar(&conf.ServerConfPath, "f", "", "Path to server configuration file in YAML format")
44-
if runtime.GOOS == "linux" {
45-
flags.StringVar(&conf.TProxy, "t", "", "Address of transparent proxy server (TPROXY) (it starts along with HTTP proxy server)")
46-
flags.StringVar(&conf.TProxyOnly, "T", "", "Address of transparent proxy server (TPROXY) (no HTTP)")
46+
if runtime.GOOS == tproxyOS {
47+
flags.StringVar(&conf.TProxy, "t", "", "Address of transparent proxy server (it starts along with HTTP proxy server)")
48+
flags.StringVar(&conf.TProxyOnly, "T", "", "Address of transparent proxy server (no HTTP)")
49+
flags.Func("M", fmt.Sprintf("Transparent proxy mode: %s", gohpts.SupportedTProxyModes), func(flagValue string) error {
50+
if !slices.Contains(gohpts.SupportedTProxyModes, flagValue) {
51+
fmt.Fprintf(os.Stderr, "%s: %s is not supported (type '%s -h' for help)\n", app, flagValue, app)
52+
os.Exit(2)
53+
}
54+
conf.TProxyMode = flagValue
55+
return nil
56+
})
4757
}
4858
flags.BoolFunc("d", "Show logs in DEBUG mode", func(flagValue string) error {
4959
conf.Debug = true
@@ -72,18 +82,31 @@ func root(args []string) error {
7282
if seen["t"] && seen["T"] {
7383
return fmt.Errorf("cannot specify both -t and -T flags")
7484
}
85+
if seen["t"] {
86+
if !seen["M"] {
87+
return fmt.Errorf("Transparent proxy mode is not provided: -M flag")
88+
}
89+
}
7590
if seen["T"] {
7691
for _, da := range []string{"U", "c", "k", "l"} {
7792
if seen[da] {
78-
return fmt.Errorf("-T flag only works with -s, -u, -f, -d and -j flags")
93+
return fmt.Errorf("-T flag only works with -s, -u, -f, -M, -d and -j flags")
7994
}
8095
}
96+
if !seen["M"] {
97+
return fmt.Errorf("Transparent proxy mode is not provided: -M flag")
98+
}
99+
}
100+
if seen["M"] {
101+
if !seen["t"] && !seen["T"] {
102+
return fmt.Errorf("Transparent proxy mode requires -t or -T flag")
103+
}
81104
}
82105
if seen["f"] {
83106
for _, da := range []string{"s", "u", "U", "c", "k", "l"} {
84107
if seen[da] {
85-
if runtime.GOOS == "linux" {
86-
return fmt.Errorf("-f flag only works with -t, -T, -d and -j flags")
108+
if runtime.GOOS == tproxyOS {
109+
return fmt.Errorf("-f flag only works with -t, -T, -M, -d and -j flags")
87110
}
88111
return fmt.Errorf("-f flag only works with -d and -j flags")
89112
}

gohpts.go

Lines changed: 69 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,14 @@ const (
3939
hopTimeout time.Duration = 3 * time.Second
4040
flushTimeout time.Duration = 10 * time.Millisecond
4141
availProxyUpdateInterval time.Duration = 30 * time.Second
42-
shutdownTimeout time.Duration = 5 * time.Second
4342
kbSize int64 = 1000
4443
rrIndexMax uint32 = 1_000_000
4544
)
4645

47-
var supportedChainTypes = []string{"strict", "dynamic", "random", "round_robin"}
46+
var (
47+
supportedChainTypes = []string{"strict", "dynamic", "random", "round_robin"}
48+
SupportedTProxyModes = []string{"redirect", "tproxy"}
49+
)
4850

4951
// Hop-by-hop headers
5052
// https://datatracker.ietf.org/doc/html/rfc2616#section-13.5.1
@@ -116,6 +118,7 @@ type proxyapp struct {
116118
keyFile string
117119
httpServerAddr string
118120
tproxyAddr string
121+
tproxyMode string
119122
user string
120123
pass string
121124
proxychain chain
@@ -135,10 +138,11 @@ func (p *proxyapp) printProxyChain(pc []proxyEntry) string {
135138
if p.tproxyAddr != "" {
136139
sb.WriteString(" | ")
137140
sb.WriteString(p.tproxyAddr)
138-
sb.WriteString(" (tproxy)")
141+
sb.WriteString(fmt.Sprintf(" (%s)", p.tproxyMode))
139142
}
140143
} else if p.tproxyAddr != "" {
141144
sb.WriteString(p.tproxyAddr)
145+
sb.WriteString(fmt.Sprintf(" (%s)", p.tproxyMode))
142146
}
143147
sb.WriteString(" -> ")
144148
for _, pe := range pc {
@@ -233,6 +237,10 @@ func (p *proxyapp) getSocks() (proxy.Dialer, *http.Client, error) {
233237
p.mu.RLock()
234238
defer p.mu.RUnlock()
235239
chainType := p.proxychain.Type
240+
if len(p.availProxyList) == 0 {
241+
p.logger.Error().Msgf("[%s] No SOCKS5 Proxy available", chainType)
242+
return nil, nil, fmt.Errorf("no socks5 proxy available")
243+
}
236244
var chainLength int
237245
if p.proxychain.Length > len(p.availProxyList) || p.proxychain.Length <= 0 {
238246
chainLength = len(p.availProxyList)
@@ -586,9 +594,30 @@ func newTproxyServer(pa *proxyapp) *tproxyServer {
586594
quit: make(chan struct{}),
587595
pa: pa,
588596
}
589-
ln, err := net.Listen("tcp", ts.pa.tproxyAddr)
597+
// https://iximiuz.com/en/posts/go-net-http-setsockopt-example/
598+
lc := net.ListenConfig{
599+
Control: func(network, address string, conn syscall.RawConn) error {
600+
var operr error
601+
if err := conn.Control(func(fd uintptr) {
602+
operr = syscall.SetsockoptInt(int(fd), unix.IPPROTO_TCP, unix.TCP_USER_TIMEOUT, int(timeout*1000))
603+
operr = syscall.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
604+
if ts.pa.tproxyMode == "tproxy" {
605+
operr = syscall.SetsockoptInt(int(fd), unix.SOL_IP, unix.IP_TRANSPARENT, 1)
606+
}
607+
}); err != nil {
608+
return err
609+
}
610+
return operr
611+
},
612+
}
613+
614+
ln, err := lc.Listen(context.Background(), "tcp4", ts.pa.tproxyAddr)
590615
if err != nil {
591-
ts.pa.logger.Fatal().Err(err).Msg("")
616+
var msg string
617+
if errors.Is(err, unix.EPERM) {
618+
msg = "try `sudo setcap 'cap_net_admin+ep` for the binary:"
619+
}
620+
ts.pa.logger.Fatal().Err(err).Msg(msg)
592621
}
593622
ts.listener = ln
594623
return ts
@@ -613,6 +642,10 @@ func (ts *tproxyServer) serve() {
613642
}
614643
} else {
615644
ts.wg.Add(1)
645+
err := conn.SetDeadline(time.Now().Add(timeout))
646+
if err != nil {
647+
ts.pa.logger.Error().Err(err).Msg("")
648+
}
616649
go func() {
617650
ts.handleConnection(conn)
618651
ts.wg.Done()
@@ -644,24 +677,34 @@ func (ts *tproxyServer) getOriginalDst(rawConn syscall.RawConn) (string, error)
644677
}
645678
dstHost := netip.AddrFrom4(originalDst.Addr)
646679
dstPort := uint16(originalDst.Port<<8) | originalDst.Port>>8
647-
ts.pa.logger.Debug().Msgf("[tproxy] getsockopt SO_ORIGINAL_DST %s:%d", dstHost, dstPort)
648680
return fmt.Sprintf("%s:%d", dstHost, dstPort), nil
649681
}
650682

651683
func (ts *tproxyServer) handleConnection(srcConn net.Conn) {
652-
var dstConn net.Conn
684+
var (
685+
dstConn net.Conn
686+
dst string
687+
err error
688+
)
653689
defer srcConn.Close()
654-
655-
rawConn, err := srcConn.(*net.TCPConn).SyscallConn()
656-
if err != nil {
657-
ts.pa.logger.Error().Err(err).Msg("[tproxy] Failed to get raw connection")
658-
return
659-
}
660-
661-
dst, err := ts.getOriginalDst(rawConn)
662-
if err != nil {
663-
ts.pa.logger.Error().Err(err).Msg("[tproxy] Failed to get destination address")
664-
return
690+
switch ts.pa.tproxyMode {
691+
case "redirect":
692+
rawConn, err := srcConn.(*net.TCPConn).SyscallConn()
693+
if err != nil {
694+
ts.pa.logger.Error().Err(err).Msg("[tproxy] Failed to get raw connection")
695+
return
696+
}
697+
dst, err = ts.getOriginalDst(rawConn)
698+
if err != nil {
699+
ts.pa.logger.Error().Err(err).Msg("[tproxy] Failed to get destination address")
700+
return
701+
}
702+
ts.pa.logger.Debug().Msgf("[tproxy] getsockopt SO_ORIGINAL_DST %s", dst)
703+
case "tproxy":
704+
dst = srcConn.LocalAddr().String()
705+
ts.pa.logger.Debug().Msgf("[tproxy] IP_TRANSPARENT %s", dst)
706+
default:
707+
ts.pa.logger.Fatal().Msg("Unknown tproxyMode")
665708
}
666709
if isLocalAddress(dst) {
667710
dstConn, err = net.DialTimeout("tcp", dst, timeout)
@@ -708,7 +751,7 @@ func (ts *tproxyServer) Shutdown() {
708751
case <-done:
709752
ts.pa.logger.Info().Msg("[tproxy] Server gracefully shutdown")
710753
return
711-
case <-time.After(shutdownTimeout):
754+
case <-time.After(timeout):
712755
ts.pa.logger.Error().Msg("[tproxy] Server timed out waiting for connections to finish")
713756
return
714757
}
@@ -737,7 +780,7 @@ func (p *proxyapp) Run() {
737780
tproxyServer.Shutdown()
738781
}
739782
p.logger.Info().Msg("Server is shutting down...")
740-
ctx, cancel := context.WithTimeout(context.Background(), shutdownTimeout)
783+
ctx, cancel := context.WithTimeout(context.Background(), timeout)
741784

742785
defer cancel()
743786
p.httpServer.SetKeepAlivesEnabled(false)
@@ -767,7 +810,7 @@ func (p *proxyapp) Run() {
767810
} else {
768811
go func() {
769812
<-quit
770-
p.logger.Info().Msg("[tproxy] server is shutting down...")
813+
p.logger.Info().Msg("[tproxy] Server is shutting down...")
771814
tproxyServer.Shutdown()
772815
close(done)
773816
}()
@@ -790,6 +833,7 @@ type Config struct {
790833
ServerConfPath string
791834
TProxy string
792835
TProxyOnly string
836+
TProxyMode string
793837
}
794838
type logWriter struct {
795839
}
@@ -883,11 +927,15 @@ func New(conf *Config) *proxyapp {
883927
p.logger = &logger
884928
if runtime.GOOS == "linux" && conf.TProxy != "" && conf.TProxyOnly != "" {
885929
p.logger.Fatal().Msg("Cannot specify TPRoxy and TProxyOnly at the same time")
930+
} else if runtime.GOOS == "linux" && conf.TProxyMode != "" && !slices.Contains(SupportedTProxyModes, conf.TProxyMode) {
931+
p.logger.Fatal().Msg("Incorrect TProxyMode provided")
886932
} else if runtime.GOOS != "linux" {
887933
conf.TProxy = ""
888934
conf.TProxyOnly = ""
935+
conf.TProxyMode = ""
889936
p.logger.Warn().Msg("[tproxy] functionality only available on linux system")
890937
}
938+
p.tproxyMode = conf.TProxyMode
891939
tproxyonly := conf.TProxyOnly != ""
892940
if tproxyonly {
893941
p.tproxyAddr = getFullAddress(conf.TProxyOnly)

0 commit comments

Comments
 (0)