Skip to content

Commit da31d1b

Browse files
Merge pull request #8 from shadowy-pycoder/auto
Auto configuration for redirect proxy
2 parents 08722c4 + 9c1feb6 commit da31d1b

File tree

5 files changed

+183
-21
lines changed

5 files changed

+183
-21
lines changed

README.md

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ You can download the binary for your platform from [Releases](https://github.com
9797
Example:
9898

9999
```shell
100-
HPTS_RELEASE=v1.8.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
100+
HPTS_RELEASE=v1.8.1; 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
101101
```
102102

103103
Alternatively, you can install it using `go install` command (requires Go [1.24](https://go.dev/doc/install) or later):
@@ -143,6 +143,8 @@ Options:
143143
Address of transparent proxy server (no HTTP)
144144
-U string
145145
User for HTTP proxy (basic auth). This flag invokes prompt for password (not echoed to terminal)
146+
-auto
147+
Automatically setup iptables for transparent proxy (requires elevated privileges)
146148
-body
147149
Collect request and response body for HTTP sniffing
148150
-c string
@@ -375,6 +377,16 @@ iptables -t nat -F GOHPTS
375377
iptables -t nat -X GOHPTS
376378
```
377379
380+
### Auto configuration for `redirect` mode
381+
382+
To configure your system automatically, run the following command:
383+
384+
```shell
385+
sudo env PATH=$PATH gohpts -d -T 8888 -M redirect -auto
386+
```
387+
388+
Please note, automatic configuration requires `sudo` and is very generic, which might not be suitable for your needs.
389+
378390
## `tproxy` (via _MANGLE_ and _IP_TRANSPARENT_)
379391
380392
[[Back]](#table-of-contents)

cmd/gohpts/cli.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ func root(args []string) error {
5454
conf.TProxyMode = flagValue
5555
return nil
5656
})
57+
flags.BoolVar(&conf.Auto, "auto", false, "Automatically setup iptables for transparent proxy (requires elevated privileges)")
5758
}
5859
flags.StringVar(&conf.LogFilePath, "logfile", "", "Log file path (Default: stdout)")
5960
flags.BoolVar(&conf.Debug, "d", false, "Show logs in DEBUG mode")
@@ -101,6 +102,14 @@ func root(args []string) error {
101102
return fmt.Errorf("transparent proxy mode requires -t or -T flag")
102103
}
103104
}
105+
if seen["auto"] {
106+
if !seen["t"] && !seen["T"] {
107+
return fmt.Errorf("-auto requires -t or -T flag")
108+
}
109+
if conf.TProxyMode != "redirect" {
110+
return fmt.Errorf("-auto is available only for -M redirect")
111+
}
112+
}
104113
if seen["f"] {
105114
for _, da := range []string{"s", "u", "U", "c", "k", "l"} {
106115
if seen[da] {

gohpts.go

Lines changed: 159 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"net"
1919
"net/http"
2020
"os"
21+
"os/exec"
2122
"os/signal"
2223
"regexp"
2324
"runtime"
@@ -86,6 +87,7 @@ type Config struct {
8687
TProxy string
8788
TProxyOnly string
8889
TProxyMode string
90+
Auto bool
8991
LogFilePath string
9092
Debug bool
9193
Json bool
@@ -107,6 +109,7 @@ type proxyapp struct {
107109
httpServerAddr string
108110
tproxyAddr string
109111
tproxyMode string
112+
auto bool
110113
user string
111114
pass string
112115
proxychain chain
@@ -1143,6 +1146,99 @@ func (p *proxyapp) handler() http.HandlerFunc {
11431146
}
11441147
}
11451148

1149+
func (p *proxyapp) applyRedirectRules() string {
1150+
cmd0 := exec.Command("bash", "-c", `
1151+
set -ex
1152+
iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true
1153+
iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true
1154+
iptables -t nat -F GOHPTS 2>/dev/null || true
1155+
iptables -t nat -X GOHPTS 2>/dev/null || true
1156+
`)
1157+
cmd0.Stdout = os.Stdout
1158+
cmd0.Stderr = os.Stderr
1159+
if err := cmd0.Run(); err != nil {
1160+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1161+
}
1162+
_, tproxyPort, _ := net.SplitHostPort(p.tproxyAddr)
1163+
cmd1 := exec.Command("bash", "-c", fmt.Sprintf(`
1164+
set -ex
1165+
iptables -t nat -N GOHPTS 2>/dev/null
1166+
iptables -t nat -F GOHPTS
1167+
1168+
iptables -t nat -A GOHPTS -d 127.0.0.0/8 -j RETURN
1169+
iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN
1170+
iptables -t nat -A GOHPTS -p tcp --dport 22 -j RETURN
1171+
`, tproxyPort))
1172+
cmd1.Stdout = os.Stdout
1173+
cmd1.Stderr = os.Stderr
1174+
if err := cmd1.Run(); err != nil {
1175+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1176+
}
1177+
if p.httpServerAddr != "" {
1178+
_, httpPort, _ := net.SplitHostPort(p.httpServerAddr)
1179+
cmd2 := exec.Command("bash", "-c", fmt.Sprintf(`
1180+
set -ex
1181+
iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN
1182+
`, httpPort))
1183+
cmd2.Stdout = os.Stdout
1184+
cmd2.Stderr = os.Stderr
1185+
if err := cmd2.Run(); err != nil {
1186+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1187+
}
1188+
}
1189+
cmd3 := exec.Command("bash", "-c", fmt.Sprintf(`
1190+
set -ex
1191+
if command -v docker >/dev/null 2>&1
1192+
then
1193+
for subnet in $(docker network inspect $(docker network ls -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'); do
1194+
iptables -t nat -A GOHPTS -d "$subnet" -j RETURN
1195+
done
1196+
fi
1197+
1198+
iptables -t nat -A GOHPTS -p tcp -j REDIRECT --to-ports %s
1199+
1200+
iptables -t nat -C PREROUTING -p tcp -j GOHPTS 2>/dev/null || \
1201+
iptables -t nat -A PREROUTING -p tcp -j GOHPTS
1202+
1203+
iptables -t nat -C OUTPUT -p tcp -j GOHPTS 2>/dev/null || \
1204+
iptables -t nat -A OUTPUT -p tcp -j GOHPTS
1205+
`, tproxyPort))
1206+
cmd3.Stdout = os.Stdout
1207+
cmd3.Stderr = os.Stderr
1208+
if err := cmd3.Run(); err != nil {
1209+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1210+
}
1211+
cmd4 := exec.Command("bash", "-c", `
1212+
cat /proc/sys/net/ipv4/ip_forward
1213+
`)
1214+
output, err := cmd4.CombinedOutput()
1215+
if err != nil {
1216+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1217+
}
1218+
cmd5 := exec.Command("bash", "-c", `
1219+
set -ex
1220+
sysctl -w net.ipv4.ip_forward=1
1221+
`)
1222+
cmd5.Stdout = os.Stdout
1223+
cmd5.Stderr = os.Stderr
1224+
_ = cmd5.Run()
1225+
return string(output)
1226+
}
1227+
1228+
func (p *proxyapp) clearRedirectRules(output string) error {
1229+
cmd := exec.Command("bash", "-c", fmt.Sprintf(`
1230+
set -ex
1231+
iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true
1232+
iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true
1233+
iptables -t nat -F GOHPTS 2>/dev/null || true
1234+
iptables -t nat -X GOHPTS 2>/dev/null || true
1235+
sysctl -w net.ipv4.ip_forward=%s
1236+
`, output))
1237+
cmd.Stdout = os.Stdout
1238+
cmd.Stderr = os.Stderr
1239+
return cmd.Run()
1240+
}
1241+
11461242
func (p *proxyapp) Run() {
11471243
done := make(chan bool)
11481244
quit := make(chan os.Signal, 1)
@@ -1151,6 +1247,10 @@ func (p *proxyapp) Run() {
11511247
if p.tproxyAddr != "" {
11521248
tproxyServer = newTproxyServer(p)
11531249
}
1250+
var output string
1251+
if p.auto {
1252+
output = p.applyRedirectRules()
1253+
}
11541254
if p.proxylist != nil {
11551255
chainType := p.proxychain.Type
11561256
var ctl string
@@ -1170,6 +1270,12 @@ func (p *proxyapp) Run() {
11701270
if p.httpServer != nil {
11711271
go func() {
11721272
<-quit
1273+
if p.auto {
1274+
err := p.clearRedirectRules(output)
1275+
if err != nil {
1276+
p.logger.Error().Err(err).Msg("Failed clearing iptables rules")
1277+
}
1278+
}
11731279
if tproxyServer != nil {
11741280
p.logger.Info().Msg("[tproxy] Server is shutting down...")
11751281
tproxyServer.Shutdown()
@@ -1205,6 +1311,12 @@ func (p *proxyapp) Run() {
12051311
} else {
12061312
go func() {
12071313
<-quit
1314+
if p.auto {
1315+
err := p.clearRedirectRules(output)
1316+
if err != nil {
1317+
p.logger.Error().Err(err).Msg("Failed clearing iptables rules")
1318+
}
1319+
}
12081320
p.logger.Info().Msg("[tproxy] Server is shutting down...")
12091321
tproxyServer.Shutdown()
12101322
close(done)
@@ -1259,20 +1371,26 @@ type serverConfig struct {
12591371
Server server `yaml:"server"`
12601372
}
12611373

1262-
func getFullAddress(v string) string {
1374+
func getFullAddress(v string) (string, error) {
12631375
if v == "" {
1264-
return ""
1265-
}
1266-
var addr string
1267-
i, err := strconv.Atoi(v)
1268-
if err == nil {
1269-
addr = fmt.Sprintf("127.0.0.1:%d", i)
1270-
} else if strings.HasPrefix(v, ":") {
1271-
addr = fmt.Sprintf("127.0.0.1%s", v)
1272-
} else {
1273-
addr = v
1376+
return "", nil
1377+
}
1378+
if i, err := strconv.Atoi(v); err == nil {
1379+
return fmt.Sprintf("127.0.0.1:%d", i), nil
1380+
}
1381+
host, port, err := net.SplitHostPort(v)
1382+
if err != nil {
1383+
return "", err
1384+
}
1385+
if host != "" && port == "" {
1386+
return "", fmt.Errorf("port is missing")
1387+
}
1388+
if host != "" && port != "" {
1389+
return v, nil
1390+
} else if port != "" {
1391+
return fmt.Sprintf("127.0.0.1:%s", port), nil
12741392
}
1275-
return addr
1393+
return "", fmt.Errorf("failed parsing address")
12761394
}
12771395

12781396
func expandPath(p string) string {
@@ -1290,6 +1408,7 @@ func New(conf *Config) *proxyapp {
12901408
var p proxyapp
12911409
var logfile *os.File = os.Stdout
12921410
var snifflog *os.File
1411+
var err error
12931412
p.sniff = conf.Sniff
12941413
p.body = conf.Body
12951414
p.json = conf.Json
@@ -1418,9 +1537,19 @@ func New(conf *Config) *proxyapp {
14181537
p.tproxyMode = conf.TProxyMode
14191538
tproxyonly := conf.TProxyOnly != ""
14201539
if tproxyonly {
1421-
p.tproxyAddr = getFullAddress(conf.TProxyOnly)
1540+
p.tproxyAddr, err = getFullAddress(conf.TProxyOnly)
1541+
if err != nil {
1542+
p.logger.Fatal().Err(err).Msg("")
1543+
}
14221544
} else {
1423-
p.tproxyAddr = getFullAddress(conf.TProxy)
1545+
p.tproxyAddr, err = getFullAddress(conf.TProxy)
1546+
if err != nil {
1547+
p.logger.Fatal().Err(err).Msg("")
1548+
}
1549+
}
1550+
p.auto = conf.Auto
1551+
if p.auto && p.tproxyMode != "" && p.tproxyMode != "redirect" {
1552+
p.logger.Fatal().Msg("Auto setup is available only for redirect mode")
14241553
}
14251554
var addrHTTP, addrSOCKS, certFile, keyFile string
14261555
if conf.ServerConfPath != "" {
@@ -1437,7 +1566,10 @@ func New(conf *Config) *proxyapp {
14371566
if sconf.Server.Address == "" {
14381567
p.logger.Fatal().Err(err).Msg("[server config] Server address is empty")
14391568
}
1440-
addrHTTP = getFullAddress(sconf.Server.Address)
1569+
addrHTTP, err = getFullAddress(sconf.Server.Address)
1570+
if err != nil {
1571+
p.logger.Fatal().Err(err).Msg("")
1572+
}
14411573
p.httpServerAddr = addrHTTP
14421574
certFile = expandPath(sconf.Server.CertFile)
14431575
keyFile = expandPath(sconf.Server.KeyFile)
@@ -1452,7 +1584,10 @@ func New(conf *Config) *proxyapp {
14521584
}
14531585
seen := make(map[string]struct{})
14541586
for idx, pr := range p.proxylist {
1455-
addr := getFullAddress(pr.Address)
1587+
addr, err := getFullAddress(pr.Address)
1588+
if err != nil {
1589+
p.logger.Fatal().Err(err).Msg("")
1590+
}
14561591
if _, ok := seen[addr]; !ok {
14571592
seen[addr] = struct{}{}
14581593
p.proxylist[idx].Address = addr
@@ -1468,14 +1603,20 @@ func New(conf *Config) *proxyapp {
14681603
p.rrIndexReset = rrIndexMax
14691604
} else {
14701605
if !tproxyonly {
1471-
addrHTTP = getFullAddress(conf.AddrHTTP)
1606+
addrHTTP, err = getFullAddress(conf.AddrHTTP)
1607+
if err != nil {
1608+
p.logger.Fatal().Err(err).Msg("")
1609+
}
14721610
p.httpServerAddr = addrHTTP
14731611
certFile = expandPath(conf.CertFile)
14741612
keyFile = expandPath(conf.KeyFile)
14751613
p.user = conf.ServerUser
14761614
p.pass = conf.ServerPass
14771615
}
1478-
addrSOCKS = getFullAddress(conf.AddrSOCKS)
1616+
addrSOCKS, err = getFullAddress(conf.AddrSOCKS)
1617+
if err != nil {
1618+
p.logger.Fatal().Err(err).Msg("")
1619+
}
14791620
auth := proxy.Auth{
14801621
User: conf.User,
14811622
Password: conf.Pass,

tproxy_linux.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ func newTproxyServer(pa *proxyapp) *tproxyServer {
5454
if err != nil {
5555
var msg string
5656
if errors.Is(err, unix.EPERM) {
57-
msg = "try `sudo setcap 'cap_net_admin+ep` for the binary:"
57+
msg = "try `sudo setcap 'cap_net_admin+ep` for the binary or run with sudo:"
5858
}
5959
ts.pa.logger.Fatal().Err(err).Msg(msg)
6060
}

version.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
package gohpts
22

3-
const Version string = "gohpts v1.8.0"
3+
const Version string = "gohpts v1.8.1"

0 commit comments

Comments
 (0)