// initReverseProxy creates a reverse proxy that attempts to exit with any of // the dialers provided by the balancer. func (client *Client) initReverseProxy(bal *balancer.Balancer, dumpHeaders bool) { transport := &http.Transport{ // We disable keepalives because some servers pretend to support // keep-alives but close their connections immediately, which // causes an error inside ReverseProxy. This is not an issue // for HTTPS because the browser is responsible for handling // the problem, which browsers like Chrome and Firefox already // know to do. // // See https://code.google.com/p/go/issues/detail?id=4677 DisableKeepAlives: true, } // TODO: would be good to make this sensitive to QOS, which // right now is only respected for HTTPS connections. The // challenge is that ReverseProxy reuses connections for // different requests, so we might have to configure different // ReverseProxies for different QOS's or something like that. if runtime.GOOS == "android" || client.ProxyAll { transport.Dial = bal.Dial } else { transport.Dial = detour.Dialer(bal.Dial) } rp := &httputil.ReverseProxy{ Director: func(req *http.Request) { // do nothing }, Transport: withDumpHeaders( dumpHeaders, transport), // Set a FlushInterval to prevent overly aggressive buffering of // responses, which helps keep memory usage down FlushInterval: 250 * time.Millisecond, ErrorLog: log.AsStdLogger(), } if client.rpInitialized { log.Trace("Draining reverse proxy channel") <-client.rpCh } else { log.Trace("Creating reverse proxy channel") client.rpCh = make(chan *httputil.ReverseProxy, 1) } log.Trace("Publishing reverse proxy") client.rpCh <- rp // We don't need to protect client.rpInitialized from race conditions because // it's only accessed here in initReverseProxy, which always gets called // under Configure, which never gets called concurrently with itself. client.rpInitialized = true }
func main() { go func() { log.Println("Starting standard proxy at localhost:8081") http.ListenAndServe("localhost:8081", &httputil.ReverseProxy{ Director: func(req *http.Request) {}, }) }() log.Println("Starting detour proxy at localhost:8080") http.ListenAndServe("localhost:8080", &httputil.ReverseProxy{ Director: func(req *http.Request) {}, Transport: &http.Transport{ // This just detours to net.Dial, meaning that it doesn't accomplish any // unblocking, it's just here for performance testing. Dial: detour.Dialer(net.Dial), }, }) }
func (client *Client) proxiedDialer(orig func(network, addr string) (net.Conn, error)) func(network, addr string) (net.Conn, error) { detourDialer := detour.Dialer(orig) return func(network, addr string) (net.Conn, error) { var proxied func(network, addr string) (net.Conn, error) if client.ProxyAll() { proxied = orig } else { proxied = detourDialer } if isLanternSpecialDomain(addr) { rewritten := rewriteLanternSpecialDomain(addr) log.Tracef("Rewriting %v to %v", addr, rewritten) return net.Dial(network, rewritten) } return proxied(network, addr) } }
// intercept intercepts an HTTP CONNECT request, hijacks the underlying client // connetion and starts piping the data over a new net.Conn obtained from the // given dial function. func (client *Client) intercept(resp http.ResponseWriter, req *http.Request) { if req.Method != httpConnectMethod { panic("Intercept used for non-CONNECT request!") } var err error // Hijack underlying connection. var clientConn net.Conn if clientConn, _, err = resp.(http.Hijacker).Hijack(); err != nil { respondBadGateway(resp, fmt.Sprintf("Unable to hijack connection: %s", err)) return } defer clientConn.Close() addr := hostIncludingPort(req, 443) // Establish outbound connection. d := func(network, addr string) (net.Conn, error) { return client.getBalancer().DialQOS("tcp", addr, client.targetQOS(req)) } var connOut net.Conn if runtime.GOOS == "android" || client.ProxyAll { connOut, err = d("tcp", addr) } else { connOut, err = detour.Dialer(d)("tcp", addr) } if err != nil { respondBadGateway(clientConn, fmt.Sprintf("Unable to handle CONNECT request: %s", err)) return } defer connOut.Close() // Pipe data between the client and the proxy. pipeData(clientConn, connOut, req) }
// intercept intercepts an HTTP CONNECT request, hijacks the underlying client // connetion and starts piping the data over a new net.Conn obtained from the // given dial function. func (client *Client) intercept(resp http.ResponseWriter, req *http.Request) { if req.Method != httpConnectMethod { panic("Intercept used for non-CONNECT request!") } var err error var clientConn net.Conn var connOut net.Conn // Make sure of closing connections only once var closeOnce sync.Once // Force closing if EOF at the request half or error encountered. // A bit arbitrary, but it's rather rare now to use half closing // as a way to notify server. Most application closes both connections // after completed send / receive so that won't cause problem. closeConns := func() { if clientConn != nil { if err := clientConn.Close(); err != nil { log.Debugf("Error closing the out connection: %s", err) } } if connOut != nil { if err := connOut.Close(); err != nil { log.Debugf("Error closing the client connection: %s", err) } } } defer closeOnce.Do(closeConns) // Hijack underlying connection. if clientConn, _, err = resp.(http.Hijacker).Hijack(); err != nil { respondBadGateway(resp, fmt.Sprintf("Unable to hijack connection: %s", err)) return } // Respond OK as soon as possible, even if we don't have the outbound connection // established yet, to avoid timeouts on the client application success := make(chan bool, 1) go func() { if e := respondOK(clientConn, req); e != nil { log.Errorf("Unable to respond OK: %s", e) success <- false return } success <- true }() // Establish outbound connection. addr := hostIncludingPort(req, 443) d := func(network, addr string) (net.Conn, error) { return client.getBalancer().DialQOS("tcp", addr, client.targetQOS(req)) } if runtime.GOOS == "android" || client.ProxyAll { connOut, err = d("tcp", addr) } else { connOut, err = detour.Dialer(d)("tcp", addr) } if err != nil { log.Debugf("Could not dial %v", err) return } if <-success { // Pipe data between the client and the proxy. pipeData(clientConn, connOut, func() { closeOnce.Do(closeConns) }) } }