├── .gitignore ├── docker ├── docker-build.sh └── start-any-proxy.sh ├── .travis.yml ├── stack_dump.go ├── version_test.go ├── Dockerfile ├── make.bash ├── dnsproxy_test.go ├── COPYING ├── common.go ├── README.md ├── config_test.go ├── config.go ├── stats.go ├── any_proxy_test.go ├── dnsproxy.go └── any_proxy.go /.gitignore: -------------------------------------------------------------------------------- 1 | version.go 2 | go-any-proxy 3 | .idea/ 4 | -------------------------------------------------------------------------------- /docker/docker-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | timestamp=`date +%s` 4 | cat <version.go 5 | package main 6 | 7 | const BUILDTIMESTAMP = $timestamp 8 | const BUILDUSER = "root" 9 | const BUILDHOST = "docker" 10 | EOF 11 | 12 | go get 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | before_install: 4 | - "cd $TRAVIS_BUILD_DIR && chmod +x ./make.bash && ./make.bash version $TRAVIS_BUILD_DIR" 5 | 6 | script: 7 | - go test -v ./... 8 | 9 | after_failure: 10 | - "cd $TRAVIS_BUILD_DIR && chmod +x ./make.bash && ./make.bash build_failed" 11 | 12 | -------------------------------------------------------------------------------- /stack_dump.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "os/signal" 5 | "syscall" 6 | "runtime" 7 | "os" 8 | "log" 9 | ) 10 | 11 | // Refer to https://stackoverflow.com/a/27398062 12 | func setupStackDump() { 13 | go func() { 14 | sigs := make(chan os.Signal, 1) 15 | signal.Notify(sigs, syscall.SIGQUIT) 16 | buf := make([]byte, 1<<20) 17 | for { 18 | <-sigs 19 | stacklen := runtime.Stack(buf, true) 20 | log.Printf("=== received SIGQUIT ===\n*** goroutine dump...\n%s\n*** end\n", buf[:stacklen]) 21 | } 22 | }() 23 | } 24 | -------------------------------------------------------------------------------- /version_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "testing" 4 | 5 | func TestVersion(t *testing.T) { 6 | if BUILDTIMESTAMP <= 1 { 7 | t.Fatalf("BUILDTIMESTAMP=%v, want > 1", BUILDTIMESTAMP) 8 | } 9 | 10 | lenBuildUser := len(BUILDUSER) 11 | if lenBuildUser <= 0 { 12 | t.Fatalf("len(BUILDUSER)=%v, want > 0; BUILDUSER=%s", lenBuildUser, BUILDUSER) 13 | } 14 | 15 | lenBuildHost := len(BUILDHOST) 16 | if lenBuildHost <= 0 { 17 | t.Fatalf("len(BUILDHOST)=%v, want > 0; BUILDHOST=%s", lenBuildUser, BUILDHOST) 18 | } 19 | } 20 | 21 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM golang:alpine AS builder 2 | 3 | RUN apk add git 4 | 5 | COPY . /go/src/go-any-proxy 6 | 7 | WORKDIR /go/src/go-any-proxy 8 | 9 | RUN docker/docker-build.sh 10 | 11 | LABEL build = "multi-stage" 12 | 13 | FROM alpine 14 | 15 | LABEL maintainer = "Feng Zhou " 16 | 17 | RUN apk add iptables; rm -rf /var/cache/apk/* 18 | 19 | COPY --from=builder /go/src/go-any-proxy/docker/start-any-proxy.sh /go/bin/go-any-proxy /bin/ 20 | 21 | ENV LISTEN_PORT=3129 HTTP_PROXY="" NO_PROXY="" IPTABLE_MARK="5" PROXY_PORTS="80,443" VERBOSE=false DNS_PORT=0 PROXY_CONFIG_FILE= 22 | 23 | CMD ["/bin/start-any-proxy.sh"] 24 | -------------------------------------------------------------------------------- /make.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Required if you are using a 32-bit vyatta. Otherwise, comment it out 4 | GOOS=linux 5 | #GOARCH=386 6 | 7 | function make_version () 8 | { 9 | local timestamp=`date +%s` 10 | local builduser=`id -un` 11 | local buildhost=`hostname` 12 | cat <$BUILD_DIR/version.go 13 | package main 14 | 15 | const BUILDTIMESTAMP = $timestamp 16 | const BUILDUSER = "$builduser" 17 | const BUILDHOST = "$buildhost" 18 | vEOF 19 | echo "Wrote $BUILD_DIR/version.go: timestamp=$timestamp; builduser=$builduser; buildhost=$buildhost" 20 | } 21 | 22 | function pull_deps() 23 | { 24 | go get -u github.com/zdannar/flogger 25 | } 26 | 27 | function build () 28 | { 29 | make_version 30 | go build any_proxy.go stats.go version.go 31 | return $? 32 | } 33 | 34 | function build_failed () 35 | { 36 | echo "TRAVIS_TEST_RESULT=$TRAVIS_TEST_RESULT" 37 | echo "Build failed." 38 | echo "CWD:" 39 | pwd | sed 's/^/ /g' 40 | echo "Environment:" 41 | set | sed 's/^/ /g' 42 | echo "$BUILD_DIR/version.go:" 43 | cat $BUILD_DIR/version.go | sed 's/^/ /g' 44 | } 45 | 46 | export BUILD_DIR=$2 47 | if [ "$BUILD_DIR" = "" ]; then export BUILD_DIR=.; fi 48 | case $1 in 49 | "clean") 50 | rm -f version.go any_proxy 51 | ;; 52 | "version") 53 | make_version 54 | ;; 55 | "build_failed") 56 | build_failed 57 | ;; 58 | *) 59 | pull_deps 60 | build 61 | ;; 62 | esac 63 | 64 | -------------------------------------------------------------------------------- /dnsproxy_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | logger "github.com/zdannar/flogger" 5 | "github.com/miekg/dns" 6 | "github.com/stretchr/testify/assert" 7 | "testing" 8 | "fmt" 9 | "math/rand" 10 | ) 11 | 12 | const remoteDns = "16.110.135.52:53" 13 | 14 | func init() { 15 | if iptablesIntegration { 16 | logger.Info("use iptables integration on root mode for dns proxy testing") 17 | } 18 | } 19 | 20 | func TestRemoteDns(t *testing.T) { 21 | assert := assert.New(t) 22 | var testDomain = [...]string{"www.example.com.", "github.com."} 23 | for _, domain := range testDomain { 24 | m := new(dns.Msg) 25 | m.SetQuestion(domain, dns.TypeA) 26 | r, err := dns.Exchange(m, remoteDns) 27 | if err != nil { 28 | t.Fatalf("failed query dns %q: %v", remoteDns, err) 29 | } 30 | if r != nil && r.Rcode != dns.RcodeSuccess { 31 | t.Fatalf("failed query %q: %v", m.Question, r) 32 | } 33 | assert.True(len(r.Answer) >= 1) 34 | } 35 | } 36 | 37 | func randomPort() int { 38 | return rand.Intn(65535-1024) + 1024 39 | } 40 | 41 | func TestDnsProxy_ListenAndServe(t *testing.T) { 42 | assert := assert.New(t) 43 | localDns := fmt.Sprintf("127.0.0.1:%v", randomPort()) 44 | // setup proxy 45 | proxy := NewDnsProxy(localDns, remoteDns) 46 | defer proxy.Close() 47 | if err := proxy.ListenAndServe(false); err != nil { 48 | t.Fatalf("failed open dns proxy on %q: %v", localDns, err) 49 | } 50 | m := new(dns.Msg) 51 | m.SetQuestion("www.example.com.", dns.TypeA) 52 | r, err := dns.Exchange(m, localDns) 53 | if err != nil { 54 | t.Fatalf("failed query dns %q: %v", remoteDns, err) 55 | } 56 | if r != nil && r.Rcode != dns.RcodeSuccess { 57 | t.Fatalf("failed query %q: %v", m.Question, r) 58 | } 59 | assert.True(len(r.Answer) >= 1) 60 | } 61 | 62 | func TestDnsProxy_ListHostNames(t *testing.T) { 63 | assert := assert.New(t) 64 | localDns := fmt.Sprintf(":%v", randomPort()) 65 | // setup proxy 66 | proxy := NewDnsProxy(localDns, remoteDns) 67 | defer proxy.Close() 68 | if err := proxy.ListenAndServe(false); err != nil { 69 | t.Fatalf("failed open dns proxy on %q: %v", localDns, err) 70 | } 71 | var testDomain = [...]string{"github.com.", "www.example.com."} 72 | for _, domain := range testDomain { 73 | m := new(dns.Msg) 74 | m.SetQuestion(domain, dns.TypeA) 75 | r, err := dns.Exchange(m, localDns) 76 | if err != nil { 77 | t.Fatalf("failed query dns %q: %v", remoteDns, err) 78 | } 79 | if r != nil && r.Rcode != dns.RcodeSuccess { 80 | t.Fatalf("failed query %q: %v", m.Question, r) 81 | } 82 | assert.True(len(r.Answer) >= 1) 83 | } 84 | names := ListHostNames() 85 | assert.Equal(len(testDomain), len(names)) 86 | for _, domain := range testDomain { 87 | ip, ok := names[domain] 88 | logger.Debugf("Hostname %v ip is %v", domain, ip) 89 | assert.Truef(ok, "hostname %v is not queried", domain) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | # 2 | # go-any-proxy 3 | # Copyright (C) 2013 Ryan A. Chapman. All rights reserved. 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted provided that the following conditions are met: 7 | # 8 | # 1. Redistributions of source code must retain the above copyright notice, 9 | # this list of conditions and the following disclaimer. 10 | # 11 | # 2. Redistributions in binary form must reproduce the above copyright notice, 12 | # this list of conditions and the following disclaimer in the documentation 13 | # and/or other materials provided with the distribution. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 16 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 17 | # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS 18 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 19 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 20 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 21 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 22 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 23 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 24 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | 26 | -- Copyright for the Flogger package -- 27 | # go pkg flogger(base.go, flogger.go) 28 | # Copyright (C) 2013 Zack Dannar. All rights reserved. 29 | # 30 | # Redistribution and use in source and binary forms, with or without 31 | # modification, are permitted provided that the following conditions are met: 32 | # 33 | # 1. Redistributions of source code must retain the above copyright notice, 34 | # this list of conditions and the following disclaimer. 35 | # 36 | # 2. Redistributions in binary form must reproduce the above copyright notice, 37 | # this list of conditions and the following disclaimer in the documentation 38 | # and/or other materials provided with the distribution. 39 | # 40 | # THE SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 41 | # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 42 | # FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS 43 | # OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 44 | # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 45 | # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 46 | # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 47 | # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 48 | # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 49 | # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 50 | -------------------------------------------------------------------------------- /common.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "strconv" 7 | "strings" 8 | "golang.org/x/net/proxy" 9 | "net/url" 10 | "encoding/base64" 11 | ) 12 | 13 | type ProxyType string 14 | 15 | var ( 16 | HttpProxyType ProxyType = "http" 17 | Socks5ProxyType ProxyType = "socks5" 18 | ) 19 | 20 | func (typ *ProxyType) UnmarshalYAML(unmarshal func(interface{}) error) error { 21 | var typeString string 22 | err := unmarshal(&typeString) 23 | if err != nil { 24 | return err 25 | } 26 | 27 | *typ, err = ParseProxyType(typeString) 28 | 29 | return err 30 | } 31 | 32 | func ParseProxyType(typeString string) (typ ProxyType, err error) { 33 | newType := ProxyType(typeString) 34 | 35 | switch newType { 36 | case HttpProxyType, Socks5ProxyType: 37 | typ = newType 38 | default: 39 | err = fmt.Errorf("invalid proxy type '%s'", newType) 40 | } 41 | return 42 | } 43 | 44 | type Proxy struct { 45 | Host string 46 | Port uint16 47 | Type ProxyType 48 | Auth *proxy.Auth 49 | } 50 | 51 | func NewProxy(host string, port uint16, typ ProxyType) *Proxy { 52 | return &Proxy{ 53 | Host: host, 54 | Port: port, 55 | Type: typ, 56 | } 57 | } 58 | 59 | func (p *Proxy) HostPort() string { 60 | return fmt.Sprintf("%s:%d", p.Host, p.Port) 61 | } 62 | 63 | func (p *Proxy) ToURL() *url.URL { 64 | var u = new(url.URL) 65 | u.Host = p.HostPort() 66 | u.Scheme = string(p.Type) 67 | if p.Auth != nil { 68 | if p.Auth.Password == "" { 69 | u.User = url.User(p.Auth.User) 70 | } else { 71 | u.User = url.UserPassword(p.Auth.User, p.Auth.Password) 72 | } 73 | } 74 | return u 75 | } 76 | 77 | func (p *Proxy) String() string { 78 | return p.ToURL().String() 79 | } 80 | 81 | func (p *Proxy) UserInfoBase64() string { 82 | if p.Auth == nil { 83 | return "" 84 | } else { 85 | return base64.StdEncoding.EncodeToString([]byte(p.Auth.User + ":" + p.Auth.Password)) 86 | } 87 | } 88 | 89 | func ParseProxy(proxySpec string) (*Proxy, error) { 90 | var proxyType = HttpProxyType // default proxy is http 91 | var auth *proxy.Auth = nil 92 | if strings.Contains(proxySpec, "://") { 93 | var strProxyType string 94 | strProxyType, proxySpec = split(proxySpec, "://") 95 | var err error 96 | proxyType, err = ParseProxyType(strProxyType) 97 | if err != nil { 98 | return nil, err 99 | } 100 | } 101 | if strings.Contains(proxySpec, "@") { 102 | var userInfo string 103 | userInfo, proxySpec = split(proxySpec, "@") 104 | if strings.Contains(userInfo, ":") { 105 | username, password := split(userInfo, ":") 106 | auth = &proxy.Auth{User: username, Password: password} 107 | } else { 108 | auth = &proxy.Auth{User: userInfo} 109 | } 110 | } 111 | 112 | proxyHost, proxyPort, err := net.SplitHostPort(proxySpec) 113 | if err != nil { 114 | return nil, fmt.Errorf("invalid proxy host:port format: '%s' (%s)", proxySpec, err) 115 | } 116 | proxyPortInt, err := strconv.Atoi(proxyPort) 117 | if err != nil { 118 | return nil, fmt.Errorf("could not convert network port from string \"%s\" to integer: %v", proxyPort, err) 119 | } 120 | var p = NewProxy(proxyHost, uint16(proxyPortInt), proxyType) 121 | p.Auth = auth 122 | return p, nil 123 | } 124 | 125 | func split(s string, c string) (string, string) { 126 | i := strings.Index(s, c) 127 | if i < 0 { 128 | return s, "" 129 | } 130 | return s[:i], s[i+len(c):] 131 | } 132 | 133 | func ParseProxyList(proxyList string) ([]*Proxy, error) { 134 | list := strings.Split(proxyList, ",") 135 | pList := make([]*Proxy, len(list)) 136 | for i, proxySpec := range list { 137 | var err error 138 | pList[i], err = ParseProxy(proxySpec) 139 | if err != nil { 140 | return nil, err 141 | } 142 | } 143 | return pList, nil 144 | } 145 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | **This is fork project from https://github.com/ryanchapman/go-any-proxy, but add a few features.** 2 | 3 | # Transparent Proxy 4 | 5 | transparent-proxy is a server that can transparently proxy any tcp connection through an upstream proxy server. This type 6 | of setup is common in corporate environments. It is written in golang and has been load tested with 10,000 concurrent 7 | connections successfully on a Vyatta running a 64-bit kernel. 8 | 9 | ## More info 10 | 11 | For more info, see original post http://blog.rchapman.org/post/47406142744/transparently-proxying-http-and-https-connections 12 | 13 | ## Authentication 14 | 15 | You can add basic authentication parameters if needed, like this: 16 | 17 | `any_proxy -l :3140 -p "MyLogin:Password25@proxy.corporate.com:8080"` 18 | 19 | ## Install Info 20 | You may need to run `go get` for library dependencies. 21 | 22 | ## Docker Integration (**New**) 23 | This project support docker and integrated iptables for container start/stop. 24 | 25 | Additional features supported: 26 | - Use DNS proxy to intercept DNS resolving request if "DNS_PORT" is enabled 27 | - Advanced proxy routing based on configuration file for multiple proxy servers on ip/ip-net/domain-pattern 28 | - Support `socks5` proxy type in advanced proxy routing (The port of socks5 proxy should not in list of proxy ports) 29 | 30 | Using the following command to start transparent proxy on host: 31 | ``` 32 | docker run -it --rm --privileged -e HTTP_PROXY=proxy.corporate.com:8080 -e NO_PROXY=192.176.0.1/8,172.10.0.1/8 -e LISTEN_PORT=3129 -e IPTABLE_MARK=2515 -e PROXY_PORTS=80,443,22 -e DNS_PORT=53 --net=host fengzhou/transparent-proxy 33 | ``` 34 | 35 | The options are important for run docker: 36 | * "--privileged": This option is required for application to set socket options for SO_MARK. 37 | * "--net=host": This option is required to setup iptables inside of container, and get original ip/port when get connection redirected from iptables. 38 | * Env "HTTP_PROXY": This is upstream proxy, should support "CONNECT" HTTP method (Usually it is http proxy for HTTPS). 39 | * Env "NO_PROXY": This is optional value to by pass network address without go through upstream proxy. 40 | * Env "LISTEN_PORT": This is optional value and it can be any open port. Default value is "3129". 41 | * Env "IPTABLE_MASK": This is optional value, and should be different with other mark value used in iptables. Default value is "5". 42 | * Env "PROXY_PORTS": This is optional value for ports that can be transparent to proxy. The default value is "80,443". 43 | * Env "DNS_PORT": This is optional value to start DNS proxy to help advanced filter by DNS. Default value is "0" (disable). Use "53" to enable it. 44 | * Env "PROXY_CONFIG_FILE": This is optional value to use advanced proxy routing configuration file. Default value is disabled. 45 | 46 | 47 | ## Proxy Configuration File 48 | To enable advanced proxy routing, "-df" option for application, or "PROXY_CONFIG_FILE" environment for docker can be used. 49 | 50 | Each proxy configuration can set proxy `type` with value: http or socks5. If no `type` defined, by default it is http proxy. 51 | 52 | Here is sample configuration file. 53 | ```yaml 54 | --- 55 | proxy: 56 | rules: 57 | - 10.0.0.0/8 58 | - 192.168.0.0/16 59 | - '*.example.net' 60 | --- 61 | proxy: proxy1.example.com:8080 62 | rules: 63 | - '172.168.1.0/16' 64 | - '*.example.com' 65 | - '*.example.*' 66 | --- 67 | proxy: proxy2.example.com:8080 68 | rules: 69 | - '*.net' 70 | proxy: proxy3.example.com:1080 71 | type: socks5 72 | rules: 73 | - '*.io' 74 | ``` 75 | 76 | Each proxy routing is separated by `---`. For proxy with empty value, it is special rule for additional direct connect (no proxy). 77 | For others, it will be tested from top to bottom and use if ip/ip-net/domain matching. If nothing matched, use default proxy in "HTTP_PROXY" environment. 78 | 79 | To support the domain-based rule, the "DNS_PORT" should be explicitly enabled. 80 | -------------------------------------------------------------------------------- /docker/start-any-proxy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PROXY_VERBOSE=0 4 | 5 | if [ "${VERBOSE}" != false ]; then 6 | PROXY_VERBOSE=1 7 | set -x 8 | fi 9 | 10 | if [ -z "${HTTP_PROXY}" ]; then 11 | echo Need upstream proxy set on environment variable HTTP_PROXY 1>&2 12 | exit 1 13 | fi 14 | 15 | IPTABLE_SET=1 16 | 17 | NO_PROXY_LIST="127.0.0.1/8,192.168.0.1/16,172.16.0.0/12,${NO_PROXY},`ip route list | grep src | awk '{print $1}' | sed -e :a -e 'N;s/\n/,/;ta'`" 18 | NO_PROXY_LIST=`echo ${NO_PROXY_LIST}|sed 's/,,/,/g'` 19 | 20 | _PROXY_CONFIG= 21 | if [ -n "${PROXY_CONFIG_FILE}" -a -e "${PROXY_CONFIG_FILE}" ]; then 22 | _PROXY_CONFIG="-pf ${PROXY_CONFIG_FILE}" 23 | fi 24 | 25 | _RANDOM=${RANDOM} 26 | IPTABELE_OUTPUT_CHAIN=PROXY_OUTPUT_${_RANDOM} 27 | IPTABELE_PREROUTING_CHAIN=PROXY_PREROUTING_${_RANDOM} 28 | 29 | function install_iptables() { 30 | iptables -t filter -I OUTPUT 1 -p tcp -m mark --mark ${IPTABLE_MARK} --dport ${LISTEN_PORT} -j REJECT 31 | 32 | iptables -t nat -N ${IPTABELE_OUTPUT_CHAIN} 33 | iptables -t nat -A ${IPTABELE_OUTPUT_CHAIN} -p tcp -j RETURN -d ${NO_PROXY_LIST} 34 | iptables -t nat -A ${IPTABELE_OUTPUT_CHAIN} -p tcp -j REDIRECT --to-port ${LISTEN_PORT} 35 | iptables -t nat -A OUTPUT -p tcp -m mark ! --mark ${IPTABLE_MARK} -m multiport --dports ${PROXY_PORTS} -j ${IPTABELE_OUTPUT_CHAIN} 36 | 37 | iptables -t nat -N ${IPTABELE_PREROUTING_CHAIN} 38 | iptables -t nat -A ${IPTABELE_PREROUTING_CHAIN} -p tcp -j RETURN -d ${NO_PROXY_LIST} 39 | iptables -t nat -A ${IPTABELE_PREROUTING_CHAIN} -p tcp -j MARK --set-mark ${IPTABLE_MARK} 40 | iptables -t nat -A ${IPTABELE_PREROUTING_CHAIN} -p tcp -j REDIRECT --to-port ${LISTEN_PORT} 41 | iptables -t nat -A PREROUTING -p tcp -m mark ! --mark ${IPTABLE_MARK} -m multiport --dports ${PROXY_PORTS} -j ${IPTABELE_PREROUTING_CHAIN} 42 | 43 | iptables -t filter -I INPUT 1 -p tcp -m mark --mark ${IPTABLE_MARK} --dport ${LISTEN_PORT} -m conntrack --ctstate NEW -j ACCEPT 44 | 45 | if [ "${DNS_PORT}" -gt 0 ]; then 46 | iptables -t nat -A OUTPUT -p udp -m mark ! --mark ${IPTABLE_MARK} -m multiport --dports ${DNS_PORT} -j REDIRECT --to-port ${LISTEN_PORT} 47 | iptables -t nat -A PREROUTING -p udp -m multiport --dports ${DNS_PORT} -j REDIRECT --to-port ${LISTEN_PORT} 48 | fi 49 | IPTABLE_SET=1 50 | } 51 | 52 | function uninstall_iptables() { 53 | if [ "${IPTABLE_SET}" == 0 ]; then 54 | return 55 | fi 56 | trap - 0 2 3 15 57 | iptables -t filter -D OUTPUT -p tcp -m mark --mark ${IPTABLE_MARK} --dport ${LISTEN_PORT} -j REJECT 58 | 59 | iptables -t nat -D OUTPUT -p tcp -m mark ! --mark ${IPTABLE_MARK} -m multiport --dports ${PROXY_PORTS} -j ${IPTABELE_OUTPUT_CHAIN} 60 | iptables -t nat -F ${IPTABELE_OUTPUT_CHAIN} 61 | iptables -t nat -X ${IPTABELE_OUTPUT_CHAIN} 62 | 63 | iptables -t nat -D PREROUTING -p tcp -m mark ! --mark ${IPTABLE_MARK} -m multiport --dports ${PROXY_PORTS} -j ${IPTABELE_PREROUTING_CHAIN} 64 | iptables -t nat -F ${IPTABELE_PREROUTING_CHAIN} 65 | iptables -t nat -X ${IPTABELE_PREROUTING_CHAIN} 66 | 67 | iptables -t filter -D INPUT -p tcp -m mark --mark ${IPTABLE_MARK} --dport ${LISTEN_PORT} -m conntrack --ctstate NEW -j ACCEPT 68 | 69 | if [ "${DNS_PORT}" -gt 0 ]; then 70 | iptables -t nat -D OUTPUT -p udp -m mark ! --mark ${IPTABLE_MARK} -m multiport --dports ${DNS_PORT} -j REDIRECT --to-port ${LISTEN_PORT} 71 | iptables -t nat -D PREROUTING -p udp -m multiport --dports ${DNS_PORT} -j REDIRECT --to-port ${LISTEN_PORT} 72 | fi 73 | IPTABLE_SET=0 74 | } 75 | 76 | _DNS_LISTEN_PORT= 77 | if [ "${DNS_PORT}" -gt 0 ]; then 78 | _DNS_LISTEN_PORT="-dns :${LISTEN_PORT}" 79 | fi 80 | 81 | trap 'uninstall_iptables; kill -TERM ${PID}' 0 2 3 15 82 | 83 | /bin/go-any-proxy -l :${LISTEN_PORT} -p ${HTTP_PROXY} -d ${NO_PROXY_LIST} -v=${PROXY_VERBOSE} -f=/dev/stdout -k ${IPTABLE_MARK} ${_DNS_LISTEN_PORT} ${_PROXY_CONFIG}& 84 | 85 | PID=$! 86 | 87 | install_iptables 88 | 89 | wait $PID 90 | 91 | uninstall_iptables 92 | 93 | wait $PID 94 | -------------------------------------------------------------------------------- /config_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "testing" 5 | "strings" 6 | "github.com/stretchr/testify/assert" 7 | "net" 8 | ) 9 | 10 | var data = ` 11 | --- 12 | proxy: 13 | rules: 14 | - 10.0.0.0/8 15 | - 192.168.0.0/16 16 | - '*.example.net' 17 | --- 18 | proxy: proxy1.example.com:8080 19 | rules: 20 | - '172.168.1.0/16' 21 | - '*.example.com' 22 | - '*.example.*' 23 | --- 24 | proxy: proxy2.example.com:8080 25 | rules: 26 | - '*.net' 27 | --- 28 | proxy: proxy3.example.com:1080 29 | type: socks5 30 | rules: 31 | - '*.io' 32 | ` 33 | 34 | var proxy1, _ = ParseProxy("proxy1.example.com:8080") 35 | var proxy2, _ = ParseProxy("proxy2.example.com:8080") 36 | var proxy3, _ = ParseProxy("socks5://proxy3.example.com:1080") 37 | 38 | var expectedData = [...]ProxyRule{ 39 | {Rules: []string{"10.0.0.0/8", "192.168.0.0/16", "*.example.net"}}, 40 | {Proxy: proxy1.HostPort(), Type: proxy1.Type, Rules: []string{"172.168.1.0/16", "*.example.com", "*.example.*"}}, 41 | {Proxy: proxy2.HostPort(), Type: proxy2.Type, Rules: []string{"*.net"}}, 42 | {Proxy: proxy3.HostPort(), Type: proxy3.Type, Rules: []string{"*.io"}}, 43 | } 44 | 45 | func TestProxyConfig_UnmarshalProxyRules(t *testing.T) { 46 | config := NewProxyConfig(strings.NewReader(data)) 47 | assert.Len(t, config.proxyRules, len(expectedData)) 48 | for i, d := range expectedData { 49 | assert.Equal(t, d, config.proxyRules[i]) 50 | } 51 | } 52 | 53 | func TestProxyConfig_DirectorFunc(t *testing.T) { 54 | config := NewProxyConfig(strings.NewReader(data)) 55 | directorFuncs := config.DirectorFunc(true) 56 | assert.Len(t, directorFuncs, 3) 57 | all := func(ip *net.IP) bool { 58 | for _, f := range directorFuncs { 59 | if f(ip) { 60 | return true 61 | } 62 | } 63 | return false 64 | } 65 | fakeHosts := map[string]string{ 66 | "12.12.12.12": "noproxy.example.net", 67 | "10.1.2.3": "www.example.net", 68 | "13.12.12.12": "www.example.com", 69 | } 70 | findHostName = func(ip string) string { 71 | return fakeHosts[ip] 72 | } 73 | results := map[string]bool{ 74 | "12.12.12.12": true, // in hosts, *.example.net 75 | "10.1.2.3": true, // in 10.0.0.0/8 76 | "13.12.12.12": false, // no proxy 77 | "10.2.2.3": true, // in 10.0.0.0/8 78 | "192.1.1.1": false, // not in 192.168.0.0/16 79 | "192.168.1.1": true, // in 192.168.0.0/16 80 | } 81 | for k, v := range results { 82 | ip := net.ParseIP(k) 83 | assert.Equalf(t, v, all(&ip), "ip director for %v?", k) 84 | } 85 | } 86 | 87 | func TestProxyConfig_DirectorFunc_All(t *testing.T) { 88 | config := NewProxyConfig(strings.NewReader(data)) 89 | directorFuncs := config.DirectorFunc(false) 90 | assert.Len(t, directorFuncs, 1) 91 | all := func(ip *net.IP) bool { 92 | for _, f := range directorFuncs { 93 | if f(ip) { 94 | return true 95 | } 96 | } 97 | return false 98 | } 99 | fakeHosts := map[string]string{ 100 | "12.12.12.12": "noproxy.example.net", 101 | "10.1.2.3": "www.example.net", 102 | "13.12.12.12": "www.example.com", 103 | } 104 | findHostName = func(ip string) string { 105 | return fakeHosts[ip] 106 | } 107 | results := map[string]bool{ 108 | "12.12.12.12": false, // in hosts, *.example.net 109 | "10.1.2.3": false, // in 10.0.0.0/8 110 | "13.12.12.12": false, // no proxy 111 | "10.2.2.3": true, // in 10.0.0.0/8 112 | "192.1.1.1": false, // not in 192.168.0.0/16 113 | "192.168.1.1": true, // in 192.168.0.0/16 114 | } 115 | for k, v := range results { 116 | ip := net.ParseIP(k) 117 | assert.Equalf(t, v, all(&ip), "ip director for %v?", k) 118 | } 119 | } 120 | 121 | func TestProxyConfig_ResolveHttpProxy(t *testing.T) { 122 | config := NewProxyConfig(strings.NewReader(data)) 123 | fakeHosts := map[string]string{ 124 | "12.12.12.12": "www.example.org", 125 | "10.1.2.3": "www.example.net", 126 | "13.12.12.12": "www.example.com", 127 | "12.12.12.13": "www.example1.org", 128 | "12.12.12.14": "www.example1.net", 129 | } 130 | findHostName = func(ip string) string { 131 | return fakeHosts[ip] 132 | } 133 | var defaultProxy, _ = ParseProxy("proxy.example.com:8080") 134 | results := map[string]*Proxy{ 135 | "172.168.1.1": proxy1, 136 | "13.12.12.12": proxy1, 137 | "12.12.12.12": proxy1, 138 | "12.12.12.13": defaultProxy, 139 | "12.12.12.14": proxy2, 140 | "10.1.2.3": proxy1, 141 | "1.1.1.1": defaultProxy, 142 | } 143 | for k, v := range results { 144 | assert.Equalf(t, v, config.ResolveProxy(k, 80, []*Proxy{defaultProxy})[0], "proxy for ip %v?", k) 145 | } 146 | } 147 | 148 | func TestProxyConfig_ResolveSocks5Proxy(t *testing.T) { 149 | config := NewProxyConfig(strings.NewReader(data)) 150 | fakeHosts := map[string]string{ 151 | "12.12.12.12": "www.example2.io", 152 | "10.1.2.3": "www.example.net", 153 | "13.12.12.12": "www.example.com", 154 | "12.12.12.13": "www.example1.org", 155 | "12.12.12.14": "www.example1.net", 156 | } 157 | findHostName = func(ip string) string { 158 | return fakeHosts[ip] 159 | } 160 | var defaultProxy, _ = ParseProxy("proxy.example.com:8080") 161 | results := map[string]*Proxy{ 162 | "172.168.1.1": proxy1, 163 | "13.12.12.12": proxy1, 164 | "12.12.12.12": proxy3, 165 | "12.12.12.13": defaultProxy, 166 | "12.12.12.14": proxy2, 167 | "10.1.2.3": proxy1, 168 | "1.1.1.1": defaultProxy, 169 | } 170 | for k, v := range results { 171 | assert.Equalf(t, v, config.ResolveProxy(k, 80, []*Proxy{defaultProxy})[0], "proxy for ip %v?", k) 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | logger "github.com/zdannar/flogger" 5 | "gopkg.in/yaml.v2" 6 | "bufio" 7 | "bytes" 8 | "io" 9 | "fmt" 10 | "strings" 11 | "unicode" 12 | "net" 13 | "regexp" 14 | ) 15 | 16 | type ProxyRule struct { 17 | Proxy string `yaml:"proxy"` 18 | Type ProxyType `yaml:"type"` 19 | Rules []string `yaml:"rules"` 20 | } 21 | 22 | type proxyConfig struct { 23 | proxyRules []ProxyRule 24 | } 25 | 26 | func NewProxyConfig(r io.Reader) *proxyConfig { 27 | config := new(proxyConfig) 28 | var err error 29 | if config.proxyRules, err = unmarshalProxyRules(r); err != nil { 30 | panic(fmt.Sprintf("error loading config: %v", err)) 31 | } 32 | return config 33 | } 34 | 35 | func unmarshalProxyRules(r io.Reader) ([]ProxyRule, error) { 36 | scanner := bufio.NewScanner(r) 37 | sep := []byte("---\n") 38 | scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { 39 | if atEOF && len(data) == 0 { 40 | return 0, nil, nil 41 | } 42 | sepIdx := bytes.Index(data, sep) 43 | if sepIdx >= 0 { 44 | if len(bytes.TrimSpace(data[:sepIdx])) == 0 { 45 | return sepIdx + len(sep), nil, nil 46 | } else { 47 | return sepIdx + len(sep), data[:sepIdx], nil 48 | } 49 | } 50 | return len(data), data, nil 51 | }) 52 | list := make([]ProxyRule, 0) 53 | for scanner.Scan() { 54 | proxyRule := ProxyRule{} 55 | 56 | err := yaml.Unmarshal([]byte(scanner.Text()), &proxyRule) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | // default is http proxy if proxy has value 62 | if proxyRule.Type == "" && proxyRule.Proxy != "" { 63 | proxyRule.Type = HttpProxyType 64 | } 65 | 66 | list = append(list, proxyRule) 67 | } 68 | if err := scanner.Err(); err != nil { 69 | return nil, err 70 | } 71 | return list, nil 72 | } 73 | 74 | func (config *proxyConfig) DirectorFunc(onlyNoProxy bool) []directorFunc { 75 | directorFuncs := make([]directorFunc, 0, 10) 76 | for _, cpr := range config.proxyRules { 77 | if cpr.Proxy == "" { 78 | for _, rule := range cpr.Rules { 79 | directorFuncs = append(directorFuncs, ruleAsDirectorFunc(rule)) 80 | } 81 | } 82 | } 83 | if onlyNoProxy { 84 | return directorFuncs 85 | } 86 | proxyDirector := func(ptestip *net.IP) bool { 87 | if len(config.ResolveProxy(ptestip.String(), 0, []*Proxy{})) > 0 { 88 | return false 89 | } else { 90 | for _, f := range directorFuncs { 91 | if f(ptestip) { 92 | return true 93 | } 94 | } 95 | return false 96 | } 97 | } 98 | return []directorFunc{proxyDirector} 99 | } 100 | 101 | var findHostName = GetHostName 102 | 103 | func ruleAsDirectorFunc(rule string) directorFunc { 104 | var dfunc directorFunc 105 | if isDomain(rule) { 106 | pattern := strings.Replace(rule, ".", "\\.", -1) 107 | pattern = strings.Replace(pattern, "*", ".*", -1) 108 | re, err := regexp.Compile(pattern) 109 | if err != nil { 110 | panic(fmt.Sprintf("Unnable to parse pattern %v", rule)) 111 | } 112 | dfunc = func(ptestip *net.IP) bool { 113 | testIp := ptestip.String() 114 | hostname := findHostName(testIp) 115 | if hostname == "" { 116 | return false 117 | } 118 | hostname = strings.TrimSuffix(hostname, ".") 119 | return re.MatchString(hostname) 120 | } 121 | } else if isCIDR(rule) { 122 | _, directorIpNet, err := net.ParseCIDR(rule) 123 | if err != nil { 124 | panic(fmt.Sprintf("Unable to parse CIDR string : %s : %s\n", rule, err)) 125 | } 126 | dfunc = func(ptestip *net.IP) bool { 127 | testIp := *ptestip 128 | return directorIpNet.Contains(testIp) 129 | } 130 | } else { 131 | // IP 132 | directorIp := net.ParseIP(rule) 133 | dfunc = func(ptestip *net.IP) bool { 134 | var testIp net.IP 135 | testIp = *ptestip 136 | return testIp.Equal(directorIp) 137 | } 138 | } 139 | return dfunc 140 | } 141 | 142 | func (config *proxyConfig) ResolveProxy(ipv4 string, port uint16, defaultProxyList []*Proxy) []*Proxy { 143 | var hostname *string = nil 144 | for _, cpr := range config.proxyRules { 145 | if cpr.Proxy != "" { 146 | proxy, err := ParseProxy(cpr.Proxy) 147 | if err != nil { 148 | panic(err) 149 | } 150 | proxy.Type = cpr.Type 151 | for _, rule := range cpr.Rules { 152 | if isDomain(rule) { 153 | pattern := strings.Replace(rule, ".", "\\.", -1) 154 | pattern = strings.Replace(pattern, "*", ".*", -1) 155 | re, err := regexp.Compile(pattern) 156 | if err != nil { 157 | panic(fmt.Sprintf("Unnable to parse pattern %v", rule)) 158 | } 159 | if hostname == nil { 160 | h := findHostName(ipv4) 161 | hostname = &h 162 | } 163 | if *hostname == "" { 164 | continue 165 | } 166 | *hostname = strings.TrimSuffix(*hostname, ".") 167 | if re.MatchString(*hostname) { 168 | logger.Debugf("resolve proxy by domain %v(%v:%v): %v", *hostname, ipv4, port, cpr.Proxy) 169 | return insert(proxy, defaultProxyList) 170 | } 171 | } else if isCIDR(rule) { 172 | _, directorIpNet, err := net.ParseCIDR(rule) 173 | if err != nil { 174 | panic(fmt.Sprintf("Unable to parse CIDR string : %s : %s\n", rule, err)) 175 | } 176 | if directorIpNet.Contains(net.ParseIP(ipv4)) { 177 | logger.Debugf("resolve proxy by IP net %v(%v:%v): %v", directorIpNet, ipv4, port, cpr.Proxy) 178 | return insert(proxy, defaultProxyList) 179 | } 180 | } else { 181 | // IP 182 | if rule == ipv4 { 183 | logger.Debugf("resolve proxy by IP %v:%v: %v", ipv4, port, cpr.Proxy) 184 | return insert(proxy, defaultProxyList) 185 | } 186 | } 187 | } 188 | } 189 | } 190 | return defaultProxyList 191 | } 192 | 193 | func insert(proxy *Proxy, list []*Proxy) []*Proxy { 194 | newList := make([]*Proxy, 1+len(list)) 195 | newList[0] = proxy 196 | copy(newList[1:], list) 197 | return newList 198 | } 199 | 200 | func isDomain(rule string) bool { 201 | return strings.IndexFunc(rule, func(r rune) bool { 202 | return unicode.IsLetter(r) 203 | }) != -1 204 | } 205 | 206 | func isCIDR(rule string) bool { 207 | return strings.Contains(rule, "/") 208 | } 209 | -------------------------------------------------------------------------------- /stats.go: -------------------------------------------------------------------------------- 1 | // 2 | // any_proxy.go - Transparently proxy a connection using Linux iptables REDIRECT 3 | // 4 | // Copyright (C) 2013 Ryan A. Chapman. All rights reserved. 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are met: 8 | // 9 | // 1. Redistributions of source code must retain the above copyright notice, 10 | // this list of conditions and the following disclaimer. 11 | // 12 | // 2. Redistributions in binary form must reproduce the above copyright notice, 13 | // this list of conditions and the following disclaimer in the documentation 14 | // and/or other materials provided with the distribution. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 18 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS 19 | // OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | // 27 | // Ryan A. Chapman, ryan@rchapman.org 28 | // Sun Apr 7 21:04:34 MDT 2013 29 | // 30 | 31 | 32 | package main 33 | 34 | import ( 35 | "fmt" 36 | log "github.com/zdannar/flogger" 37 | "os" 38 | "os/signal" 39 | "runtime" 40 | "sync" 41 | "syscall" 42 | "time" 43 | ) 44 | 45 | var acceptErrors struct { 46 | sync.Mutex 47 | n uint64 48 | } 49 | 50 | var acceptSuccesses struct { 51 | sync.Mutex 52 | n uint64 53 | } 54 | 55 | var getOriginalDstErrors struct { 56 | sync.Mutex 57 | n uint64 58 | } 59 | 60 | var directConnections struct { 61 | sync.Mutex 62 | n uint64 63 | } 64 | 65 | var proxiedConnections struct { 66 | sync.Mutex 67 | n uint64 68 | } 69 | 70 | var proxy200Responses struct { 71 | sync.Mutex 72 | n uint64 73 | } 74 | 75 | var proxy300Responses struct { 76 | sync.Mutex 77 | n uint64 78 | } 79 | 80 | var proxy400Responses struct { 81 | sync.Mutex 82 | n uint64 83 | } 84 | 85 | var proxyNon200Responses struct { 86 | sync.Mutex 87 | n uint64 88 | } 89 | 90 | var proxyNoConnectResponses struct { 91 | sync.Mutex 92 | n uint64 93 | } 94 | 95 | var proxyServerReadErr struct { 96 | sync.Mutex 97 | n uint64 98 | } 99 | 100 | var proxyServerWriteErr struct { 101 | sync.Mutex 102 | n uint64 103 | } 104 | 105 | var directServerReadErr struct { 106 | sync.Mutex 107 | n uint64 108 | } 109 | 110 | var directServerWriteErr struct { 111 | sync.Mutex 112 | n uint64 113 | } 114 | 115 | func incrAcceptErrors() { 116 | acceptErrors.Lock() 117 | acceptErrors.n++ 118 | acceptErrors.Unlock() 119 | } 120 | 121 | func numAcceptErrors() (uint64) { 122 | return acceptErrors.n 123 | } 124 | 125 | func incrAcceptSuccesses() { 126 | acceptSuccesses.Lock() 127 | acceptSuccesses.n++ 128 | acceptSuccesses.Unlock() 129 | } 130 | 131 | func numAcceptSuccesses() (uint64) { 132 | return acceptSuccesses.n 133 | } 134 | 135 | func incrGetOriginalDstErrors() { 136 | getOriginalDstErrors.Lock() 137 | getOriginalDstErrors.n++ 138 | getOriginalDstErrors.Unlock() 139 | } 140 | 141 | func numGetOriginalDstErrors() (uint64) { 142 | return getOriginalDstErrors.n 143 | } 144 | 145 | func incrDirectConnections() { 146 | directConnections.Lock() 147 | directConnections.n++ 148 | directConnections.Unlock() 149 | } 150 | 151 | func numDirectConnections() (uint64) { 152 | return directConnections.n 153 | } 154 | 155 | func incrProxiedConnections() { 156 | proxiedConnections.Lock() 157 | proxiedConnections.n++ 158 | proxiedConnections.Unlock() 159 | } 160 | 161 | func numProxiedConnections() (uint64) { 162 | return proxiedConnections.n 163 | } 164 | 165 | func incrProxy200Responses() { 166 | proxy200Responses.Lock() 167 | proxy200Responses.n++ 168 | proxy200Responses.Unlock() 169 | } 170 | 171 | func numProxy200Responses() (uint64) { 172 | return proxy200Responses.n 173 | } 174 | 175 | func incrProxy300Responses() { 176 | proxy300Responses.Lock() 177 | proxy300Responses.n++ 178 | proxy300Responses.Unlock() 179 | } 180 | 181 | func numProxy300Responses() (uint64) { 182 | return proxy300Responses.n 183 | } 184 | 185 | func incrProxy400Responses() { 186 | proxy400Responses.Lock() 187 | proxy400Responses.n++ 188 | proxy400Responses.Unlock() 189 | } 190 | 191 | func numProxy400Responses() (uint64) { 192 | return proxy400Responses.n 193 | } 194 | 195 | func incrProxyNon200Responses() { 196 | proxyNon200Responses.Lock() 197 | proxyNon200Responses.n++ 198 | proxyNon200Responses.Unlock() 199 | } 200 | 201 | func numProxyNon200Responses() (uint64) { 202 | return proxyNon200Responses.n 203 | } 204 | 205 | func incrProxyNoConnectResponses() { 206 | proxyNoConnectResponses.Lock() 207 | proxyNoConnectResponses.n++ 208 | proxyNoConnectResponses.Unlock() 209 | } 210 | 211 | func numProxyNoConnectResponses() (uint64) { 212 | return proxyNoConnectResponses.n 213 | } 214 | 215 | func incrProxyServerReadErr() { 216 | proxyServerReadErr.Lock() 217 | proxyServerReadErr.n++ 218 | proxyServerReadErr.Unlock() 219 | } 220 | 221 | func numProxyServerReadErr() (uint64) { 222 | return proxyServerReadErr.n 223 | } 224 | 225 | func incrProxyServerWriteErr() { 226 | proxyServerWriteErr.Lock() 227 | proxyServerWriteErr.n++ 228 | proxyServerWriteErr.Unlock() 229 | } 230 | 231 | func numProxyServerWriteErr() (uint64) { 232 | return proxyServerWriteErr.n 233 | } 234 | 235 | func incrDirectServerReadErr() { 236 | directServerReadErr.Lock() 237 | directServerReadErr.n++ 238 | directServerReadErr.Unlock() 239 | } 240 | 241 | func numDirectServerReadErr() (uint64) { 242 | return directServerReadErr.n 243 | } 244 | 245 | func incrDirectServerWriteErr() { 246 | directServerWriteErr.Lock() 247 | directServerWriteErr.n++ 248 | directServerWriteErr.Unlock() 249 | } 250 | 251 | func numDirectServerWriteErr()(uint64) { 252 | return directServerWriteErr.n 253 | } 254 | 255 | func setupStats() { 256 | c := make(chan os.Signal, 1) 257 | signal.Notify(c, syscall.SIGUSR1) 258 | go func() { 259 | for _ = range c { 260 | f, err := os.Create(STATSFILE) 261 | if err != nil { 262 | log.Infof("ERR: Could not open stats file \"%s\": %v", STATSFILE, err) 263 | continue 264 | } 265 | fmt.Fprintf(f, "%s\n\n", versionString()) 266 | fmt.Fprintf(f, "STATISTICS as of %v:\n", time.Now().Format(time.UnixDate)) 267 | fmt.Fprintf(f, " Go version: %v\n", runtime.Version()) 268 | fmt.Fprintf(f, " Number of logical CPUs on system: %v\n", runtime.NumCPU()) 269 | fmt.Fprintf(f, " GOMAXPROCS: %v\n", runtime.GOMAXPROCS(-1)) 270 | fmt.Fprintf(f, " Goroutines currently running: %v\n", runtime.NumGoroutine()) 271 | fmt.Fprintf(f, " Number of cgo calls made by any_proxy: %v\n", runtime.NumCgoCall()) 272 | fmt.Fprintf(f, "\n") 273 | fmt.Fprintf(f, " accept successes: %v\n", numAcceptSuccesses()) 274 | fmt.Fprintf(f, " accept errors: %v\n", numAcceptErrors()) 275 | fmt.Fprintf(f, " getsockopt(SO_ORIGINAL_DST) errors: %v\n", numGetOriginalDstErrors()) 276 | fmt.Fprintf(f, "\n") 277 | fmt.Fprintf(f, " connections sent directly: %v\n", numDirectConnections()) 278 | fmt.Fprintf(f, " direct connection read errors: %v\n", numDirectServerReadErr()) 279 | fmt.Fprintf(f, " direct connection write errors: %v\n", numDirectServerWriteErr()) 280 | fmt.Fprintf(f, "\n") 281 | fmt.Fprintf(f, " connections sent to upstream proxy: %v\n", numProxiedConnections()) 282 | fmt.Fprintf(f, " proxy connection read errors: %v\n", numProxyServerReadErr()) 283 | fmt.Fprintf(f, " proxy connection write errors: %v\n", numProxyServerWriteErr()) 284 | fmt.Fprintf(f, " code 200 response from upstream: %v\n", numProxy200Responses()) 285 | fmt.Fprintf(f, " code 400 response from upstream: %v\n", numProxy400Responses()) 286 | fmt.Fprintf(f, "other (non 200/400) response from upstream: %v\n", numProxyNon200Responses()) 287 | fmt.Fprintf(f, " no response to CONNECT from upstream: %v\n", numProxyNoConnectResponses()) 288 | f.Close() 289 | } 290 | }() 291 | } 292 | 293 | 294 | -------------------------------------------------------------------------------- /any_proxy_test.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "net" 5 | "testing" 6 | ) 7 | 8 | //func TestNilCopy(t *testing.T) { 9 | // var dstname string = "destination" 10 | // var srcname string = "source" 11 | // copy(nil, nil, dstname, srcname) 12 | //} 13 | 14 | func TestNilClientToGetOriginalDst(t *testing.T) { 15 | getOriginalDst(nil) 16 | } 17 | 18 | func TestNilClientToHandleConnection(t *testing.T) { 19 | handleConnection(nil) 20 | } 21 | 22 | func TestNilClientToHandleDirectConnection(t *testing.T) { 23 | var ipv4 string = "1.2.3.4" 24 | var port uint16 = 8999 25 | 26 | // set up 27 | gDirects = "1.2.3.4" 28 | dirFuncs := buildDirectors(gDirects) 29 | director = getDirector(dirFuncs) 30 | 31 | handleDirectConnection(nil, ipv4, port) 32 | } 33 | 34 | func TestNilClientToHandleProxyConnection(t *testing.T) { 35 | var ipv4 string = "2.3.4.5" 36 | var port uint16 = 8999 37 | handleProxyConnection(nil, ipv4, port) 38 | } 39 | 40 | // when a &net.TCPConn{} is created, the underlying fd is set to nil. 41 | // make sure we can handle this situation without a panic (it has occurred before) 42 | func TestEmptyFdToGetOriginalDst(t *testing.T) { 43 | var c1 *net.TCPConn 44 | c1 = &net.TCPConn{} 45 | getOriginalDst(c1) 46 | } 47 | 48 | func TestEmptyFdToHandleConnection(t *testing.T) { 49 | var c1 *net.TCPConn 50 | c1 = &net.TCPConn{} 51 | handleConnection(c1) 52 | } 53 | 54 | func TestEmptyFdToHandleDirectConnection(t *testing.T) { 55 | var ipv4 string = "1.2.3.4" 56 | var port uint16 = 8999 57 | 58 | // set up 59 | gDirects = "1.2.3.4" 60 | dirFuncs := buildDirectors(gDirects) 61 | director = getDirector(dirFuncs) 62 | 63 | var c1 *net.TCPConn 64 | c1 = &net.TCPConn{} 65 | handleDirectConnection(c1, ipv4, port) 66 | } 67 | 68 | func TestEmptyFdToHandleProxyConnection(t *testing.T) { 69 | var ipv4 string = "2.3.4.5" 70 | var port uint16 = 8999 71 | var c1 *net.TCPConn 72 | c1 = &net.TCPConn{} 73 | handleProxyConnection(c1, ipv4, port) 74 | } 75 | 76 | 77 | // Test if direct connections are working 78 | // Should catch issue #11 if it occurs again 79 | // (shared memory issue related to the -d cmd line option) 80 | func TestDirectConnectionFlags(t *testing.T) { 81 | // Test with the equivalent of a single IP address in the -d arg: -d 1.2.3.4 82 | gDirects = "1.2.3.4" 83 | dirFuncs := buildDirectors(gDirects) 84 | director = getDirector(dirFuncs) 85 | 86 | ipv4 := net.ParseIP("1.2.3.4") 87 | wentDirect,_ := director(&ipv4) 88 | if wentDirect == false { 89 | t.Errorf("The IP address %s should have been sent direct, but instead was proxied", ipv4) 90 | } 91 | 92 | // now make sure an address that should be proxied still works 93 | ipv4 = net.ParseIP("4.5.6.7") 94 | wentDirect,_ = director(&ipv4) 95 | if wentDirect == true { 96 | t.Errorf("The IP address %s should have been sent to an upstream proxy, but instead was sent directly", ipv4) 97 | } 98 | 99 | 100 | // Test with the equivalent of a multiple IP addresses in the -d arg: -d 1.2.3.4,2.3.4.5 101 | gDirects = "1.2.3.4,2.3.4.5" 102 | dirFuncs = buildDirectors(gDirects) 103 | director = getDirector(dirFuncs) 104 | 105 | addrsToTest := []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("2.3.4.5")} 106 | for _,ipv4 = range addrsToTest { 107 | wentDirect,_ := director(&ipv4) 108 | if wentDirect == false { 109 | t.Errorf("The IP address %s should have been sent direct, but instead was proxied", ipv4) 110 | } 111 | } 112 | 113 | // now make sure an address that should be proxied still works 114 | ipv4 = net.ParseIP("4.5.6.7") 115 | wentDirect,_ = director(&ipv4) 116 | if wentDirect == true { 117 | t.Errorf("The IP address %s should have been sent to an upstream proxy, but instead was sent directly", ipv4) 118 | } 119 | 120 | 121 | // Test with the equivalent of multiple IP address specs in the -d arg: -d 1.2.3.0/24,2.3.4.0/25,4.4.4.4" 122 | gDirects = "1.2.3.0/24,2.3.4.0/25,4.4.4.4" 123 | dirFuncs = buildDirectors(gDirects) 124 | director = getDirector(dirFuncs) 125 | 126 | addrsToTest = []net.IP{net.ParseIP("1.2.3.4"), net.ParseIP("1.2.3.254"), net.ParseIP("2.3.4.5"), net.ParseIP("4.4.4.4")} 127 | for _,ipv4 = range addrsToTest { 128 | wentDirect,_ := director(&ipv4) 129 | if wentDirect == false { 130 | t.Errorf("The IP address %s should have been sent direct, but instead was proxied", ipv4) 131 | } 132 | } 133 | 134 | // now make sure an address that should be proxied still works 135 | addrsToTest = []net.IP{net.ParseIP("4.5.6.7"), net.ParseIP("2.3.4.254")} 136 | for _,ipv4 = range addrsToTest { 137 | wentDirect,_ = director(&ipv4) 138 | if wentDirect == true { 139 | t.Errorf("The IP address %s should have been sent to an upstream proxy, but instead was sent directly", ipv4) 140 | } 141 | } 142 | } 143 | 144 | // benchmark when we have 1 direct. The address we are testing against is one that 145 | // will not match any directs, just to make sure we search through all directs 146 | func BenchmarkDirector1(b *testing.B) { 147 | gDirects = "1.2.3.0/24" 148 | dirFuncs := buildDirectors(gDirects) 149 | director := getDirector(dirFuncs) 150 | 151 | ipv4 := net.ParseIP("255.255.255.0") 152 | for n := 0; n < b.N; n++ { 153 | director(&ipv4) 154 | } 155 | } 156 | 157 | func BenchmarkDirector2(b *testing.B) { 158 | gDirects = "1.0.0.0/24,2.0.0.0/24" 159 | dirFuncs := buildDirectors(gDirects) 160 | director := getDirector(dirFuncs) 161 | 162 | ipv4 := net.ParseIP("255.255.255.0") 163 | for n := 0; n < b.N; n++ { 164 | director(&ipv4) 165 | } 166 | } 167 | 168 | func BenchmarkDirector3(b *testing.B) { 169 | gDirects = "1.0.0.0/24,2.0.0.0/24,3.0.0.0/24" 170 | dirFuncs := buildDirectors(gDirects) 171 | director := getDirector(dirFuncs) 172 | 173 | ipv4 := net.ParseIP("255.255.255.0") 174 | for n := 0; n < b.N; n++ { 175 | director(&ipv4) 176 | } 177 | } 178 | 179 | func BenchmarkDirector4(b *testing.B) { 180 | gDirects = "1.0.0.0/24,2.0.0.0/24,3.0.0.0/24,4.0.0.0/24" 181 | dirFuncs := buildDirectors(gDirects) 182 | director := getDirector(dirFuncs) 183 | 184 | ipv4 := net.ParseIP("255.255.255.0") 185 | for n := 0; n < b.N; n++ { 186 | director(&ipv4) 187 | } 188 | } 189 | 190 | func BenchmarkDirector5(b *testing.B) { 191 | gDirects = "1.0.0.0/24,2.0.0.0/24,3.0.0.0/24,4.0.0.0/24,5.0.0.0/24" 192 | dirFuncs := buildDirectors(gDirects) 193 | director := getDirector(dirFuncs) 194 | 195 | ipv4 := net.ParseIP("255.255.255.0") 196 | for n := 0; n < b.N; n++ { 197 | director(&ipv4) 198 | } 199 | } 200 | 201 | func BenchmarkDirector10(b *testing.B) { 202 | gDirects = "1.0.0.0/24,2.0.0.0/24,3.0.0.0/24,4.0.0.0/24,5.0.0.0/24," 203 | gDirects += "6.0.0.0/24,7.0.0.0/24,8.0.0.0/24,9.0.0.0/24,10.0.0.0/24" 204 | dirFuncs := buildDirectors(gDirects) 205 | director := getDirector(dirFuncs) 206 | 207 | ipv4 := net.ParseIP("255.255.255.0") 208 | for n := 0; n < b.N; n++ { 209 | director(&ipv4) 210 | } 211 | } 212 | 213 | func BenchmarkDirector100(b *testing.B) { 214 | gDirects = "1.0.0.0/24,2.0.0.0/24,3.0.0.0/24,4.0.0.0/24,5.0.0.0/24," 215 | gDirects += "6.0.0.0/24,7.0.0.0/24,8.0.0.0/24,9.0.0.0/24,10.0.0.0/24," 216 | gDirects += "11.0.0.0/24,12.0.0.0/24,13.0.0.0/24,14.0.0.0/24,15.0.0.0/24," 217 | gDirects += "16.0.0.0/24,17.0.0.0/24,18.0.0.0/24,19.0.0.0/24,20.0.0.0/24," 218 | gDirects += "21.0.0.0/24,22.0.0.0/24,23.0.0.0/24,24.0.0.0/24,25.0.0.0/24," 219 | gDirects += "26.0.0.0/24,27.0.0.0/24,28.0.0.0/24,29.0.0.0/24,30.0.0.0/24," 220 | gDirects += "31.0.0.0/24,32.0.0.0/24,33.0.0.0/24,34.0.0.0/24,35.0.0.0/24," 221 | gDirects += "36.0.0.0/24,37.0.0.0/24,38.0.0.0/24,39.0.0.0/24,40.0.0.0/24," 222 | gDirects += "41.0.0.0/24,42.0.0.0/24,43.0.0.0/24,44.0.0.0/24,45.0.0.0/24," 223 | gDirects += "46.0.0.0/24,47.0.0.0/24,48.0.0.0/24,49.0.0.0/24,50.0.0.0/24," 224 | gDirects += "51.0.0.0/24,52.0.0.0/24,53.0.0.0/24,54.0.0.0/24,55.0.0.0/24," 225 | gDirects += "56.0.0.0/24,57.0.0.0/24,58.0.0.0/24,59.0.0.0/24,60.0.0.0/24," 226 | gDirects += "61.0.0.0/24,62.0.0.0/24,63.0.0.0/24,64.0.0.0/24,65.0.0.0/24," 227 | gDirects += "66.0.0.0/24,67.0.0.0/24,68.0.0.0/24,69.0.0.0/24,70.0.0.0/24," 228 | gDirects += "71.0.0.0/24,72.0.0.0/24,73.0.0.0/24,74.0.0.0/24,75.0.0.0/24," 229 | gDirects += "76.0.0.0/24,77.0.0.0/24,78.0.0.0/24,79.0.0.0/24,80.0.0.0/24," 230 | gDirects += "81.0.0.0/24,82.0.0.0/24,83.0.0.0/24,84.0.0.0/24,85.0.0.0/24," 231 | gDirects += "86.0.0.0/24,87.0.0.0/24,88.0.0.0/24,89.0.0.0/24,90.0.0.0/24," 232 | gDirects += "91.0.0.0/24,92.0.0.0/24,93.0.0.0/24,94.0.0.0/24,95.0.0.0/24," 233 | gDirects += "96.0.0.0/24,97.0.0.0/24,98.0.0.0/24,99.0.0.0/24,100.0.0.0/24" 234 | dirFuncs := buildDirectors(gDirects) 235 | director := getDirector(dirFuncs) 236 | 237 | ipv4 := net.ParseIP("255.255.255.0") 238 | for n := 0; n < b.N; n++ { 239 | director(&ipv4) 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /dnsproxy.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | logger "github.com/zdannar/flogger" 5 | "github.com/miekg/dns" 6 | "os" 7 | "bufio" 8 | "io" 9 | "fmt" 10 | "errors" 11 | "strings" 12 | "net" 13 | "syscall" 14 | "time" 15 | "strconv" 16 | ) 17 | 18 | type dnsProxy struct { 19 | addr string 20 | remoteAddr string 21 | dnsServer *dns.Server 22 | } 23 | 24 | var iptablesIntegration = os.Getuid() == 0 25 | 26 | func NewDnsProxy(addr, remoteAddr string) *dnsProxy { 27 | proxy := new(dnsProxy) 28 | proxy.addr = addr 29 | proxy.remoteAddr = remoteAddr 30 | h := proxy.newDnsHandler() 31 | proxy.dnsServer = &dns.Server{Addr: addr, Net: "udp", Handler: h} 32 | return proxy 33 | } 34 | 35 | func (proxy *dnsProxy) newDnsHandler() dns.Handler { 36 | return dns.HandlerFunc(func(w dns.ResponseWriter, m *dns.Msg) { 37 | logger.Debugf("Got question %v", m.Question) 38 | var qName string 39 | for _, qm := range m.Question { 40 | qName = qm.Name 41 | } 42 | var remoteAddr string 43 | if iptablesIntegration { 44 | var err error 45 | if remoteAddr, err = getOriginalUdpDst(w); err != nil { 46 | if proxy.remoteAddr == "" { 47 | logger.Errorf("Abort DNS resolving for no origin dist: %v", err) 48 | dns.HandleFailed(w, m) 49 | return 50 | } else { 51 | logger.Warningf("Cannot get origin dist, use default %v as remote address: %v", proxy.remoteAddr, err) 52 | } 53 | remoteAddr = proxy.remoteAddr 54 | } 55 | } else { 56 | remoteAddr = proxy.remoteAddr 57 | } 58 | if r, err := doDnsExchange(m, remoteAddr); err != nil { 59 | logger.Warningf("failed query remote dns %q: %v", remoteAddr, err) 60 | dns.HandleFailed(w, m) 61 | } else { 62 | if r.Rcode != dns.RcodeSuccess { 63 | logger.Debugf("failed query %v: status=%v, id=%v", m.Question, dns.RcodeToString[r.Rcode], r.Id) 64 | } else { 65 | logger.Debugf("Get reply for query %v", m.Question) 66 | } 67 | for _, am := range r.Answer { 68 | h := am.Header() 69 | var ip string 70 | switch rr := am.(type) { 71 | case *dns.A: 72 | ip = rr.A.String() 73 | default: 74 | ip = "" 75 | } 76 | if h.Rrtype == dns.TypeA && h.Class == dns.ClassINET && qName != "" && ip != "" { 77 | logger.Debugf("Store ip %v for hostname %v with ttl %v+300", ip, qName, int(h.Ttl)) 78 | gReverseLookupCache.storeTtl(ip, qName, int(h.Ttl+300)) 79 | } 80 | } 81 | // make sure compress before write msg to keep len <= 512 (DNS UDP size limit) 82 | r.Compress = true 83 | w.WriteMsg(r) 84 | } 85 | }) 86 | } 87 | func doDnsExchange(msg *dns.Msg, remoteAddr string) (*dns.Msg, error) { 88 | conn, err := dialUdp(remoteAddr) 89 | if err != nil { 90 | return nil, err 91 | } 92 | localAddr := conn.LocalAddr().String() 93 | logger.Debugf("Start DNS msg from %v to %v", localAddr, remoteAddr) 94 | msgSize := uint16(0) 95 | opt := msg.IsEdns0() 96 | if opt != nil && opt.UDPSize() >= dns.MinMsgSize { 97 | msgSize = opt.UDPSize() 98 | } 99 | co := &dns.Conn{Conn: conn, UDPSize: msgSize} 100 | defer co.Close() 101 | co.SetWriteDeadline(time.Now().Add(dnsTimeout)) 102 | if err = co.WriteMsg(msg); err != nil { 103 | return nil, err 104 | } 105 | co.SetReadDeadline(time.Now().Add(dnsTimeout)) 106 | return co.ReadMsg() 107 | } 108 | 109 | const dnsTimeout = 5 * time.Second 110 | 111 | func dialUdp(remoteAddr string) (*net.UDPConn, error) { 112 | fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_DGRAM, syscall.IPPROTO_UDP) 113 | if err != nil { 114 | return nil, err 115 | } 116 | if iptablesIntegration { 117 | err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, gIpTableMark) 118 | if err != nil { 119 | logger.Debugf("Cannot set sockopt with mark %v: %v", gIpTableMark, err) 120 | syscall.Close(fd) 121 | return nil, err 122 | } 123 | } 124 | ua, err := net.ResolveUDPAddr("udp", remoteAddr) 125 | if err != nil { 126 | logger.Errorf("Cannot resolve UDP addr %v: %v", remoteAddr, err) 127 | syscall.Close(fd) 128 | return nil, err 129 | } 130 | sa := udpAddrToSockaddr(ua) 131 | err = syscall.Connect(fd, sa) 132 | if err != nil { 133 | logger.Errorf("Cannot Connect UDP: %v", err) 134 | syscall.Close(fd) 135 | return nil, err 136 | } 137 | file := os.NewFile(uintptr(fd), "") 138 | conn, err := net.FileConn(file) 139 | // duplicate file created need to close 140 | if closeErr := file.Close(); closeErr != nil { 141 | logger.Errorf("Cannot close file %v: %v", fd, closeErr) 142 | } 143 | if err != nil { 144 | logger.Errorf("Cannot create connection by fd %v: %v", fd, err) 145 | return nil, err 146 | } 147 | if udpConn, ok := conn.(*net.UDPConn); ok { 148 | return udpConn, err 149 | } 150 | return nil, errors.New("invalid connection type") 151 | } 152 | 153 | func udpAddrToSockaddr(addr *net.UDPAddr) syscall.Sockaddr { 154 | sa := ipAndZoneToSockaddr(addr.IP, addr.Zone) 155 | switch sa := sa.(type) { 156 | case *syscall.SockaddrInet4: 157 | sa.Port = addr.Port 158 | return sa 159 | case *syscall.SockaddrInet6: 160 | sa.Port = addr.Port 161 | return sa 162 | default: 163 | return nil 164 | } 165 | } 166 | 167 | func getOriginalUdpDst(w dns.ResponseWriter) (string, error) { 168 | track := new(udpTrack) 169 | track.r_dst = w.RemoteAddr().String() 170 | track.proxyPort = strconv.Itoa((w.LocalAddr()).(*net.UDPAddr).Port) 171 | if err := getMatchedOriginalUdpDst(track); err != nil { 172 | msg := fmt.Sprintf("cannot get matched original udp dsk from track %v: %v", track, err) 173 | logger.Debugf(msg) 174 | return "", errors.New(msg) 175 | } 176 | logger.Debugf("Get Original Udp Dst: %v", track.l_dst) 177 | return track.l_dst, nil 178 | } 179 | 180 | func (proxy *dnsProxy) ListenAndServe(block bool) error { 181 | if !block { 182 | c := make(chan error) 183 | proxy.dnsServer.NotifyStartedFunc = func() { 184 | logger.Debugf("dns server is started at %v.", proxy.dnsServer.PacketConn.LocalAddr()) 185 | c <- nil 186 | close(c) 187 | } 188 | go func() { 189 | if err := proxy.dnsServer.ListenAndServe(); err != nil { 190 | logger.Warningf("dns server start error: %v", err) 191 | c <- err 192 | close(c) 193 | } 194 | }() 195 | err, ok := <-c 196 | if !ok { 197 | return nil 198 | } 199 | logger.Debug("finish dns server listen") 200 | return err 201 | } else { 202 | proxy.dnsServer.NotifyStartedFunc = func() { 203 | logger.Debugf("dns server is started at %v.", proxy.addr) 204 | } 205 | return proxy.dnsServer.ListenAndServe() 206 | } 207 | } 208 | 209 | func (proxy *dnsProxy) Close() error { 210 | logger.Debug("Close dns server") 211 | return proxy.dnsServer.Shutdown() 212 | } 213 | 214 | func getMatchedOriginalUdpDst(ut *udpTrack) error { 215 | f, err := os.Open("/proc/self/net/nf_conntrack") 216 | if err != nil { 217 | if os.IsPermission(err) { 218 | logger.Debugf("Cannot open file due to permission, use 'root' to get original destination: %v", err) 219 | } else { 220 | logger.Errorf("Cannot open file: %v", err) 221 | } 222 | return err 223 | } 224 | defer f.Close() 225 | reader := bufio.NewReader(f) 226 | lines := make([]string, 0, 10) 227 | for { 228 | line, err := reader.ReadString('\n') 229 | var track *udpTrack 230 | if err == io.EOF { 231 | lines = append(lines, line) 232 | track = parseConnTrackLine(line) 233 | if track != nil && track.matches(ut) { 234 | logger.Debug(line) 235 | ut.l_dst = track.l_dst 236 | return nil 237 | } 238 | for _, l := range lines { 239 | logger.Debugf("/proc/self/net/nf_conntrack>> %v", l) 240 | } 241 | return errors.New("no original dst found from nf_conntrack") 242 | } else if err != nil { 243 | return err 244 | } 245 | lines = append(lines, line) 246 | track = parseConnTrackLine(line) 247 | if track != nil && track.matches(ut) { 248 | ut.l_dst = track.l_dst 249 | return nil 250 | } 251 | } 252 | } 253 | 254 | type udpTrack struct { 255 | l_src, l_dst, r_src, r_dst, proxyPort string 256 | } 257 | 258 | func parseConnTrackLine(line string) *udpTrack { 259 | if !strings.Contains(line, " udp ") { 260 | return nil 261 | } 262 | scanner := bufio.NewScanner(strings.NewReader(line)) 263 | scanner.Split(bufio.ScanWords) 264 | var srcSet, sportSet, dstSet, dportSet bool 265 | track := new(udpTrack) 266 | for scanner.Scan() { 267 | txt := scanner.Text() 268 | idx := strings.IndexByte(txt, '=') 269 | if idx > 0 { 270 | left := txt[:idx] 271 | right := txt[idx+1:] 272 | switch { 273 | case left == "src" && !srcSet: 274 | srcSet = true 275 | track.l_src = right 276 | case left == "src": 277 | srcSet = true 278 | track.r_src = right 279 | case left == "sport" && !sportSet: 280 | sportSet = true 281 | track.l_src += ":" + right 282 | case left == "sport": 283 | track.r_src += ":" + right 284 | track.proxyPort = right 285 | case left == "dst" && !dstSet: 286 | dstSet = true 287 | track.l_dst = right 288 | case left == "dport" && !dportSet: 289 | dportSet = true 290 | track.l_dst += ":" + right 291 | case left == "dst" && dstSet: 292 | track.r_dst = right 293 | case left == "dport" && dportSet: 294 | track.r_dst += ":" + right 295 | } 296 | } 297 | } 298 | // logger.Debugf("Parse line %q to %q", line, track) 299 | return track 300 | } 301 | 302 | func (track *udpTrack) String() string { 303 | return fmt.Sprintf("l_src=%v, l_dst=%v, r_src=%v, r_dst=%v, proxyPort=%v", track.l_src, track.l_dst, track.r_src, track.r_dst, track.proxyPort) 304 | } 305 | 306 | func (track *udpTrack) matches(another *udpTrack) bool { 307 | return track.r_dst == another.r_dst && track.proxyPort == another.proxyPort 308 | } 309 | -------------------------------------------------------------------------------- /any_proxy.go: -------------------------------------------------------------------------------- 1 | // 2 | // any_proxy.go - Transparently proxy a connection using Linux iptables REDIRECT 3 | // 4 | // Copyright (C) 2013 Ryan A. Chapman. All rights reserved. 5 | // 6 | // Redistribution and use in source and binary forms, with or without 7 | // modification, are permitted provided that the following conditions are met: 8 | // 9 | // 1. Redistributions of source code must retain the above copyright notice, 10 | // this list of conditions and the following disclaimer. 11 | // 12 | // 2. Redistributions in binary form must reproduce the above copyright notice, 13 | // this list of conditions and the following disclaimer in the documentation 14 | // and/or other materials provided with the distribution. 15 | // 16 | // THE SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 17 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 18 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS 19 | // OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 20 | // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 21 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 22 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 23 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 24 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 25 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | // 27 | // 28 | // 29 | // Tested to 2000 connections/second. If you turn off logging, you can get 10,000/sec. So logging needs 30 | // to be changed to nonblocking one day. 31 | // 32 | // TODO: 33 | // add num of connected clients to stats 34 | // add ability to print details of each connected client (src,dst,proxy or direct addr) to stats 35 | // 36 | // Ryan A. Chapman, ryan@rchapman.org 37 | // Sun Apr 7 21:04:34 MDT 2013 38 | // 39 | 40 | package main 41 | 42 | import ( 43 | "bufio" 44 | "errors" 45 | "flag" 46 | "fmt" 47 | "github.com/viki-org/dnscache" 48 | log "github.com/zdannar/flogger" 49 | "golang.org/x/net/proxy" 50 | "io" 51 | "net" 52 | "os" 53 | "os/signal" 54 | "reflect" 55 | "runtime" 56 | "runtime/pprof" 57 | "strconv" 58 | "strings" 59 | "sync" 60 | "syscall" 61 | "time" 62 | ) 63 | 64 | const VERSION = "1.2" 65 | const SO_ORIGINAL_DST = 80 66 | const DEFAULTLOG = "/var/log/any_proxy.log" 67 | const STATSFILE = "/var/log/any_proxy.stats" 68 | 69 | var gListenAddrPort string 70 | var gProxyServerSpec string 71 | var gDirects string 72 | var gVerbosity int 73 | var gSkipCheckUpstreamsReachable int 74 | var gProxyServers []*Proxy 75 | var gLogfile string 76 | var gCpuProfile string 77 | var gMemProfile string 78 | var gClientRedirects int 79 | var gReverseLookups int 80 | var gIpTableMark int 81 | var gDnsListenAddrPort string 82 | var gProxyConfigFile string 83 | 84 | type cacheEntry struct { 85 | hostname string 86 | expires time.Time 87 | } 88 | type reverseLookupCache struct { 89 | hostnames sync.Map 90 | keys []string 91 | next int 92 | } 93 | 94 | func NewReverseLookupCache() *reverseLookupCache { 95 | return &reverseLookupCache{ 96 | keys: make([]string, 65536), 97 | } 98 | } 99 | func (c *reverseLookupCache) lookup(ipv4 string) string { 100 | hit, ok := c.hostnames.Load(ipv4) 101 | if !ok { 102 | log.Debugf("lookup(): CACHE_MISS") 103 | return "" 104 | } 105 | if hit, ok := hit.(*cacheEntry); ok { 106 | if hit.expires.After(time.Now()) { 107 | return hit.hostname 108 | } else { 109 | log.Debugf("lookup(): CACHE_EXPIRED") 110 | c.hostnames.Delete(ipv4) 111 | } 112 | } 113 | return "" 114 | } 115 | 116 | func (c *reverseLookupCache) store(ipv4, hostname string) { 117 | c.storeTtl(ipv4, hostname, int(time.Hour/time.Second)) 118 | } 119 | 120 | func (c *reverseLookupCache) storeTtl(ipv4, hostname string, ttl int) { 121 | c.hostnames.Delete(c.keys[c.next]) 122 | c.keys[c.next] = ipv4 123 | c.next = (c.next + 1) & 65535 124 | c.hostnames.Store(ipv4, &cacheEntry{hostname: hostname, expires: time.Now().Add(time.Duration(ttl) * time.Second)}) 125 | } 126 | 127 | func ListHostNames() map[string]string { 128 | c := gReverseLookupCache 129 | m := make(map[string]string) 130 | c.hostnames.Range(func(ipv4, entry interface{}) bool { 131 | m[entry.(*cacheEntry).hostname] = ipv4.(string) 132 | return true 133 | }) 134 | return m 135 | } 136 | 137 | func GetHostName(ip string) string { 138 | hostname := gReverseLookupCache.lookup(ip) 139 | return hostname 140 | } 141 | 142 | var gReverseLookupCache = NewReverseLookupCache() 143 | 144 | type directorFunc func(*net.IP) bool 145 | 146 | var director func(*net.IP) (bool, int) 147 | 148 | var proxyResolver = func(ipv4 string, port uint16, defaultProxyList []*Proxy) []*Proxy { 149 | return defaultProxyList 150 | } 151 | 152 | func init() { 153 | flag.Usage = func() { 154 | fmt.Fprintf(os.Stdout, "%s\n\n", versionString()) 155 | fmt.Fprintf(os.Stdout, "usage: %s -l listenaddress -p proxies [-d directs] [-v=N] [-f file] [-c file] [-m file] [-k mark]\n", os.Args[0]) 156 | fmt.Fprintf(os.Stdout, " Proxies any tcp port transparently using Linux netfilter\n\n") 157 | fmt.Fprintf(os.Stdout, "Mandatory\n") 158 | fmt.Fprintf(os.Stdout, " -l=ADDRPORT Address and port to listen on (e.g., :3128 or 127.0.0.1:3128)\n") 159 | fmt.Fprintf(os.Stdout, "Optional\n") 160 | fmt.Fprintf(os.Stdout, " -c=FILE Write a CPU profile to FILE. The pprof program, which is part of Golang's\n") 161 | fmt.Fprintf(os.Stdout, " standard pacakge, can be used to interpret the results. You can invoke pprof\n") 162 | fmt.Fprintf(os.Stdout, " with \"go tool pprof\"\n") 163 | fmt.Fprintf(os.Stdout, " -d=DIRECTS List of IP addresses that the proxy should send to directly instead of\n") 164 | fmt.Fprintf(os.Stdout, " to the upstream proxies (e.g., -d 10.1.1.1,10.1.1.2)\n") 165 | fmt.Fprintf(os.Stdout, " -f=FILE Log file. If not specified, defaults to %s\n", DEFAULTLOG) 166 | fmt.Fprintf(os.Stdout, " -h This usage message\n") 167 | fmt.Fprintf(os.Stdout, " -k=MARK The iptable mark value to pass through in case of loop proxy\n") 168 | fmt.Fprintf(os.Stdout, " -m=FILE Write a memory profile to FILE. This file can also be interpreted by golang's pprof\n\n") 169 | fmt.Fprintf(os.Stdout, " -p=PROXIES Address and ports of upstream proxy servers to use\n") 170 | fmt.Fprintf(os.Stdout, " Multiple address/ports can be specified by separating with commas\n") 171 | fmt.Fprintf(os.Stdout, " (e.g., 10.1.1.1:80,10.2.2.2:3128 would try to proxy requests to a\n") 172 | fmt.Fprintf(os.Stdout, " server listening on port 80 at 10.1.1.1 and if that failed, would\n") 173 | fmt.Fprintf(os.Stdout, " then try port 3128 at 10.2.2.2)\n") 174 | fmt.Fprintf(os.Stdout, " Note that requests are not load balanced. If a request fails to the\n") 175 | fmt.Fprintf(os.Stdout, " first proxy, then the second is tried and so on.\n\n") 176 | fmt.Fprintf(os.Stdout, " -r=1 Enable relaying of HTTP redirects from upstream to clients\n") 177 | fmt.Fprintf(os.Stdout, " -R=1 Enable reverse lookups of destination IP address and use hostname in CONNECT\n") 178 | fmt.Fprintf(os.Stdout, " request instead of the numeric IP if available. A local DNS server could be\n") 179 | fmt.Fprintf(os.Stdout, " configured to provide a reverse lookup of the forward lookup responses seen.\n") 180 | fmt.Fprintf(os.Stdout, " -s=1 Skip checking if upstream proxy servers are reachable on startup.\n") 181 | fmt.Fprintf(os.Stdout, " -v=1 Print debug information to logfile %s\n", DEFAULTLOG) 182 | fmt.Fprintf(os.Stdout, "any_proxy should be able to achieve 2000 connections/sec with logging on, 10k with logging off (-f=/dev/null).\n") 183 | fmt.Fprintf(os.Stdout, "Before starting any_proxy, be sure to change the number of available file handles to at least 65535\n") 184 | fmt.Fprintf(os.Stdout, "with \"ulimit -n 65535\"\n") 185 | fmt.Fprintf(os.Stdout, "Some other tunables that enable higher performance:\n") 186 | fmt.Fprintf(os.Stdout, " net.core.netdev_max_backlog = 2048\n") 187 | fmt.Fprintf(os.Stdout, " net.core.somaxconn = 1024\n") 188 | fmt.Fprintf(os.Stdout, " net.core.rmem_default = 8388608\n") 189 | fmt.Fprintf(os.Stdout, " net.core.rmem_max = 16777216\n") 190 | fmt.Fprintf(os.Stdout, " net.core.wmem_max = 16777216\n") 191 | fmt.Fprintf(os.Stdout, " net.ipv4.ip_local_port_range = 2000 65000\n") 192 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_window_scaling = 1\n") 193 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_max_syn_backlog = 3240000\n") 194 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_max_tw_buckets = 1440000\n") 195 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_mem = 50576 64768 98152\n") 196 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_rmem = 4096 87380 16777216\n") 197 | fmt.Fprintf(os.Stdout, " NOTE: if you see syn flood warnings in your logs, you need to adjust tcp_max_syn_backlog, tcp_synack_retries and tcp_abort_on_overflow\n") 198 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_syncookies = 1\n") 199 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_wmem = 4096 65536 16777216\n") 200 | fmt.Fprintf(os.Stdout, " net.ipv4.tcp_congestion_control = cubic\n\n") 201 | fmt.Fprintf(os.Stdout, "To obtain statistics, send any_proxy signal SIGUSR1. Current stats will be printed to %v\n", STATSFILE) 202 | fmt.Fprintf(os.Stdout, "Report bugs to .\n") 203 | } 204 | flag.StringVar(&gCpuProfile, "c", "", "Write cpu profile to file") 205 | flag.StringVar(&gDirects, "d", "", "IP addresses to go direct") 206 | flag.StringVar(&gLogfile, "f", "", "Log file") 207 | flag.StringVar(&gListenAddrPort, "l", "", "Address and port to listen on") 208 | flag.StringVar(&gMemProfile, "m", "", "Write mem profile to file") 209 | flag.StringVar(&gProxyServerSpec, "p", "", "Proxy servers to use, separated by commas. E.g. -p proxy1.tld.com:80,proxy2.tld.com:8080,proxy3.tld.com:80") 210 | flag.IntVar(&gClientRedirects, "r", 0, "Should we relay HTTP redirects from upstream proxies? -r=1 if we should.\n") 211 | flag.IntVar(&gReverseLookups, "R", 0, "Should we perform reverse lookups of destination IPs and use hostnames? -h=1 if we should.\n") 212 | flag.IntVar(&gSkipCheckUpstreamsReachable, "s", 0, "On startup, should we check if the upstreams are available? -s=0 means we should and if one is found to be not reachable, then remove it from the upstream list.\n") 213 | flag.IntVar(&gVerbosity, "v", 0, "Control level of logging. v=1 results in debugging info printed to the log.\n") 214 | flag.IntVar(&gIpTableMark, "k", 5, "Mark value set in proxy stream, default is 5.\n") 215 | flag.StringVar(&gDnsListenAddrPort, "dns", "", "Address and port for DNS Proxy to intercept name resolving.\n") 216 | flag.StringVar(&gProxyConfigFile, "pf", "", "Additional proxy configuration file for advanced proxy routing.\n") 217 | 218 | dirFuncs := buildDirectors(gDirects) 219 | director = getDirector(dirFuncs) 220 | } 221 | 222 | func versionString() (v string) { 223 | buildNum := strings.ToUpper(strconv.FormatInt(BUILDTIMESTAMP, 36)) 224 | buildDate := time.Unix(BUILDTIMESTAMP, 0).Format(time.UnixDate) 225 | goVersion := runtime.Version() 226 | v = fmt.Sprintf("any_proxy %s (build %v, %v by %v@%v) - %s", VERSION, buildNum, buildDate, BUILDUSER, BUILDHOST, goVersion) 227 | return 228 | } 229 | 230 | func buildDirectors(gDirects string) []directorFunc { 231 | // Generates a list of directorFuncs that are have "cached" values within 232 | // the scope of the functions. 233 | 234 | directorCidrs := strings.Split(gDirects, ",") 235 | directorFuncs := make([]directorFunc, len(directorCidrs)) 236 | 237 | for idx, directorCidr := range directorCidrs { 238 | //dstring := director 239 | var dfunc directorFunc 240 | if strings.Contains(directorCidr, "/") { 241 | _, directorIpNet, err := net.ParseCIDR(directorCidr) 242 | if err != nil { 243 | panic(fmt.Sprintf("\nUnable to parse CIDR string : %s : %s\n", directorCidr, err)) 244 | } 245 | dfunc = func(ptestip *net.IP) bool { 246 | testIp := *ptestip 247 | return directorIpNet.Contains(testIp) 248 | } 249 | directorFuncs[idx] = dfunc 250 | } else { 251 | var directorIp net.IP 252 | directorIp = net.ParseIP(directorCidr) 253 | dfunc = func(ptestip *net.IP) bool { 254 | var testIp net.IP 255 | testIp = *ptestip 256 | return testIp.Equal(directorIp) 257 | } 258 | directorFuncs[idx] = dfunc 259 | } 260 | 261 | } 262 | return directorFuncs 263 | } 264 | 265 | func getDirector(directors []directorFunc) func(*net.IP) (bool, int) { 266 | // getDirector: 267 | // Returns a function(directorFunc) that loops through internally held 268 | // directors evaluating each for possible matches. 269 | // 270 | // directorFunc: 271 | // Loops through directors and returns the (true, idx) where the index is 272 | // the sequential director that returned true. Else the function returns 273 | // (false, 0) if there are no directors to handle the ip. 274 | 275 | dFunc := func(ipaddr *net.IP) (bool, int) { 276 | for idx, dfunc := range directors { 277 | if dfunc(ipaddr) { 278 | return true, idx 279 | } 280 | } 281 | return false, 0 282 | } 283 | return dFunc 284 | } 285 | 286 | func setupProfiling() { 287 | // Make sure we have enough time to write profile's to disk, even if user presses Ctrl-C 288 | if gMemProfile == "" || gCpuProfile == "" { 289 | return 290 | } 291 | 292 | var profilef *os.File 293 | var err error 294 | if gMemProfile != "" { 295 | profilef, err = os.Create(gMemProfile) 296 | if err != nil { 297 | panic(err) 298 | } 299 | } 300 | 301 | if gCpuProfile != "" { 302 | f, err := os.Create(gCpuProfile) 303 | if err != nil { 304 | panic(err) 305 | } 306 | pprof.StartCPUProfile(f) 307 | } 308 | 309 | c := make(chan os.Signal, 1) 310 | signal.Notify(c, os.Interrupt) 311 | go func() { 312 | for range c { 313 | if gCpuProfile != "" { 314 | pprof.StopCPUProfile() 315 | } 316 | if gMemProfile != "" { 317 | pprof.WriteHeapProfile(profilef) 318 | profilef.Close() 319 | } 320 | time.Sleep(5000 * time.Millisecond) 321 | os.Exit(0) 322 | } 323 | }() 324 | } 325 | 326 | func setupLogging() { 327 | if gLogfile == "" { 328 | gLogfile = DEFAULTLOG 329 | } 330 | 331 | log.SetLevel(log.INFO) 332 | if gVerbosity != 0 { 333 | log.SetLevel(log.DEBUG) 334 | } 335 | 336 | if err := log.OpenFile(gLogfile, log.FLOG_APPEND, 0644); err != nil { 337 | log.Fatalf("Unable to open log file : %s", err) 338 | } 339 | } 340 | 341 | func main() { 342 | flag.Parse() 343 | if gListenAddrPort == "" { 344 | flag.Usage() 345 | os.Exit(1) 346 | } 347 | 348 | runtime.GOMAXPROCS(runtime.NumCPU() / 2) 349 | setupLogging() 350 | setupProfiling() 351 | setupStats() 352 | setupStackDump() 353 | 354 | dirFuncs := buildDirectors(gDirects) 355 | 356 | if gProxyConfigFile != "" { 357 | file, err := os.Open(gProxyConfigFile) 358 | if err != nil { 359 | panic(err) 360 | } 361 | log.Infof("Using proxy config file :%v", gProxyConfigFile) 362 | proxyConfig := NewProxyConfig(file) 363 | if err := file.Close(); err != nil { 364 | panic(err) 365 | } 366 | proxyResolver = proxyConfig.ResolveProxy 367 | dirFuncs = append(dirFuncs, proxyConfig.DirectorFunc(false)...) 368 | } 369 | 370 | director = getDirector(dirFuncs) 371 | 372 | log.RedirectStreams() 373 | 374 | if gDnsListenAddrPort != "" { 375 | dp := NewDnsProxy(gDnsListenAddrPort, "") 376 | if err := dp.ListenAndServe(false); err != nil { 377 | log.Warningf("Error open dns proxy on %v: %v", gDnsListenAddrPort, err) 378 | } 379 | defer dp.Close() 380 | log.Infof("Listening DNS proxy on %v", gDnsListenAddrPort) 381 | } 382 | 383 | // if user gave us upstream proxies, check and see if they are alive 384 | if gProxyServerSpec != "" { 385 | checkProxies() 386 | } 387 | 388 | lnaddr, err := net.ResolveTCPAddr("tcp", gListenAddrPort) 389 | if err != nil { 390 | panic(err) 391 | } 392 | 393 | listener, err := net.ListenTCP("tcp", lnaddr) 394 | if err != nil { 395 | panic(err) 396 | } 397 | defer listener.Close() 398 | log.Infof("Listening for connections on %v\n", listener.Addr()) 399 | 400 | for { 401 | conn, err := listener.AcceptTCP() 402 | if err != nil { 403 | log.Infof("Error accepting connection: %v\n", err) 404 | incrAcceptErrors() 405 | continue 406 | } 407 | incrAcceptSuccesses() 408 | log.Debugf("main(): Get new connection:%+v\n", conn.RemoteAddr()) 409 | go handleConnection(conn) 410 | } 411 | } 412 | 413 | func checkProxies() { 414 | var err error 415 | gProxyServers, err = ParseProxyList(gProxyServerSpec) 416 | if err != nil { 417 | log.Errorf("Invalid proxy list: %s", err) 418 | msg := "Parse proxy list failure. Exiting." 419 | log.Infof("%s\n", msg) 420 | fmt.Fprintf(os.Stderr, msg) 421 | os.Exit(1) 422 | } 423 | // make sure proxies resolve and are listening on specified port, unless -s=1, then don't check for reachability 424 | for i, proxySpec := range gProxyServers { 425 | log.Infof("Added proxy server %v\n", proxySpec) 426 | if gSkipCheckUpstreamsReachable != 1 { 427 | conn, err := dial(proxySpec.HostPort()) 428 | if err != nil { 429 | log.Infof("Test connection to %v: failed. Removing from proxy server list\n", proxySpec) 430 | a := gProxyServers[:i] 431 | b := gProxyServers[i+1:] 432 | gProxyServers = append(a, b...) 433 | continue 434 | } 435 | conn.Close() 436 | } 437 | } 438 | // do we have at least one proxy server? 439 | if len(gProxyServers) == 0 { 440 | msg := "None of the proxy servers specified are available. Exiting." 441 | log.Infof("%s\n", msg) 442 | fmt.Fprintf(os.Stderr, msg) 443 | os.Exit(1) 444 | } 445 | } 446 | 447 | func ioCopy(dst net.Conn, src net.Conn, dstname string, srcname string) { 448 | if dst == nil { 449 | log.Debugf("copy(): oops, dst is nil!") 450 | return 451 | } 452 | if src == nil { 453 | log.Debugf("copy(): oops, src is nil!") 454 | return 455 | } 456 | copied, err := io.Copy(dst, src) 457 | if err != nil { 458 | if operr, ok := err.(*net.OpError); ok { 459 | if strings.Contains(operr.Err.Error(), "use of closed network connection") { 460 | log.Debugf("copy(): CLOSED %s(%v)->%s(%v): Copied=%v", srcname, src.RemoteAddr(), dstname, dst.RemoteAddr(), copied) 461 | } else { 462 | log.Debugf("copy(): ERROR %s(%v)->%s(%v): Op=%s, Net=%s, Err=%v, Copied=%v", srcname, src.RemoteAddr(), dstname, dst.RemoteAddr(), operr.Op, operr.Net, operr.Err, copied) 463 | } 464 | if strings.HasPrefix(operr.Op, "read") { 465 | if srcname == "proxyserver" { 466 | incrProxyServerReadErr() 467 | } 468 | if srcname == "directserver" { 469 | incrDirectServerReadErr() 470 | } 471 | } 472 | if strings.HasPrefix(operr.Op, "write") { 473 | if srcname == "proxyserver" { 474 | incrProxyServerWriteErr() 475 | } 476 | if srcname == "directserver" { 477 | incrDirectServerWriteErr() 478 | } 479 | } 480 | } else { 481 | log.Debugf("copy(): ERROR %s(%v)->%s(%v): Err=%v, Copied=%v", srcname, src.RemoteAddr(), dstname, dst.RemoteAddr(), err, copied) 482 | } 483 | } else { 484 | log.Debugf("copy(): DONE %s(%v)->%s(%v): Copied=%v", srcname, src.RemoteAddr(), dstname, dst.RemoteAddr(), copied) 485 | } 486 | dst.Close() 487 | src.Close() 488 | } 489 | 490 | func getOriginalDst(clientConn *net.TCPConn) (ipv4 string, port uint16, newTCPConn *net.TCPConn, err error) { 491 | if clientConn == nil { 492 | log.Debugf("copy(): oops, dst is nil!") 493 | err = errors.New("ERR: clientConn is nil") 494 | return 495 | } 496 | 497 | // test if the underlying fd is nil 498 | remoteAddr := clientConn.RemoteAddr() 499 | if remoteAddr == nil { 500 | log.Debugf("getOriginalDst(): oops, clientConn.fd is nil!") 501 | err = errors.New("ERR: clientConn.fd is nil") 502 | return 503 | } 504 | 505 | srcipport := fmt.Sprintf("%v", clientConn.RemoteAddr()) 506 | 507 | // Use reflect to get internal sysfd 508 | rawClientConn := reflect.ValueOf(clientConn).Elem() 509 | rawFd := rawClientConn.FieldByName("fd").Elem() 510 | valuePfd := rawFd.FieldByName("pfd") 511 | var rawSysFd int64 512 | if valuePfd.IsValid() { 513 | // go1.9 514 | rawSysFd = valuePfd.FieldByName("Sysfd").Int() 515 | } else { 516 | // < go1.9 517 | rawSysFd = rawFd.FieldByName("sysfd").Int() 518 | } 519 | // Get original destination 520 | // this is the only syscall in the Golang libs that I can find that returns 16 bytes 521 | // Example result: &{Multiaddr:[2 0 31 144 206 190 36 45 0 0 0 0 0 0 0 0] Interface:0} 522 | // port starts at the 3rd byte and is 2 bytes long (31 144 = port 8080) 523 | // IPv4 address starts at the 5th byte, 4 bytes long (206 190 36 45) 524 | addr, err := syscall.GetsockoptIPv6Mreq(int(rawSysFd), syscall.IPPROTO_IP, SO_ORIGINAL_DST) 525 | if err != nil { 526 | log.Infof("GETORIGINALDST|%v->?->FAILEDTOBEDETERMINED|ERR: getsocketopt(SO_ORIGINAL_DST) failed: %v", srcipport, err) 527 | return 528 | } 529 | log.Debugf("getOriginalDst(): SO_ORIGINAL_DST=%+v\n", addr) 530 | newTCPConn = clientConn 531 | 532 | ipv4 = itod(uint(addr.Multiaddr[4])) + "." + 533 | itod(uint(addr.Multiaddr[5])) + "." + 534 | itod(uint(addr.Multiaddr[6])) + "." + 535 | itod(uint(addr.Multiaddr[7])) 536 | port = uint16(addr.Multiaddr[2])<<8 + uint16(addr.Multiaddr[3]) 537 | 538 | return 539 | } 540 | 541 | // netAddrToSockaddr converts a net.Addr to a syscall.Sockaddr. 542 | // Returns nil if the input is invalid or conversion is not possible. 543 | func netAddrToSockaddr(addr net.Addr) syscall.Sockaddr { 544 | switch addr := addr.(type) { 545 | case *net.TCPAddr: 546 | return tcpAddrToSockaddr(addr) 547 | default: 548 | return nil 549 | } 550 | } 551 | 552 | // tcpAddrToSockaddr converts a net.TCPAddr to a syscall.Sockaddr. 553 | // Returns nil if conversion fails. 554 | func tcpAddrToSockaddr(addr *net.TCPAddr) syscall.Sockaddr { 555 | sa := ipAndZoneToSockaddr(addr.IP, addr.Zone) 556 | switch sa := sa.(type) { 557 | case *syscall.SockaddrInet4: 558 | sa.Port = addr.Port 559 | return sa 560 | case *syscall.SockaddrInet6: 561 | sa.Port = addr.Port 562 | return sa 563 | default: 564 | return nil 565 | } 566 | } 567 | 568 | // ipAndZoneToSockaddr converts a net.IP (with optional IPv6 Zone) to a syscall.Sockaddr 569 | // Returns nil if conversion fails. 570 | func ipAndZoneToSockaddr(ip net.IP, zone string) syscall.Sockaddr { 571 | switch { 572 | case len(ip) < net.IPv4len: // default to IPv4 573 | buf := [4]byte{0, 0, 0, 0} 574 | return &syscall.SockaddrInet4{Addr: buf} 575 | 576 | case ip.To4() != nil: 577 | var buf [4]byte 578 | ip4 := ip.To4() 579 | copy(buf[:], ip4) // last 4 bytes 580 | return &syscall.SockaddrInet4{Addr: buf} 581 | } 582 | panic("should be unreachable") 583 | } 584 | 585 | var resolver = dnscache.New(time.Minute * 30) 586 | 587 | func dial(spec string) (net.Conn, error) { 588 | host, port, err := net.SplitHostPort(spec) 589 | if err != nil { 590 | log.Infof("dial(): ERR: could not extract host and port from spec %v: %v", spec, err) 591 | return nil, err 592 | } 593 | remoteIP := net.ParseIP(host) 594 | if remoteIP == nil { 595 | remoteIP, err = resolver.FetchOne(host) 596 | if err != nil { 597 | log.Infof("dial(): ERR: could not resolve %v: %v", host, err) 598 | return nil, err 599 | } 600 | } 601 | portInt, err := strconv.Atoi(port) 602 | if err != nil { 603 | log.Infof("dial(): ERR: could not convert network port from string \"%s\" to integer: %v", port, err) 604 | return nil, err 605 | } 606 | remoteAddrAndPort := &net.TCPAddr{IP: remoteIP, Port: portInt} 607 | sa := netAddrToSockaddr(remoteAddrAndPort) 608 | fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP) 609 | if err != nil { 610 | log.Infof("dial(): ERR: could not create socket: %v", err) 611 | return nil, err 612 | } 613 | err = syscall.SetsockoptInt(fd, syscall.SOL_SOCKET, syscall.SO_MARK, gIpTableMark) 614 | if err != nil { 615 | log.Debugf("dial(): ERR: could not set sockopt with mark %v: %v", gIpTableMark, err) 616 | syscall.Close(fd) 617 | return nil, err 618 | } 619 | err = syscall.Connect(fd, sa) 620 | if err != nil { 621 | log.Infof("dial(): ERR: could not connect to %v:%v: %v", remoteIP, portInt, err) 622 | syscall.Close(fd) 623 | return nil, err 624 | } 625 | file := os.NewFile(uintptr(fd), "") 626 | conn, err := net.FileConn(file) 627 | // duplicate file created need to close 628 | if closeErr := file.Close(); closeErr != nil { 629 | log.Errorf("dial(): ERR: cannot close file %v: %v", fd, closeErr) 630 | } 631 | if err != nil { 632 | log.Infof("dial(): ERR: could not create connection with fd %v: %v", fd, err) 633 | return nil, err 634 | } 635 | if tcpConn, ok := conn.(*net.TCPConn); ok { 636 | return tcpConn, err 637 | } 638 | return nil, errors.New("invalid connection type") 639 | } 640 | 641 | func handleDirectConnection(clientConn *net.TCPConn, ipv4 string, port uint16) { 642 | if clientConn == nil { 643 | log.Debugf("handleDirectConnection(): oops, clientConn is nil!") 644 | return 645 | } 646 | 647 | // test if the underlying fd is nil 648 | remoteAddr := clientConn.RemoteAddr() 649 | if remoteAddr == nil { 650 | log.Debugf("handleDirectConnection(): oops, clientConn.fd is nil!") 651 | return 652 | } 653 | 654 | ipport := fmt.Sprintf("%s:%d", ipv4, port) 655 | directConn, err := dial(ipport) 656 | if err != nil { 657 | clientConnRemoteAddr := "?" 658 | if clientConn != nil { 659 | clientConnRemoteAddr = fmt.Sprintf("%v", clientConn.RemoteAddr()) 660 | } 661 | directConnRemoteAddr := "?" 662 | if directConn != nil { 663 | directConnRemoteAddr = fmt.Sprintf("%v", directConn.RemoteAddr()) 664 | } 665 | log.Infof("DIRECT|%v->%v|Could not connect, giving up: %v", clientConnRemoteAddr, directConnRemoteAddr, err) 666 | if err = clientConn.Close(); err != nil { 667 | log.Debugf("handleDirectConnection(): close clientConn error: %v", err) 668 | } 669 | return 670 | } 671 | log.Debugf("DIRECT|%v->%v|Connected to remote end", clientConn.RemoteAddr(), directConn.RemoteAddr()) 672 | incrDirectConnections() 673 | go ioCopy(clientConn, directConn, "client", "directserver") 674 | go ioCopy(directConn, clientConn, "directserver", "client") 675 | } 676 | 677 | func handleProxyConnection(clientConn *net.TCPConn, ipv4 string, port uint16) { 678 | var proxyConn net.Conn 679 | var err error 680 | var success bool = false 681 | var host string 682 | var headerXFF string = "" 683 | 684 | if clientConn == nil { 685 | log.Debugf("handleProxyConnection(): oops, clientConn is nil!") 686 | return 687 | } 688 | 689 | // test if the underlying fd is nil 690 | remoteAddr := clientConn.RemoteAddr() 691 | if remoteAddr == nil { 692 | log.Debugf("handleProxyConnect(): oops, clientConn.fd is nil!") 693 | err = errors.New("ERR: clientConn.fd is nil") 694 | return 695 | } 696 | 697 | host, _, err = net.SplitHostPort(remoteAddr.String()) 698 | if err == nil { 699 | headerXFF = fmt.Sprintf("X-Forwarded-For: %s\r\n", host) 700 | } 701 | 702 | if gReverseLookups == 1 { 703 | hostname := gReverseLookupCache.lookup(ipv4) 704 | if hostname != "" { 705 | ipv4 = hostname 706 | } else { 707 | names, err := net.LookupAddr(ipv4) 708 | if err == nil && len(names) > 0 { 709 | gReverseLookupCache.store(ipv4, names[0]) 710 | ipv4 = names[0] 711 | } 712 | } 713 | } 714 | 715 | for _, proxySpec := range proxyResolver(ipv4, port, gProxyServers) { 716 | log.Debugf("Using proxy %v for %v:%v", proxySpec, ipv4, port) 717 | // handle socks5 proxy 718 | if proxySpec.Type == Socks5ProxyType { 719 | socks5Dial, err := proxy.SOCKS5("tcp", proxySpec.HostPort(), proxySpec.Auth, proxy.Direct) 720 | if err == nil { 721 | log.Debugf("PROXY|%v->%v->%s:%d|Connecting via socks5 proxy\n", clientConn.RemoteAddr(), proxySpec.HostPort(), ipv4, port) 722 | proxyConn, err = socks5Dial.Dial("tcp", fmt.Sprintf("%s:%d", ipv4, port)) 723 | } 724 | if err != nil { 725 | log.Debugf("PROXY|%v->%v->%s:%d|Trying next proxy.", clientConn.RemoteAddr(), proxySpec, ipv4, port) 726 | continue 727 | } 728 | log.Debugf("PROXY|%v->%v->%s:%d|Socks5 proxied connection", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port) 729 | success = true 730 | break 731 | } 732 | proxyConn, err = dial(proxySpec.HostPort()) 733 | if err != nil { 734 | log.Debugf("PROXY|%v->%v->%s:%d|Trying next proxy.", clientConn.RemoteAddr(), proxySpec, ipv4, port) 735 | continue 736 | } 737 | log.Debugf("PROXY|%v->%v->%s:%d|Connected to proxy\n", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port) 738 | var authString = proxySpec.UserInfoBase64() 739 | if authString != "" { 740 | authString = fmt.Sprintf("\r\nProxy-Authorization: Basic %s", authString) 741 | } 742 | connectString := fmt.Sprintf("CONNECT %s:%d HTTP/1.0%s\r\n%s\r\n", ipv4, port, authString, headerXFF) 743 | log.Debugf("PROXY|%v->%v->%s:%d|Sending to proxy: %s\n", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port, strconv.Quote(connectString)) 744 | fmt.Fprintf(proxyConn, connectString) 745 | status, err := bufio.NewReader(proxyConn).ReadString('\n') 746 | log.Debugf("PROXY|%v->%v->%s:%d|Received from proxy: %s", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port, strconv.Quote(status)) 747 | if err != nil { 748 | log.Infof("PROXY|%v->%v->%s:%d|ERR: Could not find response to CONNECT: err=%v. Trying next proxy", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port, err) 749 | incrProxyNoConnectResponses() 750 | continue 751 | } 752 | if strings.Contains(status, "400") { // bad request 753 | log.Debugf("PROXY|%v->%v->%s:%d|Status from proxy=400 (Bad Request)", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port) 754 | log.Debugf("%v: Response from proxy=400", proxySpec) 755 | incrProxy400Responses() 756 | ioCopy(clientConn, proxyConn, "client", "proxyserver") 757 | return 758 | } 759 | if strings.Contains(status, "301") || strings.Contains(status, "302") && gClientRedirects == 1 { 760 | log.Debugf("PROXY|%v->%v->%s:%d|Status from proxy=%s (Redirect), relaying response to client", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port, strconv.Quote(status)) 761 | incrProxy300Responses() 762 | fmt.Fprintf(clientConn, status) 763 | ioCopy(clientConn, proxyConn, "client", "proxyserver") 764 | return 765 | } 766 | if strings.Contains(status, "200") == false { 767 | log.Infof("PROXY|%v->%v->%s:%d|ERR: Proxy response to CONNECT was: %s. Trying next proxy.\n", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port, strconv.Quote(status)) 768 | incrProxyNon200Responses() 769 | continue 770 | } else { 771 | incrProxy200Responses() 772 | } 773 | log.Debugf("PROXY|%v->%v->%s:%d|Proxied connection", clientConn.RemoteAddr(), proxyConn.RemoteAddr(), ipv4, port) 774 | success = true 775 | break 776 | } 777 | if proxyConn == nil { 778 | log.Warningf("handleProxyConnection(): oops, proxyConn is nil!") 779 | return 780 | } 781 | if success == false { 782 | log.Infof("PROXY|%v->UNAVAILABLE->%s:%d|ERR: Tried all proxies, but could not establish connection. Giving up.\n", clientConn.RemoteAddr(), ipv4, port) 783 | fmt.Fprint(clientConn, "HTTP/1.0 503 Service Unavailable\r\nServer: go-any-proxy\r\nX-AnyProxy-Error: ERR_NO_PROXIES\r\n\r\n") 784 | clientConn.Close() 785 | return 786 | } 787 | incrProxiedConnections() 788 | go ioCopy(clientConn, proxyConn, "client", "proxyserver") 789 | go ioCopy(proxyConn, clientConn, "proxyserver", "client") 790 | } 791 | 792 | func handleConnection(clientConn *net.TCPConn) { 793 | if clientConn == nil { 794 | log.Debugf("handleConnection(): oops, clientConn is nil") 795 | return 796 | } 797 | 798 | // test if the underlying fd is nil 799 | remoteAddr := clientConn.RemoteAddr() 800 | if remoteAddr == nil { 801 | log.Debugf("handleConnection(): oops, clientConn.fd is nil!") 802 | return 803 | } 804 | 805 | ipv4, port, clientConn, err := getOriginalDst(clientConn) 806 | if err != nil { 807 | log.Infof("handleConnection(): can not handle this connection, error occurred in getting original destination ip address/port: %+v\n", err) 808 | return 809 | } 810 | // If no upstream proxies were provided on the command line, assume all traffic should be sent directly 811 | if gProxyServerSpec == "" { 812 | handleDirectConnection(clientConn, ipv4, port) 813 | return 814 | } 815 | // Evaluate for direct connection 816 | ip := net.ParseIP(ipv4) 817 | if ok, _ := director(&ip); ok { 818 | handleDirectConnection(clientConn, ipv4, port) 819 | return 820 | } 821 | handleProxyConnection(clientConn, ipv4, port) 822 | } 823 | 824 | // from pkg/net/parse.go 825 | // Convert i to decimal string. 826 | func itod(i uint) string { 827 | if i == 0 { 828 | return "0" 829 | } 830 | 831 | // Assemble decimal in reverse order. 832 | var b [32]byte 833 | bp := len(b) 834 | for ; i > 0; i /= 10 { 835 | bp-- 836 | b[bp] = byte(i%10) + '0' 837 | } 838 | 839 | return string(b[bp:]) 840 | } 841 | --------------------------------------------------------------------------------