[
  {
    "path": ".circleci/config.yml",
    "content": "version: 2\njobs:\n  build:\n    docker:\n    - image: quay.io/cybozu/golang:1.11-bionic\n    working_directory: /work\n    steps:\n    - checkout\n    - run: test -z \"$(gofmt -s -l . | grep -v '^vendor' | tee /dev/stderr)\"\n    - run: golint -set_exit_status .\n    - run: go build ./...\n    - run: go test -race -v ./...\n    - run: go vet ./...\n\nworkflows:\n  version: 2\n  main:\n    jobs:\n      - build\n"
  },
  {
    "path": ".gitignore",
    "content": "# Compiled Object files, Static and Dynamic libs (Shared Objects)\n*.o\n*.a\n*.so\n\n# Editors\n*~\n.*.swp\n.#*\n\\#*#\n\n# Folders\n_obj\n_test\n\n# Architecture specific extensions/prefixes\n*.[568vq]\n[568vq].out\n\n*.cgo1.go\n*.cgo2.c\n_cgo_defun.c\n_cgo_gotypes.go\n_cgo_export.*\n\n_testmain.go\n\n*.exe\n*.test\n*.prof\n\n# Ignore go.sum\n/go.sum\n"
  },
  {
    "path": ".golangci.yml",
    "content": "linters:\n    enable:\n        - dupl\n        - goconst\n        - gofmt\n        - golint\n        - typecheck\n        - unparam\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\n## [Unreleased]\n\n## [1.1.1] - 2019-03-16\n\n### Changed\n- Replace `syscall` with `golang.org/x/sys/unix`, contriubted by @otariidae (#14).\n\n## [1.1.0] - 2018-11-13\n\n### Changed\n- Update `github.com/cybozu-go/cmd` to `github.com/cybozu-go/well` (#7, #9).\n- Replace TravisCI with CircleCI.\n\n## [1.0.0] - 2016-09-01\n\n### Added\n- transocks now adopts [github.com/cybozu-go/well][well] framework.  \n  As a result, it implements [the common spec][spec] including graceful restart.\n\n### Changed\n- The default configuration file path is now `/etc/transocks.toml`.\n- \"listen\" config option becomes optional.  Default is \"localhost:1081\".\n- Configuration items for logging is changed.\n\n[well]: https://github.com/cybozu-go/well\n[spec]: https://github.com/cybozu-go/well/blob/master/README.md#specifications\n[Unreleased]: https://github.com/cybozu-go/transocks/compare/v1.1.1...HEAD\n[1.1.1]: https://github.com/cybozu-go/transocks/compare/v1.1.0...v1.1.1\n[1.1.0]: https://github.com/cybozu-go/transocks/compare/v1.0.0...v1.1.0\n[1.0.0]: https://github.com/cybozu-go/transocks/compare/v0.1...v1.0.0\n"
  },
  {
    "path": "CONTRIBUTORS.md",
    "content": "transocks contributors\n======================\n\n* [@ayatk](https://github.com/ayatk)\n* [@otariidae](https://github.com/otariidae)\n"
  },
  {
    "path": "DESIGN.md",
    "content": "Design notes\n============\n\ntransocks should work as a SOCKS5 client used as a transparent proxy\nagent running on every hosts in trusted (i.e. data center) networks.\n\nDestination NAT (DNAT)\n----------------------\n\nOn Linux, redirecting locally-generated packet to transocks can be done\nby iptables with DNAT (or REDIRECT) target.\n\nSince DNAT/REDIRECT modifies packet's destination address, transocks\nneed to recover the destination address by using `getsockopt` with\n`SO_ORIGINAL_DST` for IPv4 or with `IP6T_SO_ORIGINAL_DST` for IPv6.\nThis is, of course, Linux-specific, and Go does not provide standard\nAPI for them.\n\nPolicy-based routing\n--------------------\n\nExcept for DNAT, some operating systems provide a way to route packets\nto a specific program.  In order to receive such packets, the program\nneed to set special options on the listening socket before `bind`.\n\nDifficult is that Go does not allow setting socket options before `bind`.\n\n### Linux TPROXY\n\nLinux iptables has [TPROXY][] target that can route packets to a\nspecific local port.  The socket option is:\n\n* IPv4\n\n    ```\n    setsockopt(IPPROTO_IP, IP_TRANSPARENT)\n    ```\n\n* IPv6\n\n    ```\n    setsockopt(IPPROTO_IPV6, IPV6_TRANSPARENT)\n    ```\n\nTo set this option, transocks must have `CAP_NET_ADMIN` capability.\nRun transocks as root user, or grant `CAP_NET_ADMIN` for the file by:\n\n```\nsudo setcap 'cap_net_admin+ep' transocks\n```\n\n### FreeBSD, NetBSD, OpenBSD\n\nUse [PF with divert-to][pf] to route packets to a specific local port.\n\nThe listening program needs to set a socket option before `bind`:\n\n* FreeBSD (IPv4)\n\n    ```\n    setsockopt(IPPROTO_IP, IP_BINDANY)\n    ```\n\n* FreeBSD (IPv6)\n\n    ```\n    setsockopt(IPPROTO_IPV6, IPV6_BINDANY)\n    ```\n\n* NetBSD, OpenBSD\n\n    ```\n    setsockopt(SOL_SOCKET, SO_BINDANY)\n    ```\n\nFor this to work, transocks must run as root.\n\nImplementation strategy\n-----------------------\n\nWe use Go for its efficiency and simpleness.\n\nFor SOCKS5, [golang.org/x/net/proxy][x/net] already provides SOCKS5 client.\n\nFor Linux NAT, we need to use [golang.org/x/sys/unix][x/sys] and\n[unsafe.Pointer][] to use non-standard `getsockopt` options.\n\nTo set socket options before `bind`, we need to create sockets manually\nby using [golang.org/x/sys/unix] and then convert the native socket to\n`*net.TCPListener` by [net.FileListener][].\n\nCONNECT tunnel\n--------------\n\nAs golang.org/x/net/proxy can add custom dialers, we can implement\na proxy using http CONNECT method for tunneling through HTTP proxies\nsuch as [Squid][].\n\nNote that the default Squid configuration file installed for\nUbuntu 14.04 prohibits CONNECT to ports other than 443.\n\n```\n# Deny CONNECT to other than secure SSL ports\nhttp_access deny CONNECT !SSL_ports\n```\n\nRemove or comment out the line to allow CONNECT to ports other than 443.\n\n[TPROXY]: https://www.kernel.org/doc/Documentation/networking/tproxy.txt\n[pf]: http://wiki.squid-cache.org/ConfigExamples/Intercept/OpenBsdPf\n[x/net]: https://godoc.org/golang.org/x/net/proxy#SOCKS5\n[x/sys]: https://godoc.org/golang.org/x/sys/unix\n[unsafe.Pointer]: https://golang.org/pkg/unsafe/#Pointer\n[net.FileListener]: https://golang.org/pkg/net/#FileListener\n[Squid]: http://www.squid-cache.org/\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2016 Cybozu\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": "README.md",
    "content": "[![GitHub release](https://img.shields.io/github/release/cybozu-go/transocks.svg?maxAge=60)][releases]\n[![GoDoc](https://godoc.org/github.com/cybozu-go/transocks?status.svg)][godoc]\n[![CircleCI](https://circleci.com/gh/cybozu-go/coil.svg?style=svg)](https://circleci.com/gh/cybozu-go/transocks)\n[![Go Report Card](https://goreportcard.com/badge/github.com/cybozu-go/transocks)](https://goreportcard.com/report/github.com/cybozu-go/transocks)\n\ntransocks - a transparent SOCKS5/HTTP proxy\n===========================================\n\n**transocks** is a background service to redirect TCP connections\ntransparently to a SOCKS5 server or a HTTP proxy server like [Squid][].\n\nCurrently, transocks supports only Linux iptables with DNAT/REDIRECT target.\n\nFeatures\n--------\n\n* IPv4 and IPv6\n\n    Both IPv4 and IPv6 are supported.\n    Note that `nf_conntrack_ipv4` or `nf_conntrack_ipv6` kernel modules\n    must be loaded beforehand.\n\n* SOCKS5 and HTTP proxy (CONNECT)\n\n    We recommend using SOCKS5 server if available.\n    Take a look at our SOCKS server [usocksd][] if you are looking for.\n\n    HTTP proxies often prohibits CONNECT method to make connections\n    to ports other than 443.  Make sure your HTTP proxy allows CONNECT\n    to the ports you want.\n\n* Graceful stop & restart\n\n    * On SIGINT/SIGTERM, transocks stops gracefully.\n    * On SIGHUP, transocks restarts gracefully.\n\n* Library and executable\n\n    transocks comes with a handy executable.\n    You may use the library to create your own.\n\nInstall\n-------\n\nUse Go 1.7 or better.\n\n```\ngo get -u github.com/cybozu-go/transocks/...\n```\n\nUsage\n-----\n\n`transocks [-h] [-f CONFIG]`\n\nThe default configuration file path is `/etc/transocks.toml`.\n\nIn addition, transocks implements [the common spec](https://github.com/cybozu-go/cmd#specifications) from [`cybozu-go/cmd`](https://github.com/cybozu-go/cmd).\n\ntransocks does not have *daemon* mode.  Use systemd to run it\nas a background service.\n\nConfiguration file format\n-------------------------\n\n`transocks.toml` is a [TOML][] file.\n\n`proxy_url` is mandatory.  Other items are optional.\n\n```\n# listening address of transocks.\nlisten = \"localhost:1081\"    # default is \"localhost:1081\"\n\nproxy_url = \"socks5://10.20.30.40:1080\"  # for SOCKS5 server\n#proxy_url = \"http://10.20.30.40:3128\"   # for HTTP proxy server\n\n[log]\nfilename = \"/path/to/file\"   # default to stderr\nlevel = \"info\"               # critical\", error, warning, info, debug\nformat = \"json\"              # plain, logfmt, json\n```\n\nRedirecting connections by iptables\n-----------------------------------\n\nUse DNAT or REDIRECT target in OUTPUT chain of the `nat` table.\n\nSave the following example to a file, then execute:\n`sudo iptables-restore < FILE`\n\n```\n*nat\n:PREROUTING ACCEPT [0:0]\n:INPUT ACCEPT [0:0]\n:OUTPUT ACCEPT [0:0]\n:POSTROUTING ACCEPT [0:0]\n:TRANSOCKS - [0:0]\n-A OUTPUT -p tcp -j TRANSOCKS\n-A TRANSOCKS -d 0.0.0.0/8 -j RETURN\n-A TRANSOCKS -d 10.0.0.0/8 -j RETURN\n-A TRANSOCKS -d 127.0.0.0/8 -j RETURN\n-A TRANSOCKS -d 169.254.0.0/16 -j RETURN\n-A TRANSOCKS -d 172.16.0.0/12 -j RETURN\n-A TRANSOCKS -d 192.168.0.0/16 -j RETURN\n-A TRANSOCKS -d 224.0.0.0/4 -j RETURN\n-A TRANSOCKS -d 240.0.0.0/4 -j RETURN\n-A TRANSOCKS -p tcp -j REDIRECT --to-ports 1081\nCOMMIT\n```\n\nUse *ip6tables* to redirect IPv6 connections.\n\n**NOTE:** If you are going to use transocks on Linux gateway to redirect transit traffic, you have to bind transocks on primary address of internal network interface because iptables REDIRECT action in PREROUTING chain changes packet destination IP to primary address of incoming interface.\n\nLibrary usage\n-------------\n\nRead [the documentation][godoc].\n\nLicense\n-------\n\n[MIT](https://opensource.org/licenses/MIT)\n\n[releases]: https://github.com/cybozu-go/transocks/releases\n[godoc]: https://godoc.org/github.com/cybozu-go/transocks\n[Squid]: http://www.squid-cache.org/\n[usocksd]: https://github.com/cybozu-go/usocksd\n[TOML]: https://github.com/toml-lang/toml\n"
  },
  {
    "path": "RELEASE.md",
    "content": "Release procedure\n=================\n\nThis document describes how to release a new version of coil.\n\nVersioning\n----------\n\nFollow [semantic versioning 2.0.0][semver] to choose the new version number.\n\nPrepare change log entries\n--------------------------\n\nAdd notable changes since the last release to [CHANGELOG.md](CHANGELOG.md).\nIt should look like:\n\n```markdown\n(snip)\n## [Unreleased]\n\n### Added\n- Implement ... (#35)\n\n### Changed\n- Fix a bug in ... (#33)\n\n### Removed\n- Deprecated `-option` is removed ... (#39)\n\n(snip)\n```\n\nBump version\n------------\n\n1. Determine a new version number.  Let it write `$VERSION`.\n1. Checkout `master` branch.\n1. Edit `CHANGELOG.md` for the new version ([example][]).\n1. Commit the change and add a git tag, then push them.\n\n    ```console\n    $ git commit -a -m \"Bump version to $VERSION\"\n    $ git tag v$VERSION\n    $ git push origin master --tags\n    ```\n\nPublish GitHub release page\n---------------------------\n\nGo to https://github.com/cybozu-go/coil/releases and edit the tag.\nFinally, press `Publish release` button.\n\n[semver]: https://semver.org/spec/v2.0.0.html\n[example]: https://github.com/cybozu-go/etcdpasswd/commit/77d95384ac6c97e7f48281eaf23cb94f68867f79\n"
  },
  {
    "path": "cmd/transocks/main.go",
    "content": "package main\n\nimport (\n\t\"flag\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\n\t\"github.com/BurntSushi/toml\"\n\t\"github.com/cybozu-go/log\"\n\t\"github.com/cybozu-go/transocks\"\n\t\"github.com/cybozu-go/well\"\n)\n\ntype tomlConfig struct {\n\tListen   string         `toml:\"listen\"`\n\tProxyURL string         `toml:\"proxy_url\"`\n\tLog      well.LogConfig `toml:\"log\"`\n}\n\nconst (\n\tdefaultAddr = \"localhost:1081\"\n)\n\nvar (\n\tconfigFile = flag.String(\"f\", \"/etc/transocks.toml\",\n\t\t\"TOML configuration file path\")\n)\n\nfunc loadConfig() (*transocks.Config, error) {\n\ttc := &tomlConfig{\n\t\tListen: defaultAddr,\n\t}\n\tmd, err := toml.DecodeFile(*configFile, tc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif len(md.Undecoded()) > 0 {\n\t\treturn nil, fmt.Errorf(\"undecoded key in TOML: %v\", md.Undecoded())\n\t}\n\n\tc := transocks.NewConfig()\n\tc.Addr = tc.Listen\n\n\tu, err := url.Parse(tc.ProxyURL)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tc.ProxyURL = u\n\n\terr = tc.Log.Apply()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\treturn c, nil\n}\n\nfunc serve(lns []net.Listener, c *transocks.Config) {\n\ts, err := transocks.NewServer(c)\n\tif err != nil {\n\t\tlog.ErrorExit(err)\n\t}\n\n\tfor _, ln := range lns {\n\t\ts.Serve(ln)\n\t}\n\terr = well.Wait()\n\tif err != nil && !well.IsSignaled(err) {\n\t\tlog.ErrorExit(err)\n\t}\n}\n\nfunc main() {\n\tflag.Parse()\n\n\tc, err := loadConfig()\n\tif err != nil {\n\t\tlog.ErrorExit(err)\n\t}\n\n\tg := &well.Graceful{\n\t\tListen: func() ([]net.Listener, error) {\n\t\t\treturn transocks.Listeners(c)\n\t\t},\n\t\tServe: func(lns []net.Listener) {\n\t\t\tserve(lns, c)\n\t\t},\n\t}\n\tg.Run()\n\n\terr = well.Wait()\n\tif err != nil && !well.IsSignaled(err) {\n\t\tlog.ErrorExit(err)\n\t}\n}\n"
  },
  {
    "path": "cmd/transocks/sample.toml",
    "content": "# This is a sample TOML file for transocks.\n\nlisten = \"localhost:1081\"\n\nproxy_url = \"socks5://10.20.30.40:1080\"  # for SOCKS5 server\n#proxy_url = \"http://10.20.30.40:3128\"   # for HTTP proxy server\n\n[log]\nlevel = \"debug\"\nfilename = \"/var/log/transocks.log\"\n"
  },
  {
    "path": "config.go",
    "content": "package transocks\n\nimport (\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"github.com/cybozu-go/log\"\n\t\"github.com/cybozu-go/well\"\n)\n\nconst (\n\tdefaultShutdownTimeout = 1 * time.Minute\n)\n\n// Mode is the type of transocks mode.\ntype Mode string\n\nfunc (m Mode) String() string {\n\treturn string(m)\n}\n\nconst (\n\t// ModeNAT is mode constant for NAT.\n\tModeNAT = Mode(\"nat\")\n)\n\n// Config keeps configurations for Server.\ntype Config struct {\n\t// Addr is the listening address.\n\tAddr string\n\n\t// ProxyURL is the URL for upstream proxy.\n\t//\n\t// For SOCKS5, URL looks like \"socks5://USER:PASSWORD@HOST:PORT\".\n\t//\n\t// For HTTP proxy, URL looks like \"http://USER:PASSWORD@HOST:PORT\".\n\t// The HTTP proxy must support CONNECT method.\n\tProxyURL *url.URL\n\n\t// Mode determines how clients are routed to transocks.\n\t// Default is ModeNAT.  No other options are available at this point.\n\tMode Mode\n\n\t// ShutdownTimeout is the maximum duration the server waits for\n\t// all connections to be closed before shutdown.\n\t//\n\t// Zero duration disables timeout.  Default is 1 minute.\n\tShutdownTimeout time.Duration\n\n\t// Dialer is the base dialer to connect to the proxy server.\n\t// The server uses the default dialer if this is nil.\n\tDialer *net.Dialer\n\n\t// Logger can be used to provide a custom logger.\n\t// If nil, the default logger is used.\n\tLogger *log.Logger\n\n\t// Env can be used to specify a well.Environment on which the server runs.\n\t// If nil, the server will run on the global environment.\n\tEnv *well.Environment\n}\n\n// NewConfig creates and initializes a new Config.\nfunc NewConfig() *Config {\n\tc := new(Config)\n\tc.Mode = ModeNAT\n\tc.ShutdownTimeout = defaultShutdownTimeout\n\treturn c\n}\n\n// validate validates the configuration.\n// It returns non-nil error if the configuration is not valid.\nfunc (c *Config) validate() error {\n\tif c.ProxyURL == nil {\n\t\treturn errors.New(\"ProxyURL is nil\")\n\t}\n\tif c.Mode != ModeNAT {\n\t\treturn fmt.Errorf(\"Unknown mode: %s\", c.Mode)\n\t}\n\treturn nil\n}\n"
  },
  {
    "path": "defs_linux.go",
    "content": "// Code generated by hand.  DO NOT EDIT.\n// +build linux\n\npackage transocks\n\nconst (\n\t// SO_ORIGINAL_DST is a Linux getsockopt optname.\n\tSO_ORIGINAL_DST = 80\n\n\t// IP6T_SO_ORIGINAL_DST a Linux getsockopt optname.\n\tIP6T_SO_ORIGINAL_DST = 80\n)\n"
  },
  {
    "path": "go.mod",
    "content": "module github.com/cybozu-go/transocks\n\nrequire (\n\tgithub.com/BurntSushi/toml v0.3.1\n\tgithub.com/cybozu-go/log v1.5.0\n\tgithub.com/cybozu-go/netutil v1.2.0\n\tgithub.com/cybozu-go/well v1.8.1\n\tgolang.org/x/net v0.0.0-20180911220305-26e67e76b6c3\n\tgolang.org/x/sys v0.0.0-20180906133057-8cf3aee42992\n)\n"
  },
  {
    "path": "http_tunnel.go",
    "content": "// This file provides a dialer type of \"http://\" scheme for\n// golang.org/x/net/proxy package.\n//\n// The dialer type will be automatically registered by init().\n//\n// The dialer requests an upstream HTTP proxy to create a TCP tunnel\n// by CONNECT method.\n\npackage transocks\n\nimport (\n\t\"bufio\"\n\t\"bytes\"\n\t\"encoding/base64\"\n\t\"errors\"\n\t\"fmt\"\n\t\"net\"\n\t\"net/http\"\n\t\"net/url\"\n\t\"time\"\n\n\t\"golang.org/x/net/proxy\"\n)\n\nfunc init() {\n\tproxy.RegisterDialerType(\"http\", httpDialType)\n}\n\ntype httpDialer struct {\n\taddr    string\n\theader  http.Header\n\tforward proxy.Dialer\n}\n\nfunc httpDialType(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {\n\tvar header http.Header\n\tif uu := u.User; uu != nil {\n\t\tpasswd, _ := uu.Password()\n\t\tup := uu.Username() + \":\" + passwd\n\t\tauthz := \"Basic \" + base64.StdEncoding.EncodeToString([]byte(up))\n\t\theader = map[string][]string{\n\t\t\t\"Proxy-Authorization\": {authz},\n\t\t}\n\t}\n\treturn &httpDialer{\n\t\taddr:    u.Host,\n\t\theader:  header,\n\t\tforward: forward,\n\t}, nil\n}\n\nfunc (d *httpDialer) Dial(network, addr string) (c net.Conn, err error) {\n\treq := &http.Request{\n\t\tMethod: \"CONNECT\",\n\t\tURL:    &url.URL{Opaque: addr},\n\t\tHost:   addr,\n\t\tHeader: d.header,\n\t}\n\tc, err = d.forward.Dial(\"tcp\", d.addr)\n\tif err != nil {\n\t\treturn\n\t}\n\treq.Write(c)\n\n\t// Read response until \"\\r\\n\\r\\n\".\n\t// bufio cannot be used as the connected server may not be\n\t// a HTTP(S) server.\n\tc.SetReadDeadline(time.Now().Add(10 * time.Second))\n\tbuf := make([]byte, 0, 4096)\n\tb := make([]byte, 1)\n\tstate := 0\n\tfor {\n\t\t_, e := c.Read(b)\n\t\tif e != nil {\n\t\t\tc.Close()\n\t\t\treturn nil, errors.New(\"reset proxy connection\")\n\t\t}\n\t\tbuf = append(buf, b[0])\n\t\tswitch state {\n\t\tcase 0:\n\t\t\tif b[0] == byte('\\r') {\n\t\t\t\tstate++\n\t\t\t}\n\t\t\tcontinue\n\t\tcase 1:\n\t\t\tif b[0] == byte('\\n') {\n\t\t\t\tstate++\n\t\t\t} else {\n\t\t\t\tstate = 0\n\t\t\t}\n\t\t\tcontinue\n\t\tcase 2:\n\t\t\tif b[0] == byte('\\r') {\n\t\t\t\tstate++\n\t\t\t} else {\n\t\t\t\tstate = 0\n\t\t\t}\n\t\t\tcontinue\n\t\tcase 3:\n\t\t\tif b[0] == byte('\\n') {\n\t\t\t\tgoto PARSE\n\t\t\t} else {\n\t\t\t\tstate = 0\n\t\t\t}\n\t\t}\n\t}\n\nPARSE:\n\tvar zero time.Time\n\tc.SetReadDeadline(zero)\n\tresp, e := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(buf)), req)\n\tif e != nil {\n\t\tc.Close()\n\t\treturn nil, e\n\t}\n\tresp.Body.Close()\n\tif resp.StatusCode != 200 {\n\t\tc.Close()\n\t\treturn nil, fmt.Errorf(\"proxy returns %s\", resp.Status)\n\t}\n\n\treturn c, nil\n}\n"
  },
  {
    "path": "http_tunnel_test.go",
    "content": "package transocks\n\nimport (\n\t\"io\"\n\t\"net\"\n\t\"os\"\n\t\"testing\"\n\t\"time\"\n)\n\nfunc TestHTTPDialer(t *testing.T) {\n\tt.Skip()\n\n\t// This test only works if Squid allowing CONNECT to port 80 is\n\t// running on the local machine on port 3128.\n\n\td := &httpDialer{\n\t\taddr:    \"127.0.0.1:3128\",\n\t\tforward: &net.Dialer{Timeout: 5 * time.Second},\n\t}\n\n\tconn, err := d.Dial(\"tcp\", \"www.yahoo.com:80\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer conn.Close()\n\n\tconn.Write([]byte(\"GET / HTTP/1.1\\r\\nHost: www.yahoo.com:80\\r\\nConnection: close\\r\\n\\r\\n\"))\n\tio.Copy(os.Stdout, conn)\n}\n"
  },
  {
    "path": "original_dst_linux.go",
    "content": "// +build linux\n\npackage transocks\n\nimport (\n\tsyscall \"golang.org/x/sys/unix\"\n\t\"net\"\n\t\"os\"\n\t\"unsafe\"\n)\n\nfunc getsockopt(s int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) {\n\t_, _, e := syscall.Syscall6(\n\t\tsyscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(optname),\n\t\tuintptr(optval), uintptr(unsafe.Pointer(optlen)), 0)\n\tif e != 0 {\n\t\treturn e\n\t}\n\treturn\n}\n\n// GetOriginalDST retrieves the original destination address from\n// NATed connection.  Currently, only Linux iptables using DNAT/REDIRECT\n// is supported.  For other operating systems, this will just return\n// conn.LocalAddr().\n//\n// Note that this function only works when nf_conntrack_ipv4 and/or\n// nf_conntrack_ipv6 is loaded in the kernel.\nfunc GetOriginalDST(conn *net.TCPConn) (*net.TCPAddr, error) {\n\tf, err := conn.File()\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tdefer f.Close()\n\n\tfd := int(f.Fd())\n\t// revert to non-blocking mode.\n\t// see http://stackoverflow.com/a/28968431/1493661\n\tif err = syscall.SetNonblock(fd, true); err != nil {\n\t\treturn nil, os.NewSyscallError(\"setnonblock\", err)\n\t}\n\n\tv6 := conn.LocalAddr().(*net.TCPAddr).IP.To4() == nil\n\tif v6 {\n\t\tvar addr syscall.RawSockaddrInet6\n\t\tvar len uint32\n\t\tlen = uint32(unsafe.Sizeof(addr))\n\t\terr = getsockopt(fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST,\n\t\t\tunsafe.Pointer(&addr), &len)\n\t\tif err != nil {\n\t\t\treturn nil, os.NewSyscallError(\"getsockopt\", err)\n\t\t}\n\t\tip := make([]byte, 16)\n\t\tfor i, b := range addr.Addr {\n\t\t\tip[i] = b\n\t\t}\n\t\tpb := *(*[2]byte)(unsafe.Pointer(&addr.Port))\n\t\treturn &net.TCPAddr{\n\t\t\tIP:   ip,\n\t\t\tPort: int(pb[0])*256 + int(pb[1]),\n\t\t}, nil\n\t}\n\n\t// IPv4\n\tvar addr syscall.RawSockaddrInet4\n\tvar len uint32\n\tlen = uint32(unsafe.Sizeof(addr))\n\terr = getsockopt(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST,\n\t\tunsafe.Pointer(&addr), &len)\n\tif err != nil {\n\t\treturn nil, os.NewSyscallError(\"getsockopt\", err)\n\t}\n\tip := make([]byte, 4)\n\tfor i, b := range addr.Addr {\n\t\tip[i] = b\n\t}\n\tpb := *(*[2]byte)(unsafe.Pointer(&addr.Port))\n\treturn &net.TCPAddr{\n\t\tIP:   ip,\n\t\tPort: int(pb[0])*256 + int(pb[1]),\n\t}, nil\n}\n"
  },
  {
    "path": "original_dst_linux_test.go",
    "content": "// +build linux\n\npackage transocks\n\nimport (\n\t\"net\"\n\t\"testing\"\n)\n\nfunc TestGetOriginalDST(t *testing.T) {\n\tt.Skip()\n\n\tl, err := net.ListenTCP(\"tcp\", &net.TCPAddr{Port: 1081})\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer l.Close()\n\n\tc, err := l.Accept()\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tdefer c.Close()\n\n\torigAddr, err := GetOriginalDST(c.(*net.TCPConn))\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\n\tt.Log(origAddr.String())\n}\n"
  },
  {
    "path": "original_dst_stub.go",
    "content": "// +build !linux\n\npackage transocks\n\nimport \"net\"\n\n// GetOriginalDST retrieves the original destination address from\n// NATed connection.  Currently, only Linux iptables using DNAT/REDIRECT\n// is supported.  For other operating systems, this will just return\n// conn.LocalAddr().\n//\n// Note that this function only works when nf_conntrack_ipv4 and/or\n// nf_conntrack_ipv6 is loaded in the kernel.\nfunc GetOriginalDST(conn *net.TCPConn) (*net.TCPAddr, error) {\n\treturn conn.LocalAddr().(*net.TCPAddr), nil\n}\n"
  },
  {
    "path": "server.go",
    "content": "package transocks\n\nimport (\n\t\"context\"\n\t\"io\"\n\t\"net\"\n\t\"sync\"\n\t\"time\"\n\n\t\"github.com/cybozu-go/log\"\n\t\"github.com/cybozu-go/netutil\"\n\t\"github.com/cybozu-go/well\"\n\t\"golang.org/x/net/proxy\"\n)\n\nconst (\n\tkeepAliveTimeout = 3 * time.Minute\n\tcopyBufferSize   = 64 << 10\n)\n\n// Listeners returns a list of net.Listener.\nfunc Listeners(c *Config) ([]net.Listener, error) {\n\tln, err := net.Listen(\"tcp\", c.Addr)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn []net.Listener{ln}, nil\n}\n\n// Server provides transparent proxy server functions.\ntype Server struct {\n\twell.Server\n\tmode   Mode\n\tlogger *log.Logger\n\tdialer proxy.Dialer\n\tpool   sync.Pool\n}\n\n// NewServer creates Server.\n// If c is not valid, this returns non-nil error.\nfunc NewServer(c *Config) (*Server, error) {\n\tif err := c.validate(); err != nil {\n\t\treturn nil, err\n\t}\n\n\tdialer := c.Dialer\n\tif dialer == nil {\n\t\tdialer = &net.Dialer{\n\t\t\tKeepAlive: keepAliveTimeout,\n\t\t\tDualStack: true,\n\t\t}\n\t}\n\tpdialer, err := proxy.FromURL(c.ProxyURL, dialer)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tlogger := c.Logger\n\tif logger == nil {\n\t\tlogger = log.DefaultLogger()\n\t}\n\n\ts := &Server{\n\t\tServer: well.Server{\n\t\t\tShutdownTimeout: c.ShutdownTimeout,\n\t\t\tEnv:             c.Env,\n\t\t},\n\t\tmode:   c.Mode,\n\t\tlogger: logger,\n\t\tdialer: pdialer,\n\t\tpool: sync.Pool{\n\t\t\tNew: func() interface{} {\n\t\t\t\treturn make([]byte, copyBufferSize)\n\t\t\t},\n\t\t},\n\t}\n\ts.Server.Handler = s.handleConnection\n\treturn s, nil\n}\n\nfunc (s *Server) handleConnection(ctx context.Context, conn net.Conn) {\n\ttc, ok := conn.(*net.TCPConn)\n\tif !ok {\n\t\ts.logger.Error(\"non-TCP connection\", map[string]interface{}{\n\t\t\t\"conn\": conn,\n\t\t})\n\t\treturn\n\t}\n\n\tfields := well.FieldsFromContext(ctx)\n\tfields[log.FnType] = \"access\"\n\tfields[\"client_addr\"] = conn.RemoteAddr().String()\n\n\tvar addr string\n\tswitch s.mode {\n\tcase ModeNAT:\n\t\torigAddr, err := GetOriginalDST(tc)\n\t\tif err != nil {\n\t\t\tfields[log.FnError] = err.Error()\n\t\t\ts.logger.Error(\"GetOriginalDST failed\", fields)\n\t\t\treturn\n\t\t}\n\t\taddr = origAddr.String()\n\tdefault:\n\t\taddr = tc.LocalAddr().String()\n\t}\n\tfields[\"dest_addr\"] = addr\n\n\tdestConn, err := s.dialer.Dial(\"tcp\", addr)\n\tif err != nil {\n\t\tfields[log.FnError] = err.Error()\n\t\ts.logger.Error(\"failed to connect to proxy server\", fields)\n\t\treturn\n\t}\n\tdefer destConn.Close()\n\n\ts.logger.Info(\"proxy starts\", fields)\n\n\t// do proxy\n\tst := time.Now()\n\tenv := well.NewEnvironment(ctx)\n\tenv.Go(func(ctx context.Context) error {\n\t\tbuf := s.pool.Get().([]byte)\n\t\t_, err := io.CopyBuffer(destConn, tc, buf)\n\t\ts.pool.Put(buf)\n\t\tif hc, ok := destConn.(netutil.HalfCloser); ok {\n\t\t\thc.CloseWrite()\n\t\t}\n\t\ttc.CloseRead()\n\t\treturn err\n\t})\n\tenv.Go(func(ctx context.Context) error {\n\t\tbuf := s.pool.Get().([]byte)\n\t\t_, err := io.CopyBuffer(tc, destConn, buf)\n\t\ts.pool.Put(buf)\n\t\ttc.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\tenv.Stop()\n\terr = env.Wait()\n\n\tfields = well.FieldsFromContext(ctx)\n\tfields[\"elapsed\"] = time.Since(st).Seconds()\n\tif err != nil {\n\t\tfields[log.FnError] = err.Error()\n\t\ts.logger.Error(\"proxy ends with an error\", fields)\n\t\treturn\n\t}\n\ts.logger.Info(\"proxy ends\", fields)\n}\n"
  }
]