Full Code of cybozu-go/transocks for AI

master 2198aaeb4d1a cached
20 files
24.3 KB
7.7k tokens
29 symbols
1 requests
Download .txt
Repository: cybozu-go/transocks
Branch: master
Commit: 2198aaeb4d1a
Files: 20
Total size: 24.3 KB

Directory structure:
gitextract_fyfbg3on/

├── .circleci/
│   └── config.yml
├── .gitignore
├── .golangci.yml
├── CHANGELOG.md
├── CONTRIBUTORS.md
├── DESIGN.md
├── LICENSE
├── README.md
├── RELEASE.md
├── cmd/
│   └── transocks/
│       ├── main.go
│       └── sample.toml
├── config.go
├── defs_linux.go
├── go.mod
├── http_tunnel.go
├── http_tunnel_test.go
├── original_dst_linux.go
├── original_dst_linux_test.go
├── original_dst_stub.go
└── server.go

================================================
FILE CONTENTS
================================================

================================================
FILE: .circleci/config.yml
================================================
version: 2
jobs:
  build:
    docker:
    - image: quay.io/cybozu/golang:1.11-bionic
    working_directory: /work
    steps:
    - checkout
    - run: test -z "$(gofmt -s -l . | grep -v '^vendor' | tee /dev/stderr)"
    - run: golint -set_exit_status .
    - run: go build ./...
    - run: go test -race -v ./...
    - run: go vet ./...

workflows:
  version: 2
  main:
    jobs:
      - build


================================================
FILE: .gitignore
================================================
# Compiled Object files, Static and Dynamic libs (Shared Objects)
*.o
*.a
*.so

# Editors
*~
.*.swp
.#*
\#*#

# Folders
_obj
_test

# Architecture specific extensions/prefixes
*.[568vq]
[568vq].out

*.cgo1.go
*.cgo2.c
_cgo_defun.c
_cgo_gotypes.go
_cgo_export.*

_testmain.go

*.exe
*.test
*.prof

# Ignore go.sum
/go.sum


================================================
FILE: .golangci.yml
================================================
linters:
    enable:
        - dupl
        - goconst
        - gofmt
        - golint
        - typecheck
        - unparam


================================================
FILE: CHANGELOG.md
================================================
# Change Log

All notable changes to this project will be documented in this file.

## [Unreleased]

## [1.1.1] - 2019-03-16

