From 377154a7b408f4b4556366f8426c131966849f27 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Wed, 15 Nov 2017 16:03:21 +0900 Subject: [PATCH 1/3] Add explicit proxy --- cmd/transproxy/main.go | 13 ++++++ explicit.go | 96 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 explicit.go diff --git a/cmd/transproxy/main.go b/cmd/transproxy/main.go index 576e008..d795516 100644 --- a/cmd/transproxy/main.go +++ b/cmd/transproxy/main.go @@ -57,6 +57,10 @@ var ( "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`", + ) + dnsOverTCPDisabled = fs.Bool( "dns-over-tcp-disabled", false, "Disable DNS-over-TCP for querying to public DNS") @@ -150,6 +154,15 @@ func main() { log.Fatalf(err.Error()) } + explicitProxy := transproxy.NewExplicitProxy( + transproxy.ExplicitProxyConfig{ + ListenAddress: *explicitProxyListenAddress, + }, + ) + if err := explicitProxy.Start(); err != nil { + log.Fatalf(err.Error()) + } + log.Infoln("All proxy servers started.") dnsToPort := toPort(*dnsProxyListenAddress) diff --git a/explicit.go b/explicit.go new file mode 100644 index 0000000..d28f4d1 --- /dev/null +++ b/explicit.go @@ -0,0 +1,96 @@ +package transproxy + +import ( + "encoding/base64" + "io" + "net" + "net/http" + "net/url" + "os" + + log "github.com/Sirupsen/logrus" +) + +type ExplicitProxy struct { + ExplicitProxyConfig + proxyHost string + proxyAuthorization string +} + +type ExplicitProxyConfig struct { + ListenAddress string +} + +func NewExplicitProxy(c ExplicitProxyConfig) *ExplicitProxy { + return &ExplicitProxy{ + ExplicitProxyConfig: c, + } +} + +func (s ExplicitProxy) Start() error { + u, err := url.Parse(os.Getenv("http_proxy")) + if err != nil { + return err + } + + // For HTTPS + s.proxyAuthorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(u.User.String())) + s.proxyHost = u.Host + + // For HTTP + http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(u)} + + handler := http.HandlerFunc(s.handleRequest) + + log.Infof("Explicit-Proxy: Start listener on %s", s.ListenAddress) + + go func() { + http.ListenAndServe(s.ListenAddress, handler) + }() + + return nil +} + +func (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Request) { + log.Infof("Explicit-Proxy: %s %s", r.Method, r.URL) + if r.Method == "CONNECT" { + handleHttps(s.proxyHost, s.proxyAuthorization, w, r) + } else { + handleHttp(w, r) + } +} + +func handleHttps(proxyHost, auth string, w http.ResponseWriter, r *http.Request) { + hj, _ := w.(http.Hijacker) + if proxyConn, err := net.Dial("tcp", proxyHost); err != nil { + log.Fatal(err) + } else if clientConn, _, err := hj.Hijack(); err != nil { + proxyConn.Close() + log.Fatal(err) + } else { + r.Header.Set("Proxy-Authorization", auth) + r.Write(proxyConn) + go func() { + io.Copy(clientConn, proxyConn) + proxyConn.Close() + }() + go func() { + io.Copy(proxyConn, clientConn) + clientConn.Close() + }() + } +} + +func handleHttp(w http.ResponseWriter, r *http.Request) { + hj, _ := w.(http.Hijacker) + client := &http.Client{} + r.RequestURI = "" + if resp, err := client.Do(r); err != nil { + log.Fatal(err) + } else if conn, _, err := hj.Hijack(); err != nil { + log.Fatal(err) + } else { + defer conn.Close() + resp.Write(conn) + } +} From 1c5bf8dd2f3c1449c9aeea6184710b4bec45e050 Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Wed, 15 Nov 2017 21:45:09 +0900 Subject: [PATCH 2/3] Use colog for logging and add access logging --- Gopkg.lock | 8 +++++- Gopkg.toml | 4 +++ build.sh | 2 +- cmd/transproxy/main.go | 55 +++++++++++++++++++++++------------------- common.go | 28 ++++++++++++--------- dns.go | 44 +++++++++++++++++++-------------- explicit.go | 32 +++++++++++++----------- http.go | 19 +++++++-------- https.go | 16 +++++++----- tcp.go | 10 +++++--- 10 files changed, 129 insertions(+), 89 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 5ef506b..c76a1c9 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -7,6 +7,12 @@ revision = "a3f95b5c423586578a4e099b11a46c2479628cac" version = "1.0.2" +[[projects]] + branch = "master" + name = "github.com/comail/colog" + packages = ["."] + revision = "fba8e7b1f46c3607f09760ce3880066e7ff57c5a" + [[projects]] name = "github.com/coreos/go-iptables" packages = ["iptables"] @@ -82,6 +88,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "05d316e0b36d8fefb78fc7458ea9e209dce1324b9ddfc9d51b6405b3a1f5bf15" + inputs-digest = "a6cdd7a2369c816715a35c467bacb3b374a17101de3b5405a2e0328533cc4722" solver-name = "gps-cdcl" solver-version = 1 diff --git a/Gopkg.toml b/Gopkg.toml index 4372e66..50fe27d 100644 --- a/Gopkg.toml +++ b/Gopkg.toml @@ -21,3 +21,7 @@ # version = "2.4.0" required = ["github.com/cybozu-go/cmd"] + +[[constraint]] + branch = "master" + name = "github.com/comail/colog" diff --git a/build.sh b/build.sh index 662fd82..7c48349 100755 --- a/build.sh +++ b/build.sh @@ -1,6 +1,6 @@ #!/bin/sh -VERSION=0.3 +VERSION=0.4 DIR=$(cd $(dirname $0); pwd) cd $DIR diff --git a/cmd/transproxy/main.go b/cmd/transproxy/main.go index d795516..3323484 100644 --- a/cmd/transproxy/main.go +++ b/cmd/transproxy/main.go @@ -3,6 +3,7 @@ package main import ( "flag" "fmt" + "log" "math/rand" "net" "os" @@ -13,7 +14,7 @@ import ( "syscall" "time" - log "github.com/Sirupsen/logrus" + "github.com/comail/colog" transproxy "github.com/wadahiro/go-transproxy" ) @@ -89,16 +90,20 @@ func main() { // seed the global random number generator, used in secureoperator rand.Seed(time.Now().UTC().UnixNano()) - level, err := log.ParseLevel(*loglevel) + // setup logger + colog.SetDefaultLevel(colog.LDebug) + colog.SetMinLevel(colog.LInfo) + level, err := colog.ParseLevel(*loglevel) if err != nil { - log.Fatalf("Invalid log level: %s", err.Error()) + log.Fatalf("alert: Invalid log level: %s", err.Error()) } - formatter := &log.TextFormatter{ - FullTimestamp: true, - DisableColors: true, - } - log.SetFormatter(formatter) - log.SetLevel(level) + colog.SetMinLevel(level) + colog.SetFormatter(&colog.StdFormatter{ + Colors: true, + Flag: log.Ldate | log.Ltime | log.Lmicroseconds, + }) + colog.ParseFields(true) + colog.Register() // handling no_proxy environment noProxy := os.Getenv("no_proxy") @@ -115,7 +120,7 @@ func main() { }, ) if err := tcpProxy.Start(); err != nil { - log.Fatalf(err.Error()) + log.Fatalf("alert: %s", err.Error()) } dnsProxy := transproxy.NewDNSProxy( @@ -137,11 +142,11 @@ func main() { transproxy.HTTPProxyConfig{ ListenAddress: *httpProxyListenAddress, NoProxy: np, - Verbose: level == log.DebugLevel, + Verbose: level == colog.LDebug, }, ) if err := httpProxy.Start(); err != nil { - log.Fatalf(err.Error()) + log.Fatalf("alert: %s", err.Error()) } httpsProxy := transproxy.NewHTTPSProxy( @@ -151,7 +156,7 @@ func main() { }, ) if err := httpsProxy.Start(); err != nil { - log.Fatalf(err.Error()) + log.Fatalf("alert: %s", err.Error()) } explicitProxy := transproxy.NewExplicitProxy( @@ -160,10 +165,10 @@ func main() { }, ) if err := explicitProxy.Start(); err != nil { - log.Fatalf(err.Error()) + log.Fatalf("alert: %s", err.Error()) } - log.Infoln("All proxy servers started.") + log.Printf("info: All proxy servers started.") dnsToPort := toPort(*dnsProxyListenAddress) httpToPort := toPort(*httpProxyListenAddress) @@ -185,12 +190,12 @@ func main() { PublicDNS: outgoingPublicDNS, }) if err != nil { - log.Fatalf("IPTables: %s", err.Error()) + log.Printf("alert: %s", err.Error()) } t.Start() - log.Infof(`IPTables: iptables rules inserted as follows. + log.Printf(`info: iptables rules inserted as follows. --- %s ---`, t.Show()) @@ -200,17 +205,17 @@ func main() { signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM) <-sig - log.Infoln("Proxy servers stopping.") + log.Printf("info: Proxy servers stopping.") // start shutdown process t.Stop() - log.Infoln("IPTables: iptables rules deleted.") + log.Printf("info: iptables rules deleted.") if dnsProxy != nil { dnsProxy.Stop() } - log.Infoln("go-transproxy exited.") + log.Printf("info: go-transproxy exited.") } func useDNSProxy() bool { @@ -223,16 +228,16 @@ func useDNSProxy() bool { func toPort(addr string) int { array := strings.Split(addr, ":") if len(array) != 2 { - log.Fatalf("Invalid address, no port: %s", addr) + log.Printf("alert: Invalid address, no port: %s", addr) } i, err := strconv.Atoi(array[1]) if err != nil { - log.Fatalf("Invalid address, the port isn't number: %s", addr) + log.Printf("alert: Invalid address, the port isn't number: %s", addr) } if i > 65535 || i < 0 { - log.Fatalf("Invalid address, the port must be an integer value in the range 0-65535: %s", addr) + log.Printf("alert: Invalid address, the port must be an integer value in the range 0-65535: %s", addr) } return i @@ -246,11 +251,11 @@ func toPorts(ports string) []int { for _, v := range array { i, err := strconv.Atoi(v) if err != nil { - log.Fatalf("Invalid port, It's not number: %s", ports) + log.Printf("alert: Invalid port, It's not number: %s", ports) } if i > 65535 || i < 0 { - log.Fatalf("Invalid port, It must be an integer value in the range 0-65535: %s", ports) + log.Printf("alert: Invalid port, It must be an integer value in the range 0-65535: %s", ports) } p = append(p, i) diff --git a/common.go b/common.go index b3b632d..cd46f85 100644 --- a/common.go +++ b/common.go @@ -3,13 +3,13 @@ package transproxy import ( "fmt" "io" + "log" "net" "net/http" "net/url" "strings" "sync" - log "github.com/Sirupsen/logrus" "github.com/cybozu-go/netutil" "github.com/cybozu-go/transocks" ) @@ -28,7 +28,9 @@ func NewTCPListener(listenAddress string) (*TCPListener, error) { if err != nil { return nil, err } - return &TCPListener{l}, nil + return &TCPListener{ + Listener: l, + }, nil } func (l *TCPListener) Accept() (net.Conn, error) { @@ -47,23 +49,27 @@ func (l *TCPListener) Accept() (net.Conn, error) { 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("Error listening for tcp connections - %s", err.Error()) + log.Fatalf("alert: Error listening for tcp connections - %s", err.Error()) } for { conn, err := l.Accept() // wait here if err != nil { - log.Warnf("Error accepting new connection - %s", err.Error()) + log.Printf("warn: Error accepting new connection - %s", err.Error()) return } - log.Infoln("Accepted new connection") + log.Printf("debug: Accepted new connection") go func(conn net.Conn) { defer func() { @@ -86,7 +92,7 @@ var pool = sync.Pool{ func Pipe(srcConn *TCPConn, destConn net.Conn) { defer destConn.Close() - log.Debug("Start proxy") + log.Printf("debug: Start proxy") wg := &sync.WaitGroup{} @@ -124,7 +130,7 @@ func Pipe(srcConn *TCPConn, destConn net.Conn) { wg.Wait() - log.Debug("End proxy") + log.Printf("debug: End proxy") } type NoProxy struct { @@ -149,14 +155,14 @@ func useProxy(noProxy NoProxy, target string) bool { for _, d := range noProxy.Domains { if strings.HasSuffix(target, d) { - log.Infof("NO_PROXY: Matched no_proxy domain. Direct for %s", target) + log.Printf("debug: NO_PROXY: Matched no_proxy domain. Direct for %s", target) return false } } for _, ip := range noProxy.IPs { if ip == target { - log.Infof("NO_PROXY: Matched no_proxy ip. Direct for %s", target) + log.Printf("debug: NO_PROXY: Matched no_proxy ip. Direct for %s", target) return false } } @@ -164,11 +170,11 @@ func useProxy(noProxy NoProxy, target string) bool { for _, cidr := range noProxy.CIDRs { targetIP := net.ParseIP(target) if cidr.Contains(targetIP) { - log.Infof("NO_PROXY: Matched no_proxy cidr. Direct for %s", target) + log.Printf("debug: NO_PROXY: Matched no_proxy cidr. Direct for %s", target) return false } } - log.Infof("Use proxy for %s", target) + log.Printf("debug: Use proxy for %s", target) return true } diff --git a/dns.go b/dns.go index 153933d..ec57d13 100644 --- a/dns.go +++ b/dns.go @@ -1,12 +1,13 @@ package transproxy import ( + "log" "net" "strings" "sync" "time" - log "github.com/Sirupsen/logrus" + "github.com/Sirupsen/logrus" secop "github.com/fardog/secureoperator" "github.com/miekg/dns" ) @@ -33,6 +34,8 @@ type DNSProxyConfig struct { } func NewDNSProxy(c DNSProxyConfig) *DNSProxy { + // Suppress standard logger for secureoperator + logrus.SetLevel(logrus.ErrorLevel) // fix dns address if c.PublicDNS != "" { @@ -78,19 +81,19 @@ func NewDNSProxy(c DNSProxyConfig) *DNSProxy { func (s *DNSProxy) Start() error { if !s.Enabled { - log.Infof("DNS-Proxy: Disabled") + log.Printf("debug: Disabled category='DNS-Proxy'") return nil } - log.Infof("DNS-Proxy: Start listener on %s", s.ListenAddress) + log.Printf("info: Start listener on %s category='DNS-Proxy'", s.ListenAddress) if s.DNSOverHTTPSEnabled { - log.Infof("DNS-Proxy: Use DNS-over-HTTPS service as public DNS") + log.Printf("info: Use DNS-over-HTTPS service as public DNS category='DNS-Proxy'") } if !s.DNSOverHTTPSEnabled && s.PublicDNS != "" { - log.Infof("DNS-Proxy: Use %s as public DNS", s.PublicDNS) + log.Printf("info: Use %s as public DNS category='DNS-Proxy'", s.PublicDNS) } if s.PrivateDNS != "" { - log.Infof("DNS-Proxy: Use %s as private DNS for %s domains", s.PrivateDNS, s.NoProxyDomains) + log.Printf("info: Use %s as private DNS for %s domains category='DNS-Proxy'", s.PrivateDNS, s.NoProxyDomains) } // Prepare external DNS handler @@ -99,7 +102,7 @@ func (s *DNSProxy) Start() error { }) if err != nil { - log.Fatal(err) + log.Fatal("alert: %s category='DNS-Proxy'", err) } options := &secop.HandlerOptions{} @@ -111,11 +114,16 @@ func (s *DNSProxy) Start() error { 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.Debugf("DNS-Proxy: Checking DNS route, request: %s, no_proxy: %s", req.Question[0].Name, domain) + 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.Debugf("DNS-Proxy: Matched! Routing to private DNS, request: %s, no_proxy: %s", 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 } @@ -153,12 +161,12 @@ func (s *DNSProxy) Start() error { go func() { if s.udpServer != nil { if err := s.udpServer.ListenAndServe(); err != nil { - log.Fatal(err.Error()) + log.Fatal("alert: %s", err.Error()) } } if s.tcpServer != nil { if err := s.tcpServer.ListenAndServe(); err != nil { - log.Fatal(err.Error()) + log.Fatal("alert: %s", err.Error()) } } }() @@ -167,12 +175,12 @@ func (s *DNSProxy) Start() error { } func (s *DNSProxy) handlePublic(w dns.ResponseWriter, req *dns.Msg) { - log.Debugf("DNS-Proxy: DNS request. %#v, %s", req, req) + 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.Warnf("DNS-Proxy: DNS Client failed. %s, %#v, %s", err.Error(), req, req) + log.Printf("warn: DNS Client failed. %s, %#v, %s", err.Error(), req, req) dns.HandleFailed(w, req) return } @@ -187,11 +195,11 @@ func (s *DNSProxy) handlePrivate(w dns.ResponseWriter, req *dns.Msg) { c = s.udpClient } - log.Debugf("DNS-Proxy: DNS request. %#v, %s", req, req) + log.Printf("debug: DNS request. %#v, %s", req, req) resp, _, err := c.Exchange(req, s.PrivateDNS) if err != nil { - log.Warnf("DNS-Proxy: DNS Client failed. %s, %#v, %s", err.Error(), req, req) + log.Printf("warn: DNS Client failed. %s, %#v, %s", err.Error(), req, req) dns.HandleFailed(w, req) return } @@ -203,17 +211,17 @@ func (s *DNSProxy) Stop() { return } - log.Infof("DNS-Proxy: Shutting down DNS service on interrupt\n") + log.Printf("info: Shutting down DNS service on interrupt\n") if s.udpServer != nil { if err := s.udpServer.Shutdown(); err != nil { - log.Error(err.Error()) + log.Printf("error: %s", err.Error()) } s.udpServer = nil } if s.tcpServer != nil { if err := s.tcpServer.Shutdown(); err != nil { - log.Error(err.Error()) + log.Printf("error: %s", err.Error()) } s.tcpServer = nil } diff --git a/explicit.go b/explicit.go index d28f4d1..a2152f5 100644 --- a/explicit.go +++ b/explicit.go @@ -3,16 +3,16 @@ package transproxy import ( "encoding/base64" "io" + "log" "net" "net/http" "net/url" "os" - - log "github.com/Sirupsen/logrus" ) type ExplicitProxy struct { ExplicitProxyConfig + user string proxyHost string proxyAuthorization string } @@ -32,6 +32,7 @@ func (s ExplicitProxy) Start() error { if err != nil { return err } + s.user = u.User.Username() // For HTTPS s.proxyAuthorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(u.User.String())) @@ -42,7 +43,7 @@ func (s ExplicitProxy) Start() error { handler := http.HandlerFunc(s.handleRequest) - log.Infof("Explicit-Proxy: Start listener on %s", s.ListenAddress) + log.Printf("info: Start listener on %s category='Explicit-Proxy'", s.ListenAddress) go func() { http.ListenAndServe(s.ListenAddress, handler) @@ -52,23 +53,26 @@ func (s ExplicitProxy) Start() error { } func (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Request) { - log.Infof("Explicit-Proxy: %s %s", r.Method, r.URL) + // access logging + host, _, _ := net.SplitHostPort(r.RemoteAddr) + log.Printf("info: category='Explicit-Proxy' remoteAddr='%s' method='%s' uri='%s'", host, r.Method, r.RequestURI) + if r.Method == "CONNECT" { - handleHttps(s.proxyHost, s.proxyAuthorization, w, r) + s.handleHttps(w, r) } else { - handleHttp(w, r) + s.handleHttp(w, r) } } -func handleHttps(proxyHost, auth string, w http.ResponseWriter, r *http.Request) { +func (s ExplicitProxy) handleHttps(w http.ResponseWriter, r *http.Request) { hj, _ := w.(http.Hijacker) - if proxyConn, err := net.Dial("tcp", proxyHost); err != nil { - log.Fatal(err) + if proxyConn, err := net.Dial("tcp", s.proxyHost); err != nil { + log.Printf("error: %s", err) } else if clientConn, _, err := hj.Hijack(); err != nil { proxyConn.Close() - log.Fatal(err) + log.Printf("error: %s", err) } else { - r.Header.Set("Proxy-Authorization", auth) + r.Header.Set("Proxy-Authorization", s.proxyAuthorization) r.Write(proxyConn) go func() { io.Copy(clientConn, proxyConn) @@ -81,14 +85,14 @@ func handleHttps(proxyHost, auth string, w http.ResponseWriter, r *http.Request) } } -func handleHttp(w http.ResponseWriter, r *http.Request) { +func (s ExplicitProxy) handleHttp(w http.ResponseWriter, r *http.Request) { hj, _ := w.(http.Hijacker) client := &http.Client{} r.RequestURI = "" if resp, err := client.Do(r); err != nil { - log.Fatal(err) + log.Printf("error: %s", err) } else if conn, _, err := hj.Hijack(); err != nil { - log.Fatal(err) + log.Printf("error: %s", err) } else { defer conn.Close() resp.Write(conn) diff --git a/http.go b/http.go index fea6ff5..17719bf 100644 --- a/http.go +++ b/http.go @@ -2,18 +2,13 @@ package transproxy import ( "fmt" + "log" + "net" "net/http" - log "github.com/Sirupsen/logrus" "github.com/elazarl/goproxy" ) -func orPanic(err error) { - if err != nil { - panic(err) - } -} - type HTTPProxy struct { HTTPProxyConfig } @@ -33,7 +28,7 @@ func NewHTTPProxy(c HTTPProxyConfig) *HTTPProxy { func (s HTTPProxy) Start() error { l, err := NewTCPListener(s.ListenAddress) if err != nil { - log.Fatalf("HTTP-Proxy: Error listening for tcp connections - %s", err.Error()) + log.Printf("error: Failed listening for tcp connections - %s category='HTTP-Proxy'", err.Error()) } proxy := goproxy.NewProxyHttpServer() @@ -41,7 +36,7 @@ func (s HTTPProxy) Start() error { proxy.Verbose = s.Verbose proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - log.Debugf("HTTP-Proxy: Accept: %s, %s", req.Host, req.URL) + 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") @@ -52,11 +47,15 @@ func (s HTTPProxy) Start() error { 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.Infof("HTTP-Proxy: Start listener on %s", s.ListenAddress) + log.Printf("info: Start listener on %s category='HTTP-Proxy'", s.ListenAddress) go func() { http.Serve(l, proxy) diff --git a/https.go b/https.go index ee12c08..ad26475 100644 --- a/https.go +++ b/https.go @@ -1,13 +1,13 @@ package transproxy import ( + "log" "net" "net/url" "os" "strings" "time" - log "github.com/Sirupsen/logrus" "github.com/inconshreveable/go-vhost" "golang.org/x/net/proxy" ) @@ -44,13 +44,13 @@ func (s HTTPSProxy) Start() error { npdialer := proxy.Direct - log.Infof("HTTPS-Proxy: Start listener on %s", s.ListenAddress) + 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.Errorf("HTTPS-Proxy: Error handling TLS connection - %s", err.Error()) + log.Printf("error: Failed handling TLS connection - %s", err.Error()) return } @@ -60,15 +60,19 @@ func (s HTTPSProxy) Start() error { origServer := tlsConn.Host() if origServer == "" { - log.Warn("HTTPS-Proxy: Cannot get SNI, so fallback using `SO_ORIGINAL_DST` or `IP6T_SO_ORIGINAL_DST`") + 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.Debugf("HTTPS-Proxy: SNI: %s", origServer) + 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]) { @@ -78,7 +82,7 @@ func (s HTTPSProxy) Start() error { } if err != nil { - log.Warnf("HTTPS-Proxy: Failed to connect to destination - %s", err.Error()) + log.Printf("warn: Failed to connect to destination - %s", err.Error()) return } diff --git a/tcp.go b/tcp.go index b566dd1..07a5d0a 100644 --- a/tcp.go +++ b/tcp.go @@ -1,13 +1,13 @@ package transproxy import ( + "log" "net" "net/url" "os" "strings" "time" - log "github.com/Sirupsen/logrus" "golang.org/x/net/proxy" ) @@ -45,10 +45,14 @@ func (s TCPProxy) Start() error { npdialer := proxy.Direct - log.Infof("TCP-Proxy: Start listener on %s", s.ListenAddress) + 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]) { @@ -59,7 +63,7 @@ func (s TCPProxy) Start() error { } if err != nil { - log.Errorf("TCP-Proxy: Failed to connect to destination - %s", err.Error()) + log.Printf("error: Failed to connect to destination - %s category='TCP-Proxy'", err.Error()) return } From 6f601e083d9f48f62bffd70b22ba7a343d59397e Mon Sep 17 00:00:00 2001 From: Hiroyuki Wada Date: Fri, 17 Nov 2017 11:54:39 +0900 Subject: [PATCH 3/3] Add proxy with no auth --- README.md | 7 ++++ cmd/transproxy/main.go | 19 +++++++++-- explicit.go | 75 +++++++++++++++++++++++++++++++++++------- 3 files changed, 87 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 01df2f8..18abf7b 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,9 @@ 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. @@ -49,6 +52,10 @@ Options: 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-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 diff --git a/cmd/transproxy/main.go b/cmd/transproxy/main.go index 3323484..0f7fc45 100644 --- a/cmd/transproxy/main.go +++ b/cmd/transproxy/main.go @@ -59,7 +59,11 @@ var ( ) explicitProxyListenAddress = fs.String( - "explicit-proxy-listen", ":3132", "Explicit Proxy listen address for HTTP/HTTPS, as `[host]:port`", + "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", ) dnsOverTCPDisabled = fs.Bool( @@ -159,9 +163,20 @@ func main() { log.Fatalf("alert: %s", err.Error()) } + 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, + ListenAddress: *explicitProxyListenAddress, + UseProxyAuthorization: false, }, ) if err := explicitProxy.Start(); err != nil { diff --git a/explicit.go b/explicit.go index a2152f5..1b0c4ea 100644 --- a/explicit.go +++ b/explicit.go @@ -8,17 +8,24 @@ import ( "net/http" "net/url" "os" + "strings" ) type ExplicitProxy struct { ExplicitProxyConfig - user string + user string + category string + // For HTTP + proxyTransport *http.Transport + proxyAuthTransport *http.Transport + // For HTTPS proxyHost string proxyAuthorization string } type ExplicitProxyConfig struct { - ListenAddress string + ListenAddress string + UseProxyAuthorization bool } func NewExplicitProxy(c ExplicitProxyConfig) *ExplicitProxy { @@ -34,16 +41,30 @@ func (s ExplicitProxy) Start() error { } s.user = u.User.Username() - // For HTTPS - s.proxyAuthorization = "Basic " + base64.StdEncoding.EncodeToString([]byte(u.User.String())) - s.proxyHost = u.Host + if s.UseProxyAuthorization { + s.category = "Explicit-Proxy(Auth)" - // For HTTP - http.DefaultTransport = &http.Transport{Proxy: http.ProxyURL(u)} + // 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='Explicit-Proxy'", s.ListenAddress) + log.Printf("info: Start listener on %s category='%s'", s.ListenAddress, s.category) go func() { http.ListenAndServe(s.ListenAddress, handler) @@ -54,8 +75,7 @@ func (s ExplicitProxy) Start() error { func (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Request) { // access logging - host, _, _ := net.SplitHostPort(r.RemoteAddr) - log.Printf("info: category='Explicit-Proxy' remoteAddr='%s' method='%s' uri='%s'", host, r.Method, r.RequestURI) + s.accessLog(r) if r.Method == "CONNECT" { s.handleHttps(w, r) @@ -64,6 +84,25 @@ func (s ExplicitProxy) handleRequest(w http.ResponseWriter, r *http.Request) { } } +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) { hj, _ := w.(http.Hijacker) if proxyConn, err := net.Dial("tcp", s.proxyHost); err != nil { @@ -72,7 +111,9 @@ func (s ExplicitProxy) handleHttps(w http.ResponseWriter, r *http.Request) { proxyConn.Close() log.Printf("error: %s", err) } else { - r.Header.Set("Proxy-Authorization", s.proxyAuthorization) + if s.UseProxyAuthorization { + r.Header.Set("Proxy-Authorization", s.proxyAuthorization) + } r.Write(proxyConn) go func() { io.Copy(clientConn, proxyConn) @@ -87,7 +128,17 @@ func (s ExplicitProxy) handleHttps(w http.ResponseWriter, r *http.Request) { func (s ExplicitProxy) handleHttp(w http.ResponseWriter, r *http.Request) { hj, _ := w.(http.Hijacker) - client := &http.Client{} + 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)