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&dcr=0&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
}
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
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.