[
  {
    "path": ".gitignore",
    "content": "bin\nvendor\ndist\n.DS_store\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2017 Hiroyuki Wada\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "Makefile",
    "content": "NAME := transproxy\nVERSION := v0.6.0\nREVISION := $(shell git rev-parse --short HEAD)\n\nSRCS    := $(shell find . -type f -name '*.go')\n#LDFLAGS := -ldflags=\"-s -w -extldflags -static\"\n\nDIST_DIRS := find * -type d -exec\n\n.DEFAULT_GOAL := bin/$(NAME)\n\nbin/$(NAME): $(SRCS)\n\tgo build $(LDFLAGS) -o bin/$(NAME) cmd/transproxy/main.go\n\n.PHONY: clean\nclean:\n\trm -rf bin/*\n\trm -rf dist/*\n\trm -rf vendor/*\n\n.PHONY: cross-build\ncross-build:\n\tfor os in linux darwin; do \\\n\t    [ $$os = \"windows\" ] && EXT=\".exe\"; \\\n\t\tfor arch in amd64; do \\\n\t\t\tGOOS=$$os GOARCH=$$arch CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o dist/$$os-$$arch/$(NAME)$$EXT cmd/transproxy/main.go; \\\n\t\tdone; \\\n\tdone\n\n.PHONY: deps\ndeps:\n\tGO111MODULE=on\n\n.PHONY: dist\ndist:\n\tcd dist && \\\n\t$(DIST_DIRS) cp ../LICENSE {} \\; && \\\n\t$(DIST_DIRS) cp ../README.md {} \\; && \\\n\t$(DIST_DIRS) tar -zcf $(NAME)-$(VERSION)-{}.tar.gz {} \\; && \\\n\t$(DIST_DIRS) zip -r $(NAME)-$(VERSION)-{}.zip {} \\; && \\\n\tcd ..\n\n.PHONY: fast\nfast:\n\tgo build $(LDFLAGS) -o bin/$(NAME)\n\n.PHONY: install\ninstall:\n\tgo install $(LDFLAGS)\n\n.PHONY: release\nrelease:\n\tgit tag $(VERSION)\n\tgit push origin $(VERSION)\n\n.PHONY: test\ntest:\n\tgo test -cover -v\n\n.PHONY: it\nit:\n\tgo test -cover -v -tags integration\n"
  },
  {
    "path": "README.md",
    "content": "# go-transproxy\n\nTransparent proxy servers for HTTP, HTTPS, DNS and TCP. \nThis repository is heavily under development.\n\n## Description\n\n**go-transproxy** provides transparent proxy servers for HTTP, HTTPS, DNS and TCP with single binary.\nNothing needs to setup many tools. Nothing needs to configure iptables.\n**go-transproxy** will start multiple proxy servers for these protocols.\nFutheremore, it will configure iptables automatically.\n\n**go-transproxy** also provides two types of explicit proxy(not transparent proxy).\nOne is a simple proxy delegating to upstream your proxy, another is for adding `Proxy-Authorization` header automatically.\n\n## Requirement\n\n**go-transproxy** supports only Linux iptables.\n\n## Install\n\n### Binaly install\nDownload from [Releases page](https://github.com/wadahiro/go-transproxy/releases).\n\n### Source install\nUse Go 1.13 for the build.\n\n```\nmake\n```\n\n## Usage\n\n```\nUsage:\n\n  transproxy [options]\n\nOptions:\n\n  -disable-iptables\n    \tDisable automatic iptables configuration\n  -dns-over-https-enabled\n        Use DNS-over-HTTPS service as public DNS\n  -dns-over-https-endpoint string\n        DNS-over-HTTPS endpoint URL (default \"https://dns.google.com/resolve\")\n  -dns-over-tcp-disabled\n        Disable DNS-over-TCP for querying to public DNS\n  -dns-proxy-listen [host]:port\n        DNS Proxy listen address, as [host]:port (default \":3131\")\n  -dns-tcp\n        DNS Listen on TCP (default true)\n  -dns-udp\n        DNS Listen on UDP (default true)\n  -explicit-proxy-listen [host]:port\n        Explicit Proxy listen address for HTTP/HTTPS, as [host]:port Note: This proxy doesn't use authentication info of the `http_proxy` and `https_proxy` environment variables (default \":3132\")\n  -explicit-proxy-only\n        Boot Explicit Proxies only\n  -explicit-proxy-with-auth-listen [host]:port\n        Explicit Proxy with auth listen address for HTTP/HTTPS, as [host]:port Note: This proxy uses authentication info of the `http_proxy` and `https_proxy` environment variables (default \":3133\")\n  -http-proxy-listen [host]:port\n        HTTP Proxy listen address, as [host]:port (default \":3129\")\n  -https-proxy-listen [host]:port\n        HTTPS Proxy listen address, as [host]:port (default \":3130\")\n  -loglevel string\n        Log level, one of: debug, info, warn, error, fatal, panic (default \"info\")\n  -private-dns string\n        Private DNS address for no_proxy targets (IP[:port])\n  -public-dns string\n        Public DNS address (IP[:port]) Note: Your proxy needs to support CONNECT method to the Public DNS port, and the public DNS needs to support TCP\n  -tcp-proxy-dports port1,port2,...\n        TCP Proxy dports, as port1,port2,... (default \"22\")\n  -tcp-proxy-listen [host]:port\n        TCP Proxy listen address, as [host]:port (default \":3128\")\n```\n\nProxy configuration is used from standard environment variables, `http_proxy`, `https_proxy` and `no_proxy`.\nAlso you can use **IP Address**, **CIDR**, **Suffix Domain Name** in `no_proxy`.\n\n### Example \n\n```\n# Set your proxy environment\nexport http_proxy=http://foo:bar@yourproxy.example.org:3128\n\n# Set no_proxy if you need to access directly for internal\nexport no_proxy=example.org,192.168.0.0/24\n\n# Start go-transproxy with admin privileges(sudo)\nsudo -E transproxy -private-dns 192.168.0.100 -public-dns 8.8.8.8\n```\n\nFor testing, using docker is easy way. Now, you can access to google from docker container with no proxy configuration as follows.\n\n```\ndocker run --rm -it centos curl http://www.google.com\n<HTML><HEAD><meta http-equiv=\"content-type\" content=\"text/html;charset=utf-8\">\n<TITLE>302 Moved</TITLE></HEAD><BODY>\n<H1>302 Moved</H1>\nThe document has moved\n<A HREF=\"http://www.google.co.jp/?gfe_rd=cr&amp;dcr=0&amp;ei=GCKtWbD0AaLEXuTmr7gK\">here</A>.\n</BODY></HTML>\n```\n\nIf your proxy doesn't support CONNECT method to DNS port, it cannot resolve public domain name transparently.\nFortunately, Google privides [DNS-over-HTTPS service](https://developers.google.com/speed/public-dns/docs/dns-over-https), so you can use this service as public DNS by adding `-dns-over-https-enabled` option instead of `-public-dns` option as below even if your proxy supports CONNECT method to 443 port only.\n\n```\nsudo -E transproxy -private-dns 192.168.0.100 -dns-over-https-enabled\n```\n\nIf you can resolve all domains directly from local LAN, run command without dns related options as below. \nIt disables DNS-Proxy.\n\n```\nsudo -E transproxy\n```\n\nIf you need to use both public DNS and private DNS, and need to use public DNS directly, run command with `-dns-over-tcp-disabled` option as below.\nIt suppresses to insert a iptables OUTPUT rule for DNS over TCP.\n\n```\nsudo -E transproxy -private-dns 192.168.0.100 -public-dns 172.16.0.1 -dns-over-tcp-disabled\n```\n\nIf you want to use an application which access to internet using port 5000, run command with `-tcp-proxy-dports` option as below.\n\n```\nsudo -E transproxy -private-dns 192.168.0.100 -public-dns 8.8.8.8 -tcp-proxy-dports 22,5000\n```\n\n## Current Limitation\n\n* HTTP proxy: Only works with HTTP host header.\n* HTTPS proxy: `no_proxy` only works with IP Address and CIDR if your https client doesn't support [SNI](https://en.wikipedia.org/wiki/Server_Name_Indication).\n* TCP proxy: `no_proxy` only works with IP Address and CIDR.\n\n## Licence\n\nLicensed under the [MIT](/LICENSE) license.\n\n## Author\n\n[Hiroyuki Wada](https://github.com/wadahiro)\n\n"
  },
  {
    "path": "cmd/transproxy/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"log\"\n\t\"math/rand\"\n\t\"net\"\n\t\"os\"\n\t\"os/signal\"\n\t\"path/filepath\"\n\t\"strconv\"\n\t\"strings\"\n\t\"syscall\"\n\t\"time\"\n\n\t\"github.com/comail/colog\"\n\ttransproxy \"github.com/wadahiro/go-transproxy\"\n)\n\nfunc orPanic(err error) {\n\tif err != nil {\n\t\tpanic(err)\n\t}\n}\n\nvar (\n\tfs       = flag.NewFlagSet(os.Args[0], flag.ExitOnError)\n\tloglevel = fs.String(\n\t\t\"loglevel\",\n\t\t\"info\",\n\t\t\"Log level, one of: debug, info, warn, error, fatal, panic\",\n\t)\n\n\tprivateDNS = fs.String(\"private-dns\", \"\",\n\t\t\"Private DNS address for no_proxy targets (IP[:port])\")\n\n\tpublicDNS = fs.String(\"public-dns\", \"\",\n\t\t\"Public DNS address (IP[:port]) Note: Your proxy needs to support CONNECT method to the Public DNS port, and the public DNS needs to support TCP\")\n\n\ttcpProxyDestPorts = fs.String(\n\t\t\"tcp-proxy-dports\", \"22\", \"TCP Proxy dports, as `port1,port2,...`\",\n\t)\n\n\ttcpProxyListenAddress = fs.String(\n\t\t\"tcp-proxy-listen\", \":3128\", \"TCP Proxy listen address, as `[host]:port`\",\n\t)\n\n\thttpProxyListenAddress = fs.String(\n\t\t\"http-proxy-listen\", \":3129\", \"HTTP Proxy listen address, as `[host]:port`\",\n\t)\n\n\thttpsProxyListenAddress = fs.String(\n\t\t\"https-proxy-listen\", \":3130\", \"HTTPS Proxy listen address, as `[host]:port`\",\n\t)\n\n\tdnsProxyListenAddress = fs.String(\n\t\t\"dns-proxy-listen\", \":3131\", \"DNS Proxy listen address, as `[host]:port`\",\n\t)\n\n\texplicitProxyListenAddress = fs.String(\n\t\t\"explicit-proxy-listen\", \":3132\", \"Explicit Proxy listen address for HTTP/HTTPS, as `[host]:port` Note: This proxy doesn't use authentication info of the `http_proxy` and `https_proxy` environment variables\",\n\t)\n\n\texplicitProxyWithAuthListenAddress = fs.String(\n\t\t\"explicit-proxy-with-auth-listen\", \":3133\", \"Explicit Proxy with auth listen address for HTTP/HTTPS, as `[host]:port` Note: This proxy uses authentication info of the `http_proxy` and `https_proxy` environment variables\",\n\t)\n\n\texplicitProxyOnly = fs.Bool(\n\t\t\"explicit-proxy-only\", false, \"Boot Explicit Proxies only\",\n\t)\n\n\tdnsOverTCPDisabled = fs.Bool(\n\t\t\"dns-over-tcp-disabled\", false, \"Disable DNS-over-TCP for querying to public DNS\")\n\n\tdnsOverHTTPSEnabled = fs.Bool(\n\t\t\"dns-over-https-enabled\", false, \"Use DNS-over-HTTPS service as public DNS\")\n\n\tdnsOverHTTPSEndpoint = fs.String(\n\t\t\"dns-over-https-endpoint\",\n\t\t\"https://dns.google.com/resolve\",\n\t\t\"DNS-over-HTTPS endpoint URL\",\n\t)\n\n\tdnsEnableTCP = fs.Bool(\"dns-tcp\", true, \"DNS Listen on TCP\")\n\tdnsEnableUDP = fs.Bool(\"dns-udp\", true, \"DNS Listen on UDP\")\n\tdisableIPTables = fs.Bool(\"disable-iptables\", false, \"Disable automatic iptables configuration\")\n)\n\nfunc main() {\n\tfs.Usage = func() {\n\t\t_, exe := filepath.Split(os.Args[0])\n\t\tfmt.Fprint(os.Stderr, \"go-transproxy.\\n\\n\")\n\t\tfmt.Fprintf(os.Stderr, \"Usage:\\n\\n  %s [options]\\n\\nOptions:\\n\\n\", exe)\n\t\tfs.PrintDefaults()\n\t}\n\tfs.Parse(os.Args[1:])\n\n\t// seed the global random number generator, used in secureoperator\n\trand.Seed(time.Now().UTC().UnixNano())\n\n\t// setup logger\n\tcolog.SetDefaultLevel(colog.LDebug)\n\tcolog.SetMinLevel(colog.LInfo)\n\tlevel, err := colog.ParseLevel(*loglevel)\n\tif err != nil {\n\t\tlog.Fatalf(\"alert: Invalid log level: %s\", err.Error())\n\t}\n\tcolog.SetMinLevel(level)\n\tcolog.SetFormatter(&colog.StdFormatter{\n\t\tColors: true,\n\t\tFlag:   log.Ldate | log.Ltime | log.Lmicroseconds,\n\t})\n\tcolog.ParseFields(true)\n\tcolog.Register()\n\n\tif *explicitProxyOnly {\n\t\tstartExplicitProxyOnly(level)\n\t} else {\n\t\tstartAllProxy(level)\n\t}\n}\n\nfunc startExplicitProxyOnly(level colog.Level) {\n\tstartExplicitProxy()\n\n\t// serve until exit\n\tsig := make(chan os.Signal)\n\tsignal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n\t<-sig\n\n\tlog.Printf(\"info: Proxy servers stopping.\")\n\tlog.Printf(\"info: go-transproxy exited.\")\n}\n\nfunc startAllProxy(level colog.Level) {\n\t// handling no_proxy environment\n\tnoProxy := os.Getenv(\"no_proxy\")\n\tif noProxy == \"\" {\n\t\tnoProxy = os.Getenv(\"NO_PROXY\")\n\t}\n\n\tnp := parseNoProxy(noProxy)\n\t// start servers\n\ttcpProxy := transproxy.NewTCPProxy(\n\t\ttransproxy.TCPProxyConfig{\n\t\t\tListenAddress: *tcpProxyListenAddress,\n\t\t\tNoProxy:       np,\n\t\t},\n\t)\n\tif err := tcpProxy.Start(); err != nil {\n\t\tlog.Fatalf(\"alert: %s\", err.Error())\n\t}\n\n\tdnsProxy := transproxy.NewDNSProxy(\n\t\ttransproxy.DNSProxyConfig{\n\t\t\tEnabled:             useDNSProxy(),\n\t\t\tListenAddress:       *dnsProxyListenAddress,\n\t\t\tEnableUDP:           *dnsEnableUDP,\n\t\t\tEnableTCP:           *dnsEnableTCP,\n\t\t\tEndpoint:            *dnsOverHTTPSEndpoint,\n\t\t\tPublicDNS:           *publicDNS,\n\t\t\tPrivateDNS:          *privateDNS,\n\t\t\tDNSOverHTTPSEnabled: *dnsOverHTTPSEnabled,\n\t\t\tNoProxyDomains:      np.Domains,\n\t\t},\n\t)\n\tdnsProxy.Start()\n\n\thttpProxy := transproxy.NewHTTPProxy(\n\t\ttransproxy.HTTPProxyConfig{\n\t\t\tListenAddress: *httpProxyListenAddress,\n\t\t\tNoProxy:       np,\n\t\t\tVerbose:       level == colog.LDebug,\n\t\t},\n\t)\n\tif err := httpProxy.Start(); err != nil {\n\t\tlog.Fatalf(\"alert: %s\", err.Error())\n\t}\n\n\thttpsProxy := transproxy.NewHTTPSProxy(\n\t\ttransproxy.HTTPSProxyConfig{\n\t\t\tListenAddress: *httpsProxyListenAddress,\n\t\t\tNoProxy:       np,\n\t\t},\n\t)\n\tif err := httpsProxy.Start(); err != nil {\n\t\tlog.Fatalf(\"alert: %s\", err.Error())\n\t}\n\n\tstartExplicitProxy()\n\n\tlog.Printf(\"info: All proxy servers started.\")\n\n\tdnsToPort := toPort(*dnsProxyListenAddress)\n\thttpToPort := toPort(*httpProxyListenAddress)\n\thttpsToPort := toPort(*httpsProxyListenAddress)\n\ttcpToPort := toPort(*tcpProxyListenAddress)\n\ttcpDPorts := toPorts(*tcpProxyDestPorts)\n\n\toutgoingPublicDNS := *publicDNS\n\tif *dnsOverTCPDisabled {\n\t\toutgoingPublicDNS = \"\"\n\t}\n\n\tvar t *transproxy.IPTables\n\tvar err error\n\n\tif !*disableIPTables {\n\t\tt, err = transproxy.NewIPTables(&transproxy.IPTablesConfig{\n\t\t\tDNSToPort:   dnsToPort,\n\t\t\tHTTPToPort:  httpToPort,\n\t\t\tHTTPSToPort: httpsToPort,\n\t\t\tTCPToPort:   tcpToPort,\n\t\t\tTCPDPorts:   tcpDPorts,\n\t\t\tPublicDNS:   outgoingPublicDNS,\n\t\t})\n\t\tif err != nil {\n\t\t\tlog.Printf(\"alert: %s\", err.Error())\n\t\t}\n\n\t\tt.Start()\n\n\t\tlog.Printf(`info: iptables rules inserted as follows.\n---\n%s\n---`, t.Show())\n\t}\n\n\t// serve until exit\n\tsig := make(chan os.Signal)\n\tsignal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)\n\t<-sig\n\n\tlog.Printf(\"info: Proxy servers stopping.\")\n\n\t// start shutdown process\n\tif !*disableIPTables {\n\t\tt.Stop()\n\t\tlog.Printf(\"info: iptables rules deleted.\")\n\t}\n\n\tif dnsProxy != nil {\n\t\tdnsProxy.Stop()\n\t}\n\n\tlog.Printf(\"info: go-transproxy exited.\")\n}\n\nfunc startExplicitProxy() {\n\texplicitProxyWithAuth := transproxy.NewExplicitProxy(\n\t\ttransproxy.ExplicitProxyConfig{\n\t\t\tListenAddress:         *explicitProxyWithAuthListenAddress,\n\t\t\tUseProxyAuthorization: true,\n\t\t},\n\t)\n\tif err := explicitProxyWithAuth.Start(); err != nil {\n\t\tlog.Fatalf(\"alert: %s\", err.Error())\n\t}\n\n\texplicitProxy := transproxy.NewExplicitProxy(\n\t\ttransproxy.ExplicitProxyConfig{\n\t\t\tListenAddress:         *explicitProxyListenAddress,\n\t\t\tUseProxyAuthorization: false,\n\t\t},\n\t)\n\tif err := explicitProxy.Start(); err != nil {\n\t\tlog.Fatalf(\"alert: %s\", err.Error())\n\t}\n}\n\nfunc useDNSProxy() bool {\n\tif *privateDNS == \"\" && *publicDNS == \"\" && *dnsOverHTTPSEnabled == false {\n\t\treturn false\n\t}\n\treturn true\n}\n\nfunc toPort(addr string) int {\n\tarray := strings.Split(addr, \":\")\n\tif len(array) != 2 {\n\t\tlog.Printf(\"alert: Invalid address, no port: %s\", addr)\n\t}\n\n\ti, err := strconv.Atoi(array[1])\n\tif err != nil {\n\t\tlog.Printf(\"alert: Invalid address, the port isn't number: %s\", addr)\n\t}\n\n\tif i > 65535 || i < 0 {\n\t\tlog.Printf(\"alert: Invalid address, the port must be an integer value in the range 0-65535: %s\", addr)\n\t}\n\n\treturn i\n}\n\nfunc toPorts(ports string) []int {\n\tarray := strings.Split(ports, \",\")\n\n\tvar p []int\n\n\tfor _, v := range array {\n\t\ti, err := strconv.Atoi(v)\n\t\tif err != nil {\n\t\t\tlog.Printf(\"alert: Invalid port, It's not number: %s\", ports)\n\t\t}\n\n\t\tif i > 65535 || i < 0 {\n\t\t\tlog.Printf(\"alert: Invalid port, It must be an integer value in the range 0-65535: %s\", ports)\n\t\t}\n\n\t\tp = append(p, i)\n\t}\n\n\treturn p\n}\n\nfunc parseNoProxy(noProxy string) transproxy.NoProxy {\n\tp := strings.Split(noProxy, \",\")\n\n\tvar ipArray []string\n\tvar cidrArray []*net.IPNet\n\tvar domainArray []string\n\n\tfor _, v := range p {\n\t\tip := net.ParseIP(v)\n\t\tif ip != nil {\n\t\t\tipArray = append(ipArray, v)\n\t\t\tcontinue\n\t\t}\n\n\t\t_, ipnet, err := net.ParseCIDR(v)\n\t\tif err == nil {\n\t\t\tcidrArray = append(cidrArray, ipnet)\n\t\t\tcontinue\n\t\t}\n\n\t\tdomainArray = append(domainArray, v)\n\t}\n\n\treturn transproxy.NoProxy{\n\t\tIPs:     ipArray,\n\t\tCIDRs:   cidrArray,\n\t\tDomains: domainArray,\n\t}\n}\n"
  },
  {
    "path": "common.go",
    "content": "package transproxy\n\nimport (\n\t\"fmt\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"sync\"\n\n\t\"github.com/cybozu-go/netutil\"\n\t\"github.com/cybozu-go/transocks\"\n\t\"os\"\n)\n\ntype TCPListener struct {\n\tnet.Listener\n}\n\ntype TCPConn struct {\n\t*net.TCPConn\n\tOrigAddr string // ip:port\n}\n\nfunc NewTCPListener(listenAddress string) (*TCPListener, error) {\n\tl, err := net.Listen(\"tcp\", listenAddress)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &TCPListener{\n\t\tListener: l,\n\t}, nil\n}\n\nfunc (l *TCPListener) Accept() (net.Conn, error) {\n\tc, err := l.Listener.Accept()\n\tif err != nil {\n\t\treturn c, err\n\t}\n\n\ttc, ok := c.(*net.TCPConn)\n\tif !ok {\n\t\treturn c, fmt.Errorf(\"Accepted non-TCP connection - %v\", c)\n\t}\n\n\torigAddr, err := transocks.GetOriginalDST(tc)\n\tif err != nil {\n\t\treturn c, fmt.Errorf(\"GetOriginalDST failed - %s\", err.Error())\n\t}\n\n\tlog.Printf(\"debug: OriginalDST: %s\", origAddr)\n\tlog.Printf(\"debug: LocalAddr: %s\", tc.LocalAddr().String())\n\tlog.Printf(\"debug: RemoteAddr: %s\", tc.RemoteAddr().String())\n\n\treturn &TCPConn{tc, origAddr.String()}, nil\n}\n\nfunc ListenTCP(listenAddress string, handler func(tc *TCPConn)) {\n\tl, err := NewTCPListener(listenAddress)\n\tif err != nil {\n\t\tlog.Fatalf(\"alert: Error listening for tcp connections - %s\", err.Error())\n\t}\n\n\tfor {\n\t\tconn, err := l.Accept() // wait here\n\t\tif err != nil {\n\t\t\tlog.Printf(\"warn: Error accepting new connection - %s\", err.Error())\n\t\t\treturn\n\t\t}\n\n\t\tlog.Printf(\"debug: Accepted new connection\")\n\n\t\tgo func(conn net.Conn) {\n\t\t\tdefer func() {\n\t\t\t\tconn.Close()\n\t\t\t}()\n\n\t\t\ttc, _ := conn.(*TCPConn)\n\n\t\t\thandler(tc)\n\t\t}(conn)\n\t}\n}\n\nvar pool = sync.Pool{\n\tNew: func() interface{} {\n\t\treturn make([]byte, 64<<10)\n\t},\n}\n\nfunc Pipe(srcConn *TCPConn, destConn net.Conn) {\n\tdefer destConn.Close()\n\n\tlog.Printf(\"debug: Start proxy\")\n\n\twg := &sync.WaitGroup{}\n\n\twg.Add(1)\n\tgo func() error {\n\t\tdefer func() {\n\t\t\twg.Done()\n\t\t}()\n\n\t\tbuf := pool.Get().([]byte)\n\t\t_, err := io.CopyBuffer(destConn, srcConn, buf)\n\t\tpool.Put(buf)\n\t\tif hc, ok := destConn.(netutil.HalfCloser); ok {\n\t\t\thc.CloseWrite()\n\t\t}\n\t\tsrcConn.CloseRead()\n\t\treturn err\n\t}()\n\n\twg.Add(1)\n\tgo func() error {\n\t\tdefer func() {\n\t\t\twg.Done()\n\t\t}()\n\n\t\tbuf := pool.Get().([]byte)\n\t\t_, err := io.CopyBuffer(srcConn, destConn, buf)\n\t\tpool.Put(buf)\n\t\tsrcConn.CloseWrite()\n\t\tif hc, ok := destConn.(netutil.HalfCloser); ok {\n\t\t\thc.CloseRead()\n\t\t}\n\t\treturn err\n\t}()\n\n\twg.Wait()\n\n\tlog.Printf(\"debug: End proxy\")\n}\n\ntype NoProxy struct {\n\tIPs     []string\n\tCIDRs   []*net.IPNet\n\tDomains []string\n}\n\nfunc httpProxyFromRule(noProxy NoProxy) func(*http.Request) (*url.URL, error) {\n\treturn func(req *http.Request) (*url.URL, error) {\n\t\tif useProxy(noProxy, strings.Split(req.Host, \":\")[0]) {\n\n\t\t\treturn http.ProxyFromEnvironment(req)\n\t\t} else {\n\t\t\treturn nil, nil\n\t\t}\n\t}\n}\n\nfunc useProxy(noProxy NoProxy, target string) bool {\n\t// TODO resolve target domain\n\n\tfor _, d := range noProxy.Domains {\n\t\tif strings.HasSuffix(target, d) {\n\t\t\tlog.Printf(\"debug: NO_PROXY: Matched no_proxy domain. Direct for %s\", target)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, ip := range noProxy.IPs {\n\t\tif ip == target {\n\t\t\tlog.Printf(\"debug: NO_PROXY: Matched no_proxy ip. Direct for %s\", target)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tfor _, cidr := range noProxy.CIDRs {\n\t\ttargetIP := net.ParseIP(target)\n\t\tif cidr.Contains(targetIP) {\n\t\t\tlog.Printf(\"debug: NO_PROXY: Matched no_proxy cidr. Direct for %s\", target)\n\t\t\treturn false\n\t\t}\n\t}\n\n\tlog.Printf(\"debug: Use proxy for %s\", target)\n\treturn true\n}\n\nfunc GetProxyEnv(key string) string {\n\tenv := os.Getenv(key)\n\tif env == \"\" {\n\t\tenv = os.Getenv(strings.ToUpper(key))\n\t}\n\treturn env\n}\n"
  },
  {
    "path": "dns.go",
    "content": "package transproxy\n\nimport (\n\t\"log\"\n\t\"net\"\n\t\"strings\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/Sirupsen/logrus\"\n\tsecop \"github.com/fardog/secureoperator\"\n\t\"github.com/miekg/dns\"\n)\n\ntype DNSProxy struct {\n\tDNSProxyConfig\n\tudpServer *dns.Server\n\ttcpServer *dns.Server\n\tudpClient *dns.Client // used for fowarding to internal DNS\n\ttcpClient *dns.Client // used for fowarding to internal DNS\n\twaitGroup *sync.WaitGroup\n}\n\ntype DNSProxyConfig struct {\n\tEnabled             bool\n\tListenAddress       string\n\tEnableUDP           bool\n\tEnableTCP           bool\n\tEndpoint            string\n\tPublicDNS           string\n\tPrivateDNS          string\n\tDNSOverHTTPSEnabled bool\n\tNoProxyDomains      []string\n}\n\nfunc NewDNSProxy(c DNSProxyConfig) *DNSProxy {\n\t// Suppress standard logger for secureoperator\n\tlogrus.SetLevel(logrus.ErrorLevel)\n\n\t// fix dns address\n\tif c.PublicDNS != \"\" {\n\t\t_, _, err := net.SplitHostPort(c.PublicDNS)\n\t\tif err != nil {\n\t\t\tc.PublicDNS = net.JoinHostPort(c.PublicDNS, \"53\")\n\t\t}\n\t}\n\tif c.PrivateDNS != \"\" {\n\t\t_, _, err := net.SplitHostPort(c.PrivateDNS)\n\t\tif err != nil {\n\t\t\tc.PrivateDNS = net.JoinHostPort(c.PrivateDNS, \"53\")\n\t\t}\n\t}\n\n\t// fix domains\n\tvar noProxyRoutes []string\n\tfor _, s := range c.NoProxyDomains {\n\t\tif !strings.HasSuffix(s, \".\") {\n\t\t\ts += \".\"\n\t\t}\n\t\tnoProxyRoutes = append(noProxyRoutes, s)\n\t}\n\tc.NoProxyDomains = noProxyRoutes\n\n\treturn &DNSProxy{\n\t\tDNSProxyConfig: c,\n\t\tudpServer:      nil,\n\t\ttcpServer:      nil,\n\t\tudpClient: &dns.Client{\n\t\t\tNet:            \"udp\",\n\t\t\tTimeout:        time.Duration(10) * time.Second,\n\t\t\tSingleInflight: true,\n\t\t},\n\t\ttcpClient: &dns.Client{\n\t\t\tNet:            \"tcp\",\n\t\t\tTimeout:        time.Duration(10) * time.Second,\n\t\t\tSingleInflight: true,\n\t\t},\n\t\twaitGroup: new(sync.WaitGroup),\n\t}\n}\n\nfunc (s *DNSProxy) Start() error {\n\tif !s.Enabled {\n\t\tlog.Printf(\"debug: Disabled category='DNS-Proxy'\")\n\t\treturn nil\n\t}\n\n\tlog.Printf(\"info: Start listener on %s category='DNS-Proxy'\", s.ListenAddress)\n\tif s.DNSOverHTTPSEnabled {\n\t\tlog.Printf(\"info: Use DNS-over-HTTPS service as public DNS category='DNS-Proxy'\")\n\t}\n\tif !s.DNSOverHTTPSEnabled && s.PublicDNS != \"\" {\n\t\tlog.Printf(\"info: Use %s as public DNS category='DNS-Proxy'\", s.PublicDNS)\n\t}\n\tif s.PrivateDNS != \"\" {\n\t\tlog.Printf(\"info: Use %s as private DNS for %s domains category='DNS-Proxy'\", s.PrivateDNS, s.NoProxyDomains)\n\t}\n\n\t// Prepare external DNS handler\n\tprovider, err := secop.NewGDNSProvider(s.Endpoint, &secop.GDNSOptions{\n\t\tPad: true,\n\t})\n\n\tif err != nil {\n\t\tlog.Fatal(\"alert: %s category='DNS-Proxy'\", err)\n\t}\n\n\toptions := &secop.HandlerOptions{}\n\tpublicOverHTTPSHandler := secop.NewHandler(provider, options)\n\n\t// Setup DNS Handler\n\tdnsHandle := func(w dns.ResponseWriter, req *dns.Msg) {\n\t\tif len(req.Question) == 0 {\n\t\t\tdns.HandleFailed(w, req)\n\t\t\treturn\n\t\t}\n\n\t\t// access logging\n\t\thost, _, _ := net.SplitHostPort(w.RemoteAddr().String())\n\t\tlog.Printf(\"info: category='DNS-Proxy' remoteAddr='%s' questionName='%s' questionType='%s'\", host, req.Question[0].Name, dns.TypeToString[req.Question[0].Qtype])\n\n\t\t// Resolve by proxied private DNS\n\t\tfor _, domain := range s.NoProxyDomains {\n\t\t\tlog.Printf(\"debug: Checking DNS route, request: %s, no_proxy: %s\", req.Question[0].Name, domain)\n\t\t\tif strings.HasSuffix(req.Question[0].Name, domain) {\n\t\t\t\tlog.Printf(\"debug: Matched! Routing to private DNS, request: %s, no_proxy: %s\", req.Question[0].Name, domain)\n\t\t\t\ts.handlePrivate(w, req)\n\t\t\t\treturn\n\t\t\t}\n\t\t}\n\n\t\t// Resolve by public DNS over HTTPS over http proxy\n\t\tif s.DNSOverHTTPSEnabled {\n\t\t\tpublicOverHTTPSHandler.Handle(w, req)\n\t\t\treturn\n\t\t}\n\n\t\t// Resolve by specified public DNS over http proxy\n\t\ts.handlePublic(w, req)\n\t}\n\n\tdns.HandleFunc(\".\", dnsHandle)\n\n\t// Start DNS Server\n\n\tif s.EnableUDP {\n\t\ts.udpServer = &dns.Server{\n\t\t\tAddr:       s.ListenAddress,\n\t\t\tNet:        \"udp\",\n\t\t\tTsigSecret: nil,\n\t\t}\n\t}\n\tif s.EnableTCP {\n\t\ts.tcpServer = &dns.Server{\n\t\t\tAddr:       s.ListenAddress,\n\t\t\tNet:        \"tcp\",\n\t\t\tTsigSecret: nil,\n\t\t}\n\t}\n\n\tgo func() {\n\t\tif s.udpServer != nil {\n\t\t\tif err := s.udpServer.ListenAndServe(); err != nil {\n\t\t\t\tlog.Fatal(\"alert: %s\", err.Error())\n\t\t\t}\n\t\t}\n\t\tif s.tcpServer != nil {\n\t\t\tif err := s.tcpServer.ListenAndServe(); err != nil {\n\t\t\t\tlog.Fatal(\"alert: %s\", err.Error())\n\t\t\t}\n\t\t}\n\t}()\n\n\treturn nil\n}\n\nfunc (s *DNSProxy) handlePublic(w dns.ResponseWriter, req *dns.Msg) {\n\tlog.Printf(\"debug: DNS request. %#v, %s\", req, req)\n\n\t// Need to use TCP because of using TCP-Proxy\n\tresp, _, err := s.tcpClient.Exchange(req, s.PublicDNS)\n\tif err != nil {\n\t\tlog.Printf(\"warn: DNS Client failed. %s, %#v, %s\", err.Error(), req, req)\n\t\tdns.HandleFailed(w, req)\n\t\treturn\n\t}\n\tw.WriteMsg(resp)\n}\n\nfunc (s *DNSProxy) handlePrivate(w dns.ResponseWriter, req *dns.Msg) {\n\tvar c *dns.Client\n\tif _, ok := w.RemoteAddr().(*net.TCPAddr); ok {\n\t\tc = s.tcpClient\n\t} else {\n\t\tc = s.udpClient\n\t}\n\n\tlog.Printf(\"debug: DNS request. %#v, %s\", req, req)\n\n\tresp, _, err := c.Exchange(req, s.PrivateDNS)\n\tif err != nil {\n\t\tlog.Printf(\"warn: DNS Client failed. %s, %#v, %s\", err.Error(), req, req)\n\t\tdns.HandleFailed(w, req)\n\t\treturn\n\t}\n\tw.WriteMsg(resp)\n}\n\nfunc (s *DNSProxy) Stop() {\n\tif !s.Enabled {\n\t\treturn\n\t}\n\n\tlog.Printf(\"info: Shutting down DNS service on interrupt\\n\")\n\n\tif s.udpServer != nil {\n\t\tif err := s.udpServer.Shutdown(); err != nil {\n\t\t\tlog.Printf(\"error: %s\", err.Error())\n\t\t}\n\t\ts.udpServer = nil\n\t}\n\tif s.tcpServer != nil {\n\t\tif err := s.tcpServer.Shutdown(); err != nil {\n\t\t\tlog.Printf(\"error: %s\", err.Error())\n\t\t}\n\t\ts.tcpServer = nil\n\t}\n}\n"
  },
  {
    "path": "explicit.go",
    "content": "package transproxy\n\nimport (\n\t\"encoding/base64\"\n\t\"io\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n)\n\ntype ExplicitProxy struct {\n\tExplicitProxyConfig\n\tuser     string\n\tcategory string\n\t// For HTTP\n\tproxyTransport     *http.Transport\n\tproxyAuthTransport *http.Transport\n\t// For HTTPS\n\tproxyHost          string\n\tproxyAuthorization string\n}\n\ntype ExplicitProxyConfig struct {\n\tListenAddress         string\n\tUseProxyAuthorization bool\n}\n\nfunc NewExplicitProxy(c ExplicitProxyConfig) *ExplicitProxy {\n\treturn &ExplicitProxy{\n\t\tExplicitProxyConfig: c,\n\t}\n}\n\nfunc (s ExplicitProxy) Start() error {\n\tu, err := url.Parse(GetProxyEnv(\"http_proxy\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tif s.UseProxyAuthorization {\n\t\ts.category = \"Explicit-Proxy(Auth)\"\n\n\t\tif u.User == nil {\n\t\t\tlog.Printf(\"info: Not Started because of no proxy user category='%s'\", s.category)\n\t\t\treturn nil\n\t\t}\n\t\t// For HTTPS\n\t\ts.proxyAuthorization = \"Basic \" + base64.StdEncoding.EncodeToString([]byte(u.User.String()))\n\t\ts.proxyHost = u.Host\n\n\t\t// For HTTP\n\t\ts.proxyAuthTransport = &http.Transport{Proxy: http.ProxyURL(u)}\n\t} else {\n\t\ts.category = \"Explicit-Proxy(NoAuth)\"\n\n\t\t// For HTTPS\n\t\ts.proxyHost = u.Host\n\n\t\t// For HTTP\n\t\tu, _ = url.Parse(u.String())\n\t\tu.User = nil\n\t\ts.proxyTransport = &http.Transport{Proxy: http.ProxyURL(u)}\n\t}\n\n\thandler := http.HandlerFunc(s.handleRequest)\n\n\tlog.Printf(\"info: Start listener on %s category='%s'\", s.ListenAddress, s.category)\n\n\tgo func() {\n\t\thttp.ListenAndServe(s.ListenAddress, handler)\n\t}()\n\n\treturn nil\n}\n\nfunc (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Request) {\n\t// access logging\n\ts.accessLog(r)\n\n\tif r.Method == \"CONNECT\" {\n\t\ts.handleHttps(w, r)\n\t} else {\n\t\ts.handleHttp(w, r)\n\t}\n}\n\nfunc (s ExplicitProxy) accessLog(r *http.Request) {\n\thost, _, _ := net.SplitHostPort(r.RemoteAddr)\n\tif s.UseProxyAuthorization {\n\t\tlog.Printf(\"info: category='%s' remoteAddr='%s' method='%s' uri='%s'\", s.category, host, r.Method, r.RequestURI)\n\t} else {\n\t\tvar decodedAuth string\n\n\t\tvalues := r.Header[\"Proxy-Authorization\"]\n\t\tif len(values) > 0 {\n\t\t\tauth := strings.Split(values[0], \" \")\n\t\t\tif len(auth) > 0 {\n\t\t\t\tb, _ := base64.StdEncoding.DecodeString(auth[1])\n\t\t\t\tdecodedAuth = strings.Split(string(b[:]), \":\")[0]\n\t\t\t}\n\t\t}\n\t\tlog.Printf(\"info: category='%s' user='%s' remoteAddr='%s' method='%s' uri='%s'\", s.category, decodedAuth, host, r.Method, r.RequestURI)\n\t}\n}\n\nfunc (s ExplicitProxy) handleHttps(w http.ResponseWriter, r *http.Request) {\n\tdestConn, err := net.DialTimeout(\"tcp\", s.proxyHost, 10*time.Second)\n\tif err != nil {\n\t\tlog.Printf(\"error: %s category='%s'\", err, s.category)\n\t\thttp.Error(w, err.Error(), http.StatusServiceUnavailable)\n\t\treturn\n\t}\n\n\thj, ok := w.(http.Hijacker)\n\tif !ok {\n\t\tlog.Printf(\"error: Hijacking not supported category='%s'\", s.category)\n\t\thttp.Error(w, \"Hijacking not supported\", http.StatusInternalServerError)\n\t\tdestConn.Close()\n\t\treturn\n\t}\n\tclientConn, _, err := hj.Hijack()\n\tif err != nil {\n\t\tlog.Printf(\"error: %s category='%s'\", err, s.category)\n\t\thttp.Error(w, err.Error(), http.StatusServiceUnavailable)\n\t\tdestConn.Close()\n\t\treturn\n\t}\n\n\tif s.UseProxyAuthorization {\n\t\tr.Header.Set(\"Proxy-Authorization\", s.proxyAuthorization)\n\t}\n\n\tr.Write(destConn)\n\n\tgo transfer(clientConn, destConn)\n\tgo transfer(destConn, clientConn)\n\n\tlog.Printf(\"debug: End proxy category='%s'\", s.category)\n}\n\nfunc transfer(destination io.WriteCloser, source io.ReadCloser) {\n\tdefer destination.Close()\n\tdefer source.Close()\n\tio.Copy(destination, source)\n}\n\nfunc (s ExplicitProxy) handleHttp(w http.ResponseWriter, r *http.Request) {\n\thj, _ := w.(http.Hijacker)\n\tvar client *http.Client\n\tif s.UseProxyAuthorization {\n\t\tclient = &http.Client{\n\t\t\tTransport: s.proxyAuthTransport,\n\t\t}\n\t} else {\n\t\tclient = &http.Client{\n\t\t\tTransport: s.proxyTransport,\n\t\t}\n\t}\n\n\tr.RequestURI = \"\"\n\tif resp, err := client.Do(r); err != nil {\n\t\tlog.Printf(\"error: %s\", err)\n\t} else if conn, _, err := hj.Hijack(); err != nil {\n\t\tlog.Printf(\"error: %s\", err)\n\t} else {\n\t\tdefer conn.Close()\n\t\tresp.Write(conn)\n\t}\n}\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/wadahiro/go-transproxy\n\ngo 1.13\n\nrequire (\n\tgithub.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235\n\tgithub.com/comail/colog v0.0.0-20160416085026-fba8e7b1f46c\n\tgithub.com/coreos/go-iptables v0.2.0\n\tgithub.com/cybozu-go/cmd v1.5.0\n\tgithub.com/cybozu-go/log v1.4.1\n\tgithub.com/cybozu-go/netutil v1.1.0\n\tgithub.com/cybozu-go/transocks v1.0.0\n\tgithub.com/elazarl/goproxy v0.0.0-20170413182129-aacba83f36a5\n\tgithub.com/fardog/secureoperator v2.1.0+incompatible\n\tgithub.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b\n\tgithub.com/miekg/dns v0.0.0-20170812192144-0598bd43cf51\n\tgithub.com/pkg/errors v0.8.0\n\tgolang.org/x/net v0.0.0-20170809000501-1c05540f6879\n\tgolang.org/x/sys v0.0.0-20170814191752-2d3e384235de\n)\n"
  },
  {
    "path": "go.sum",
    "content": "github.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235 h1:m190Z0077f/CDftqBTbt7IlAqkmvkPEafq0byCfeisg=\ngithub.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U=\ngithub.com/comail/colog v0.0.0-20160416085026-fba8e7b1f46c h1:bzYQ6WpR+t35/y19HUkolcg7SYeWZ15IclC9Z4naGHI=\ngithub.com/comail/colog v0.0.0-20160416085026-fba8e7b1f46c/go.mod h1:1WwgAwMKQLYG5I2FBhpVx94YTOAuB2W59IZ7REjSE6Y=\ngithub.com/coreos/go-iptables v0.2.0 h1:RmVRALeVCicZcF3rF05e0ooU9x9TmalN0HcT4hkhG5s=\ngithub.com/coreos/go-iptables v0.2.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=\ngithub.com/cybozu-go/cmd v1.5.0 h1:ootYtwvAikKShkE6NRwAVGID7x1BrQwVH7IDeqFe8zM=\ngithub.com/cybozu-go/cmd v1.5.0/go.mod h1:VW0nNKV31B3qvEhHZI26Rjgq5afWYm6Ulai24Qkgx5M=\ngithub.com/cybozu-go/log v1.4.1 h1:EGx01xTw+ZJEM/sJcTyHm++VFqN2WUlnOi1zMOd6ZJQ=\ngithub.com/cybozu-go/log v1.4.1/go.mod h1:qS5RWuo80yknMR3he2WCWxqfVlbOuSFMz0r1mJwS018=\ngithub.com/cybozu-go/netutil v1.1.0 h1:9o33zFrGqXpexOX3hlrN4ebYwQvThr9UP33md7mGNmc=\ngithub.com/cybozu-go/netutil v1.1.0/go.mod h1:UiOBRGyD1OiABE7Hk2bXac0mqDwuG4xMQc7T5XidXRA=\ngithub.com/cybozu-go/transocks v1.0.0 h1:IyemjpF4TA/5qJd2royU4fjWBwpJKmFpi1tvCkE39dg=\ngithub.com/cybozu-go/transocks v1.0.0/go.mod h1:CXrq7WldMyROCLqg8JLxvhxxr8b8inYS/GjJ08b9UyQ=\ngithub.com/elazarl/goproxy v0.0.0-20170413182129-aacba83f36a5 h1:6hndtckUKiV+txASiPN72sRWuHJQycm6B9BI18YYmzs=\ngithub.com/elazarl/goproxy v0.0.0-20170413182129-aacba83f36a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=\ngithub.com/fardog/secureoperator v2.1.0+incompatible h1:l0nfc8iv87iAa6FBgRnH9wnFkfKHMnVJhQFVhaLZfQ8=\ngithub.com/fardog/secureoperator v2.1.0+incompatible/go.mod h1:N/UFQradwwlO7ZCxDMWHPRdXW6D+nnEU6nQ8Zo0o9T4=\ngithub.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE=\ngithub.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=\ngithub.com/miekg/dns v0.0.0-20170812192144-0598bd43cf51 h1:hZ1ngpLE9G7RaoMM6LlixdiCedgR4GhqzOYBcarqaho=\ngithub.com/miekg/dns v0.0.0-20170812192144-0598bd43cf51/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=\ngithub.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=\ngithub.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=\ngolang.org/x/net v0.0.0-20170809000501-1c05540f6879 h1:0rFa7EaCGdQPmZVbo9F7MNF65b8dyzS6EUnXjs9Cllk=\ngolang.org/x/net v0.0.0-20170809000501-1c05540f6879/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=\ngolang.org/x/sys v0.0.0-20170814191752-2d3e384235de/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=\n"
  },
  {
    "path": "http.go",
    "content": "package transproxy\n\nimport (\n\t\"fmt\"\n\t\"log\"\n\t\"net\"\n\t\"net/http\"\n\n\t\"github.com/elazarl/goproxy\"\n)\n\ntype HTTPProxy struct {\n\tHTTPProxyConfig\n}\n\ntype HTTPProxyConfig struct {\n\tListenAddress string\n\tNoProxy       NoProxy\n\tVerbose       bool\n}\n\nfunc NewHTTPProxy(c HTTPProxyConfig) *HTTPProxy {\n\treturn &HTTPProxy{\n\t\tHTTPProxyConfig: c,\n\t}\n}\n\nfunc (s HTTPProxy) Start() error {\n\tl, err := NewTCPListener(s.ListenAddress)\n\tif err != nil {\n\t\tlog.Printf(\"error: Failed listening for tcp connections - %s category='HTTP-Proxy'\", err.Error())\n\t}\n\n\tproxy := goproxy.NewProxyHttpServer()\n\tproxy.Tr.Proxy = httpProxyFromRule(s.NoProxy)\n\tproxy.Verbose = s.Verbose\n\n\tproxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {\n\t\tlog.Printf(\"debug: Accept: %s, %s\", req.Host, req.URL)\n\t\tif req.Host == \"\" {\n\t\t\t// TODO use origAddr from TCPCon\n\t\t\tfmt.Fprintln(w, \"Cannot handle requests without Host header, e.g., HTTP 1.0\")\n\t\t\treturn\n\t\t}\n\n\t\t// Convert to proxy request (abs URL request) for passing goproxy handler\n\t\treq.URL.Scheme = \"http\"\n\t\treq.URL.Host = req.Host\n\n\t\t// access logging\n\t\thost, _, _ := net.SplitHostPort(req.RemoteAddr)\n\t\tlog.Printf(\"info: category='HTTP-Proxy' remoteAddr='%s' method='%s' url='%s'\", host, req.Method, req.URL)\n\n\t\t// proxy to real target\n\t\tproxy.ServeHTTP(w, req)\n\t})\n\n\tlog.Printf(\"info: Start listener on %s category='HTTP-Proxy'\", s.ListenAddress)\n\n\tgo func() {\n\t\thttp.Serve(l, proxy)\n\t}()\n\n\treturn nil\n}\n"
  },
  {
    "path": "https.go",
    "content": "package transproxy\n\nimport (\n\t\"log\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/inconshreveable/go-vhost\"\n\t\"golang.org/x/net/proxy\"\n)\n\ntype HTTPSProxy struct {\n\tHTTPSProxyConfig\n}\n\ntype HTTPSProxyConfig struct {\n\tListenAddress string\n\tNoProxy       NoProxy\n}\n\nfunc NewHTTPSProxy(c HTTPSProxyConfig) *HTTPSProxy {\n\treturn &HTTPSProxy{\n\t\tHTTPSProxyConfig: c,\n\t}\n}\n\nfunc (s HTTPSProxy) Start() error {\n\tdialer := &net.Dialer{\n\t\tKeepAlive: 3 * time.Minute,\n\t\tDualStack: true,\n\t}\n\tu, err := url.Parse(GetProxyEnv(\"http_proxy\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpdialer, err := proxy.FromURL(u, dialer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnpdialer := proxy.Direct\n\n\tlog.Printf(\"info: Start listener on %s category='HTTPS-Proxy'\", s.ListenAddress)\n\n\tgo func() {\n\t\tListenTCP(s.ListenAddress, func(tc *TCPConn) {\n\t\t\ttlsConn, err := vhost.TLS(tc)\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"error: Failed handling TLS connection - %s\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tdefer func() {\n\t\t\t\ttlsConn.Free()\n\t\t\t}()\n\n\t\t\torigServer := tlsConn.Host()\n\t\t\tif origServer == \"\" {\n\t\t\t\tlog.Printf(\"warn: Cannot get SNI, so fallback using `SO_ORIGINAL_DST` or `IP6T_SO_ORIGINAL_DST`\")\n\t\t\t\torigServer = tc.OrigAddr // IPAddress:Port\n\n\t\t\t\t// TODO getting domain from origAddr, then check whether we should use proxy or not\n\t\t\t} else {\n\t\t\t\tlog.Printf(\"debug: SNI: %s\", origServer)\n\t\t\t\torigServer = net.JoinHostPort(origServer, \"443\")\n\t\t\t}\n\n\t\t\t// access logging\n\t\t\thost, _, _ := net.SplitHostPort(tc.RemoteAddr().String())\n\t\t\tlog.Printf(\"info: category='HTTPS-Proxy' remoteAddr='%s' method=CONNECT host='%s'\", host, origServer)\n\n\t\t\tvar destConn net.Conn\n\t\t\tif useProxy(s.NoProxy, strings.Split(origServer, \":\")[0]) {\n\n\t\t\t\tdestConn, err = pdialer.Dial(\"tcp\", origServer)\n\t\t\t} else {\n\t\t\t\tdestConn, err = npdialer.Dial(\"tcp\", origServer)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"warn: Failed to connect to destination - %s\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\t// First, write ClientHello to real destination because we have already read it\n\t\t\tch := tlsConn.ClientHelloMsg.Raw\n\t\t\tchSize := len(ch)\n\t\t\tchHeader := []byte{0x16, 0x03, 0x01, byte(chSize >> 8), byte(chSize)}\n\t\t\tchRecord := append(chHeader, ch...)\n\t\t\tdestConn.Write(chRecord)\n\n\t\t\t// Then, pipe the data\n\t\t\tPipe(tc, destConn)\n\t\t})\n\t}()\n\n\treturn nil\n}\n"
  },
  {
    "path": "iptables.go",
    "content": "package transproxy\n\nimport (\n\t\"fmt\"\n\t\"net\"\n\t\"strconv\"\n\t\"strings\"\n\n\t\"github.com/coreos/go-iptables/iptables\"\n)\n\nconst (\n\tNAT        = \"nat\"\n\tPREROUTING = \"PREROUTING\"\n\tOUTPUT     = \"OUTPUT\"\n)\n\ntype IPTables struct {\n\tiptables      *iptables.IPTables\n\tdnsTCPOutRule []string\n\tdnsTCPRule    []string\n\tdnsUDPRule    []string\n\thttpRule      []string\n\thttpsRule     []string\n\ttcpRule       []string\n\terr           error\n}\n\ntype IPTablesConfig struct {\n\tDNSToPort   int\n\tHTTPToPort  int\n\tHTTPSToPort int\n\tTCPToPort   int\n\tTCPDPorts   []int\n\tPublicDNS   string\n}\n\nfunc NewIPTables(c *IPTablesConfig) (*IPTables, error) {\n\tt, err := iptables.New()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\tvar tcpDPorts []string\n\tfor _, v := range c.TCPDPorts {\n\t\ttcpDPorts = append(tcpDPorts, strconv.Itoa(v))\n\t}\n\n\tvar dnsTCPOutRule []string\n\tif c.PublicDNS != \"\" {\n\t\th, p, err := net.SplitHostPort(c.PublicDNS)\n\t\tif err != nil {\n\t\t\tc.PublicDNS = net.JoinHostPort(c.PublicDNS, \"53\")\n\t\t}\n\t\th, p, _ = net.SplitHostPort(c.PublicDNS)\n\t\tdnsTCPOutRule = []string{NAT, OUTPUT, \"-p\", \"tcp\", \"-d\", h, \"--dport\", p, \"-j\", \"REDIRECT\", \"--to-ports\", strconv.Itoa(c.TCPToPort)}\n\t}\n\n\tdnsTCPRule := []string{NAT, PREROUTING, \"-p\", \"tcp\", \"--dport\", \"53\", \"-j\", \"REDIRECT\", \"--to-ports\", strconv.Itoa(c.DNSToPort)}\n\tdnsUDPRule := []string{NAT, PREROUTING, \"-p\", \"udp\", \"--dport\", \"53\", \"-j\", \"REDIRECT\", \"--to-ports\", strconv.Itoa(c.DNSToPort)}\n\thttpRule := []string{NAT, PREROUTING, \"-p\", \"tcp\", \"--dport\", \"80\", \"-j\", \"REDIRECT\", \"--to-ports\", strconv.Itoa(c.HTTPToPort)}\n\thttpsRule := []string{NAT, PREROUTING, \"-p\", \"tcp\", \"--dport\", \"443\", \"-j\", \"REDIRECT\", \"--to-ports\", strconv.Itoa(c.HTTPSToPort)}\n\ttcpRule := []string{NAT, PREROUTING, \"-p\", \"tcp\", \"-m\", \"multiport\", \"--dport\", strings.Join(tcpDPorts, \",\"), \"-j\", \"REDIRECT\", \"--to-ports\", strconv.Itoa(c.TCPToPort)}\n\n\treturn &IPTables{\n\t\tiptables:      t,\n\t\tdnsTCPOutRule: dnsTCPOutRule,\n\t\tdnsTCPRule:    dnsTCPRule,\n\t\tdnsUDPRule:    dnsUDPRule,\n\t\thttpRule:      httpRule,\n\t\thttpsRule:     httpsRule,\n\t\ttcpRule:       tcpRule,\n\t}, nil\n}\n\nfunc (t *IPTables) Start() error {\n\tt.Check(t.dnsTCPOutRule)\n\tt.Check(t.dnsTCPRule)\n\tt.Check(t.dnsUDPRule)\n\tt.Check(t.httpRule)\n\tt.Check(t.httpsRule)\n\tt.Check(t.tcpRule)\n\n\tt.insertRule(t.dnsTCPOutRule)\n\tt.insertRule(t.dnsTCPRule)\n\tt.insertRule(t.dnsUDPRule)\n\tt.insertRule(t.httpRule)\n\tt.insertRule(t.httpsRule)\n\tt.insertRule(t.tcpRule)\n\n\treturn t.err\n}\n\nfunc (t *IPTables) Stop() error {\n\tt.deleteRule(t.dnsTCPOutRule)\n\tt.deleteRule(t.dnsTCPRule)\n\tt.deleteRule(t.dnsUDPRule)\n\tt.deleteRule(t.httpRule)\n\tt.deleteRule(t.httpsRule)\n\tt.deleteRule(t.tcpRule)\n\n\treturn t.err\n}\n\nfunc (t *IPTables) Show() string {\n\ts := fmt.Sprintf(`iptables -t %s -I %s\niptables -t %s -I %s\niptables -t %s -I %s\niptables -t %s -I %s\niptables -t %s -I %s`,\n\t\tt.tcpRule[0], strings.Join(t.tcpRule[1:], \" \"),\n\t\tt.httpsRule[0], strings.Join(t.httpsRule[1:], \" \"),\n\t\tt.httpRule[0], strings.Join(t.httpRule[1:], \" \"),\n\t\tt.dnsUDPRule[0], strings.Join(t.dnsUDPRule[1:], \" \"),\n\t\tt.dnsTCPRule[0], strings.Join(t.dnsTCPRule[1:], \" \"),\n\t)\n\n\tif len(t.dnsTCPOutRule) > 0 {\n\t\ts += fmt.Sprintf(`\niptables -t %s -I %s`,\n\t\t\tt.dnsTCPOutRule[0], strings.Join(t.dnsTCPOutRule[1:], \" \"),\n\t\t)\n\t}\n\n\treturn s\n}\n\nfunc (t *IPTables) Check(rule []string) {\n\tif t.err != nil || len(rule) < 3 {\n\t\treturn\n\t}\n\n\texists, err := t.iptables.Exists(rule[0], rule[1], rule[2:]...)\n\tif exists {\n\t\tt.err = fmt.Errorf(\"Same iptables rule already exists : iptables -t %s -I %s\", rule[0], strings.Join(rule[1:], \" \"))\n\t}\n\n\tif err != nil {\n\t\tt.err = fmt.Errorf(\"Checking iptables rule failed : %s\", err.Error())\n\t}\n}\n\nfunc (t *IPTables) insertRule(rule []string) {\n\tif t.err != nil || len(rule) < 3 {\n\t\treturn\n\t}\n\n\tif err := t.iptables.Insert(rule[0], rule[1], 1, rule[2:]...); err != nil {\n\t\tt.err = fmt.Errorf(\"Insert iptables rule failed : %s\", err.Error())\n\t}\n}\n\nfunc (t *IPTables) deleteRule(rule []string) {\n\t// Don't skip when it has error for deleting all rules\n\tif len(rule) < 3 {\n\t\treturn\n\t}\n\n\tif err := t.iptables.Delete(rule[0], rule[1], rule[2:]...); err != nil {\n\t\tt.err = fmt.Errorf(\"Delete iptables rule failed : %s\", err.Error())\n\t}\n}\n"
  },
  {
    "path": "tcp.go",
    "content": "package transproxy\n\nimport (\n\t\"log\"\n\t\"net\"\n\t\"net/url\"\n\t\"strings\"\n\t\"time\"\n\n\t\"golang.org/x/net/proxy\"\n)\n\ntype TCPProxy struct {\n\tTCPProxyConfig\n}\n\ntype TCPProxyConfig struct {\n\tListenAddress string\n\tNoProxy       NoProxy\n}\n\nfunc NewTCPProxy(c TCPProxyConfig) *TCPProxy {\n\treturn &TCPProxy{\n\t\tTCPProxyConfig: c,\n\t}\n}\n\nfunc (s TCPProxy) Start() error {\n\t//pdialer := proxy.FromEnvironment()\n\n\tdialer := &net.Dialer{\n\t\tKeepAlive: 3 * time.Minute,\n\t\tDualStack: true,\n\t}\n\tu, err := url.Parse(GetProxyEnv(\"http_proxy\"))\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpdialer, err := proxy.FromURL(u, dialer)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tnpdialer := proxy.Direct\n\n\tlog.Printf(\"info: Start listener on %s category='TCP-Proxy'\", s.ListenAddress)\n\n\tgo func() {\n\t\tListenTCP(s.ListenAddress, func(tc *TCPConn) {\n\t\t\t// access logging\n\t\t\thost, _, _ := net.SplitHostPort(tc.RemoteAddr().String())\n\t\t\tlog.Printf(\"info: category='TCP-Proxy' remoteAddr='%s' method=CONNECT host='%s'\", host, tc.OrigAddr)\n\n\t\t\tvar destConn net.Conn\n\t\t\t// TODO Convert OrigAddr to domain and check useProxy with domain too?\n\t\t\tif useProxy(s.NoProxy, strings.Split(tc.OrigAddr, \":\")[0]) {\n\n\t\t\t\tdestConn, err = pdialer.Dial(\"tcp\", tc.OrigAddr)\n\t\t\t} else {\n\t\t\t\tdestConn, err = npdialer.Dial(\"tcp\", tc.OrigAddr)\n\t\t\t}\n\n\t\t\tif err != nil {\n\t\t\t\tlog.Printf(\"error: Failed to connect to destination - %s category='TCP-Proxy'\", err.Error())\n\t\t\t\treturn\n\t\t\t}\n\n\t\t\tPipe(tc, destConn)\n\t\t})\n\t}()\n\n\treturn nil\n}\n"
  }
]