Skip to content

Transparent proxy support #2

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Jun 12, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 166 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ Specify http server in proxy configuration of Postman
- **Proxy Chain functionality**
Supports `strict`, `dynamic`, `random`, `round_robin` chains of SOCKS5 proxy

- **Transparent proxy**
Supports `redirect` (SO_ORIGINAL_DST) and `tproxy` (IP_TRANSPARENT) modes

- **DNS Leak Protection**
DNS resolution occurs on SOCKS5 server side.

Expand Down Expand Up @@ -65,7 +68,7 @@ You can download the binary for your platform from [Releases](https://github.com
Example:

```shell
HPTS_RELEASE=v1.5.0; 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.6.0; 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):
Expand Down Expand Up @@ -102,27 +105,31 @@ GitHub: https://github.com/shadowy-pycoder/go-http-proxy-to-socks
Usage: gohpts [OPTIONS]
Options:
-h Show this help message and exit.
-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)
User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)
-c string
Path to certificate PEM encoded file
-d Show logs in DEBUG mode
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
Path to server configuration file in YAML format
-j Show logs in JSON format
-k string
Path to private key PEM encoded file
Path to private key PEM encoded file
-l string
Address of HTTP proxy server (default "127.0.0.1:8080")
Address of HTTP proxy server (default "127.0.0.1:8080")
-s string
Address of SOCKS5 proxy server (default "127.0.0.1:1080")
Address of SOCKS5 proxy server (default "127.0.0.1:1080")
-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
User for SOCKS5 proxy authentication. This flag invokes prompt for password (not echoed to terminal)
-v print version
```

## Example

### Configuration via CLI flags

```shell
Expand Down Expand Up @@ -217,6 +224,152 @@ server:

To learn more about proxy chains visit [Proxychains Github](https://github.com/rofl0r/proxychains-ng)

## Transparent proxy

> Also known as an `intercepting proxy`, `inline proxy`, or `forced proxy`, a transparent proxy intercepts normal application layer communication without requiring any special client configuration. Clients need not be aware of the existence of the proxy. A transparent proxy is normally located between the client and the Internet, with the proxy performing some of the functions of a gateway or router
>
> -- _From [Wiki](https://en.wikipedia.org/wiki/Proxy_server)_

This functionality available only on Linux systems and requires additional setup (`iptables`, ip route, etc)

`-T address` flag specifies the address of transparent proxy server (`GoHPTS` will be running without HTTP server).

`-t address` flag specifies the address of transparent proxy server (`HTTP` proxy and other functionality stays the same).

In other words, `-T` spins up a single server, but `-t` two servers, `http` and `tcp`.

There are two modes `redirect` and `tproxy` that can be specified with `-M` flag

## `redirect` (via _NAT_ and _SO_ORIGINAL_DST_)

In this mode proxying happens with `iptables` `nat` table and `REDIRECT` target. Host of incoming packet changes to the address of running `redirect` transparent proxy, but it also contains original destination that can be retrieved with `getsockopt(SO_ORIGINAL_DST)`

To run `GoHPTS` in this mode you use `-t` or `-T` flags with `-M redirect`

### Example

```shell
# run the proxy
gohpts -s 1080 -t 1090 -M redirect -d
```

```shell
# run socks5 server on 127.0.0.1:1080
ssh remote -D 1080 -Nf
```

Setup your operating system:

```shell
# commands below require elevated privileges (you can run it with `sudo -i`)

#enable ip forwarding
sysctl -w net.ipv4.ip_forward=1

# create `GOHPTS` nat chain
iptables -t nat -N GOHPTS

# set no redirection rules for local, http proxy, ssh and redirect procy itself
iptables -t nat -A GOHPTS -d 127.0.0.0/8 -j RETURN
iptables -t nat -A GOHPTS -p tcp --dport 8080 -j RETURN
iptables -t nat -A GOHPTS -p tcp --dport 1090 -j RETURN
iptables -t nat -A GOHPTS -p tcp --dport 22 -j RETURN

# redirect traffic to transparent proxy
iptables -t nat -A GOHPTS -p tcp -j REDIRECT --to-ports 1090

# setup prerouting by adding our proxy
iptables -t nat -A PREROUTING -p tcp -j GOHPTS

# intercept local traffic for testing
iptables -t nat -A OUTPUT -p tcp -j GOHPTS
```

Test connection:

```shell
#traffic should be redirected via 127.0.0.1:1090
curl http://example.com
```

```shell
#traffic should be redirected via 127.0.0.1:8080
curl --proxy http://127.0.0.1:8080 http://example.com
```

Undo everything:

```shell
sysctl -w net.ipv4.ip_forward=0
iptables -t nat -D PREROUTING -p tcp -j GOHPTS
iptables -t nat -D OUTPUT -p tcp -j GOHPTS
iptables -t nat -F GOHPTS
iptables -t nat -X GOHPTS
```

## `tproxy` (via _MANGLE_ and _IP_TRANSPARENT_)

In this mode proxying happens with `iptables` `mangle` table and `TPROXY` target. Transparent proxy sees destination address as is, it is not being rewrited by the kernel. For this to work the proxy binds with socket option `IP_TRANSPARENT`, `iptables` intercepts traffic using TPROXY target, routing rules tell marked packets to go to the local proxy without changing their original destination.

This mode requires elevated privileges to run `GoHPTS`. You can do that by running the follwing command:

```shell
sudo setcap 'cap_net_admin+ep' ~/go/bin/gohpts
```

To run `GoHPTS` in this mode you use `-t` or `-T` flags with `-M tproxy`

### Example

```shell
# run the proxy
gohpts -s 1080 -T 0.0.0.0:1090 -M tproxy -d
```

```shell
# run socks5 server on 127.0.0.1:1080
ssh remote -D 1080 -Nf
```

Setup your operating system:

```shell
ip netns exec ns-client ip route add default via 10.0.0.1
sysctl -w net.ipv4.ip_forward=1

iptables -t mangle -A PREROUTING -i veth1 -p tcp -j TPROXY --on-port 1090 --tproxy-mark 0x1/0x1

ip rule add fwmark 1 lookup 100
ip route add local 0.0.0.0/0 dev lo table 100
```

Test connection:

```shell
ip netns exec ns-client curl http://1.1.1.1
```

Undo everything:

```shell
sysctl -w net.ipv4.ip_forward=0
iptables -t mangle -F
ip rule del fwmark 1 lookup 100
ip route flush table 100
ip netns del ns-client
ip link del veth1
```

## Links

Learn more about transparent proxies by visiting the following links:

- [Transparent proxy support in Linux Kernel](https://docs.kernel.org/networking/tproxy.html)
- [Transparent proxy tutorial by Gost](https://latest.gost.run/en/tutorials/redirect/)
- [Simple tproxy example](https://github.com/FarFetchd/simple_tproxy_example)
- [Golang TProxy](https://github.com/KatelynHaworth/go-tproxy)
- [Transparent Proxy Implementation using eBPF and Go](https://medium.com/all-things-ebpf/building-a-transparent-proxy-with-ebpf-50a012237e76)

## License

MIT
42 changes: 42 additions & 0 deletions cmd/gohpts/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"flag"
"fmt"
"os"
"runtime"
"slices"

gohpts "github.com/shadowy-pycoder/go-http-proxy-to-socks"
"golang.org/x/term"
Expand All @@ -13,6 +15,7 @@ const (
app string = "gohpts"
addrSOCKS = "127.0.0.1:1080"
addrHTTP = "127.0.0.1:8080"
tproxyOS = "linux"
)
const usagePrefix string = `
_____ _ _ _____ _______ _____
Expand Down Expand Up @@ -40,6 +43,18 @@ func root(args []string) error {
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")
if runtime.GOOS == tproxyOS {
flags.StringVar(&conf.TProxy, "t", "", "Address of transparent proxy server (it starts along with HTTP proxy server)")
flags.StringVar(&conf.TProxyOnly, "T", "", "Address of transparent proxy server (no HTTP)")
flags.Func("M", fmt.Sprintf("Transparent proxy mode: %s", gohpts.SupportedTProxyModes), func(flagValue string) error {
if !slices.Contains(gohpts.SupportedTProxyModes, flagValue) {
fmt.Fprintf(os.Stderr, "%s: %s is not supported (type '%s -h' for help)\n", app, flagValue, app)
os.Exit(2)
}
conf.TProxyMode = flagValue
return nil
})
}
flags.BoolFunc("d", "Show logs in DEBUG mode", func(flagValue string) error {
conf.Debug = true
return nil
Expand All @@ -64,9 +79,35 @@ func root(args []string) error {
}
seen := make(map[string]bool)
flags.Visit(func(f *flag.Flag) { seen[f.Name] = true })
if seen["t"] && seen["T"] {
return fmt.Errorf("cannot specify both -t and -T flags")
}
if seen["t"] {
if !seen["M"] {
return fmt.Errorf("Transparent proxy mode is not provided: -M flag")
}
}
if seen["T"] {
for _, da := range []string{"U", "c", "k", "l"} {
if seen[da] {
return fmt.Errorf("-T flag only works with -s, -u, -f, -M, -d and -j flags")
}
}
if !seen["M"] {
return fmt.Errorf("Transparent proxy mode is not provided: -M flag")
}
}
if seen["M"] {
if !seen["t"] && !seen["T"] {
return fmt.Errorf("Transparent proxy mode requires -t or -T flag")
}
}
if seen["f"] {
for _, da := range []string{"s", "u", "U", "c", "k", "l"} {
if seen[da] {
if runtime.GOOS == tproxyOS {
return fmt.Errorf("-f flag only works with -t, -T, -M, -d and -j flags")
}
return fmt.Errorf("-f flag only works with -d and -j flags")
}
}
Expand All @@ -89,6 +130,7 @@ func root(args []string) error {
conf.ServerPass = string(bytepw)
fmt.Print("\033[2K\r")
}

p := gohpts.New(&conf)
p.Run()
return nil
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ require (
github.com/goccy/go-yaml v1.18.0
github.com/rs/zerolog v1.34.0
golang.org/x/net v0.40.0
golang.org/x/sys v0.33.0
golang.org/x/term v0.32.0
)

require (
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
golang.org/x/sys v0.33.0 // indirect
)
Loading