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
302 Moved
302 Moved
The document has moved
here.
```
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
}