diff --git a/README.md b/README.md index e9a07ba..557e688 100644 --- a/README.md +++ b/README.md @@ -14,17 +14,23 @@ This proxy implements the 3rd way. Nor admin privileges neither any system-wide A proxy-switching extension for your browser (FoxyProxy or SwitchyOmega e.g.) is highly recommended. +## WARNING & DISCLAIMER + +⚠️ DO NOT RUN this server on a publicly available network interface! This may compromise your network, privacy, wallet, etc. Never run this proxy on `0.0.0.0` if you do not understand what you're doing! + +Contributors are not responsible for any damage incurred by using this proxy server. + ## What this proxy does and what's not -- :white_check_mark: HTTP proxy protocol is supported -- :white_check_mark: HTTPS CONNECT method is supported -- :green_square: SOCKS5 support is expected -- :green_square: HTTP proxy-chaining support is expected -- :x: Config file support is possible but not planned -- :x: Daemon mode support is possible but not planned -- :x: HTTPS MitM support is not planned (use mitmproxy) -- :x: Request/response rewrite support not planned (use mitmproxy) -- :x: ACL support is not planned +- ✅ HTTP proxy protocol is supported +- ✅ HTTPS CONNECT method is supported +- ✅ SOCKS5 protocol is supported +- ✅ HTTP proxy-chaining is supported (use `HTTP_PROXY` environment variable) +- ❌ Config file support is possible but not planned +- ❌ Daemon mode support is possible but not planned +- ❌ HTTPS MitM support is not planned (use mitmproxy) +- ❌ Request/response rewrite support is not planned (use mitmproxy) +- ❌ ACL support is not planned ## Usage @@ -34,8 +40,6 @@ Run the HTTP proxy on 127.0.0.1:8080 and redirect some hostnames to a local web etc-hosts-proxy run -H example.com=127.0.0.1 -H www.example.com=127.0.0.1 ``` -Note: you may use comma-separated list of `=` pairs in a single `-H` option too: `-H example.com=127.0.0.1,www.example.com=127.0.0.1` - Test the above with curl: ```bash @@ -43,15 +47,25 @@ curl -v -x 127.0.0.1:8080 http://example.com curl -v -x 127.0.0.1:8080 http://www.example.com ``` -Proxy listens on `127.0.0.1:8080` by default. Use `-L` (or `--listen-address`) CLI option to change this. +NOTE: Proxy listens on `127.0.0.1:8080` by default. Use `-L` (or `--listen-address`) CLI option to change this. + +Run the SOCKS5 proxy on 127.0.0.1:1080 and redirect some hostnames to a local web server: + +```bash +etc-hosts-proxy run -M socks5 -L 127.0.0.1:1080 -H example.com=127.0.0.1,www.example.com=127.0.0.1 + +curl -v -x socks5h://127.0.0.1:1080 http://example.com +curl -v -x socks5h://127.0.0.1:1080 http://www.example.com +``` + +See `etc-hosts-proxy --help` for general CLI usage information. ## Docker usage -You may use a docker image too if you'd like: +You may also use a Docker image if you'd prefer: ```bash -# Run the proxy in background -docker run -d --name=etc-hosts-proxy -p 8080:8080 --rm \ +docker run -d --name=etc-hosts-proxy -p 127.0.0.1:8080:8080 --rm \ -e ETC_HOSTS_PROXY_HOSTS_LIST="akamai.com=2.21.250.7,www.akamai.com=2.21.250.7" \ -e ETC_HOSTS_PROXY_DEBUG=true \ ghcr.io/jay7x/etc-hosts-proxy:latest @@ -59,9 +73,10 @@ docker run -d --name=etc-hosts-proxy -p 8080:8080 --rm \ curl -v -x 127.0.0.1:8080 http://akamai.com docker logs etc-hosts-proxy +docker stop etc-hosts-proxy ``` -NOTE: You should not use 127.0.0.1 (or ::1) as your redirection target in the hosts list while running in a container. This will redirect the request to the container's localhost, which is not what you might expect. +NOTE: You should not use 127.0.0.1 (or ::1) as your redirection destination in the hosts list while running in a container. This will redirect the request to the container's localhost, which is not what you might expect. You can use `host` Docker network if you really need it. A bit more complex example to redirect some domains to a nginx container: @@ -69,11 +84,11 @@ A bit more complex example to redirect some domains to a nginx container: # Create a docker network docker network create somenet -# Run a web server exposed in somenet and on 0.0.0.0:8080 on the host -docker run -d --name=nginx --net=somenet -p 8080:80 --rm nginx:latest +# Run a web server exposed in somenet and on 127.0.0.1:8080 on the host +docker run -d --name=nginx --net=somenet -p 127.0.0.1:8080:80 --rm nginx:latest -# Run the proxy connected to somenet and exposed on 0.0.0.0:3128 on the host -docker run -d --name=etc-hosts-proxy --net=somenet -p 3128:8080 --rm \ +# Run the proxy connected to somenet and exposed on 127.0.0.1:3128 on the host +docker run -d --name=etc-hosts-proxy --net=somenet -p 127.0.0.1:3128:8080 --rm \ -e ETC_HOSTS_PROXY_HOSTS_LIST="example.com=nginx,www.example.com=nginx" \ -e ETC_HOSTS_PROXY_DEBUG=true \ ghcr.io/jay7x/etc-hosts-proxy:latest @@ -88,11 +103,11 @@ curl -v -x 127.0.0.1:3128 http://www.example.com curl -v -x 127.0.0.1:3128 http://example.net # Check logs -docker logs proxy +docker logs etc-hosts-proxy docker logs nginx # Cleanup -docker stop proxy +docker stop etc-hosts-proxy docker stop nginx docker network rm somenet ``` @@ -109,4 +124,4 @@ See [etc-hosts-proxy Github Container registry](https://github.com/jay7x/etc-hos | `ETC_HOSTS_PROXY_LOG_LEVEL` | Set the logging level [`trace`, `debug`, `info`, `warn`, `error`] | | `ETC_HOSTS_PROXY_MODE` | Mode to start proxy in (`http` or `socks5`) | | `ETC_HOSTS_PROXY_LISTEN_ADDRESS` | [``]:`` to listen for proxy requests on | -| `ETC_HOSTS_PROXY_HOSTS_LIST` | comma-separated list of `=` pairs to resolve `` to `` | +| `ETC_HOSTS_PROXY_HOSTS_LIST` | comma-separated list of `=` pairs to redirect `` to `` | diff --git a/go.mod b/go.mod index f7f2e1f..867f16e 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 github.com/sirupsen/logrus v1.9.3 github.com/spf13/cobra v1.7.0 + github.com/things-go/go-socks5 v0.0.3 ) require ( diff --git a/go.sum b/go.sum index 24cb143..e2569ae 100644 --- a/go.sum +++ b/go.sum @@ -19,8 +19,11 @@ github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRM github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/things-go/go-socks5 v0.0.3 h1:QtlIhkwDuLNCwW3wnt2uTjn1mQzpyjnwct2xdPuqroI= +github.com/things-go/go-socks5 v0.0.3/go.mod h1:f8Zx+n8kfzyT90hXM767cP6sysAud93+t9rV90IgMcg= +golang.org/x/net v0.6.0 h1:L4ZwwTvKW9gr0ZMS1yrHD9GZhIuVjOBBnaKH+SPQK0Q= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/run.go b/run.go index 28d0c56..c87777f 100644 --- a/run.go +++ b/run.go @@ -1,13 +1,17 @@ package main import ( + "context" "fmt" "net" "net/http" + "strconv" "github.com/elazarl/goproxy" "github.com/sirupsen/logrus" "github.com/spf13/cobra" + "github.com/things-go/go-socks5" + "github.com/things-go/go-socks5/statute" ) func newRunCommand() *cobra.Command { @@ -34,7 +38,7 @@ func newRunCommand() *cobra.Command { runCommand.Flags().StringToStringP("hosts", "H", GetEnvStrMap("ETC_HOSTS_PROXY_HOSTS_LIST"), - "= pairs to resolve to (ETC_HOSTS_PROXY_HOSTS_LIST)") + "= pairs to redirect to (ETC_HOSTS_PROXY_HOSTS_LIST)") runCommand.Flags().StringP("mode", "M", GetEnvWithDefault("ETC_HOSTS_PROXY_MODE", "http"), "Mode to start proxy in (http or socks5) (ETC_HOSTS_PROXY_MODE)") @@ -44,6 +48,27 @@ func newRunCommand() *cobra.Command { return runCommand } +// SOCKS5 destination address rewriter +type HostRewriter struct { + hostsMap map[string]string +} + +func (r HostRewriter) Rewrite(ctx context.Context, request *socks5.Request) (context.Context, *statute.AddrSpec) { + dst, found := r.hostsMap[request.DestAddr.FQDN] + if !found { + dst, found = r.hostsMap[request.DestAddr.IP.String()] + } + + if found { + daSpec, err := statute.ParseAddrSpec(net.JoinHostPort(dst, strconv.Itoa(request.DestAddr.Port))) + if err == nil { + return ctx, &daSpec + } + logrus.Warnf("Unable to parse AddrSpec(%v:%v), skipping...", dst, request.DestAddr.Port) + } + return ctx, request.DestAddr +} + func runAction(cmd *cobra.Command, args []string) error { listenAddress, err := cmd.Flags().GetString("listen-address") if err != nil { @@ -54,13 +79,13 @@ func runAction(cmd *cobra.Command, args []string) error { if err != nil { return err } - for host, ip := range hostsMap { - logrus.Debugf("Mapping %s to %s", host, ip) + for src, dst := range hostsMap { + logrus.Debugf("Mapping %s to %s", src, dst) } switch proxyMode, _ := cmd.Flags().GetString("mode"); proxyMode { case "http": - logrus.Debugln("Starting HTTP proxy...") + logrus.Debugf("Starting HTTP proxy on %s", listenAddress) proxy := goproxy.NewProxyHttpServer() proxy.Logger = logrus.StandardLogger() if logrus.GetLevel() >= logrus.DebugLevel { @@ -68,23 +93,31 @@ func runAction(cmd *cobra.Command, args []string) error { } proxy.OnRequest().DoFunc( func(r *http.Request, ctx *goproxy.ProxyCtx) (*http.Request, *http.Response) { - if ip, found := hostsMap[r.Host]; found { - r.URL.Host = ip + if dst, found := hostsMap[r.Host]; found { + r.URL.Host = dst } return r, nil }) proxy.OnRequest().HandleConnectFunc( func(host string, ctx *goproxy.ProxyCtx) (*goproxy.ConnectAction, string) { h, port, _ := net.SplitHostPort(host) - if ip, found := hostsMap[h]; found { - return goproxy.OkConnect, net.JoinHostPort(ip, port) + if dst, found := hostsMap[h]; found { + return goproxy.OkConnect, net.JoinHostPort(dst, port) } return goproxy.OkConnect, host }) logrus.Fatal(http.ListenAndServe(listenAddress, proxy)) + case "socks5": - logrus.Debugln("Starting SOCKS5 proxy...") - logrus.Fatalln("SOCKS5 proxy is not implemented yet...") + logrus.Debugf("Starting SOCKS5 proxy on %s", listenAddress) + proxy := socks5.NewServer( + socks5.WithLogger(logrus.StandardLogger()), + socks5.WithRewriter(HostRewriter{hostsMap: hostsMap}), + ) + if err := proxy.ListenAndServe("tcp", listenAddress); err != nil { + logrus.Fatal(err) + } + default: logrus.Fatalf("Unsupported proxy mode %v", proxyMode) }