Full Code of wadahiro/go-transproxy for AI

master 2ed44dd74f87 cached
14 files
41.0 KB
13.3k tokens
59 symbols
1 requests
Download .txt
Repository: wadahiro/go-transproxy
Branch: master
Commit: 2ed44dd74f87
Files: 14
Total size: 41.0 KB

Directory structure:
gitextract_m5cqkbr0/

├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   └── transproxy/
│       └── main.go
├── common.go
├── dns.go
├── explicit.go
├── go.mod
├── go.sum
├── http.go
├── https.go
├── iptables.go
└── tcp.go

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

================================================
FILE: .gitignore
================================================
bin
vendor
dist
.DS_store


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

Copyright (c) 2017 Hiroyuki Wada

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: Makefile
================================================
NAME := transproxy
VERSION := v0.6.0
REVISION := $(shell git rev-parse --short HEAD)

SRCS    := $(shell find . -type f -name '*.go')
#LDFLAGS := -ldflags="-s -w -extldflags -static"

DIST_DIRS := find * -type d -exec

.DEFAULT_GOAL := bin/$(NAME)

bin/$(NAME): $(SRCS)
	go build $(LDFLAGS) -o bin/$(NAME) cmd/transproxy/main.go

.PHONY: clean
clean:
	rm -rf bin/*
	rm -rf dist/*
	rm -rf vendor/*

.PHONY: cross-build
cross-build:
	for os in linux darwin; do \
	    [ $$os = "windows" ] && EXT=".exe"; \
		for arch in amd64; do \
			GOOS=$$os GOARCH=$$arch CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo $(LDFLAGS) -o dist/$$os-$$arch/$(NAME)$$EXT cmd/transproxy/main.go; \
		done; \
	done

.PHONY: deps
deps:
	GO111MODULE=on

.PHONY: dist
dist:
	cd dist && \
	$(DIST_DIRS) cp ../LICENSE {} \; && \
	$(DIST_DIRS) cp ../README.md {} \; && \
	$(DIST_DIRS) tar -zcf $(NAME)-$(VERSION)-{}.tar.gz {} \; && \
	$(DIST_DIRS) zip -r $(NAME)-$(VERSION)-{}.zip {} \; && \
	cd ..

.PHONY: fast
fast:
	go build $(LDFLAGS) -o bin/$(NAME)

.PHONY: install
install:
	go install $(LDFLAGS)

.PHONY: release
release:
	git tag $(VERSION)
	git push origin $(VERSION)

.PHONY: test
test:
	go test -cover -v

.PHONY: it
it:
	go test -cover -v -tags integration


================================================
FILE: README.md
================================================
# go-transproxy

Transparent proxy servers for HTTP, HTTPS, DNS and TCP. 
This repository is heavily under development.

## Description

**go-transproxy** provides transparent proxy servers for HTTP, HTTPS, DNS and TCP with single binary.
Nothing needs to setup many tools. Nothing needs to configure iptables.
**go-transproxy** will start multiple proxy servers for these protocols.
Futheremore, it will configure iptables automatically.

**go-transproxy** also provides two types of explicit proxy(not transparent proxy).
One is a simple proxy delegating to upstream your proxy, another is for adding `Proxy-Authorization` header automatically.

## Requirement

**go-transproxy** supports only Linux iptables.

## Install

### Binaly install
Download from [Releases page](https://github.com/wadahiro/go-transproxy/releases).

### Source install
Use Go 1.13 for the build.

```
make
```

## Usage

```
Usage:

  transproxy [options]

Options:

  -disable-iptables
    	Disable automatic iptables configuration
  -dns-over-https-enabled
        Use DNS-over-HTTPS service as public DNS
  -dns-over-https-endpoint string
        DNS-over-HTTPS endpoint URL (default "https://dns.google.com/resolve")
  -dns-over-tcp-disabled
        Disable DNS-over-TCP for querying to public DNS
  -dns-proxy-listen [host]:port
        DNS Proxy listen address, as [host]:port (default ":3131")
  -dns-tcp
        DNS Listen on TCP (default true)
  -dns-udp
        DNS Listen on UDP (default true)
  -explicit-proxy-listen [host]:port
        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")
  -explicit-proxy-only
        Boot Explicit Proxies only
  -explicit-proxy-with-auth-listen [host]:port
        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")
  -http-proxy-listen [host]:port
        HTTP Proxy listen address, as [host]:port (default ":3129")
  -https-proxy-listen [host]:port
        HTTPS Proxy listen address, as [host]:port (default ":3130")
  -loglevel string
        Log level, one of: debug, info, warn, error, fatal, panic (default "info")
  -private-dns string
        Private DNS address for no_proxy targets (IP[:port])
  -public-dns string
        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
  -tcp-proxy-dports port1,port2,...
        TCP Proxy dports, as port1,port2,... (default "22")
  -tcp-proxy-listen [host]:port
        TCP Proxy listen address, as [host]:port (default ":3128")
```

Proxy configuration is used from standard environment variables, `http_proxy`, `https_proxy` and `no_proxy`.
Also you can use **IP Address**, **CIDR**, **Suffix Domain Name** in `no_proxy`.

### Example 

```
# Set your proxy environment
export http_proxy=http://foo:bar@yourproxy.example.org:3128

# Set no_proxy if you need to access directly for internal
export no_proxy=example.org,192.168.0.0/24

# Start go-transproxy with admin privileges(sudo)
sudo -E transproxy -private-dns 192.168.0.100 -public-dns 8.8.8.8
```

For testing, using docker is easy way. Now, you can access to google from docker container with no proxy configuration as follows.

```
docker run --rm -it centos curl http://www.google.com
<HTML><HEAD><meta http-equiv="content-type" content="text/html;charset=utf-8">
<TITLE>302 Moved</TITLE></HEAD><BODY>
<H1>302 Moved</H1>
The document has moved
<A HREF="http://www.google.co.jp/?gfe_rd=cr&amp;dcr=0&amp;ei=GCKtWbD0AaLEXuTmr7gK">here</A>.
</BODY></HTML>
```

If your proxy doesn't support CONNECT method to DNS port, it cannot resolve public domain name transparently.
Fortunately, 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.

```
sudo -E transproxy -private-dns 192.168.0.100 -dns-over-https-enabled
```

If you can resolve all domains directly from local LAN, run command without dns related options as below. 
It disables DNS-Proxy.

```
sudo -E transproxy
```

If 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.
It suppresses to insert a iptables OUTPUT rule for DNS over TCP.

```
sudo -E transproxy -private-dns 192.168.0.100 -public-dns 172.16.0.1 -dns-over-tcp-disabled
```

If you want to use an application which access to internet using port 5000, run command with `-tcp-proxy-dports` option as below.

```
sudo -E transproxy -private-dns 192.168.0.100 -public-dns 8.8.8.8 -tcp-proxy-dports 22,5000
```

## Current Limitation

* HTTP proxy: Only works with HTTP host header.
* 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).
* TCP proxy: `no_proxy` only works with IP Address and CIDR.

## Licence

Licensed under the [MIT](/LICENSE) license.

## Author

[Hiroyuki Wada](https://github.com/wadahiro)



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

import (
	"flag"
	"fmt"
	"log"
	"math/rand"
	"net"
	"os"
	"os/signal"
	"path/filepath"
	"strconv"
	"strings"
	"syscall"
	"time"

	"github.com/comail/colog"
	transproxy "github.com/wadahiro/go-transproxy"
)

func orPanic(err error) {
	if err != nil {
		panic(err)
	}
}

var (
	fs       = flag.NewFlagSet(os.Args[0], flag.ExitOnError)
	loglevel = fs.String(
		"loglevel",
		"info",
		"Log level, one of: debug, info, warn, error, fatal, panic",
	)

	privateDNS = fs.String("private-dns", "",
		"Private DNS address for no_proxy targets (IP[:port])")

	publicDNS = fs.String("public-dns", "",
		"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")

	tcpProxyDestPorts = fs.String(
		"tcp-proxy-dports", "22", "TCP Proxy dports, as `port1,port2,...`",
	)

	tcpProxyListenAddress = fs.String(
		"tcp-proxy-listen", ":3128", "TCP Proxy listen address, as `[host]:port`",
	)

	httpProxyListenAddress = fs.String(
		"http-proxy-listen", ":3129", "HTTP Proxy listen address, as `[host]:port`",
	)

	httpsProxyListenAddress = fs.String(
		"https-proxy-listen", ":3130", "HTTPS Proxy listen address, as `[host]:port`",
	)

	dnsProxyListenAddress = fs.String(
		"dns-proxy-listen", ":3131", "DNS Proxy listen address, as `[host]:port`",
	)

	explicitProxyListenAddress = fs.String(
		"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",
	)

	explicitProxyWithAuthListenAddress = fs.String(
		"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",
	)

	explicitProxyOnly = fs.Bool(
		"explicit-proxy-only", false, "Boot Explicit Proxies only",
	)

	dnsOverTCPDisabled = fs.Bool(
		"dns-over-tcp-disabled", false, "Disable DNS-over-TCP for querying to public DNS")

	dnsOverHTTPSEnabled = fs.Bool(
		"dns-over-https-enabled", false, "Use DNS-over-HTTPS service as public DNS")

	dnsOverHTTPSEndpoint = fs.String(
		"dns-over-https-endpoint",
		"https://dns.google.com/resolve",
		"DNS-over-HTTPS endpoint URL",
	)

	dnsEnableTCP = fs.Bool("dns-tcp", true, "DNS Listen on TCP")
	dnsEnableUDP = fs.Bool("dns-udp", true, "DNS Listen on UDP")
	disableIPTables = fs.Bool("disable-iptables", false, "Disable automatic iptables configuration")
)

func main() {
	fs.Usage = func() {
		_, exe := filepath.Split(os.Args[0])
		fmt.Fprint(os.Stderr, "go-transproxy.\n\n")
		fmt.Fprintf(os.Stderr, "Usage:\n\n  %s [options]\n\nOptions:\n\n", exe)
		fs.PrintDefaults()
	}
	fs.Parse(os.Args[1:])

	// seed the global random number generator, used in secureoperator
	rand.Seed(time.Now().UTC().UnixNano())

	// setup logger
	colog.SetDefaultLevel(colog.LDebug)
	colog.SetMinLevel(colog.LInfo)
	level, err := colog.ParseLevel(*loglevel)
	if err != nil {
		log.Fatalf("alert: Invalid log level: %s", err.Error())
	}
	colog.SetMinLevel(level)
	colog.SetFormatter(&colog.StdFormatter{
		Colors: true,
		Flag:   log.Ldate | log.Ltime | log.Lmicroseconds,
	})
	colog.ParseFields(true)
	colog.Register()

	if *explicitProxyOnly {
		startExplicitProxyOnly(level)
	} else {
		startAllProxy(level)
	}
}

func startExplicitProxyOnly(level colog.Level) {
	startExplicitProxy()

	// serve until exit
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig

	log.Printf("info: Proxy servers stopping.")
	log.Printf("info: go-transproxy exited.")
}

func startAllProxy(level colog.Level) {
	// handling no_proxy environment
	noProxy := os.Getenv("no_proxy")
	if noProxy == "" {
		noProxy = os.Getenv("NO_PROXY")
	}

	np := parseNoProxy(noProxy)
	// start servers
	tcpProxy := transproxy.NewTCPProxy(
		transproxy.TCPProxyConfig{
			ListenAddress: *tcpProxyListenAddress,
			NoProxy:       np,
		},
	)
	if err := tcpProxy.Start(); err != nil {
		log.Fatalf("alert: %s", err.Error())
	}

	dnsProxy := transproxy.NewDNSProxy(
		transproxy.DNSProxyConfig{
			Enabled:             useDNSProxy(),
			ListenAddress:       *dnsProxyListenAddress,
			EnableUDP:           *dnsEnableUDP,
			EnableTCP:           *dnsEnableTCP,
			Endpoint:            *dnsOverHTTPSEndpoint,
			PublicDNS:           *publicDNS,
			PrivateDNS:          *privateDNS,
			DNSOverHTTPSEnabled: *dnsOverHTTPSEnabled,
			NoProxyDomains:      np.Domains,
		},
	)
	dnsProxy.Start()

	httpProxy := transproxy.NewHTTPProxy(
		transproxy.HTTPProxyConfig{
			ListenAddress: *httpProxyListenAddress,
			NoProxy:       np,
			Verbose:       level == colog.LDebug,
		},
	)
	if err := httpProxy.Start(); err != nil {
		log.Fatalf("alert: %s", err.Error())
	}

	httpsProxy := transproxy.NewHTTPSProxy(
		transproxy.HTTPSProxyConfig{
			ListenAddress: *httpsProxyListenAddress,
			NoProxy:       np,
		},
	)
	if err := httpsProxy.Start(); err != nil {
		log.Fatalf("alert: %s", err.Error())
	}

	startExplicitProxy()

	log.Printf("info: All proxy servers started.")

	dnsToPort := toPort(*dnsProxyListenAddress)
	httpToPort := toPort(*httpProxyListenAddress)
	httpsToPort := toPort(*httpsProxyListenAddress)
	tcpToPort := toPort(*tcpProxyListenAddress)
	tcpDPorts := toPorts(*tcpProxyDestPorts)

	outgoingPublicDNS := *publicDNS
	if *dnsOverTCPDisabled {
		outgoingPublicDNS = ""
	}

	var t *transproxy.IPTables
	var err error

	if !*disableIPTables {
		t, err = transproxy.NewIPTables(&transproxy.IPTablesConfig{
			DNSToPort:   dnsToPort,
			HTTPToPort:  httpToPort,
			HTTPSToPort: httpsToPort,
			TCPToPort:   tcpToPort,
			TCPDPorts:   tcpDPorts,
			PublicDNS:   outgoingPublicDNS,
		})
		if err != nil {
			log.Printf("alert: %s", err.Error())
		}

		t.Start()

		log.Printf(`info: iptables rules inserted as follows.
---
%s
---`, t.Show())
	}

	// serve until exit
	sig := make(chan os.Signal)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	<-sig

	log.Printf("info: Proxy servers stopping.")

	// start shutdown process
	if !*disableIPTables {
		t.Stop()
		log.Printf("info: iptables rules deleted.")
	}

	if dnsProxy != nil {
		dnsProxy.Stop()
	}

	log.Printf("info: go-transproxy exited.")
}

func startExplicitProxy() {
	explicitProxyWithAuth := transproxy.NewExplicitProxy(
		transproxy.ExplicitProxyConfig{
			ListenAddress:         *explicitProxyWithAuthListenAddress,
			UseProxyAuthorization: true,
		},
	)
	if err := explicitProxyWithAuth.Start(); err != nil {
		log.Fatalf("alert: %s", err.Error())
	}

	explicitProxy := transproxy.NewExplicitProxy(
		transproxy.ExplicitProxyConfig{
			ListenAddress:         *explicitProxyListenAddress,
			UseProxyAuthorization: false,
		},
	)
	if err := explicitProxy.Start(); err != nil {
		log.Fatalf("alert: %s", err.Error())
	}
}

func useDNSProxy() bool {
	if *privateDNS == "" && *publicDNS == "" && *dnsOverHTTPSEnabled == false {
		return false
	}
	return true
}

func toPort(addr string) int {
	array := strings.Split(addr, ":")
	if len(array) != 2 {
		log.Printf("alert: Invalid address, no port: %s", addr)
	}

	i, err := strconv.Atoi(array[1])
	if err != nil {
		log.Printf("alert: Invalid address, the port isn't number: %s", addr)
	}

	if i > 65535 || i < 0 {
		log.Printf("alert: Invalid address, the port must be an integer value in the range 0-65535: %s", addr)
	}

	return i
}

func toPorts(ports string) []int {
	array := strings.Split(ports, ",")

	var p []int

	for _, v := range array {
		i, err := strconv.Atoi(v)
		if err != nil {
			log.Printf("alert: Invalid port, It's not number: %s", ports)
		}

		if i > 65535 || i < 0 {
			log.Printf("alert: Invalid port, It must be an integer value in the range 0-65535: %s", ports)
		}

		p = append(p, i)
	}

	return p
}

func parseNoProxy(noProxy string) transproxy.NoProxy {
	p := strings.Split(noProxy, ",")

	var ipArray []string
	var cidrArray []*net.IPNet
	var domainArray []string

	for _, v := range p {
		ip := net.ParseIP(v)
		if ip != nil {
			ipArray = append(ipArray, v)
			continue
		}

		_, ipnet, err := net.ParseCIDR(v)
		if err == nil {
			cidrArray = append(cidrArray, ipnet)
			continue
		}

		domainArray = append(domainArray, v)
	}

	return transproxy.NoProxy{
		IPs:     ipArray,
		CIDRs:   cidrArray,
		Domains: domainArray,
	}
}


================================================
FILE: common.go
================================================
package transproxy

import (
	"fmt"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"
	"sync"

	"github.com/cybozu-go/netutil"
	"github.com/cybozu-go/transocks"
	"os"
)

type TCPListener struct {
	net.Listener
}

type TCPConn struct {
	*net.TCPConn
	OrigAddr string // ip:port
}

func NewTCPListener(listenAddress string) (*TCPListener, error) {
	l, err := net.Listen("tcp", listenAddress)
	if err != nil {
		return nil, err
	}
	return &TCPListener{
		Listener: l,
	}, nil
}

func (l *TCPListener) Accept() (net.Conn, error) {
	c, err := l.Listener.Accept()
	if err != nil {
		return c, err
	}

	tc, ok := c.(*net.TCPConn)
	if !ok {
		return c, fmt.Errorf("Accepted non-TCP connection - %v", c)
	}

	origAddr, err := transocks.GetOriginalDST(tc)
	if err != nil {
		return c, fmt.Errorf("GetOriginalDST failed - %s", err.Error())
	}

	log.Printf("debug: OriginalDST: %s", origAddr)
	log.Printf("debug: LocalAddr: %s", tc.LocalAddr().String())
	log.Printf("debug: RemoteAddr: %s", tc.RemoteAddr().String())

	return &TCPConn{tc, origAddr.String()}, nil
}

func ListenTCP(listenAddress string, handler func(tc *TCPConn)) {
	l, err := NewTCPListener(listenAddress)
	if err != nil {
		log.Fatalf("alert: Error listening for tcp connections - %s", err.Error())
	}

	for {
		conn, err := l.Accept() // wait here
		if err != nil {
			log.Printf("warn: Error accepting new connection - %s", err.Error())
			return
		}

		log.Printf("debug: Accepted new connection")

		go func(conn net.Conn) {
			defer func() {
				conn.Close()
			}()

			tc, _ := conn.(*TCPConn)

			handler(tc)
		}(conn)
	}
}

var pool = sync.Pool{
	New: func() interface{} {
		return make([]byte, 64<<10)
	},
}

func Pipe(srcConn *TCPConn, destConn net.Conn) {
	defer destConn.Close()

	log.Printf("debug: Start proxy")

	wg := &sync.WaitGroup{}

	wg.Add(1)
	go func() error {
		defer func() {
			wg.Done()
		}()

		buf := pool.Get().([]byte)
		_, err := io.CopyBuffer(destConn, srcConn, buf)
		pool.Put(buf)
		if hc, ok := destConn.(netutil.HalfCloser); ok {
			hc.CloseWrite()
		}
		srcConn.CloseRead()
		return err
	}()

	wg.Add(1)
	go func() error {
		defer func() {
			wg.Done()
		}()

		buf := pool.Get().([]byte)
		_, err := io.CopyBuffer(srcConn, destConn, buf)
		pool.Put(buf)
		srcConn.CloseWrite()
		if hc, ok := destConn.(netutil.HalfCloser); ok {
			hc.CloseRead()
		}
		return err
	}()

	wg.Wait()

	log.Printf("debug: End proxy")
}

type NoProxy struct {
	IPs     []string
	CIDRs   []*net.IPNet
	Domains []string
}

func httpProxyFromRule(noProxy NoProxy) func(*http.Request) (*url.URL, error) {
	return func(req *http.Request) (*url.URL, error) {
		if useProxy(noProxy, strings.Split(req.Host, ":")[0]) {

			return http.ProxyFromEnvironment(req)
		} else {
			return nil, nil
		}
	}
}

func useProxy(noProxy NoProxy, target string) bool {
	// TODO resolve target domain

	for _, d := range noProxy.Domains {
		if strings.HasSuffix(target, d) {
			log.Printf("debug: NO_PROXY: Matched no_proxy domain. Direct for %s", target)
			return false
		}
	}

	for _, ip := range noProxy.IPs {
		if ip == target {
			log.Printf("debug: NO_PROXY: Matched no_proxy ip. Direct for %s", target)
			return false
		}
	}

	for _, cidr := range noProxy.CIDRs {
		targetIP := net.ParseIP(target)
		if cidr.Contains(targetIP) {
			log.Printf("debug: NO_PROXY: Matched no_proxy cidr. Direct for %s", target)
			return false
		}
	}

	log.Printf("debug: Use proxy for %s", target)
	return true
}

func GetProxyEnv(key string) string {
	env := os.Getenv(key)
	if env == "" {
		env = os.Getenv(strings.ToUpper(key))
	}
	return env
}


================================================
FILE: dns.go
================================================
package transproxy

import (
	"log"
	"net"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	secop "github.com/fardog/secureoperator"
	"github.com/miekg/dns"
)

type DNSProxy struct {
	DNSProxyConfig
	udpServer *dns.Server
	tcpServer *dns.Server
	udpClient *dns.Client // used for fowarding to internal DNS
	tcpClient *dns.Client // used for fowarding to internal DNS
	waitGroup *sync.WaitGroup
}

type DNSProxyConfig struct {
	Enabled             bool
	ListenAddress       string
	EnableUDP           bool
	EnableTCP           bool
	Endpoint            string
	PublicDNS           string
	PrivateDNS          string
	DNSOverHTTPSEnabled bool
	NoProxyDomains      []string
}

func NewDNSProxy(c DNSProxyConfig) *DNSProxy {
	// Suppress standard logger for secureoperator
	logrus.SetLevel(logrus.ErrorLevel)

	// fix dns address
	if c.PublicDNS != "" {
		_, _, err := net.SplitHostPort(c.PublicDNS)
		if err != nil {
			c.PublicDNS = net.JoinHostPort(c.PublicDNS, "53")
		}
	}
	if c.PrivateDNS != "" {
		_, _, err := net.SplitHostPort(c.PrivateDNS)
		if err != nil {
			c.PrivateDNS = net.JoinHostPort(c.PrivateDNS, "53")
		}
	}

	// fix domains
	var noProxyRoutes []string
	for _, s := range c.NoProxyDomains {
		if !strings.HasSuffix(s, ".") {
			s += "."
		}
		noProxyRoutes = append(noProxyRoutes, s)
	}
	c.NoProxyDomains = noProxyRoutes

	return &DNSProxy{
		DNSProxyConfig: c,
		udpServer:      nil,
		tcpServer:      nil,
		udpClient: &dns.Client{
			Net:            "udp",
			Timeout:        time.Duration(10) * time.Second,
			SingleInflight: true,
		},
		tcpClient: &dns.Client{
			Net:            "tcp",
			Timeout:        time.Duration(10) * time.Second,
			SingleInflight: true,
		},
		waitGroup: new(sync.WaitGroup),
	}
}

func (s *DNSProxy) Start() error {
	if !s.Enabled {
		log.Printf("debug: Disabled category='DNS-Proxy'")
		return nil
	}

	log.Printf("info: Start listener on %s category='DNS-Proxy'", s.ListenAddress)
	if s.DNSOverHTTPSEnabled {
		log.Printf("info: Use DNS-over-HTTPS service as public DNS category='DNS-Proxy'")
	}
	if !s.DNSOverHTTPSEnabled && s.PublicDNS != "" {
		log.Printf("info: Use %s as public DNS category='DNS-Proxy'", s.PublicDNS)
	}
	if s.PrivateDNS != "" {
		log.Printf("info: Use %s as private DNS for %s domains category='DNS-Proxy'", s.PrivateDNS, s.NoProxyDomains)
	}

	// Prepare external DNS handler
	provider, err := secop.NewGDNSProvider(s.Endpoint, &secop.GDNSOptions{
		Pad: true,
	})

	if err != nil {
		log.Fatal("alert: %s category='DNS-Proxy'", err)
	}

	options := &secop.HandlerOptions{}
	publicOverHTTPSHandler := secop.NewHandler(provider, options)

	// Setup DNS Handler
	dnsHandle := func(w dns.ResponseWriter, req *dns.Msg) {
		if len(req.Question) == 0 {
			dns.HandleFailed(w, req)
			return
		}

		// access logging
		host, _, _ := net.SplitHostPort(w.RemoteAddr().String())
		log.Printf("info: category='DNS-Proxy' remoteAddr='%s' questionName='%s' questionType='%s'", host, req.Question[0].Name, dns.TypeToString[req.Question[0].Qtype])

		// Resolve by proxied private DNS
		for _, domain := range s.NoProxyDomains {
			log.Printf("debug: Checking DNS route, request: %s, no_proxy: %s", req.Question[0].Name, domain)
			if strings.HasSuffix(req.Question[0].Name, domain) {
				log.Printf("debug: Matched! Routing to private DNS, request: %s, no_proxy: %s", req.Question[0].Name, domain)
				s.handlePrivate(w, req)
				return
			}
		}

		// Resolve by public DNS over HTTPS over http proxy
		if s.DNSOverHTTPSEnabled {
			publicOverHTTPSHandler.Handle(w, req)
			return
		}

		// Resolve by specified public DNS over http proxy
		s.handlePublic(w, req)
	}

	dns.HandleFunc(".", dnsHandle)

	// Start DNS Server

	if s.EnableUDP {
		s.udpServer = &dns.Server{
			Addr:       s.ListenAddress,
			Net:        "udp",
			TsigSecret: nil,
		}
	}
	if s.EnableTCP {
		s.tcpServer = &dns.Server{
			Addr:       s.ListenAddress,
			Net:        "tcp",
			TsigSecret: nil,
		}
	}

	go func() {
		if s.udpServer != nil {
			if err := s.udpServer.ListenAndServe(); err != nil {
				log.Fatal("alert: %s", err.Error())
			}
		}
		if s.tcpServer != nil {
			if err := s.tcpServer.ListenAndServe(); err != nil {
				log.Fatal("alert: %s", err.Error())
			}
		}
	}()

	return nil
}

func (s *DNSProxy) handlePublic(w dns.ResponseWriter, req *dns.Msg) {
	log.Printf("debug: DNS request. %#v, %s", req, req)

	// Need to use TCP because of using TCP-Proxy
	resp, _, err := s.tcpClient.Exchange(req, s.PublicDNS)
	if err != nil {
		log.Printf("warn: DNS Client failed. %s, %#v, %s", err.Error(), req, req)
		dns.HandleFailed(w, req)
		return
	}
	w.WriteMsg(resp)
}

func (s *DNSProxy) handlePrivate(w dns.ResponseWriter, req *dns.Msg) {
	var c *dns.Client
	if _, ok := w.RemoteAddr().(*net.TCPAddr); ok {
		c = s.tcpClient
	} else {
		c = s.udpClient
	}

	log.Printf("debug: DNS request. %#v, %s", req, req)

	resp, _, err := c.Exchange(req, s.PrivateDNS)
	if err != nil {
		log.Printf("warn: DNS Client failed. %s, %#v, %s", err.Error(), req, req)
		dns.HandleFailed(w, req)
		return
	}
	w.WriteMsg(resp)
}

func (s *DNSProxy) Stop() {
	if !s.Enabled {
		return
	}

	log.Printf("info: Shutting down DNS service on interrupt\n")

	if s.udpServer != nil {
		if err := s.udpServer.Shutdown(); err != nil {
			log.Printf("error: %s", err.Error())
		}
		s.udpServer = nil
	}
	if s.tcpServer != nil {
		if err := s.tcpServer.Shutdown(); err != nil {
			log.Printf("error: %s", err.Error())
		}
		s.tcpServer = nil
	}
}


================================================
FILE: explicit.go
================================================
package transproxy

import (
	"encoding/base64"
	"io"
	"log"
	"net"
	"net/http"
	"net/url"
	"strings"
	"time"
)

type ExplicitProxy struct {
	ExplicitProxyConfig
	user     string
	category string
	// For HTTP
	proxyTransport     *http.Transport
	proxyAuthTransport *http.Transport
	// For HTTPS
	proxyHost          string
	proxyAuthorization string
}

type ExplicitProxyConfig struct {
	ListenAddress         string
	UseProxyAuthorization bool
}

func NewExplicitProxy(c ExplicitProxyConfig) *ExplicitProxy {
	return &ExplicitProxy{
		ExplicitProxyConfig: c,
	}
}

func (s ExplicitProxy) Start() error {
	u, err := url.Parse(GetProxyEnv("http_proxy"))
	if err != nil {
		return err
	}

	if s.UseProxyAuthorization {
		s.category = "Explicit-Proxy(Auth)"

		if u.User == nil {
			log.Printf("info: Not Started because of no proxy user category='%s'", s.category)
			return nil
		}
		// For HTTPS
		s.proxyAuthorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(u.User.String()))
		s.proxyHost = u.Host

		// For HTTP
		s.proxyAuthTransport = &http.Transport{Proxy: http.ProxyURL(u)}
	} else {
		s.category = "Explicit-Proxy(NoAuth)"

		// For HTTPS
		s.proxyHost = u.Host

		// For HTTP
		u, _ = url.Parse(u.String())
		u.User = nil
		s.proxyTransport = &http.Transport{Proxy: http.ProxyURL(u)}
	}

	handler := http.HandlerFunc(s.handleRequest)

	log.Printf("info: Start listener on %s category='%s'", s.ListenAddress, s.category)

	go func() {
		http.ListenAndServe(s.ListenAddress, handler)
	}()

	return nil
}

func (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Request) {
	// access logging
	s.accessLog(r)

	if r.Method == "CONNECT" {
		s.handleHttps(w, r)
	} else {
		s.handleHttp(w, r)
	}
}

func (s ExplicitProxy) accessLog(r *http.Request) {
	host, _, _ := net.SplitHostPort(r.RemoteAddr)
	if s.UseProxyAuthorization {
		log.Printf("info: category='%s' remoteAddr='%s' method='%s' uri='%s'", s.category, host, r.Method, r.RequestURI)
	} else {
		var decodedAuth string

		values := r.Header["Proxy-Authorization"]
		if len(values) > 0 {
			auth := strings.Split(values[0], " ")
			if len(auth) > 0 {
				b, _ := base64.StdEncoding.DecodeString(auth[1])
				decodedAuth = strings.Split(string(b[:]), ":")[0]
			}
		}
		log.Printf("info: category='%s' user='%s' remoteAddr='%s' method='%s' uri='%s'", s.category, decodedAuth, host, r.Method, r.RequestURI)
	}
}

func (s ExplicitProxy) handleHttps(w http.ResponseWriter, r *http.Request) {
	destConn, err := net.DialTimeout("tcp", s.proxyHost, 10*time.Second)
	if err != nil {
		log.Printf("error: %s category='%s'", err, s.category)
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		return
	}

	hj, ok := w.(http.Hijacker)
	if !ok {
		log.Printf("error: Hijacking not supported category='%s'", s.category)
		http.Error(w, "Hijacking not supported", http.StatusInternalServerError)
		destConn.Close()
		return
	}
	clientConn, _, err := hj.Hijack()
	if err != nil {
		log.Printf("error: %s category='%s'", err, s.category)
		http.Error(w, err.Error(), http.StatusServiceUnavailable)
		destConn.Close()
		return
	}

	if s.UseProxyAuthorization {
		r.Header.Set("Proxy-Authorization", s.proxyAuthorization)
	}

	r.Write(destConn)

	go transfer(clientConn, destConn)
	go transfer(destConn, clientConn)

	log.Printf("debug: End proxy category='%s'", s.category)
}

func transfer(destination io.WriteCloser, source io.ReadCloser) {
	defer destination.Close()
	defer source.Close()
	io.Copy(destination, source)
}

func (s ExplicitProxy) handleHttp(w http.ResponseWriter, r *http.Request) {
	hj, _ := w.(http.Hijacker)
	var client *http.Client
	if s.UseProxyAuthorization {
		client = &http.Client{
			Transport: s.proxyAuthTransport,
		}
	} else {
		client = &http.Client{
			Transport: s.proxyTransport,
		}
	}

	r.RequestURI = ""
	if resp, err := client.Do(r); err != nil {
		log.Printf("error: %s", err)
	} else if conn, _, err := hj.Hijack(); err != nil {
		log.Printf("error: %s", err)
	} else {
		defer conn.Close()
		resp.Write(conn)
	}
}


================================================
FILE: go.mod
================================================
module github.com/wadahiro/go-transproxy

go 1.13

require (
	github.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235
	github.com/comail/colog v0.0.0-20160416085026-fba8e7b1f46c
	github.com/coreos/go-iptables v0.2.0
	github.com/cybozu-go/cmd v1.5.0
	github.com/cybozu-go/log v1.4.1
	github.com/cybozu-go/netutil v1.1.0
	github.com/cybozu-go/transocks v1.0.0
	github.com/elazarl/goproxy v0.0.0-20170413182129-aacba83f36a5
	github.com/fardog/secureoperator v2.1.0+incompatible
	github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b
	github.com/miekg/dns v0.0.0-20170812192144-0598bd43cf51
	github.com/pkg/errors v0.8.0
	golang.org/x/net v0.0.0-20170809000501-1c05540f6879
	golang.org/x/sys v0.0.0-20170814191752-2d3e384235de
)


================================================
FILE: go.sum
================================================
github.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235 h1:m190Z0077f/CDftqBTbt7IlAqkmvkPEafq0byCfeisg=
github.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235/go.mod h1:rmk17hk6i8ZSAJkSDa7nOxamrG+SP4P0mm+DAvExv4U=
github.com/comail/colog v0.0.0-20160416085026-fba8e7b1f46c h1:bzYQ6WpR+t35/y19HUkolcg7SYeWZ15IclC9Z4naGHI=
github.com/comail/colog v0.0.0-20160416085026-fba8e7b1f46c/go.mod h1:1WwgAwMKQLYG5I2FBhpVx94YTOAuB2W59IZ7REjSE6Y=
github.com/coreos/go-iptables v0.2.0 h1:RmVRALeVCicZcF3rF05e0ooU9x9TmalN0HcT4hkhG5s=
github.com/coreos/go-iptables v0.2.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU=
github.com/cybozu-go/cmd v1.5.0 h1:ootYtwvAikKShkE6NRwAVGID7x1BrQwVH7IDeqFe8zM=
github.com/cybozu-go/cmd v1.5.0/go.mod h1:VW0nNKV31B3qvEhHZI26Rjgq5afWYm6Ulai24Qkgx5M=
github.com/cybozu-go/log v1.4.1 h1:EGx01xTw+ZJEM/sJcTyHm++VFqN2WUlnOi1zMOd6ZJQ=
github.com/cybozu-go/log v1.4.1/go.mod h1:qS5RWuo80yknMR3he2WCWxqfVlbOuSFMz0r1mJwS018=
github.com/cybozu-go/netutil v1.1.0 h1:9o33zFrGqXpexOX3hlrN4ebYwQvThr9UP33md7mGNmc=
github.com/cybozu-go/netutil v1.1.0/go.mod h1:UiOBRGyD1OiABE7Hk2bXac0mqDwuG4xMQc7T5XidXRA=
github.com/cybozu-go/transocks v1.0.0 h1:IyemjpF4TA/5qJd2royU4fjWBwpJKmFpi1tvCkE39dg=
github.com/cybozu-go/transocks v1.0.0/go.mod h1:CXrq7WldMyROCLqg8JLxvhxxr8b8inYS/GjJ08b9UyQ=
github.com/elazarl/goproxy v0.0.0-20170413182129-aacba83f36a5 h1:6hndtckUKiV+txASiPN72sRWuHJQycm6B9BI18YYmzs=
github.com/elazarl/goproxy v0.0.0-20170413182129-aacba83f36a5/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
github.com/fardog/secureoperator v2.1.0+incompatible h1:l0nfc8iv87iAa6FBgRnH9wnFkfKHMnVJhQFVhaLZfQ8=
github.com/fardog/secureoperator v2.1.0+incompatible/go.mod h1:N/UFQradwwlO7ZCxDMWHPRdXW6D+nnEU6nQ8Zo0o9T4=
github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b h1:IpLPmn6Re21F0MaV6Zsc5RdSE6KuoFpWmHiUSEs3PrE=
github.com/inconshreveable/go-vhost v0.0.0-20160627193104-06d84117953b/go.mod h1:aA6DnFhALT3zH0y+A39we+zbrdMC2N0X/q21e6FI0LU=
github.com/miekg/dns v0.0.0-20170812192144-0598bd43cf51 h1:hZ1ngpLE9G7RaoMM6LlixdiCedgR4GhqzOYBcarqaho=
github.com/miekg/dns v0.0.0-20170812192144-0598bd43cf51/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/pkg/errors v0.8.0 h1:WdK/asTD0HN+q6hsWO3/vpuAkAr+tw6aNJNDFFf0+qw=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
golang.org/x/net v0.0.0-20170809000501-1c05540f6879 h1:0rFa7EaCGdQPmZVbo9F7MNF65b8dyzS6EUnXjs9Cllk=
golang.org/x/net v0.0.0-20170809000501-1c05540f6879/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20170814191752-2d3e384235de/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=


================================================
FILE: http.go
================================================
package transproxy

import (
	"fmt"
	"log"
	"net"
	"net/http"

	"github.com/elazarl/goproxy"
)

type HTTPProxy struct {
	HTTPProxyConfig
}

type HTTPProxyConfig struct {
	ListenAddress string
	NoProxy       NoProxy
	Verbose       bool
}

func NewHTTPProxy(c HTTPProxyConfig) *HTTPProxy {
	return &HTTPProxy{
		HTTPProxyConfig: c,
	}
}

func (s HTTPProxy) Start() error {
	l, err := NewTCPListener(s.ListenAddress)
	if err != nil {
		log.Printf("error: Failed listening for tcp connections - %s category='HTTP-Proxy'", err.Error())
	}

	proxy := goproxy.NewProxyHttpServer()
	proxy.Tr.Proxy = httpProxyFromRule(s.NoProxy)
	proxy.Verbose = s.Verbose

	proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
		log.Printf("debug: Accept: %s, %s", req.Host, req.URL)
		if req.Host == "" {
			// TODO use origAddr from TCPCon
			fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0")
			return
		}

		// Convert to proxy request (abs URL request) for passing goproxy handler
		req.URL.Scheme = "http"
		req.URL.Host = req.Host

		// access logging
		host, _, _ := net.SplitHostPort(req.RemoteAddr)
		log.Printf("info: category='HTTP-Proxy' remoteAddr='%s' method='%s' url='%s'", host, req.Method, req.URL)

		// proxy to real target
		proxy.ServeHTTP(w, req)
	})

	log.Printf("info: Start listener on %s category='HTTP-Proxy'", s.ListenAddress)

	go func() {
		http.Serve(l, proxy)
	}()

	return nil
}


================================================
FILE: https.go
================================================
package transproxy

import (
	"log"
	"net"
	"net/url"
	"strings"
	"time"

	"github.com/inconshreveable/go-vhost"
	"golang.org/x/net/proxy"
)

type HTTPSProxy struct {
	HTTPSProxyConfig
}

type HTTPSProxyConfig struct {
	ListenAddress string
	NoProxy       NoProxy
}

func NewHTTPSProxy(c HTTPSProxyConfig) *HTTPSProxy {
	return &HTTPSProxy{
		HTTPSProxyConfig: c,
	}
}

func (s HTTPSProxy) Start() error {
	dialer := &net.Dialer{
		KeepAlive: 3 * time.Minute,
		DualStack: true,
	}
	u, err := url.Parse(GetProxyEnv("http_proxy"))
	if err != nil {
		return err
	}

	pdialer, err := proxy.FromURL(u, dialer)
	if err != nil {
		return err
	}

	npdialer := proxy.Direct

	log.Printf("info: Start listener on %s category='HTTPS-Proxy'", s.ListenAddress)

	go func() {
		ListenTCP(s.ListenAddress, func(tc *TCPConn) {
			tlsConn, err := vhost.TLS(tc)
			if err != nil {
				log.Printf("error: Failed handling TLS connection - %s", err.Error())
				return
			}

			defer func() {
				tlsConn.Free()
			}()

			origServer := tlsConn.Host()
			if origServer == "" {
				log.Printf("warn: Cannot get SNI, so fallback using `SO_ORIGINAL_DST` or `IP6T_SO_ORIGINAL_DST`")
				origServer = tc.OrigAddr // IPAddress:Port

				// TODO getting domain from origAddr, then check whether we should use proxy or not
			} else {
				log.Printf("debug: SNI: %s", origServer)
				origServer = net.JoinHostPort(origServer, "443")
			}

			// access logging
			host, _, _ := net.SplitHostPort(tc.RemoteAddr().String())
			log.Printf("info: category='HTTPS-Proxy' remoteAddr='%s' method=CONNECT host='%s'", host, origServer)

			var destConn net.Conn
			if useProxy(s.NoProxy, strings.Split(origServer, ":")[0]) {

				destConn, err = pdialer.Dial("tcp", origServer)
			} else {
				destConn, err = npdialer.Dial("tcp", origServer)
			}

			if err != nil {
				log.Printf("warn: Failed to connect to destination - %s", err.Error())
				return
			}

			// First, write ClientHello to real destination because we have already read it
			ch := tlsConn.ClientHelloMsg.Raw
			chSize := len(ch)
			chHeader := []byte{0x16, 0x03, 0x01, byte(chSize >> 8), byte(chSize)}
			chRecord := append(chHeader, ch...)
			destConn.Write(chRecord)

			// Then, pipe the data
			Pipe(tc, destConn)
		})
	}()

	return nil
}


================================================
FILE: iptables.go
================================================
package transproxy

import (
	"fmt"
	"net"
	"strconv"
	"strings"

	"github.com/coreos/go-iptables/iptables"
)

const (
	NAT        = "nat"
	PREROUTING = "PREROUTING"
	OUTPUT     = "OUTPUT"
)

type IPTables struct {
	iptables      *iptables.IPTables
	dnsTCPOutRule []string
	dnsTCPRule    []string
	dnsUDPRule    []string
	httpRule      []string
	httpsRule     []string
	tcpRule       []string
	err           error
}

type IPTablesConfig struct {
	DNSToPort   int
	HTTPToPort  int
	HTTPSToPort int
	TCPToPort   int
	TCPDPorts   []int
	PublicDNS   string
}

func NewIPTables(c *IPTablesConfig) (*IPTables, error) {
	t, err := iptables.New()
	if err != nil {
		return nil, err
	}

	var tcpDPorts []string
	for _, v := range c.TCPDPorts {
		tcpDPorts = append(tcpDPorts, strconv.Itoa(v))
	}

	var dnsTCPOutRule []string
	if c.PublicDNS != "" {
		h, p, err := net.SplitHostPort(c.PublicDNS)
		if err != nil {
			c.PublicDNS = net.JoinHostPort(c.PublicDNS, "53")
		}
		h, p, _ = net.SplitHostPort(c.PublicDNS)
		dnsTCPOutRule = []string{NAT, OUTPUT, "-p", "tcp", "-d", h, "--dport", p, "-j", "REDIRECT", "--to-ports", strconv.Itoa(c.TCPToPort)}
	}

	dnsTCPRule := []string{NAT, PREROUTING, "-p", "tcp", "--dport", "53", "-j", "REDIRECT", "--to-ports", strconv.Itoa(c.DNSToPort)}
	dnsUDPRule := []string{NAT, PREROUTING, "-p", "udp", "--dport", "53", "-j", "REDIRECT", "--to-ports", strconv.Itoa(c.DNSToPort)}
	httpRule := []string{NAT, PREROUTING, "-p", "tcp", "--dport", "80", "-j", "REDIRECT", "--to-ports", strconv.Itoa(c.HTTPToPort)}
	httpsRule := []string{NAT, PREROUTING, "-p", "tcp", "--dport", "443", "-j", "REDIRECT", "--to-ports", strconv.Itoa(c.HTTPSToPort)}
	tcpRule := []string{NAT, PREROUTING, "-p", "tcp", "-m", "multiport", "--dport", strings.Join(tcpDPorts, ","), "-j", "REDIRECT", "--to-ports", strconv.Itoa(c.TCPToPort)}

	return &IPTables{
		iptables:      t,
		dnsTCPOutRule: dnsTCPOutRule,
		dnsTCPRule:    dnsTCPRule,
		dnsUDPRule:    dnsUDPRule,
		httpRule:      httpRule,
		httpsRule:     httpsRule,
		tcpRule:       tcpRule,
	}, nil
}

func (t *IPTables) Start() error {
	t.Check(t.dnsTCPOutRule)
	t.Check(t.dnsTCPRule)
	t.Check(t.dnsUDPRule)
	t.Check(t.httpRule)
	t.Check(t.httpsRule)
	t.Check(t.tcpRule)

	t.insertRule(t.dnsTCPOutRule)
	t.insertRule(t.dnsTCPRule)
	t.insertRule(t.dnsUDPRule)
	t.insertRule(t.httpRule)
	t.insertRule(t.httpsRule)
	t.insertRule(t.tcpRule)

	return t.err
}

func (t *IPTables) Stop() error {
	t.deleteRule(t.dnsTCPOutRule)
	t.deleteRule(t.dnsTCPRule)
	t.deleteRule(t.dnsUDPRule)
	t.deleteRule(t.httpRule)
	t.deleteRule(t.httpsRule)
	t.deleteRule(t.tcpRule)

	return t.err
}

func (t *IPTables) Show() string {
	s := fmt.Sprintf(`iptables -t %s -I %s
iptables -t %s -I %s
iptables -t %s -I %s
iptables -t %s -I %s
iptables -t %s -I %s`,
		t.tcpRule[0], strings.Join(t.tcpRule[1:], " "),
		t.httpsRule[0], strings.Join(t.httpsRule[1:], " "),
		t.httpRule[0], strings.Join(t.httpRule[1:], " "),
		t.dnsUDPRule[0], strings.Join(t.dnsUDPRule[1:], " "),
		t.dnsTCPRule[0], strings.Join(t.dnsTCPRule[1:], " "),
	)

	if len(t.dnsTCPOutRule) > 0 {
		s += fmt.Sprintf(`
iptables -t %s -I %s`,
			t.dnsTCPOutRule[0], strings.Join(t.dnsTCPOutRule[1:], " "),
		)
	}

	return s
}

func (t *IPTables) Check(rule []string) {
	if t.err != nil || len(rule) < 3 {
		return
	}

	exists, err := t.iptables.Exists(rule[0], rule[1], rule[2:]...)
	if exists {
		t.err = fmt.Errorf("Same iptables rule already exists : iptables -t %s -I %s", rule[0], strings.Join(rule[1:], " "))
	}

	if err != nil {
		t.err = fmt.Errorf("Checking iptables rule failed : %s", err.Error())
	}
}

func (t *IPTables) insertRule(rule []string) {
	if t.err != nil || len(rule) < 3 {
		return
	}

	if err := t.iptables.Insert(rule[0], rule[1], 1, rule[2:]...); err != nil {
		t.err = fmt.Errorf("Insert iptables rule failed : %s", err.Error())
	}
}

func (t *IPTables) deleteRule(rule []string) {
	// Don't skip when it has error for deleting all rules
	if len(rule) < 3 {
		return
	}

	if err := t.iptables.Delete(rule[0], rule[1], rule[2:]...); err != nil {
		t.err = fmt.Errorf("Delete iptables rule failed : %s", err.Error())
	}
}


================================================
FILE: tcp.go
================================================
package transproxy

import (
	"log"
	"net"
	"net/url"
	"strings"
	"time"

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

type TCPProxy struct {
	TCPProxyConfig
}

type TCPProxyConfig struct {
	ListenAddress string
	NoProxy       NoProxy
}

func NewTCPProxy(c TCPProxyConfig) *TCPProxy {
	return &TCPProxy{
		TCPProxyConfig: c,
	}
}

func (s TCPProxy) Start() error {
	//pdialer := proxy.FromEnvironment()

	dialer := &net.Dialer{
		KeepAlive: 3 * time.Minute,
		DualStack: true,
	}
	u, err := url.Parse(GetProxyEnv("http_proxy"))
	if err != nil {
		return err
	}

	pdialer, err := proxy.FromURL(u, dialer)
	if err != nil {
		return err
	}

	npdialer := proxy.Direct

	log.Printf("info: Start listener on %s category='TCP-Proxy'", s.ListenAddress)

	go func() {
		ListenTCP(s.ListenAddress, func(tc *TCPConn) {
			// access logging
			host, _, _ := net.SplitHostPort(tc.RemoteAddr().String())
			log.Printf("info: category='TCP-Proxy' remoteAddr='%s' method=CONNECT host='%s'", host, tc.OrigAddr)

			var destConn net.Conn
			// TODO Convert OrigAddr to domain and check useProxy with domain too?
			if useProxy(s.NoProxy, strings.Split(tc.OrigAddr, ":")[0]) {

				destConn, err = pdialer.Dial("tcp", tc.OrigAddr)
			} else {
				destConn, err = npdialer.Dial("tcp", tc.OrigAddr)
			}

			if err != nil {
				log.Printf("error: Failed to connect to destination - %s category='TCP-Proxy'", err.Error())
				return
			}

			Pipe(tc, destConn)
		})
	}()

	return nil
}
Download .txt
gitextract_m5cqkbr0/

├── .gitignore
├── LICENSE
├── Makefile
├── README.md
├── cmd/
│   └── transproxy/
│       └── main.go
├── common.go
├── dns.go
├── explicit.go
├── go.mod
├── go.sum
├── http.go
├── https.go
├── iptables.go
└── tcp.go
Download .txt
SYMBOL INDEX (59 symbols across 8 files)

FILE: cmd/transproxy/main.go
  function orPanic (line 21) | func orPanic(err error) {
  function main (line 90) | func main() {
  function startExplicitProxyOnly (line 124) | func startExplicitProxyOnly(level colog.Level) {
  function startAllProxy (line 136) | func startAllProxy(level colog.Level) {
  function startExplicitProxy (line 250) | func startExplicitProxy() {
  function useDNSProxy (line 272) | func useDNSProxy() bool {
  function toPort (line 279) | func toPort(addr string) int {
  function toPorts (line 297) | func toPorts(ports string) []int {
  function parseNoProxy (line 318) | func parseNoProxy(noProxy string) transproxy.NoProxy {

FILE: common.go
  type TCPListener (line 18) | type TCPListener struct
    method Accept (line 37) | func (l *TCPListener) Accept() (net.Conn, error) {
  type TCPConn (line 22) | type TCPConn struct
  function NewTCPListener (line 27) | func NewTCPListener(listenAddress string) (*TCPListener, error) {
  function ListenTCP (line 60) | func ListenTCP(listenAddress string, handler func(tc *TCPConn)) {
  function Pipe (line 93) | func Pipe(srcConn *TCPConn, destConn net.Conn) {
  type NoProxy (line 137) | type NoProxy struct
  function httpProxyFromRule (line 143) | func httpProxyFromRule(noProxy NoProxy) func(*http.Request) (*url.URL, e...
  function useProxy (line 154) | func useProxy(noProxy NoProxy, target string) bool {
  function GetProxyEnv (line 183) | func GetProxyEnv(key string) string {

FILE: dns.go
  type DNSProxy (line 15) | type DNSProxy struct
    method Start (line 82) | func (s *DNSProxy) Start() error {
    method handlePublic (line 177) | func (s *DNSProxy) handlePublic(w dns.ResponseWriter, req *dns.Msg) {
    method handlePrivate (line 190) | func (s *DNSProxy) handlePrivate(w dns.ResponseWriter, req *dns.Msg) {
    method Stop (line 209) | func (s *DNSProxy) Stop() {
  type DNSProxyConfig (line 24) | type DNSProxyConfig struct
  function NewDNSProxy (line 36) | func NewDNSProxy(c DNSProxyConfig) *DNSProxy {

FILE: explicit.go
  type ExplicitProxy (line 14) | type ExplicitProxy struct
    method Start (line 37) | func (s ExplicitProxy) Start() error {
    method handleRequest (line 79) | func (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Re...
    method accessLog (line 90) | func (s ExplicitProxy) accessLog(r *http.Request) {
    method handleHttps (line 109) | func (s ExplicitProxy) handleHttps(w http.ResponseWriter, r *http.Requ...
    method handleHttp (line 150) | func (s ExplicitProxy) handleHttp(w http.ResponseWriter, r *http.Reque...
  type ExplicitProxyConfig (line 26) | type ExplicitProxyConfig struct
  function NewExplicitProxy (line 31) | func NewExplicitProxy(c ExplicitProxyConfig) *ExplicitProxy {
  function transfer (line 144) | func transfer(destination io.WriteCloser, source io.ReadCloser) {

FILE: http.go
  type HTTPProxy (line 12) | type HTTPProxy struct
    method Start (line 28) | func (s HTTPProxy) Start() error {
  type HTTPProxyConfig (line 16) | type HTTPProxyConfig struct
  function NewHTTPProxy (line 22) | func NewHTTPProxy(c HTTPProxyConfig) *HTTPProxy {

FILE: https.go
  type HTTPSProxy (line 14) | type HTTPSProxy struct
    method Start (line 29) | func (s HTTPSProxy) Start() error {
  type HTTPSProxyConfig (line 18) | type HTTPSProxyConfig struct
  function NewHTTPSProxy (line 23) | func NewHTTPSProxy(c HTTPSProxyConfig) *HTTPSProxy {

FILE: iptables.go
  constant NAT (line 13) | NAT        = "nat"
  constant PREROUTING (line 14) | PREROUTING = "PREROUTING"
  constant OUTPUT (line 15) | OUTPUT     = "OUTPUT"
  type IPTables (line 18) | type IPTables struct
    method Start (line 76) | func (t *IPTables) Start() error {
    method Stop (line 94) | func (t *IPTables) Stop() error {
    method Show (line 105) | func (t *IPTables) Show() string {
    method Check (line 128) | func (t *IPTables) Check(rule []string) {
    method insertRule (line 143) | func (t *IPTables) insertRule(rule []string) {
    method deleteRule (line 153) | func (t *IPTables) deleteRule(rule []string) {
  type IPTablesConfig (line 29) | type IPTablesConfig struct
  function NewIPTables (line 38) | func NewIPTables(c *IPTablesConfig) (*IPTables, error) {

FILE: tcp.go
  type TCPProxy (line 13) | type TCPProxy struct
    method Start (line 28) | func (s TCPProxy) Start() error {
  type TCPProxyConfig (line 17) | type TCPProxyConfig struct
  function NewTCPProxy (line 22) | func NewTCPProxy(c TCPProxyConfig) *TCPProxy {
Condensed preview — 14 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (46K chars).
[
  {
    "path": ".gitignore",
    "chars": 26,
    "preview": "bin\nvendor\ndist\n.DS_store\n"
  },
  {
    "path": "LICENSE",
    "chars": 1080,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2017 Hiroyuki Wada\n\nPermission is hereby granted, free of charge, to any person obt"
  },
  {
    "path": "Makefile",
    "chars": 1251,
    "preview": "NAME := transproxy\nVERSION := v0.6.0\nREVISION := $(shell git rev-parse --short HEAD)\n\nSRCS    := $(shell find . -type f "
  },
  {
    "path": "README.md",
    "chars": 5380,
    "preview": "# go-transproxy\n\nTransparent proxy servers for HTTP, HTTPS, DNS and TCP. \nThis repository is heavily under development.\n"
  },
  {
    "path": "cmd/transproxy/main.go",
    "chars": 8356,
    "preview": "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\"string"
  },
  {
    "path": "common.go",
    "chars": 3590,
    "preview": "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-g"
  },
  {
    "path": "dns.go",
    "chars": 5480,
    "preview": "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/"
  },
  {
    "path": "explicit.go",
    "chars": 4036,
    "preview": "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 Ex"
  },
  {
    "path": "go.mod",
    "chars": 750,
    "preview": "module github.com/wadahiro/go-transproxy\n\ngo 1.13\n\nrequire (\n\tgithub.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5"
  },
  {
    "path": "go.sum",
    "chars": 2688,
    "preview": "github.com/Sirupsen/logrus v1.0.2-0.20170713114250-a3f95b5c4235 h1:m190Z0077f/CDftqBTbt7IlAqkmvkPEafq0byCfeisg=\ngithub.c"
  },
  {
    "path": "http.go",
    "chars": 1454,
    "preview": "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"
  },
  {
    "path": "https.go",
    "chars": 2276,
    "preview": "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\"golan"
  },
  {
    "path": "iptables.go",
    "chars": 4140,
    "preview": "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\t"
  },
  {
    "path": "tcp.go",
    "chars": 1448,
    "preview": "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 str"
  }
]

About this extraction

This page contains the full source code of the wadahiro/go-transproxy GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 14 files (41.0 KB), approximately 13.3k tokens, and a symbol index with 59 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!