From d4f521e49442f46c14e8ddf6641917b92f2e4861 Mon Sep 17 00:00:00 2001 From: shadowy-pycoder <35629483+shadowy-pycoder@users.noreply.github.com> Date: Tue, 1 Jul 2025 09:13:20 +0300 Subject: [PATCH] added auto configuration for tproxy mode, categorized cli flags, small logging improvements --- README.md | 109 +++++++++------ cmd/gohpts/cli.go | 47 +++++-- gohpts.go | 342 ++++++++++++++++++++++++++++++---------------- tproxy_linux.go | 10 +- version.go | 2 +- 5 files changed, 338 insertions(+), 172 deletions(-) diff --git a/README.md b/README.md index 0aceb3b..62146b0 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,7 @@ You can download the binary for your platform from [Releases](https://github.com Example: ```shell -HPTS_RELEASE=v1.8.2; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h +HPTS_RELEASE=v1.8.3; wget -v https://github.com/shadowy-pycoder/go-http-proxy-to-socks/releases/download/$HPTS_RELEASE/gohpts-$HPTS_RELEASE-linux-amd64.tar.gz -O gohpts && tar xvzf gohpts && mv -f gohpts-$HPTS_RELEASE-linux-amd64 gohpts && ./gohpts -h ``` Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later): @@ -135,45 +135,36 @@ GitHub: https://github.com/shadowy-pycoder/go-http-proxy-to-socks Usage: gohpts [OPTIONS] Options: - -h Show this help message and exit. - -D Run as a daemon (provide -logfile to see logs) - -M value - Transparent proxy mode: [redirect tproxy] - -T string - Address of transparent proxy server (no HTTP) - -U string - User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal) - -auto - Automatically setup iptables for transparent proxy (requires elevated privileges) - -body - Collect request and response body for HTTP sniffing - -c string - Path to certificate PEM encoded file - -d Show logs in DEBUG mode - -f string - Path to server configuration file in YAML format - -j Show logs in JSON format - -k string - Path to private key PEM encoded file - -l string - Address of HTTP proxy server (default "127.0.0.1:8080") - -logfile string - Log file path (Default: stdout) - -mark uint - Set the mark for each packet sent through transparent proxy - -nocolor - Disable colored output for logs (no effect if -j flag specified) - -s string - Address of SOCKS5 proxy server (default "127.0.0.1:1080") - -sniff - Enable traffic sniffing for HTTP and TLS - -snifflog string - Sniffed traffic log file path (Default: the same as -logfile) - -t string - Address of transparent proxy server (it starts along with HTTP proxy server) - -u string - User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal) - -v print version + -h Show this help message and exit + -v Show version and build information + -D Run as a daemon (provide -logfile to see logs) + + Proxy: + -l Address of HTTP proxy server (default "127.0.0.1:8080") + -s Address of SOCKS5 proxy server (default "127.0.0.1:1080") + -c Path to certificate PEM encoded file + -k Path to private key PEM encoded file + -U User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal) + -u User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal) + -f Path to server configuration file in YAML format (overrides other proxy flags) + + Logs: + -d Show logs in DEBUG mode + -j Show logs in JSON format + -logfile Log file path (Default: stdout) + -nocolor Disable colored output for logs (no effect if -j flag specified) + + Sniffing: + -sniff Enable traffic sniffing for HTTP and TLS + -snifflog Sniffed traffic log file path (Default: the same as -logfile) + -body Collect request and response body for HTTP traffic (credentials, tokens, etc) + + TProxy: + -t Address of transparent proxy server (it starts along with HTTP proxy server) + -T Address of transparent proxy server (no HTTP) + -M Transparent proxy mode: (redirect, tproxy) + -auto Automatically setup iptables for transparent proxy (requires elevated privileges) + -mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100) ``` ### Configuration via CLI flags @@ -450,6 +441,44 @@ ip netns del ns-client ip link del veth1 ``` +### Auto configuration for `tproxy` mode + +To configure your system automatically, run the following command (for example, on a separate VM): + +```shell +ssh remote -D 1080 -Nf +sudo env PATH=$PATH gohpts -d -T 8888 -M tproxy -auto -mark 100 +``` + +Run the following on your host: + +```shell +ip route show default > /tmp/default-route.txt + +ip route add 0.0.0.0/1 via 192.168.0.1 # change with ip of your VM +ip route add 128.0.0.0/1 via 192.168.0.1 +``` + +Test connection: + +```shell +curl http://example.com #check logs on your VM +``` + +Undo everything: + +```shell +ip route del 0.0.0.0/1 via 192.168.0.1 2>/dev/null || true +ip route del 128.0.0.0/1 via 192.168.0.1 2>/dev/null || true + +if [[ -f /tmp/default-route.txt ]]; then + eval $(awk '{print "ip route add "$0}' /tmp/default-route.txt) + rm -f /tmp/default-route.txt +else + echo "Something went wrong" +fi +``` + ## Traffic sniffing [[Back]](#table-of-contents) diff --git a/cmd/gohpts/cli.go b/cmd/gohpts/cli.go index 61b9a10..e969a84 100644 --- a/cmd/gohpts/cli.go +++ b/cmd/gohpts/cli.go @@ -29,7 +29,37 @@ GitHub: https://github.com/shadowy-pycoder/go-http-proxy-to-socks Usage: gohpts [OPTIONS] Options: - -h Show this help message and exit. + -h Show this help message and exit + -v Show version and build information + -D Run as a daemon (provide -logfile to see logs) + + Proxy: + -l Address of HTTP proxy server (default "127.0.0.1:8080") + -s Address of SOCKS5 proxy server (default "127.0.0.1:1080") + -c Path to certificate PEM encoded file + -k Path to private key PEM encoded file + -U User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal) + -u User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal) + -f Path to server configuration file in YAML format (overrides other proxy flags) + + Logs: + -d Show logs in DEBUG mode + -j Show logs in JSON format + -logfile Log file path (Default: stdout) + -nocolor Disable colored output for logs (no effect if -j flag specified) + + Sniffing: + -sniff Enable traffic sniffing for HTTP and TLS + -snifflog Sniffed traffic log file path (Default: the same as -logfile) + -body Collect request and response body for HTTP traffic (credentials, tokens, etc) +` +const usageTproxy string = ` + TProxy: + -t Address of transparent proxy server (it starts along with HTTP proxy server) + -T Address of transparent proxy server (no HTTP) + -M Transparent proxy mode: (redirect, tproxy) + -auto Automatically setup iptables for transparent proxy (requires elevated privileges) + -mark Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100) ` func root(args []string) error { @@ -41,7 +71,7 @@ func root(args []string) error { flags.StringVar(&conf.ServerUser, "U", "", "User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)") flags.StringVar(&conf.CertFile, "c", "", "Path to certificate PEM encoded file") flags.StringVar(&conf.KeyFile, "k", "", "Path to private key PEM encoded file") - flags.StringVar(&conf.ServerConfPath, "f", "", "Path to server configuration file in YAML format") + flags.StringVar(&conf.ServerConfPath, "f", "", "Path to server configuration file in YAML format (overrides other proxy flags)") daemon := flags.Bool("D", false, "Run as a daemon (provide -logfile to see logs)") if runtime.GOOS == tproxyOS { flags.StringVar(&conf.TProxy, "t", "", "Address of transparent proxy server (it starts along with HTTP proxy server)") @@ -55,7 +85,7 @@ func root(args []string) error { return nil }) flags.BoolVar(&conf.Auto, "auto", false, "Automatically setup iptables for transparent proxy (requires elevated privileges)") - flags.UintVar(&conf.Mark, "mark", 0, "Set the mark for each packet sent through transparent proxy") + flags.UintVar(&conf.Mark, "mark", 0, "Set mark for each packet sent through transparent proxy (Default: redirect 0, tproxy 100)") } flags.StringVar(&conf.LogFilePath, "logfile", "", "Log file path (Default: stdout)") flags.BoolVar(&conf.Debug, "d", false, "Show logs in DEBUG mode") @@ -63,8 +93,8 @@ func root(args []string) error { flags.BoolVar(&conf.Sniff, "sniff", false, "Enable traffic sniffing for HTTP and TLS") flags.StringVar(&conf.SniffLogFile, "snifflog", "", "Sniffed traffic log file path (Default: the same as -logfile)") flags.BoolVar(&conf.NoColor, "nocolor", false, "Disable colored output for logs (no effect if -j flag specified)") - flags.BoolVar(&conf.Body, "body", false, "Collect request and response body for HTTP sniffing") - flags.BoolFunc("v", "print version", func(flagValue string) error { + flags.BoolVar(&conf.Body, "body", false, "Collect request and response body for HTTP traffic (credentials, tokens, etc)") + flags.BoolFunc("v", "Show version and build information", func(flagValue string) error { fmt.Printf("%s (built for %s %s with %s)\n", gohpts.Version, runtime.GOOS, runtime.GOARCH, runtime.Version()) os.Exit(0) return nil @@ -72,7 +102,9 @@ func root(args []string) error { flags.Usage = func() { fmt.Print(usagePrefix) - flags.PrintDefaults() + if runtime.GOOS == tproxyOS { + fmt.Print(usageTproxy) + } } if err := flags.Parse(args); err != nil { @@ -107,9 +139,6 @@ func root(args []string) error { if !seen["t"] && !seen["T"] { return fmt.Errorf("-auto requires -t or -T flag") } - if conf.TProxyMode != "redirect" { - return fmt.Errorf("-auto is available only for -M redirect") - } } if seen["mark"] { if !seen["t"] && !seen["T"] { diff --git a/gohpts.go b/gohpts.go index 84fbef4..b94da93 100644 --- a/gohpts.go +++ b/gohpts.go @@ -930,7 +930,7 @@ func (p *proxyapp) handleTunnel(w http.ResponseWriter, r *http.Request) { defer srcConn.Close() dstConnStr := fmt.Sprintf("%s->%s->%s", dstConn.LocalAddr().String(), dstConn.RemoteAddr().String(), r.Host) - srcConnStr := fmt.Sprintf("%s->%s", srcConn.LocalAddr().String(), srcConn.RemoteAddr().String()) + srcConnStr := fmt.Sprintf("%s->%s", srcConn.RemoteAddr().String(), srcConn.LocalAddr().String()) p.logger.Debug().Msgf("%s - %s - %s", r.Proto, r.Method, r.Host) p.logger.Debug().Msgf("src: %s - dst: %s", srcConnStr, dstConnStr) @@ -945,8 +945,8 @@ func (p *proxyapp) handleTunnel(w http.ResponseWriter, r *http.Request) { sniffheader := make([]string, 0, 6) id := p.getId() if p.json { - sniffheader = append(sniffheader, fmt.Sprintf("{\"connection\":{\"src_local\":%s,\"src_remote\":%s,\"dst_local\":%s,\"dst_remote\":%s}}", - srcConn.LocalAddr(), srcConn.RemoteAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr())) + sniffheader = append(sniffheader, fmt.Sprintf("{\"connection\":{\"src_remote\":%s,\"src_local\":%s,\"dst_local\":%s,\"dst_remote\":%s}}", + srcConn.RemoteAddr(), srcConn.LocalAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr())) j, err := json.Marshal(&layers.HTTPMessage{Request: r}) if err == nil { sniffheader = append(sniffheader, string(j)) @@ -955,14 +955,14 @@ func (p *proxyapp) handleTunnel(w http.ResponseWriter, r *http.Request) { var sb strings.Builder if p.nocolor { sb.WriteString(id) - sb.WriteString(fmt.Sprintf(" Src: %s->%s -> Dst: %s->%s", srcConn.LocalAddr(), srcConn.RemoteAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr())) + sb.WriteString(fmt.Sprintf(" Src: %s->%s -> Dst: %s->%s", srcConn.RemoteAddr(), srcConn.LocalAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr())) sb.WriteString("\n") sb.WriteString(fmt.Sprintf("%s ", p.colorizeTimestamp())) sb.WriteString(id) sb.WriteString(fmt.Sprintf(" %s %s %s ", r.Method, r.Host, r.Proto)) } else { sb.WriteString(id) - sb.WriteString(colors.Green(fmt.Sprintf(" Src: %s->%s", srcConn.LocalAddr(), srcConn.RemoteAddr())).String()) + sb.WriteString(colors.Green(fmt.Sprintf(" Src: %s->%s", srcConn.RemoteAddr(), srcConn.LocalAddr())).String()) sb.WriteString(colors.Magenta(" -> ").String()) sb.WriteString(colors.Blue(fmt.Sprintf("Dst: %s->%s", dstConn.LocalAddr(), dstConn.RemoteAddr())).String()) sb.WriteString("\n") @@ -1156,103 +1156,173 @@ func (p *proxyapp) handler() http.HandlerFunc { } func (p *proxyapp) applyRedirectRules() string { - cmdClear := exec.Command("bash", "-c", ` - set -ex - iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true - iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true - iptables -t nat -F GOHPTS 2>/dev/null || true - iptables -t nat -X GOHPTS 2>/dev/null || true - `) - cmdClear.Stdout = os.Stdout - cmdClear.Stderr = os.Stderr - if err := cmdClear.Run(); err != nil { - p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") - } - cmdInit := exec.Command("bash", "-c", ` - set -ex - iptables -t nat -N GOHPTS 2>/dev/null - iptables -t nat -F GOHPTS - - iptables -t nat -A GOHPTS -d 127.0.0.0/8 -j RETURN - iptables -t nat -A GOHPTS -p tcp --dport 22 -j RETURN - `) - cmdInit.Stdout = os.Stdout - cmdInit.Stderr = os.Stderr - if err := cmdInit.Run(); err != nil { - p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") - } - if p.httpServerAddr != "" { - _, httpPort, _ := net.SplitHostPort(p.httpServerAddr) - cmdHttp := exec.Command("bash", "-c", fmt.Sprintf(` + _, tproxyPort, _ := net.SplitHostPort(p.tproxyAddr) + switch p.tproxyMode { + case "redirect": + cmdClear := exec.Command("bash", "-c", ` + set -ex + iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true + iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true + iptables -t nat -F GOHPTS 2>/dev/null || true + iptables -t nat -X GOHPTS 2>/dev/null || true + `) + cmdClear.Stdout = os.Stdout + cmdClear.Stderr = os.Stderr + if err := cmdClear.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + cmdInit := exec.Command("bash", "-c", ` + set -ex + iptables -t nat -N GOHPTS 2>/dev/null + iptables -t nat -F GOHPTS + + iptables -t nat -A GOHPTS -d 127.0.0.0/8 -j RETURN + iptables -t nat -A GOHPTS -p tcp --dport 22 -j RETURN + `) + cmdInit.Stdout = os.Stdout + cmdInit.Stderr = os.Stderr + if err := cmdInit.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + if p.httpServerAddr != "" { + _, httpPort, _ := net.SplitHostPort(p.httpServerAddr) + cmdHttp := exec.Command("bash", "-c", fmt.Sprintf(` set -ex iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN `, httpPort)) - cmdHttp.Stdout = os.Stdout - cmdHttp.Stderr = os.Stderr - if err := cmdHttp.Run(); err != nil { + cmdHttp.Stdout = os.Stdout + cmdHttp.Stderr = os.Stderr + if err := cmdHttp.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + } + if p.mark > 0 { + cmdMark := exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t nat -A GOHPTS -p tcp -m mark --mark %d -j RETURN + `, p.mark)) + cmdMark.Stdout = os.Stdout + cmdMark.Stderr = os.Stderr + if err := cmdMark.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + } else { + cmd0 := exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN + `, tproxyPort)) + cmd0.Stdout = os.Stdout + cmd0.Stderr = os.Stderr + if err := cmd0.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + if len(p.proxylist) > 0 { + for _, pr := range p.proxylist { + _, port, _ := net.SplitHostPort(pr.Address) + cmd1 := exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN + `, port)) + cmd1.Stdout = os.Stdout + cmd1.Stderr = os.Stderr + if err := cmd1.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + if p.proxychain.Type == "strict" { + break + } + } + } + } + cmdDocker := exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + if command -v docker >/dev/null 2>&1 + then + for subnet in $(docker network inspect $(docker network ls -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'); do + iptables -t nat -A GOHPTS -d "$subnet" -j RETURN + done + fi + + iptables -t nat -A GOHPTS -p tcp -j REDIRECT --to-ports %s + + iptables -t nat -C PREROUTING -p tcp -j GOHPTS 2>/dev/null || \ + iptables -t nat -A PREROUTING -p tcp -j GOHPTS + + iptables -t nat -C OUTPUT -p tcp -j GOHPTS 2>/dev/null || \ + iptables -t nat -A OUTPUT -p tcp -j GOHPTS + `, tproxyPort)) + cmdDocker.Stdout = os.Stdout + cmdDocker.Stderr = os.Stderr + if err := cmdDocker.Run(); err != nil { p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") } - } - _, tproxyPort, _ := net.SplitHostPort(p.tproxyAddr) - if p.mark > 0 { - cmdMark := exec.Command("bash", "-c", fmt.Sprintf(` + case "tproxy": + cmdClear := exec.Command("bash", "-c", ` set -ex - iptables -t nat -A GOHPTS -p tcp -m mark --mark %d -j RETURN - `, p.mark)) - cmdMark.Stdout = os.Stdout - cmdMark.Stderr = os.Stderr - if err := cmdMark.Run(); err != nil { + iptables -t mangle -D PREROUTING -p tcp -m socket -j DIVERT 2>/dev/null || true + iptables -t mangle -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true + iptables -t mangle -F DIVERT 2>/dev/null || true + iptables -t mangle -F GOHPTS 2>/dev/null || true + iptables -t mangle -X DIVERT 2>/dev/null || true + iptables -t mangle -X GOHPTS 2>/dev/null || true + + ip rule del fwmark 1 lookup 100 2>/dev/null || true + ip route flush table 100 || true + `) + cmdClear.Stdout = os.Stdout + cmdClear.Stderr = os.Stderr + if err := cmdClear.Run(); err != nil { p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") } - } else { - cmd0 := exec.Command("bash", "-c", fmt.Sprintf(` + cmdInit0 := exec.Command("bash", "-c", ` set -ex - iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN - `, tproxyPort)) - cmd0.Stdout = os.Stdout - cmd0.Stderr = os.Stderr - if err := cmd0.Run(); err != nil { + ip rule add fwmark 1 lookup 100 2>/dev/null || true + ip route add local 0.0.0.0/0 dev lo table 100 2>/dev/null || true + + iptables -t mangle -N DIVERT 2>/dev/null || true + iptables -t mangle -F DIVERT + iptables -t mangle -A DIVERT -j MARK --set-mark 1 + iptables -t mangle -A DIVERT -j ACCEPT + + iptables -t mangle -N GOHPTS 2>/dev/null || true + iptables -t mangle -F GOHPTS + iptables -t mangle -A GOHPTS -d 127.0.0.0/8 -j RETURN + iptables -t mangle -A GOHPTS -d 224.0.0.0/4 -j RETURN + iptables -t mangle -A GOHPTS -d 255.255.255.255/32 -j RETURN + `) + cmdInit0.Stdout = os.Stdout + cmdInit0.Stderr = os.Stderr + if err := cmdInit0.Run(); err != nil { p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") } - if len(p.proxylist) > 0 { - for _, pr := range p.proxylist { - _, port, _ := net.SplitHostPort(pr.Address) - cmd1 := exec.Command("bash", "-c", fmt.Sprintf(` - set -ex - iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN - `, port)) - cmd1.Stdout = os.Stdout - cmd1.Stderr = os.Stderr - if err := cmd1.Run(); err != nil { - p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") - } - if p.proxychain.Type == "strict" { - break - } - } + cmdDocker := exec.Command("bash", "-c", ` + set -ex + if command -v docker >/dev/null 2>&1 + then + for subnet in $(docker network inspect $(docker network ls -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'); do + iptables -t mangle -A GOHPTS -d "$subnet" -j RETURN + done + fi`) + cmdDocker.Stdout = os.Stdout + cmdDocker.Stderr = os.Stderr + if err := cmdDocker.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") } - } - cmdDocker := exec.Command("bash", "-c", fmt.Sprintf(` - set -ex - if command -v docker >/dev/null 2>&1 - then - for subnet in $(docker network inspect $(docker network ls -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'); do - iptables -t nat -A GOHPTS -d "$subnet" -j RETURN - done - fi - - iptables -t nat -A GOHPTS -p tcp -j REDIRECT --to-ports %s - - iptables -t nat -C PREROUTING -p tcp -j GOHPTS 2>/dev/null || \ - iptables -t nat -A PREROUTING -p tcp -j GOHPTS - - iptables -t nat -C OUTPUT -p tcp -j GOHPTS 2>/dev/null || \ - iptables -t nat -A OUTPUT -p tcp -j GOHPTS - `, tproxyPort)) - cmdDocker.Stdout = os.Stdout - cmdDocker.Stderr = os.Stderr - if err := cmdDocker.Run(); err != nil { - p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + cmdInit := exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t mangle -A GOHPTS -p tcp -m mark --mark %d -j RETURN + iptables -t mangle -A GOHPTS -p tcp -j TPROXY --on-port %s --tproxy-mark 1 + + iptables -t mangle -A PREROUTING -p tcp -m socket -j DIVERT + iptables -t mangle -A PREROUTING -p tcp -j GOHPTS + `, p.mark, tproxyPort)) + cmdInit.Stdout = os.Stdout + cmdInit.Stderr = os.Stderr + if err := cmdInit.Run(); err != nil { + p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?") + } + default: + p.logger.Fatal().Msgf("Unreachable, unknown mode: %s", p.tproxyMode) } cmdCat := exec.Command("bash", "-c", ` cat /proc/sys/net/ipv4/ip_forward @@ -1272,16 +1342,36 @@ func (p *proxyapp) applyRedirectRules() string { } func (p *proxyapp) clearRedirectRules(output string) error { - cmd := exec.Command("bash", "-c", fmt.Sprintf(` - set -ex - iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true - iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true - iptables -t nat -F GOHPTS 2>/dev/null || true - iptables -t nat -X GOHPTS 2>/dev/null || true - sysctl -w net.ipv4.ip_forward=%s - `, output)) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr + var cmd *exec.Cmd + switch p.tproxyMode { + case "redirect": + cmd = exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true + iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true + iptables -t nat -F GOHPTS 2>/dev/null || true + iptables -t nat -X GOHPTS 2>/dev/null || true + sysctl -w net.ipv4.ip_forward=%s + `, output)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + case "tproxy": + cmd = exec.Command("bash", "-c", fmt.Sprintf(` + set -ex + iptables -t mangle -D PREROUTING -p tcp -m socket -j DIVERT 2>/dev/null || true + iptables -t mangle -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true + iptables -t mangle -F DIVERT 2>/dev/null || true + iptables -t mangle -F GOHPTS 2>/dev/null || true + iptables -t mangle -X DIVERT 2>/dev/null || true + iptables -t mangle -X GOHPTS 2>/dev/null || true + + ip rule del fwmark 1 lookup 100 2>/dev/null || true + ip route flush table 100 || true + sysctl -w net.ipv4.ip_forward=%s + `, output)) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } return cmd.Run() } @@ -1420,12 +1510,16 @@ type serverConfig struct { Server server `yaml:"server"` } -func getFullAddress(v string) (string, error) { +func getFullAddress(v string, all bool) (string, error) { if v == "" { return "", nil } - if i, err := strconv.Atoi(v); err == nil { - return fmt.Sprintf("127.0.0.1:%d", i), nil + var ip string = "127.0.0.1" + if all { + ip = "0.0.0.0" + } + if port, err := strconv.Atoi(v); err == nil { + return fmt.Sprintf("%s:%d", ip, port), nil } host, port, err := net.SplitHostPort(v) if err != nil { @@ -1437,7 +1531,7 @@ func getFullAddress(v string) (string, error) { if host != "" && port != "" { return v, nil } else if port != "" { - return fmt.Sprintf("127.0.0.1:%s", port), nil + return fmt.Sprintf("%s:%s", ip, port), nil } return "", fmt.Errorf("failed parsing address") } @@ -1586,23 +1680,34 @@ func New(conf *Config) *proxyapp { p.tproxyMode = conf.TProxyMode tproxyonly := conf.TProxyOnly != "" if tproxyonly { - p.tproxyAddr, err = getFullAddress(conf.TProxyOnly) - if err != nil { - p.logger.Fatal().Err(err).Msg("") + if p.tproxyMode == "tproxy" { + p.tproxyAddr, err = getFullAddress(conf.TProxyOnly, true) + if err != nil { + p.logger.Fatal().Err(err).Msg("") + } + } else { + p.tproxyAddr, err = getFullAddress(conf.TProxyOnly, false) + if err != nil { + p.logger.Fatal().Err(err).Msg("") + } } } else { - p.tproxyAddr, err = getFullAddress(conf.TProxy) - if err != nil { - p.logger.Fatal().Err(err).Msg("") + if p.tproxyMode == "tproxy" { + p.tproxyAddr, err = getFullAddress(conf.TProxy, true) + if err != nil { + p.logger.Fatal().Err(err).Msg("") + } + } else { + p.tproxyAddr, err = getFullAddress(conf.TProxy, false) + if err != nil { + p.logger.Fatal().Err(err).Msg("") + } } } p.auto = conf.Auto if p.auto && runtime.GOOS != "linux" { p.logger.Fatal().Msg("Auto setup is available only for linux system") } - if p.auto && p.tproxyMode != "redirect" { - p.logger.Fatal().Msg("Auto setup is available only for redirect mode") - } p.mark = conf.Mark if p.mark > 0 && runtime.GOOS != "linux" { p.logger.Fatal().Msg("SO_MARK is available only for linux system") @@ -1610,6 +1715,9 @@ func New(conf *Config) *proxyapp { if p.mark > 0xFFFFFFFF { p.logger.Fatal().Msg("SO_MARK is out of range") } + if p.mark == 0 && p.tproxyMode == "tproxy" { + p.mark = 100 + } var addrHTTP, addrSOCKS, certFile, keyFile string if conf.ServerConfPath != "" { var sconf serverConfig @@ -1625,7 +1733,7 @@ func New(conf *Config) *proxyapp { if sconf.Server.Address == "" { p.logger.Fatal().Err(err).Msg("[server config] Server address is empty") } - addrHTTP, err = getFullAddress(sconf.Server.Address) + addrHTTP, err = getFullAddress(sconf.Server.Address, false) if err != nil { p.logger.Fatal().Err(err).Msg("") } @@ -1643,7 +1751,7 @@ func New(conf *Config) *proxyapp { } seen := make(map[string]struct{}) for idx, pr := range p.proxylist { - addr, err := getFullAddress(pr.Address) + addr, err := getFullAddress(pr.Address, false) if err != nil { p.logger.Fatal().Err(err).Msg("") } @@ -1662,7 +1770,7 @@ func New(conf *Config) *proxyapp { p.rrIndexReset = rrIndexMax } else { if !tproxyonly { - addrHTTP, err = getFullAddress(conf.AddrHTTP) + addrHTTP, err = getFullAddress(conf.AddrHTTP, false) if err != nil { p.logger.Fatal().Err(err).Msg("") } @@ -1672,7 +1780,7 @@ func New(conf *Config) *proxyapp { p.user = conf.ServerUser p.pass = conf.ServerPass } - addrSOCKS, err = getFullAddress(conf.AddrSOCKS) + addrSOCKS, err = getFullAddress(conf.AddrSOCKS, false) if err != nil { p.logger.Fatal().Err(err).Msg("") } diff --git a/tproxy_linux.go b/tproxy_linux.go index a7c95f3..d06a436 100644 --- a/tproxy_linux.go +++ b/tproxy_linux.go @@ -168,7 +168,7 @@ func (ts *tproxyServer) handleConnection(srcConn net.Conn) { defer dstConn.Close() dstConnStr := fmt.Sprintf("%s->%s->%s", dstConn.LocalAddr().String(), dstConn.RemoteAddr().String(), dst) - srcConnStr := fmt.Sprintf("%s->%s", srcConn.LocalAddr().String(), srcConn.RemoteAddr().String()) + srcConnStr := fmt.Sprintf("%s->%s", srcConn.RemoteAddr().String(), srcConn.LocalAddr().String()) ts.pa.logger.Debug().Msgf("[tproxy] src: %s - dst: %s", srcConnStr, dstConnStr) @@ -183,16 +183,16 @@ func (ts *tproxyServer) handleConnection(srcConn net.Conn) { sniffheader := make([]string, 0, 6) id := ts.pa.getId() if ts.pa.json { - sniffheader = append(sniffheader, fmt.Sprintf("{\"connection\":{\"tproxy_mode\":%s,\"src_local\":%s,\"src_remote\":%s,\"dst_local\":%s,\"dst_remote\":%s,\"original_dst\":%s}}", - ts.pa.tproxyMode, srcConn.LocalAddr(), srcConn.RemoteAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr(), dst)) + sniffheader = append(sniffheader, fmt.Sprintf("{\"connection\":{\"tproxy_mode\":%s,\"src_remote\":%s,\"src_local\":%s,\"dst_local\":%s,\"dst_remote\":%s,\"original_dst\":%s}}", + ts.pa.tproxyMode, srcConn.RemoteAddr(), srcConn.LocalAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr(), dst)) } else { var sb strings.Builder if ts.pa.nocolor { sb.WriteString(id) - sb.WriteString(fmt.Sprintf(" Src: %s->%s -> Dst: %s->%s Orig: %s", srcConn.LocalAddr(), srcConn.RemoteAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr(), dst)) + sb.WriteString(fmt.Sprintf(" Src: %s->%s -> Dst: %s->%s Orig: %s", srcConn.RemoteAddr(), srcConn.LocalAddr(), dstConn.LocalAddr(), dstConn.RemoteAddr(), dst)) } else { sb.WriteString(id) - sb.WriteString(colors.Green(fmt.Sprintf(" Src: %s->%s", srcConn.LocalAddr(), srcConn.RemoteAddr())).String()) + sb.WriteString(colors.Green(fmt.Sprintf(" Src: %s->%s", srcConn.RemoteAddr(), srcConn.LocalAddr())).String()) sb.WriteString(colors.Magenta(" -> ").String()) sb.WriteString(colors.Blue(fmt.Sprintf("Dst: %s->%s ", dstConn.LocalAddr(), dstConn.RemoteAddr())).String()) sb.WriteString(colors.BeigeBg(fmt.Sprintf("Orig Dst: %s", dst)).String()) diff --git a/version.go b/version.go index d1f5869..ed72300 100644 --- a/version.go +++ b/version.go @@ -1,3 +1,3 @@ package gohpts -const Version string = "gohpts v1.8.2" +const Version string = "gohpts v1.8.3"