### Changed
- Replace `syscall` with `golang.org/x/sys/unix`, contriubted by @otariidae (#14).

## [1.1.0] - 2018-11-13

### Changed
- Update `github.com/cybozu-go/cmd` to `github.com/cybozu-go/well` (#7, #9).
- Replace TravisCI with CircleCI.

## [1.0.0] - 2016-09-01

### Added
- transocks now adopts [github.com/cybozu-go/well][well] framework.  
  As a result, it implements [the common spec][spec] including graceful restart.

### Changed
- The default configuration file path is now `/etc/transocks.toml`.
- "listen" config option becomes optional.  Default is "localhost:1081".
- Configuration items for logging is changed.

[well]: https://github.com/cybozu-go/well
[spec]: https://github.com/cybozu-go/well/blob/master/README.md#specifications
[Unreleased]: https://github.com/cybozu-go/transocks/compare/v1.1.1...HEAD
[1.1.1]: https://github.com/cybozu-go/transocks/compare/v1.1.0...v1.1.1
[1.1.0]: https://github.com/cybozu-go/transocks/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/cybozu-go/transocks/compare/v0.1...v1.0.0


================================================
FILE: CONTRIBUTORS.md
================================================
transocks contributors
======================

* [@ayatk](https://github.com/ayatk)
* [@otariidae](https://github.com/otariidae)


================================================
FILE: DESIGN.md
================================================
Design notes
============

transocks should work as a SOCKS5 client used as a transparent proxy
agent running on every hosts in trusted (i.e. data center) networks.

Destination NAT (DNAT)
----------------------

On Linux, redirecting locally-generated packet to transocks can be done
by iptables with DNAT (or REDIRECT) target.

Since DNAT/REDIRECT modifies packet's destination address, transocks
need to recover the destination address by using `getsockopt` with
`SO_ORIGINAL_DST` for IPv4 or with `IP6T_SO_ORIGINAL_DST` for IPv6.
This is, of course, Linux-specific, and Go does not provide standard
API for them.

Policy-based routing
--------------------

Except for DNAT, some operating systems provide a way to route packets
to a specific program.  In order to receive such packets, the program
need to set special options on the listening socket before `bind`.

Difficult is that Go does not allow setting socket options before `bind`.

### Linux TPROXY

Linux iptables has [TPROXY][] target that can route packets to a
specific local port.  The socket option is:

* IPv4

    ```
    setsockopt(IPPROTO_IP, IP_TRANSPARENT)
    ```

* IPv6

    ```
    setsockopt(IPPROTO_IPV6, IPV6_TRANSPARENT)
    ```

To set this option, transocks must have `CAP_NET_ADMIN` capability.
Run transocks as root user, or grant `CAP_NET_ADMIN` for the file by:

```
sudo setcap 'cap_net_admin+ep' transocks
```

### FreeBSD, NetBSD, OpenBSD

Use [PF with divert-to][pf] to route packets to a specific local port.

The listening program needs to set a socket option before `bind`:

* FreeBSD (IPv4)

    ```
    setsockopt(IPPROTO_IP, IP_BINDANY)
    ```

* FreeBSD (IPv6)

    ```
    setsockopt(IPPROTO_IPV6, IPV6_BINDANY)
    ```

* NetBSD, OpenBSD

    ```
    setsockopt(SOL_SOCKET, SO_BINDANY)
    ```

For this to work, transocks must run as root.

Implementation strategy
-----------------------

We use Go for its efficiency and simpleness.

For SOCKS5, [golang.org/x/net/proxy][x/net] already provides SOCKS5 client.

For Linux NAT, we need to use [golang.org/x/sys/unix][x/sys] and
[unsafe.Pointer][] to use non-standard `getsockopt` options.

To set socket options before `bind`, we need to create sockets manually
by using [golang.org/x/sys/unix] and then convert the native socket to
`*net.TCPListener` by [net.FileListener][].

CONNECT tunnel
--------------

As golang.org/x/net/proxy can add custom dialers, we can implement
a proxy using http CONNECT method for tunneling through HTTP proxies
such as [Squid][].

Note that the default Squid configuration file installed for
Ubuntu 14.04 prohibits CONNECT to ports other than 443.

```
# Deny CONNECT to other than secure SSL ports
http_access deny CONNECT !SSL_ports
```

Remove or comment out the line to allow CONNECT to ports other than 443.

[TPROXY]: https://www.kernel.org/doc/Documentation/networking/tproxy.txt
[pf]: http://wiki.squid-cache.org/ConfigExamples/Intercept/OpenBsdPf
[x/net]: https://godoc.org/golang.org/x/net/proxy#SOCKS5
[x/sys]: https://godoc.org/golang.org/x/sys/unix
[unsafe.Pointer]: https://golang.org/pkg/unsafe/#Pointer
[net.FileListener]: https://golang.org/pkg/net/#FileListener
[Squid]: http://www.squid-cache.org/


================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2016 Cybozu

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
[![GitHub release](https://img.shields.io/github/release/cybozu-go/transocks.svg?maxAge=60)][releases]
[![GoDoc](https://godoc.org/github.com/cybozu-go/transocks?status.svg)][godoc]
[![CircleCI](https://circleci.com/gh/cybozu-go/coil.svg?style=svg)](https://circleci.com/gh/cybozu-go/transocks)
[![Go Report Card](https://goreportcard.com/badge/github.com/cybozu-go/transocks)](https://goreportcard.com/report/github.com/cybozu-go/transocks)

transocks - a transparent SOCKS5/HTTP proxy
===========================================

**transocks** is a background service to redirect TCP connections
transparently to a SOCKS5 server or a HTTP proxy server like [Squid][].

Currently, transocks supports only Linux iptables with DNAT/REDIRECT target.

Features
--------

* IPv4 and IPv6

    Both IPv4 and IPv6 are supported.
    Note that `nf_conntrack_ipv4` or `nf_conntrack_ipv6` kernel modules
    must be loaded beforehand.

* SOCKS5 and HTTP proxy (CONNECT)

    We recommend using SOCKS5 server if available.
    Take a look at our SOCKS server [usocksd][] if you are looking for.

    HTTP proxies often prohibits CONNECT method to make connections
    to ports other than 443.  Make sure your HTTP proxy allows CONNECT
    to the ports you want.

* Graceful stop & restart

    * On SIGINT/SIGTERM, transocks stops gracefully.
    * On SIGHUP, transocks restarts gracefully.

* Library and executable

    transocks comes with a handy executable.
    You may use the library to create your own.

Install
-------

Use Go 1.7 or better.

```
go get -u github.com/cybozu-go/transocks/...
```

Usage
-----

`transocks [-h] [-f CONFIG]`

The default configuration file path is `/etc/transocks.toml`.

In addition, transocks implements [the common spec](https://github.com/cybozu-go/cmd#specifications) from [`cybozu-go/cmd`](https://github.com/cybozu-go/cmd).

transocks does not have *daemon* mode.  Use systemd to run it
as a background service.

Configuration file format
-------------------------

`transocks.toml` is a [TOML][] file.

`proxy_url` is mandatory.  Other items are optional.

```
# listening address of transocks.
listen = "localhost:1081"    # default is "localhost:1081"

proxy_url = "socks5://10.20.30.40:1080"  # for SOCKS5 server
#proxy_url = "http://10.20.30.40:3128"   # for HTTP proxy server

[log]
filename = "/path/to/file"   # default to stderr
level = "info"               # critical", error, warning, info, debug
format = "json"              # plain, logfmt, json
```

Redirecting connections by iptables
-----------------------------------

Use DNAT or REDIRECT target in OUTPUT chain of the `nat` table.

Save the following example to a file, then execute:
`sudo iptables-restore < FILE`

```
*nat
:PREROUTING ACCEPT [0:0]
:INPUT ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:POSTROUTING ACCEPT [0:0]
:TRANSOCKS - [0:0]
-A OUTPUT -p tcp -j TRANSOCKS
-A TRANSOCKS -d 0.0.0.0/8 -j RETURN
-A TRANSOCKS -d 10.0.0.0/8 -j RETURN
-A TRANSOCKS -d 127.0.0.0/8 -j RETURN
-A TRANSOCKS -d 169.254.0.0/16 -j RETURN
-A TRANSOCKS -d 172.16.0.0/12 -j RETURN
-A TRANSOCKS -d 192.168.0.0/16 -j RETURN
-A TRANSOCKS -d 224.0.0.0/4 -j RETURN
-A TRANSOCKS -d 240.0.0.0/4 -j RETURN
-A TRANSOCKS -p tcp -j REDIRECT --to-ports 1081
COMMIT
```

Use *ip6tables* to redirect IPv6 connections.

**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.

Library usage
-------------

Read [the documentation][godoc].

License
-------

[MIT](https://opensource.org/licenses/MIT)

[releases]: https://github.com/cybozu-go/transocks/releases
[godoc]: https://godoc.org/github.com/cybozu-go/transocks
[Squid]: http://www.squid-cache.org/
[usocksd]: https://github.com/cybozu-go/usocksd
[TOML]: https://github.com/toml-lang/toml


================================================
FILE: RELEASE.md
================================================
Release procedure
=================

This document describes how to release a new version of coil.

Versioning
----------

Follow [semantic versioning 2.0.0][semver] to choose the new version number.

Prepare change log entries
--------------------------

Add notable changes since the last release to [CHANGELOG.md](CHANGELOG.md).
It should look like:

```markdown
(snip)
## [Unreleased]

### Added
- Implement ... (#35)

### Changed
- Fix a bug in ... (#33)

### Removed
- Deprecated `-option` is removed ... (#39)

(snip)
```

Bump version
------------

1. Determine a new version number.  Let it write `$VERSION`.
1. Checkout `master` branch.
1. Edit `CHANGELOG.md` for the new version ([example][]).
1. Commit the change and add a git tag, then push them.

    ```console
    $ git commit -a -m "Bump version to $VERSION"
    $ git tag v$VERSION
    $ git push origin master --tags
    ```

Publish GitHub release page
---------------------------

Go to https://github.com/cybozu-go/coil/releases and edit the tag.
Finally, press `Publish release` button.

[semver]: https://semver.org/spec/v2.0.0.html
[example]: https://github.com/cybozu-go/etcdpasswd/commit/77d95384ac6c97e7f48281eaf23cb94f68867f79


================================================
FILE: cmd/transocks/main.go
================================================
package main

import (
	"flag"
	"fmt"
	"net"
	"net/url"

	"github.com/BurntSushi/toml"
	"github.com/cybozu-go/log"
	"github.com/cybozu-go/transocks"
	"github.com/cybozu-go/well"
)

type tomlConfig struct {
	Listen   string         `toml:"listen"`
	ProxyURL string         `toml:"proxy_url"`
	Log      well.LogConfig `toml:"log"`
}

const (
	defaultAddr = "localhost:1081"
)

var (
	configFile = flag.String("f", "/etc/transocks.toml",
		"TOML configuration file path")
)

func loadConfig() (*transocks.Config, error) {
	tc := &tomlConfig{
		Listen: defaultAddr,
	}
	md, err := toml.DecodeFile(*configFile, tc)
	if err != nil {
		return nil, err
	}
	if len(md.Undecoded()) > 0 {
		return nil, fmt.Errorf("undecoded key in TOML: %v", md.Undecoded())
	}

	c := transocks.NewConfig()
	c.Addr = tc.Listen

	u, err := url.Parse(tc.ProxyURL)
	if err != nil {
		return nil, err
	}
	c.ProxyURL = u

	err = tc.Log.Apply()
	if err != nil {
		return nil, err
	}

	return c, nil
}

func serve(lns []net.Listener, c *transocks.Config) {
	s, err := transocks.NewServer(c)
	if err != nil {
		log.ErrorExit(err)
	}

	for _, ln := range lns {
		s.Serve(ln)
	}
	err = well.Wait()
	if err != nil && !well.IsSignaled(err) {
		log.ErrorExit(err)
	}
}

func main() {
	flag.Parse()

	c, err := loadConfig()
	if err != nil {
		log.ErrorExit(err)
	}

	g := &well.Graceful{
		Listen: func() ([]net.Listener, error) {
			return transocks.Listeners(c)
		},
		Serve: func(lns []net.Listener) {
			serve(lns, c)
		},
	}
	g.Run()

	err = well.Wait()
	if err != nil && !well.IsSignaled(err) {
		log.ErrorExit(err)
	}
}


================================================
FILE: cmd/transocks/sample.toml
================================================
# This is a sample TOML file for transocks.

listen = "localhost:1081"

proxy_url = "socks5://10.20.30.40:1080"  # for SOCKS5 server
#proxy_url = "http://10.20.30.40:3128"   # for HTTP proxy server

[log]
level = "debug"
filename = "/var/log/transocks.log"


================================================
FILE: config.go
================================================
package transocks

import (
	"errors"
	"fmt"
	"net"
	"net/url"
	"time"

	"github.com/cybozu-go/log"
	"github.com/cybozu-go/well"
)

const (
	defaultShutdownTimeout = 1 * time.Minute
)

// Mode is the type of transocks mode.
type Mode string

func (m Mode) String() string {
	return string(m)
}

const (
	// ModeNAT is mode constant for NAT.
	ModeNAT = Mode("nat")
)

// Config keeps configurations for Server.
type Config struct {
	// Addr is the listening address.
	Addr string

	// ProxyURL is the URL for upstream proxy.
	//
	// For SOCKS5, URL looks like "socks5://USER:PASSWORD@HOST:PORT".
	//
	// For HTTP proxy, URL looks like "http://USER:PASSWORD@HOST:PORT".
	// The HTTP proxy must support CONNECT method.
	ProxyURL *url.URL

	// Mode determines how clients are routed to transocks.
	// Default is ModeNAT.  No other options are available at this point.
	Mode Mode

	// ShutdownTimeout is the maximum duration the server waits for
	// all connections to be closed before shutdown.
	//
	// Zero duration disables timeout.  Default is 1 minute.
	ShutdownTimeout time.Duration

	// Dialer is the base dialer to connect to the proxy server.
	// The server uses the default dialer if this is nil.
	Dialer *net.Dialer

	// Logger can be used to provide a custom logger.
	// If nil, the default logger is used.
	Logger *log.Logger

	// Env can be used to specify a well.Environment on which the server runs.
	// If nil, the server will run on the global environment.
	Env *well.Environment
}

// NewConfig creates and initializes a new Config.
func NewConfig() *Config {
	c := new(Config)
	c.Mode = ModeNAT
	c.ShutdownTimeout = defaultShutdownTimeout
	return c
}

// validate validates the configuration.
// It returns non-nil error if the configuration is not valid.
func (c *Config) validate() error {
	if c.ProxyURL == nil {
		return errors.New("ProxyURL is nil")
	}
	if c.Mode != ModeNAT {
		return fmt.Errorf("Unknown mode: %s", c.Mode)
	}
	return nil
}


================================================
FILE: defs_linux.go
================================================
// Code generated by hand.  DO NOT EDIT.
// +build linux

package transocks

const (
	// SO_ORIGINAL_DST is a Linux getsockopt optname.
	SO_ORIGINAL_DST = 80

	// IP6T_SO_ORIGINAL_DST a Linux getsockopt optname.
	IP6T_SO_ORIGINAL_DST = 80
)


================================================
FILE: go.mod
================================================
module github.com/cybozu-go/transocks

require (
	github.com/BurntSushi/toml v0.3.1
	github.com/cybozu-go/log v1.5.0
	github.com/cybozu-go/netutil v1.2.0
	github.com/cybozu-go/well v1.8.1
	golang.org/x/net v0.0.0-20180911220305-26e67e76b6c3
	golang.org/x/sys v0.0.0-20180906133057-8cf3aee42992
)


================================================
FILE: http_tunnel.go
================================================
// This file provides a dialer type of "http://" scheme for
// golang.org/x/net/proxy package.
//
// The dialer type will be automatically registered by init().
//
// The dialer requests an upstream HTTP proxy to create a TCP tunnel
// by CONNECT method.

package transocks

import (
	"bufio"
	"bytes"
	"encoding/base64"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/url"
	"time"

	"golang.org/x/net/proxy"
)

func init() {
	proxy.RegisterDialerType("http", httpDialType)
}

type httpDialer struct {
	addr    string
	header  http.Header
	forward proxy.Dialer
}

func httpDialType(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {
	var header http.Header
	if uu := u.User; uu != nil {
		passwd, _ := uu.Password()
		up := uu.Username() + ":" + passwd
		authz := "Basic " + base64.StdEncoding.EncodeToString([]byte(up))
		header = map[string][]string{
			"Proxy-Authorization": {authz},
		}
	}
	return &httpDialer{
		addr:    u.Host,
		header:  header,
		forward: forward,
	}, nil
}

func (d *httpDialer) Dial(network, addr string) (c net.Conn, err error) {
	req := &http.Request{
		Method: "CONNECT",
		URL:    &url.URL{Opaque: addr},
		Host:   addr,
		Header: d.header,
	}
	c, err = d.forward.Dial("tcp", d.addr)
	if err != nil {
		return
	}
	req.Write(c)

	// Read response until "\r\n\r\n".
	// bufio cannot be used as the connected server may not be
	// a HTTP(S) server.
	c.SetReadDeadline(time.Now().Add(10 * time.Second))
	buf := make([]byte, 0, 4096)
	b := make([]byte, 1)
	state := 0
	for {
		_, e := c.Read(b)
		if e != nil {
			c.Close()
			return nil, errors.New("reset proxy connection")
		}
		buf = append(buf, b[0])
		switch state {
		case 0:
			if b[0] == byte('\r') {
				state++
			}
			continue
		case 1:
			if b[0] == byte('\n') {
				state++
			} else {
				state = 0
			}
			continue
		case 2:
			if b[0] == byte('\r') {
				state++
			} else {
				state = 0
			}
			continue
		case 3:
			if b[0] == byte('\n') {
				goto PARSE
			} else {
				state = 0
			}
		}
	}

PARSE:
	var zero time.Time
	c.SetReadDeadline(zero)
	resp, e := http.ReadResponse(bufio.NewReader(bytes.NewBuffer(buf)), req)
	if e != nil {
		c.Close()
		return nil, e
	}
	resp.Body.Close()
	if resp.StatusCode != 200 {
		c.Close()
		return nil, fmt.Errorf("proxy returns %s", resp.Status)
	}

	return c, nil
}


================================================
FILE: http_tunnel_test.go
================================================
package transocks

import (
	"io"
	"net"
	"os"
	"testing"
	"time"
)

func TestHTTPDialer(t *testing.T) {
	t.Skip()

	// This test only works if Squid allowing CONNECT to port 80 is
	// running on the local machine on port 3128.

	d := &httpDialer{
		addr:    "127.0.0.1:3128",
		forward: &net.Dialer{Timeout: 5 * time.Second},
	}

	conn, err := d.Dial("tcp", "www.yahoo.com:80")
	if err != nil {
		t.Fatal(err)
	}
	defer conn.Close()

	conn.Write([]byte("GET / HTTP/1.1\r\nHost: www.yahoo.com:80\r\nConnection: close\r\n\r\n"))
	io.Copy(os.Stdout, conn)
}


================================================
FILE: original_dst_linux.go
================================================
// +build linux

package transocks

import (
	syscall "golang.org/x/sys/unix"
	"net"
	"os"
	"unsafe"
)

func getsockopt(s int, level int, optname int, optval unsafe.Pointer, optlen *uint32) (err error) {
	_, _, e := syscall.Syscall6(
		syscall.SYS_GETSOCKOPT, uintptr(s), uintptr(level), uintptr(optname),
		uintptr(optval), uintptr(unsafe.Pointer(optlen)), 0)
	if e != 0 {
		return e
	}
	return
}

// GetOriginalDST retrieves the original destination address from
// NATed connection.  Currently, only Linux iptables using DNAT/REDIRECT
// is supported.  For other operating systems, this will just return
// conn.LocalAddr().
//
// Note that this function only works when nf_conntrack_ipv4 and/or
// nf_conntrack_ipv6 is loaded in the kernel.
func GetOriginalDST(conn *net.TCPConn) (*net.TCPAddr, error) {
	f, err := conn.File()
	if err != nil {
		return nil, err
	}
	defer f.Close()

	fd := int(f.Fd())
	// revert to non-blocking mode.
	// see http://stackoverflow.com/a/28968431/1493661
	if err = syscall.SetNonblock(fd, true); err != nil {
		return nil, os.NewSyscallError("setnonblock", err)
	}

	v6 := conn.LocalAddr().(*net.TCPAddr).IP.To4() == nil
	if v6 {
		var addr syscall.RawSockaddrInet6
		var len uint32
		len = uint32(unsafe.Sizeof(addr))
		err = getsockopt(fd, syscall.IPPROTO_IPV6, IP6T_SO_ORIGINAL_DST,
			unsafe.Pointer(&addr), &len)
		if err != nil {
			return nil, os.NewSyscallError("getsockopt", err)
		}
		ip := make([]byte, 16)
		for i, b := range addr.Addr {
			ip[i] = b
		}
		pb := *(*[2]byte)(unsafe.Pointer(&addr.Port))
		return &net.TCPAddr{
			IP:   ip,
			Port: int(pb[0])*256 + int(pb[1]),
		}, nil
	}

	// IPv4
	var addr syscall.RawSockaddrInet4
	var len uint32
	len = uint32(unsafe.Sizeof(addr))
	err = getsockopt(fd, syscall.IPPROTO_IP, SO_ORIGINAL_DST,
		unsafe.Pointer(&addr), &len)
	if err != nil {
		return nil, os.NewSyscallError("getsockopt", err)
	}
	ip := make([]byte, 4)
	for i, b := range addr.Addr {
		ip[i] = b
	}
	pb := *(*[2]byte)(unsafe.Pointer(&addr.Port))
	return &net.TCPAddr{
		IP:   ip,
		Port: int(pb[0])*256 + int(pb[1]),
	}, nil
}


================================================
FILE: original_dst_linux_test.go
================================================
// +build linux

package transocks

import (
	"net"
	"testing"
)

func TestGetOriginalDST(t *testing.T) {
	t.Skip()

	l, err := net.ListenTCP("tcp", &net.TCPAddr{Port: 1081})
	if err != nil {
		t.Fatal(err)
	}
	defer l.Close()

	c, err := l.Accept()
	if err != nil {
		t.Fatal(err)
	}
	defer c.Close()

	origAddr, err := GetOriginalDST(c.(*net.TCPConn))
	if err != nil {
		t.Fatal(err)
	}

	t.Log(origAddr.String())
}


================================================
FILE: original_dst_stub.go
================================================
// +build !linux

package transocks

import "net"

// GetOriginalDST retrieves the original destination address from
// NATed connection.  Currently, only Linux iptables using DNAT/REDIRECT
// is supported.  For other operating systems, this will just return
// conn.LocalAddr().
//
// Note that this function only works when nf_conntrack_ipv4 and/or
// nf_conntrack_ipv6 is loaded in the kernel.
func GetOriginalDST(conn *net.TCPConn) (*net.TCPAddr, error) {
	return conn.LocalAddr().(*net.TCPAddr), nil
}


================================================
FILE: server.go
================================================
package transocks

import (
	"context"
	"io"
	"net"
	"sync"
	"time"

	"github.com/cybozu-go/log"
	"github.com/cybozu-go/netutil"
	"github.com/cybozu-go/well"
	"golang.org/x/net/proxy"
)

const (
	keepAliveTimeout = 3 * time.Minute
	copyBufferSize   = 64 << 10
)

// Listeners returns a list of net.Listener.
func Listeners(c *Config) ([]net.Listener, error) {
	ln, err := net.Listen("tcp", c.Addr)
	if err != nil {
		return nil, err
	}
	return []net.Listener{ln}, nil
}

// Server provides transparent proxy server functions.
type Server struct {
	well.Server
	mode   Mode
	logger *log.Logger
	dialer proxy.Dialer
	pool   sync.Pool
}

// NewServer creates Server.
// If c is not valid, this returns non-nil error.
func NewServer(c *Config) (*Server, error) {
	if err := c.validate(); err != nil {
		return nil, err
	}

	dialer := c.Dialer
	if dialer == nil {
		dialer = &net.Dialer{
			KeepAlive: keepAliveTimeout,
			DualStack: true,
		}
	}
	pdialer, err := proxy.FromURL(c.ProxyURL, dialer)
	if err != nil {
		return nil, err
	}
	logger := c.Logger
	if logger == nil {
		logger = log.DefaultLogger()
	}

	s := &Server{
		Server: well.Server{
			ShutdownTimeout: c.ShutdownTimeout,
			Env:             c.Env,
		},
		mode:   c.Mode,
		logger: logger,
		dialer: pdialer,
		pool: sync.Pool{
			New: func() interface{} {
				return make([]byte, copyBufferSize)
			},
		},
	}
	s.Server.Handler = s.handleConnection
	return s, nil
}

func (s *Server) handleConnection(ctx context.Context, conn net.Conn) {
	tc, ok := conn.(*net.TCPConn)
	if !ok {
		s.logger.Error("non-TCP connection", map[string]interface{}{
			"conn": conn,
		})
		return
	}

	fields := well.FieldsFromContext(ctx)
	fields[log.FnType] = "access"
	fields["client_addr"] = conn.RemoteAddr().String()

	var addr string
	switch s.mode {
	case ModeNAT:
		origAddr, err := GetOriginalDST(tc)
		if err != nil {
			fields[log.FnError] = err.Error()
			s.logger.Error("GetOriginalDST failed", fields)
			return
		}
		addr = origAddr.String()
	default:
		addr = tc.LocalAddr().String()
	}
	fields["dest_addr"] = addr

	destConn, err := s.dialer.Dial("tcp", addr)
	if err != nil {
		fields[log.FnError] = err.Error()
		s.logger.Error("failed to connect to proxy server", fields)
		return
	}
	defer destConn.Close()

	s.logger.Info("proxy starts", fields)

	// do proxy
	st := time.Now()
	env := well.NewEnvironment(ctx)
	env.Go(func(ctx context.Context) error {
		buf := s.pool.Get().([]byte)
		_, err := io.CopyBuffer(destConn, tc, buf)
		s.pool.Put(buf)
		if hc, ok := destConn.(netutil.HalfCloser); ok {
			hc.CloseWrite()
		}
		tc.CloseRead()
		return err
	})
	env.Go(func(ctx context.Context) error {
		buf := s.pool.Get().([]byte)
		_, err := io.CopyBuffer(tc, destConn, buf)
		s.pool.Put(buf)
		tc.CloseWrite()
		if hc, ok := destConn.(netutil.HalfCloser); ok {
			hc.CloseRead()
		}
		return err
	})
	env.Stop()
	err = env.Wait()

	fields = well.FieldsFromContext(ctx)
	fields["elapsed"] = time.Since(st).Seconds()
	if err != nil {
		fields[log.FnError] = err.Error()
		s.logger.Error("proxy ends with an error", fields)
		return
	}
	s.logger.Info("proxy ends", fields)
}
Download .txt
gitextract_fyfbg3on/

├── .circleci/
│   └── config.yml
├── .gitignore
├── .golangci.yml
├── CHANGELOG.md
├── CONTRIBUTORS.md
├── DESIGN.md
├── LICENSE
├── README.md
├── RELEASE.md
├── cmd/
│   └── transocks/
│       ├── main.go
│       └── sample.toml
├── config.go
├── defs_linux.go
├── go.mod
├── http_tunnel.go
├── http_tunnel_test.go
├── original_dst_linux.go
├── original_dst_linux_test.go
├── original_dst_stub.go
└── server.go
Download .txt
SYMBOL INDEX (29 symbols across 9 files)

FILE: cmd/transocks/main.go
  type tomlConfig (line 15) | type tomlConfig struct
  constant defaultAddr (line 22) | defaultAddr = "localhost:1081"
  function loadConfig (line 30) | func loadConfig() (*transocks.Config, error) {
  function serve (line 59) | func serve(lns []net.Listener, c *transocks.Config) {
  function main (line 74) | func main() {

FILE: config.go
  constant defaultShutdownTimeout (line 15) | defaultShutdownTimeout = 1 * time.Minute
  type Mode (line 19) | type Mode
    method String (line 21) | func (m Mode) String() string {
  constant ModeNAT (line 27) | ModeNAT = Mode("nat")
  type Config (line 31) | type Config struct
    method validate (line 76) | func (c *Config) validate() error {
  function NewConfig (line 67) | func NewConfig() *Config {

FILE: defs_linux.go
  constant SO_ORIGINAL_DST (line 8) | SO_ORIGINAL_DST = 80
  constant IP6T_SO_ORIGINAL_DST (line 11) | IP6T_SO_ORIGINAL_DST = 80

FILE: http_tunnel.go
  function init (line 25) | func init() {
  type httpDialer (line 29) | type httpDialer struct
    method Dial (line 52) | func (d *httpDialer) Dial(network, addr string) (c net.Conn, err error) {
  function httpDialType (line 35) | func httpDialType(u *url.URL, forward proxy.Dialer) (proxy.Dialer, error) {

FILE: http_tunnel_test.go
  function TestHTTPDialer (line 11) | func TestHTTPDialer(t *testing.T) {

FILE: original_dst_linux.go
  function getsockopt (line 12) | func getsockopt(s int, level int, optname int, optval unsafe.Pointer, op...
  function GetOriginalDST (line 29) | func GetOriginalDST(conn *net.TCPConn) (*net.TCPAddr, error) {

FILE: original_dst_linux_test.go
  function TestGetOriginalDST (line 10) | func TestGetOriginalDST(t *testing.T) {

FILE: original_dst_stub.go
  function GetOriginalDST (line 14) | func GetOriginalDST(conn *net.TCPConn) (*net.TCPAddr, error) {

FILE: server.go
  constant keepAliveTimeout (line 17) | keepAliveTimeout = 3 * time.Minute
  constant copyBufferSize (line 18) | copyBufferSize   = 64 << 10
  function Listeners (line 22) | func Listeners(c *Config) ([]net.Listener, error) {
  type Server (line 31) | type Server struct
    method handleConnection (line 80) | func (s *Server) handleConnection(ctx context.Context, conn net.Conn) {
  function NewServer (line 41) | func NewServer(c *Config) (*Server, error) {
Condensed preview — 20 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (28K chars).
[
  {
    "path": ".circleci/config.yml",
    "chars": 394,
    "preview": "version: 2\njobs:\n  build:\n    docker:\n    - image: quay.io/cybozu/golang:1.11-bionic\n    working_directory: /work\n    st"
  },
  {
    "path": ".gitignore",
    "chars": 321,
    "preview": "# 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"
  },
  {
    "path": ".golangci.yml",
    "chars": 125,
    "preview": "linters:\n    enable:\n        - dupl\n        - goconst\n        - gofmt\n        - golint\n        - typecheck\n        - unp"
  },
  {
    "path": "CHANGELOG.md",
    "chars": 1168,
    "preview": "# Change Log\n\nAll notable changes to this project will be documented in this file.\n\n## [Unreleased]\n\n## [1.1.1] - 2019-0"
  },
  {
    "path": "CONTRIBUTORS.md",
    "chars": 129,
    "preview": "transocks contributors\n======================\n\n* [@ayatk](https://github.com/ayatk)\n* [@otariidae](https://github.com/ot"
  },
  {
    "path": "DESIGN.md",
    "chars": 3205,
    "preview": "Design notes\n============\n\ntransocks should work as a SOCKS5 client used as a transparent proxy\nagent running on every h"
  },
  {
    "path": "LICENSE",
    "chars": 1073,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Cybozu\n\nPermission is hereby granted, free of charge, to any person obtaining "
  },
  {
    "path": "README.md",
    "chars": 3950,
    "preview": "[![GitHub release](https://img.shields.io/github/release/cybozu-go/transocks.svg?maxAge=60)][releases]\n[![GoDoc](https:/"
  },
  {
    "path": "RELEASE.md",
    "chars": 1207,
    "preview": "Release procedure\n=================\n\nThis document describes how to release a new version of coil.\n\nVersioning\n---------"
  },
  {
    "path": "cmd/transocks/main.go",
    "chars": 1586,
    "preview": "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\"git"
  },
  {
    "path": "cmd/transocks/sample.toml",
    "chars": 257,
    "preview": "# This is a sample TOML file for transocks.\n\nlisten = \"localhost:1081\"\n\nproxy_url = \"socks5://10.20.30.40:1080\"  # for S"
  },
  {
    "path": "config.go",
    "chars": 1962,
    "preview": "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-"
  },
  {
    "path": "defs_linux.go",
    "chars": 241,
    "preview": "// Code generated by hand.  DO NOT EDIT.\n// +build linux\n\npackage transocks\n\nconst (\n\t// SO_ORIGINAL_DST is a Linux gets"
  },
  {
    "path": "go.mod",
    "chars": 296,
    "preview": "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\tgi"
  },
  {
    "path": "http_tunnel.go",
    "chars": 2303,
    "preview": "// This file provides a dialer type of \"http://\" scheme for\n// golang.org/x/net/proxy package.\n//\n// The dialer type wil"
  },
  {
    "path": "http_tunnel_test.go",
    "chars": 556,
    "preview": "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// "
  },
  {
    "path": "original_dst_linux.go",
    "chars": 2092,
    "preview": "// +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("
  },
  {
    "path": "original_dst_linux_test.go",
    "chars": 418,
    "preview": "// +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,"
  },
  {
    "path": "original_dst_stub.go",
    "chars": 507,
    "preview": "// +build !linux\n\npackage transocks\n\nimport \"net\"\n\n// GetOriginalDST retrieves the original destination address from\n// "
  },
  {
    "path": "server.go",
    "chars": 3133,
    "preview": "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/"
  }
]

About this extraction

This page contains the full source code of the cybozu-go/transocks GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 20 files (24.3 KB), approximately 7.7k tokens, and a symbol index with 29 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!