Skip to content

Commit 82371c1

Browse files
Added auto configuration for redirect proxy
1 parent 08722c4 commit 82371c1

File tree

5 files changed

+171
-21
lines changed

5 files changed

+171
-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: 147 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,87 @@ func (p *proxyapp) handler() http.HandlerFunc {
11431146
}
11441147
}
11451148

1149+
func (p *proxyapp) applyRedirectRules() string {
1150+
_, tproxyPort, _ := net.SplitHostPort(p.tproxyAddr)
1151+
cmd1 := exec.Command("bash", "-c", fmt.Sprintf(`
1152+
set -ex
1153+
iptables -t nat -N GOHPTS 2>/dev/null
1154+
iptables -t nat -F GOHPTS
1155+
1156+
iptables -t nat -A GOHPTS -d 127.0.0.0/8 -j RETURN
1157+
iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN
1158+
iptables -t nat -A GOHPTS -p tcp --dport 22 -j RETURN
1159+
`, tproxyPort))
1160+
cmd1.Stdout = os.Stdout
1161+
cmd1.Stderr = os.Stderr
1162+
if err := cmd1.Run(); err != nil {
1163+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1164+
}
1165+
if p.httpServerAddr != "" {
1166+
_, httpPort, _ := net.SplitHostPort(p.httpServerAddr)
1167+
cmd2 := exec.Command("bash", "-c", fmt.Sprintf(`
1168+
set -ex
1169+
iptables -t nat -A GOHPTS -p tcp --dport %s -j RETURN
1170+
`, httpPort))
1171+
cmd2.Stdout = os.Stdout
1172+
cmd2.Stderr = os.Stderr
1173+
if err := cmd2.Run(); err != nil {
1174+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1175+
}
1176+
}
1177+
cmd3 := exec.Command("bash", "-c", fmt.Sprintf(`
1178+
set -ex
1179+
if command -v docker >/dev/null 2>&1
1180+
then
1181+
for subnet in $(docker network inspect $(docker network ls -q) --format '{{range .IPAM.Config}}{{.Subnet}}{{end}}'); do
1182+
iptables -t nat -A GOHPTS -d "$subnet" -j RETURN
1183+
done
1184+
fi
1185+
1186+
iptables -t nat -A GOHPTS -p tcp -j REDIRECT --to-ports %s
1187+
1188+
iptables -t nat -C PREROUTING -p tcp -j GOHPTS 2>/dev/null || \
1189+
iptables -t nat -A PREROUTING -p tcp -j GOHPTS
1190+
1191+
iptables -t nat -C OUTPUT -p tcp -j GOHPTS 2>/dev/null || \
1192+
iptables -t nat -A OUTPUT -p tcp -j GOHPTS
1193+
`, tproxyPort))
1194+
cmd3.Stdout = os.Stdout
1195+
cmd3.Stderr = os.Stderr
1196+
if err := cmd3.Run(); err != nil {
1197+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1198+
}
1199+
cmd4 := exec.Command("bash", "-c", `
1200+
cat /proc/sys/net/ipv4/ip_forward
1201+
`)
1202+
output, err := cmd4.CombinedOutput()
1203+
if err != nil {
1204+
p.logger.Fatal().Err(err).Msg("Failed while configuring iptables. Are you root?")
1205+
}
1206+
cmd5 := exec.Command("bash", "-c", `
1207+
set -ex
1208+
sysctl -w net.ipv4.ip_forward=1
1209+
`)
1210+
cmd5.Stdout = os.Stdout
1211+
cmd5.Stderr = os.Stderr
1212+
_ = cmd5.Run()
1213+
return string(output)
1214+
}
1215+
1216+
func (p *proxyapp) clearRedirectRules(output string) error {
1217+
cmd := exec.Command("bash", "-c", fmt.Sprintf(`
1218+
set -ex
1219+
sysctl -w net.ipv4.ip_forward=%s || true
1220+
iptables -t nat -D PREROUTING -p tcp -j GOHPTS 2>/dev/null || true
1221+
iptables -t nat -D OUTPUT -p tcp -j GOHPTS 2>/dev/null || true
1222+
iptables -t nat -F GOHPTS 2>/dev/null || true
1223+
iptables -t nat -X GOHPTS 2>/dev/null || true
1224+
`, output))
1225+
cmd.Stdout = os.Stdout
1226+
cmd.Stderr = os.Stderr
1227+
return cmd.Run()
1228+
}
1229+
11461230
func (p *proxyapp) Run() {
11471231
done := make(chan bool)
11481232
quit := make(chan os.Signal, 1)
@@ -1151,6 +1235,10 @@ func (p *proxyapp) Run() {
11511235
if p.tproxyAddr != "" {
11521236
tproxyServer = newTproxyServer(p)
11531237
}
1238+
var output string
1239+
if p.auto {
1240+
output = p.applyRedirectRules()
1241+
}
11541242
if p.proxylist != nil {
11551243
chainType := p.proxychain.Type
11561244
var ctl string
@@ -1170,6 +1258,12 @@ func (p *proxyapp) Run() {
11701258
if p.httpServer != nil {
11711259
go func() {
11721260
<-quit
1261+
if p.auto {
1262+
err := p.clearRedirectRules(output)
1263+
if err != nil {
1264+
p.logger.Error().Err(err).Msg("Failed clearing iptables rules")
1265+
}
1266+
}
11731267
if tproxyServer != nil {
11741268
p.logger.Info().Msg("[tproxy] Server is shutting down...")
11751269
tproxyServer.Shutdown()
@@ -1205,6 +1299,12 @@ func (p *proxyapp) Run() {
12051299
} else {
12061300
go func() {
12071301
<-quit
1302+
if p.auto {
1303+
err := p.clearRedirectRules(output)
1304+
if err != nil {
1305+
p.logger.Error().Err(err).Msg("Failed clearing iptables rules")
1306+
}
1307+
}
12081308
p.logger.Info().Msg("[tproxy] Server is shutting down...")
12091309
tproxyServer.Shutdown()
12101310
close(done)
@@ -1259,20 +1359,26 @@ type serverConfig struct {
12591359
Server server `yaml:"server"`
12601360
}
12611361

1262-
func getFullAddress(v string) string {
1362+
func getFullAddress(v string) (string, error) {
12631363
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
1364+
return "", nil
1365+
}
1366+
if i, err := strconv.Atoi(v); err == nil {
1367+
return fmt.Sprintf("127.0.0.1:%d", i), nil
1368+
}
1369+
host, port, err := net.SplitHostPort(v)
1370+
if err != nil {
1371+
return "", err
1372+
}
1373+
if host != "" && port == "" {
1374+
return "", fmt.Errorf("port is missing")
1375+
}
1376+
if host != "" && port != "" {
1377+
return v, nil
1378+
} else if port != "" {
1379+
return fmt.Sprintf("127.0.0.1:%s", port), nil
12741380
}
1275-
return addr
1381+
return "", fmt.Errorf("failed parsing address")
12761382
}
12771383

12781384
func expandPath(p string) string {
@@ -1290,6 +1396,7 @@ func New(conf *Config) *proxyapp {
12901396
var p proxyapp
12911397
var logfile *os.File = os.Stdout
12921398
var snifflog *os.File
1399+
var err error
12931400
p.sniff = conf.Sniff
12941401
p.body = conf.Body
12951402
p.json = conf.Json
@@ -1418,9 +1525,19 @@ func New(conf *Config) *proxyapp {
14181525
p.tproxyMode = conf.TProxyMode
14191526
tproxyonly := conf.TProxyOnly != ""
14201527
if tproxyonly {
1421-
p.tproxyAddr = getFullAddress(conf.TProxyOnly)
1528+
p.tproxyAddr, err = getFullAddress(conf.TProxyOnly)
1529+
if err != nil {
1530+
p.logger.Fatal().Msg("")
1531+
}
14221532
} else {
1423-
p.tproxyAddr = getFullAddress(conf.TProxy)
1533+
p.tproxyAddr, err = getFullAddress(conf.TProxy)
1534+
if err != nil {
1535+
p.logger.Fatal().Msg("")
1536+
}
1537+
}
1538+
p.auto = conf.Auto
1539+
if p.auto && p.tproxyMode != "" && p.tproxyMode != "redirect" {
1540+
p.logger.Fatal().Msg("Auto setup is available only for redirect mode")
14241541
}
14251542
var addrHTTP, addrSOCKS, certFile, keyFile string
14261543
if conf.ServerConfPath != "" {
@@ -1437,7 +1554,10 @@ func New(conf *Config) *proxyapp {
14371554
if sconf.Server.Address == "" {
14381555
p.logger.Fatal().Err(err).Msg("[server config] Server address is empty")
14391556
}
1440-
addrHTTP = getFullAddress(sconf.Server.Address)
1557+
addrHTTP, err = getFullAddress(sconf.Server.Address)
1558+
if err != nil {
1559+
p.logger.Fatal().Msg("")
1560+
}
14411561
p.httpServerAddr = addrHTTP
14421562
certFile = expandPath(sconf.Server.CertFile)
14431563
keyFile = expandPath(sconf.Server.KeyFile)
@@ -1452,7 +1572,10 @@ func New(conf *Config) *proxyapp {
14521572
}
14531573
seen := make(map[string]struct{})
14541574
for idx, pr := range p.proxylist {
1455-
addr := getFullAddress(pr.Address)
1575+
addr, err := getFullAddress(pr.Address)
1576+
if err != nil {
1577+
p.logger.Fatal().Msg("")
1578+
}
14561579
if _, ok := seen[addr]; !ok {
14571580
seen[addr] = struct{}{}
14581581
p.proxylist[idx].Address = addr
@@ -1468,14 +1591,20 @@ func New(conf *Config) *proxyapp {
14681591
p.rrIndexReset = rrIndexMax
14691592
} else {
14701593
if !tproxyonly {
1471-
addrHTTP = getFullAddress(conf.AddrHTTP)
1594+
addrHTTP, err = getFullAddress(conf.AddrHTTP)
1595+
if err != nil {
1596+
p.logger.Fatal().Msg("")
1597+
}
14721598
p.httpServerAddr = addrHTTP
14731599
certFile = expandPath(conf.CertFile)
14741600
keyFile = expandPath(conf.KeyFile)
14751601
p.user = conf.ServerUser
14761602
p.pass = conf.ServerPass
14771603
}
1478-
addrSOCKS = getFullAddress(conf.AddrSOCKS)
1604+
addrSOCKS, err = getFullAddress(conf.AddrSOCKS)
1605+
if err != nil {
1606+
p.logger.Fatal().Msg("")
1607+
}
14791608
auth := proxy.Auth{
14801609
User: conf.User,
14811610
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)