github.com/coreos/goproxy@v0.0.0-20190513173959-f8dc2d7ba04e/examples/goproxy-transparent/transparent.go (about)

     1  package main
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"flag"
     7  	"fmt"
     8  	"log"
     9  	"net"
    10  	"net/http"
    11  	"net/url"
    12  	"regexp"
    13  
    14  	"github.com/elazarl/goproxy"
    15  	"github.com/inconshreveable/go-vhost"
    16  )
    17  
    18  func orPanic(err error) {
    19  	if err != nil {
    20  		panic(err)
    21  	}
    22  }
    23  
    24  func main() {
    25  	verbose := flag.Bool("v", true, "should every proxy request be logged to stdout")
    26  	http_addr := flag.String("httpaddr", ":3129", "proxy http listen address")
    27  	https_addr := flag.String("httpsaddr", ":3128", "proxy https listen address")
    28  	flag.Parse()
    29  
    30  	proxy := goproxy.NewProxyHttpServer()
    31  	proxy.Verbose = *verbose
    32  	if proxy.Verbose {
    33  		log.Printf("Server starting up! - configured to listen on http interface %s and https interface %s", *http_addr, *https_addr)
    34  	}
    35  
    36  	proxy.NonproxyHandler = http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    37  		if req.Host == "" {
    38  			fmt.Fprintln(w, "Cannot handle requests without Host header, e.g., HTTP 1.0")
    39  			return
    40  		}
    41  		req.URL.Scheme = "http"
    42  		req.URL.Host = req.Host
    43  		proxy.ServeHTTP(w, req)
    44  	})
    45  	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*$"))).
    46  		HandleConnect(goproxy.AlwaysMitm)
    47  	proxy.OnRequest(goproxy.ReqHostMatches(regexp.MustCompile("^.*:80$"))).
    48  		HijackConnect(func(req *http.Request, client net.Conn, ctx *goproxy.ProxyCtx) {
    49  		defer func() {
    50  			if e := recover(); e != nil {
    51  				ctx.Logf("error connecting to remote: %v", e)
    52  				client.Write([]byte("HTTP/1.1 500 Cannot reach destination\r\n\r\n"))
    53  			}
    54  			client.Close()
    55  		}()
    56  		clientBuf := bufio.NewReadWriter(bufio.NewReader(client), bufio.NewWriter(client))
    57  		remote, err := connectDial(proxy, "tcp", req.URL.Host)
    58  		orPanic(err)
    59  		remoteBuf := bufio.NewReadWriter(bufio.NewReader(remote), bufio.NewWriter(remote))
    60  		for {
    61  			req, err := http.ReadRequest(clientBuf.Reader)
    62  			orPanic(err)
    63  			orPanic(req.Write(remoteBuf))
    64  			orPanic(remoteBuf.Flush())
    65  			resp, err := http.ReadResponse(remoteBuf.Reader, req)
    66  			orPanic(err)
    67  			orPanic(resp.Write(clientBuf.Writer))
    68  			orPanic(clientBuf.Flush())
    69  		}
    70  	})
    71  
    72  	go func() {
    73  		log.Fatalln(http.ListenAndServe(*http_addr, proxy))
    74  	}()
    75  
    76  	// listen to the TLS ClientHello but make it a CONNECT request instead
    77  	ln, err := net.Listen("tcp", *https_addr)
    78  	if err != nil {
    79  		log.Fatalf("Error listening for https connections - %v", err)
    80  	}
    81  	for {
    82  		c, err := ln.Accept()
    83  		if err != nil {
    84  			log.Printf("Error accepting new connection - %v", err)
    85  			continue
    86  		}
    87  		go func(c net.Conn) {
    88  			tlsConn, err := vhost.TLS(c)
    89  			if err != nil {
    90  				log.Printf("Error accepting new connection - %v", err)
    91  			}
    92  			if tlsConn.Host() == "" {
    93  				log.Printf("Cannot support non-SNI enabled clients")
    94  				return
    95  			}
    96  			connectReq := &http.Request{
    97  				Method: "CONNECT",
    98  				URL: &url.URL{
    99  					Opaque: tlsConn.Host(),
   100  					Host:   net.JoinHostPort(tlsConn.Host(), "443"),
   101  				},
   102  				Host:   tlsConn.Host(),
   103  				Header: make(http.Header),
   104  			}
   105  			resp := dumbResponseWriter{tlsConn}
   106  			proxy.ServeHTTP(resp, connectReq)
   107  		}(c)
   108  	}
   109  }
   110  
   111  // copied/converted from https.go
   112  func dial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
   113  	if proxy.Tr.Dial != nil {
   114  		return proxy.Tr.Dial(network, addr)
   115  	}
   116  	return net.Dial(network, addr)
   117  }
   118  
   119  // copied/converted from https.go
   120  func connectDial(proxy *goproxy.ProxyHttpServer, network, addr string) (c net.Conn, err error) {
   121  	if proxy.ConnectDial == nil {
   122  		return dial(proxy, network, addr)
   123  	}
   124  	return proxy.ConnectDial(network, addr)
   125  }
   126  
   127  type dumbResponseWriter struct {
   128  	net.Conn
   129  }
   130  
   131  func (dumb dumbResponseWriter) Header() http.Header {
   132  	panic("Header() should not be called on this ResponseWriter")
   133  }
   134  
   135  func (dumb dumbResponseWriter) Write(buf []byte) (int, error) {
   136  	if bytes.Equal(buf, []byte("HTTP/1.0 200 OK\r\n\r\n")) {
   137  		return len(buf), nil // throw away the HTTP OK response from the faux CONNECT request
   138  	}
   139  	return dumb.Conn.Write(buf)
   140  }
   141  
   142  func (dumb dumbResponseWriter) WriteHeader(code int) {
   143  	panic("WriteHeader() should not be called on this ResponseWriter")
   144  }
   145  
   146  func (dumb dumbResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
   147  	return dumb, bufio.NewReadWriter(bufio.NewReader(dumb), bufio.NewWriter(dumb)), nil
   148  }