├── .gitignore ├── .travis.yml ├── test ├── server │ └── server.go └── client │ ├── client.go │ └── conn_http.go ├── proxy_test.go ├── lazyconn.go ├── README.md ├── conn_impl_reads.go ├── conn_impl_writes.go ├── metrics.go ├── conn_impl_requests.go ├── request_strategy.go ├── conn_impl.go ├── conn_intf.go ├── LICENSE ├── conn_test.go └── proxy.go /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | 3 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: go 2 | 3 | go: 4 | - 1.4.1 5 | 6 | install: 7 | - go get -d -t -v ./... 8 | - go build -v ./... 9 | - go get golang.org/x/tools/cmd/cover 10 | - go get -v github.com/axw/gocov/gocov 11 | - go get -v github.com/mattn/goveralls 12 | 13 | script: 14 | - $HOME/gopath/bin/goveralls -v -service travis-ci github.com/getlantern/enproxy 15 | -------------------------------------------------------------------------------- /test/server/server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "os" 6 | 7 | "github.com/getlantern/enproxy" 8 | ) 9 | 10 | func main() { 11 | if len(os.Args) < 2 { 12 | log.Fatal("Usage: server ") 13 | } 14 | proxy := &enproxy.Proxy{} 15 | err := proxy.ListenAndServe(os.Args[1]) 16 | if err != nil { 17 | log.Fatalf("Unable to listen and serve: %s", err) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /proxy_test.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "net/http" 5 | "net/http/httptest" 6 | "testing" 7 | ) 8 | 9 | func TestCustomHeaders(t *testing.T) { 10 | 11 | proxy := &Proxy{} 12 | 13 | w := httptest.NewRecorder() 14 | 15 | req, err := http.NewRequest("GET", "http://example.com/foo", nil) 16 | if err != nil { 17 | t.Fatal(err) 18 | } 19 | 20 | xff := "7.7.7.7" 21 | req.Header.Set("X-Forwarded-For", xff) 22 | 23 | ipc := "US" 24 | req.Header.Set("Cf-Ipcountry", ipc) 25 | 26 | proxy.ServeHTTP(w, req) 27 | 28 | ip := w.Header().Get("Lantern-IP") 29 | country := w.Header().Get("Lantern-Country") 30 | 31 | log.Debugf("Testing IP: %v", ip) 32 | log.Debugf("Testing country: %v", country) 33 | if ip != xff { 34 | t.Fatalf("Unexpected ip: %v", ip) 35 | } 36 | if country != ipc { 37 | t.Fatalf("Unexpected country: %v", country) 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /lazyconn.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "sync" 7 | 8 | "github.com/getlantern/idletiming" 9 | ) 10 | 11 | // lazyConn is a lazily initializing conn that makes sure it is only initialized 12 | // once. Using these allows us to ensure that we only create one connection per 13 | // connection id, but to still support doing the Dial calls concurrently. 14 | type lazyConn struct { 15 | p *Proxy 16 | id string 17 | addr string 18 | hitEOF bool 19 | connOut net.Conn 20 | err error 21 | mutex sync.Mutex 22 | } 23 | 24 | func (p *Proxy) newLazyConn(id string, addr string) *lazyConn { 25 | return &lazyConn{ 26 | p: p, 27 | id: id, 28 | addr: addr, 29 | } 30 | } 31 | 32 | func (l *lazyConn) get() (conn net.Conn, err error) { 33 | l.mutex.Lock() 34 | defer l.mutex.Unlock() 35 | if l.err != nil { 36 | // If dial already resulted in an error, return that 37 | return nil, err 38 | } 39 | if l.connOut == nil { 40 | // Lazily dial out 41 | conn, err := l.p.Dial(l.addr) 42 | if err != nil { 43 | l.err = fmt.Errorf("Unable to dial out to %s: %s", l.addr, err) 44 | return nil, l.err 45 | } 46 | 47 | // Wrap the connection in an idle timing one 48 | l.connOut = idletiming.Conn(conn, l.p.IdleTimeout, func() { 49 | l.p.connMapMutex.Lock() 50 | defer l.p.connMapMutex.Unlock() 51 | delete(l.p.connMap, l.id) 52 | if err := conn.Close(); err != nil { 53 | log.Debugf("Unable to close connection: %v", err) 54 | } 55 | }) 56 | } 57 | 58 | return l.connOut, l.err 59 | } 60 | -------------------------------------------------------------------------------- /test/client/client.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "log" 6 | "net" 7 | "net/http" 8 | "net/http/httputil" 9 | "os" 10 | 11 | "github.com/getlantern/enproxy" 12 | ) 13 | 14 | func main() { 15 | if len(os.Args) < 2 { 16 | log.Fatal("Usage: client ") 17 | } 18 | enproxyConfig := &enproxy.Config{ 19 | DialProxy: func(addr string) (net.Conn, error) { 20 | return net.Dial("tcp", os.Args[2]) 21 | }, 22 | NewRequest: func(host string, method string, body io.Reader) (req *http.Request, err error) { 23 | if host == "" { 24 | host = os.Args[2] 25 | } 26 | return http.NewRequest(method, "http://"+host+"/", body) 27 | }, 28 | } 29 | httpServer := &http.Server{ 30 | Addr: os.Args[1], 31 | Handler: &ClientHandler{ 32 | ProxyAddr: os.Args[2], 33 | Config: enproxyConfig, 34 | ReverseProxy: &httputil.ReverseProxy{ 35 | Director: func(req *http.Request) { 36 | // do nothing 37 | }, 38 | Transport: &http.Transport{ 39 | Dial: func(network string, addr string) (net.Conn, error) { 40 | return enproxy.Dial(addr, enproxyConfig) 41 | }, 42 | }, 43 | }, 44 | }, 45 | } 46 | err := httpServer.ListenAndServe() 47 | if err != nil { 48 | log.Fatal(err) 49 | } 50 | } 51 | 52 | type ClientHandler struct { 53 | ProxyAddr string 54 | Config *enproxy.Config 55 | ReverseProxy *httputil.ReverseProxy 56 | } 57 | 58 | func (c *ClientHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 59 | if req.Method == "CONNECT" { 60 | c.Intercept(resp, req) 61 | } else { 62 | c.ReverseProxy.ServeHTTP(resp, req) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /test/client/conn_http.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "net/http" 8 | "strconv" 9 | 10 | "github.com/getlantern/enproxy" 11 | ) 12 | 13 | // Intercept intercepts a CONNECT request, hijacks the underlying client 14 | // connetion and starts piping the data over a new enproxy.Conn configured using 15 | // this Config. 16 | func (c *ClientHandler) Intercept(resp http.ResponseWriter, req *http.Request) { 17 | if req.Method != "CONNECT" { 18 | panic("Intercept used for non-CONNECT request!") 19 | } 20 | 21 | // Hijack underlying connection 22 | clientConn, _, err := resp.(http.Hijacker).Hijack() 23 | if err != nil { 24 | resp.WriteHeader(502) 25 | fmt.Fprintf(resp, "Unable to hijack connection: %s", err) 26 | return 27 | } 28 | defer clientConn.Close() 29 | 30 | addr := hostIncludingPort(req, 443) 31 | 32 | // Establish outbound connection 33 | connOut, err := enproxy.Dial(addr, c.Config) 34 | if err != nil { 35 | resp.WriteHeader(502) 36 | fmt.Fprintf(resp, "Unable to open enproxy connection: %s", err) 37 | return 38 | } 39 | defer connOut.Close() 40 | 41 | // Pipe data 42 | pipeData(clientConn, connOut, req) 43 | } 44 | 45 | // pipeData pipes data between the client and proxy connections. It's also 46 | // responsible for responding to the initial CONNECT request with a 200 OK. 47 | func pipeData(clientConn net.Conn, connOut net.Conn, req *http.Request) { 48 | // Start piping to proxy 49 | go io.Copy(connOut, clientConn) 50 | 51 | // Respond OK 52 | err := respondOK(clientConn, req) 53 | if err != nil { 54 | fmt.Printf("Unable to respond OK: %s", err) 55 | return 56 | } 57 | 58 | // Then start coyping from out to writer 59 | io.Copy(clientConn, connOut) 60 | } 61 | 62 | func respondOK(writer io.Writer, req *http.Request) error { 63 | defer req.Body.Close() 64 | resp := &http.Response{ 65 | StatusCode: 200, 66 | ProtoMajor: 1, 67 | ProtoMinor: 1, 68 | } 69 | return resp.Write(writer) 70 | } 71 | 72 | // hostIncludingPort extracts the host:port from a request. It fills in a 73 | // a default port if none was found in the request. 74 | func hostIncludingPort(req *http.Request, defaultPort int) string { 75 | _, port, err := net.SplitHostPort(req.Host) 76 | if port == "" || err != nil { 77 | return req.Host + ":" + strconv.Itoa(defaultPort) 78 | } else { 79 | return req.Host 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | enproxy [![Travis CI Status](https://travis-ci.org/getlantern/enproxy.svg?branch=master)](https://travis-ci.org/getlantern/enproxy) [![Coverage Status](https://coveralls.io/repos/getlantern/enproxy/badge.png)](https://coveralls.io/r/getlantern/enproxy) [![GoDoc](https://godoc.org/github.com/getlantern/enproxy?status.png)](http://godoc.org/github.com/getlantern/enproxy) 2 | ========== 3 | 4 | enproxy provides an implementation of net.Conn that sends and receives data to/ 5 | from a proxy using HTTP request/response pairs that encapsulate the data. This 6 | is useful when you need to tunnel arbitrary protocols over an HTTP proxy that 7 | doesn't support HTTP CONNECT. Content distribution networks are one example of 8 | such a proxy. 9 | 10 | To open such a connection: 11 | 12 | ```go 13 | conn := &enproxy.Conn{ 14 | Addr: addr, 15 | Config: &enproxy.Config{ 16 | DialProxy: func(addr string) (net.Conn, error) { 17 | // This opens a TCP connection to the proxy 18 | return net.Dial("tcp", proxyAddress) 19 | }, 20 | NewRequest: func(method string, body io.Reader) (req *http.Request, err error) { 21 | // This is called for every request from enproxy.Conn to the proxy 22 | return http.NewRequest(method, "http://"+proxyAddress+"/", body) 23 | }, 24 | }, 25 | } 26 | err := conn.Connect() 27 | if err == nil { 28 | // start using conn as any other net.Conn 29 | } 30 | ``` 31 | 32 | To start the corresponding proxy server: 33 | 34 | ```go 35 | proxy := &enproxy.Proxy{} 36 | err := proxy.ListenAndServe(proxyAddress) 37 | if err != nil { 38 | log.Fatalf("Unable to listen and serve: %s", err) 39 | } 40 | ``` 41 | 42 | ## Debugging 43 | 44 | enproxy allows tracing various global metrics about connections, which can be 45 | useful when debugging issues like file descriptor leaks. To enable this tracing, 46 | just set the environment variable `TRACE_CONN_STATE=true`. This will cause the 47 | program to output information like the below every 5 seconds: 48 | 49 | ``` 50 | ---- Enproxy Connections---- 51 | Open: 34 52 | Closing: 0 53 | Blocked on Closing: 0 54 | Blocked on Read: 33 55 | Reading: 33 56 | Reading Finishing: 0 57 | Blocked on Write: 0 58 | Writing: 33 59 | Selecting: 33 60 | Writing: 0 61 | Write Pipe Open: 0 62 | Request Pending: 0 63 | Submitting Req.: 0 64 | Processing Req.: 1 65 | Posting Req. Fin: 0 66 | Posting Resp: 0 67 | Dialing First: 0 68 | Redialing: 0 69 | Doing Write: 0 70 | Posting Response: 0 71 | Writing Empty: 0 72 | Finishing Body: 0 73 | Finishing: 0 74 | Requesting: 33 75 | Requesting Finishing: 0 76 | ``` -------------------------------------------------------------------------------- /conn_impl_reads.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net/http" 7 | ) 8 | 9 | // processReads processes read requests by polling the proxy with GET requests 10 | // and reading the data from the resulting response body 11 | func (c *conn) processReads() { 12 | increment(&reading) 13 | 14 | var resp *http.Response 15 | var proxyConn *connInfo 16 | var err error 17 | 18 | defer func() { 19 | increment(&readingFinishing) 20 | // be sure to close connection before close response body, 21 | // or it will continuously receives data until hit EOF, 22 | // which is a waste of bandwidth. 23 | if proxyConn != nil { 24 | if err := proxyConn.conn.Close(); err != nil { 25 | log.Debugf("Unable to close proxy connection: %v", err) 26 | } 27 | } 28 | if resp != nil { 29 | if err := resp.Body.Close(); err != nil { 30 | log.Debugf("Unable to close response body: %v", err) 31 | } 32 | } 33 | c.doneReadingCh <- true 34 | decrement(&readingFinishing) 35 | decrement(&reading) 36 | }() 37 | 38 | // Wait for connection and response from first write request so that we know 39 | // where to send read requests. 40 | initialResponse, more := <-c.initialResponseCh 41 | if !more { 42 | return 43 | } 44 | 45 | proxyHost := initialResponse.proxyHost 46 | proxyConn = initialResponse.proxyConn 47 | resp = initialResponse.resp 48 | 49 | mkerror := func(text string, err error) error { 50 | return fmt.Errorf("Dest: %s ProxyHost: %s %s: %s", c.addr, proxyHost, text, err) 51 | } 52 | 53 | for b := range c.readRequestsCh { 54 | if resp == nil { 55 | // Old response finished 56 | proxyConn, err = c.redialProxyIfNecessary(proxyConn) 57 | if err != nil { 58 | c.readResponsesCh <- rwResponse{0, mkerror("Unable to redial proxy", err)} 59 | return 60 | } 61 | 62 | resp, err = c.doRequest(proxyConn, proxyHost, OP_READ, nil) 63 | if err != nil { 64 | err = mkerror("Unable to issue read request", err) 65 | log.Error(err) 66 | c.readResponsesCh <- rwResponse{0, err} 67 | return 68 | } 69 | } 70 | 71 | n, err := resp.Body.Read(b) 72 | 73 | hitEOFUpstream := resp.Header.Get(X_ENPROXY_EOF) == "true" 74 | errToClient := err 75 | if err == io.EOF && !hitEOFUpstream { 76 | // The current response hit EOF, but we haven't hit EOF upstream 77 | // so suppress EOF to reader 78 | errToClient = nil 79 | } 80 | c.readResponsesCh <- rwResponse{n, errToClient} 81 | 82 | if err != nil { 83 | if err == io.EOF { 84 | // Current response is done 85 | if err := resp.Body.Close(); err != nil { 86 | log.Debugf("Unable to close response body: %v", err) 87 | } 88 | resp = nil 89 | if hitEOFUpstream { 90 | // True EOF, stop reading 91 | return 92 | } 93 | continue 94 | } else { 95 | log.Errorf("Error reading: %s", err) 96 | return 97 | } 98 | } 99 | } 100 | } 101 | 102 | // submitRead submits a read to the processReads goroutine, returning true if 103 | // the read was accepted or false if reads are no longer being accepted 104 | func (c *conn) submitRead(b []byte) bool { 105 | c.closingMutex.RLock() 106 | defer c.closingMutex.RUnlock() 107 | if c.closing { 108 | return false 109 | } else { 110 | increment(&blockedOnRead) 111 | c.readRequestsCh <- b 112 | return true 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /conn_impl_writes.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "time" 5 | ) 6 | 7 | var ( 8 | emptyBytes = []byte{} 9 | ) 10 | 11 | // processWrites processes write requests by writing them to the body of a POST 12 | // request. Note - processWrites doesn't actually send the POST requests, 13 | // that's handled by the processRequests goroutine. The reason that we do this 14 | // on a separate goroutine is that the call to Request.Write() blocks until the 15 | // body has finished, and of course the body is written to as a result of 16 | // processing writes, so we need 2 goroutines to allow us to continue to 17 | // accept writes and pipe these to the request body while actually sending that 18 | // request body to the server. 19 | func (c *conn) processWrites() { 20 | increment(&writing) 21 | 22 | defer c.finishWriting() 23 | 24 | firstRequest := true 25 | hasWritten := false 26 | 27 | for { 28 | increment(&writingSelecting) 29 | select { 30 | case b, more := <-c.writeRequestsCh: 31 | decrement(&writingSelecting) 32 | 33 | if !more { 34 | return 35 | } 36 | hasWritten = true 37 | if !c.processWrite(b) { 38 | // There was a problem processing a write, stop 39 | return 40 | } 41 | case <-time.After(c.config.FlushTimeout): 42 | // We waited more than FlushTimeout for a write, finish our request 43 | decrement(&writingSelecting) 44 | 45 | if firstRequest && !hasWritten { 46 | // Write empty data just so that we can get a response and get 47 | // on with reading. 48 | // TODO: it might be more efficient to instead start by reading, 49 | // but that's a fairly big structural change on client and 50 | // server. 51 | increment(&writingWritingEmpty) 52 | if _, err := c.rs.write(emptyBytes); err != nil { 53 | log.Debugf("Unable to write to connection: %v", err) 54 | } 55 | decrement(&writingWritingEmpty) 56 | } 57 | 58 | increment(&writingFinishingBody) 59 | if err := c.rs.finishBody(); err != nil { 60 | log.Debugf("Unable to write connection finishing body: %v", err) 61 | } 62 | decrement(&writingFinishingBody) 63 | 64 | firstRequest = false 65 | } 66 | } 67 | } 68 | 69 | // processWrite processes a single write request, encapsulated in the body of a 70 | // POST request to the proxy. It uses the configured requestStrategy to process 71 | // the request. It returns true if the write was successful. 72 | func (c *conn) processWrite(b []byte) bool { 73 | increment(&writingWriting) 74 | n, err := c.rs.write(b) 75 | decrement(&writingWriting) 76 | 77 | increment(&writingPostingResponse) 78 | c.writeResponsesCh <- rwResponse{n, err} 79 | decrement(&writingPostingResponse) 80 | 81 | return err == nil 82 | } 83 | 84 | // submitWrite submits a write to the processWrites goroutine, returning true if 85 | // the write was accepted or false if writes are no longer being accepted 86 | func (c *conn) submitWrite(b []byte) bool { 87 | c.closingMutex.RLock() 88 | defer c.closingMutex.RUnlock() 89 | if c.closing { 90 | return false 91 | } else { 92 | increment(&blockedOnWrite) 93 | c.writeRequestsCh <- b 94 | return true 95 | } 96 | } 97 | 98 | func (c *conn) finishWriting() { 99 | increment(&writingFinishing) 100 | if c.rs != nil { 101 | if err := c.rs.finishBody(); err != nil { 102 | log.Debugf("Unable to write connection finishing body: %v", err) 103 | } 104 | } 105 | close(c.requestOutCh) 106 | c.doneWritingCh <- true 107 | decrement(&writingFinishing) 108 | decrement(&writing) 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /metrics.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "os" 5 | "strconv" 6 | "sync/atomic" 7 | "time" 8 | ) 9 | 10 | var ( 11 | // Connection metrics 12 | open = int32(0) 13 | reading = int32(0) 14 | readingFinishing = int32(0) 15 | blockedOnRead = int32(0) 16 | writing = int32(0) 17 | writingSelecting = int32(0) 18 | writingWriting = int32(0) 19 | writePipeOpen = int32(0) 20 | writingRequestPending = int32(0) 21 | writingSubmittingRequest = int32(0) 22 | writingProcessingRequest = int32(0) 23 | writingProcessingRequestPostingRequestFinished = int32(0) 24 | writingProcessingRequestPostingResponse = int32(0) 25 | writingProcessingRequestDialingFirst = int32(0) 26 | writingProcessingRequestRedialing = int32(0) 27 | writingDoingWrite = int32(0) 28 | writingWritingEmpty = int32(0) 29 | writingFinishingBody = int32(0) 30 | writingPostingResponse = int32(0) 31 | writingFinishing = int32(0) 32 | blockedOnWrite = int32(0) 33 | requesting = int32(0) 34 | requestingFinishing = int32(0) 35 | closing = int32(0) 36 | blockedOnClosing = int32(0) 37 | ) 38 | 39 | func init() { 40 | traceOn, _ := strconv.ParseBool(os.Getenv("TRACE_CONN_STATE")) 41 | if !traceOn { 42 | return 43 | } 44 | 45 | go func() { 46 | for { 47 | time.Sleep(5 * time.Second) 48 | DumpConnTrace() 49 | } 50 | }() 51 | } 52 | 53 | // DumpConnTrace dumps connection tracing information to the debug log. 54 | func DumpConnTrace() { 55 | log.Debugf( 56 | ` 57 | ---- Enproxy Connections---- 58 | Open: %4d 59 | Closing: %4d 60 | Blocked on Closing: %4d 61 | Blocked on Read: %4d 62 | Reading: %4d 63 | Reading Finishing: %4d 64 | Blocked on Write: %4d 65 | Writing: %4d 66 | Selecting: %4d 67 | Writing: %4d 68 | Write Pipe Open: %4d 69 | Request Pending: %4d 70 | Submitting Req.: %4d 71 | Processing Req.: %4d 72 | Posting Req. Fin: %4d 73 | Posting Resp: %4d 74 | Dialing First: %4d 75 | Redialing: %4d 76 | Doing Write: %4d 77 | Posting Response: %4d 78 | Writing Empty: %4d 79 | Finishing Body: %4d 80 | Finishing: %4d 81 | Requesting: %4d 82 | Requesting Finishing: %4d 83 | `, atomic.LoadInt32(&open), 84 | atomic.LoadInt32(&closing), 85 | atomic.LoadInt32(&blockedOnClosing), 86 | atomic.LoadInt32(&blockedOnRead), 87 | atomic.LoadInt32(&reading), 88 | atomic.LoadInt32(&readingFinishing), 89 | atomic.LoadInt32(&blockedOnWrite), 90 | atomic.LoadInt32(&writing), 91 | atomic.LoadInt32(&writingSelecting), 92 | atomic.LoadInt32(&writingWriting), 93 | atomic.LoadInt32(&writePipeOpen), 94 | atomic.LoadInt32(&writingRequestPending), 95 | atomic.LoadInt32(&writingSubmittingRequest), 96 | atomic.LoadInt32(&writingProcessingRequest), 97 | atomic.LoadInt32(&writingProcessingRequestPostingRequestFinished), 98 | atomic.LoadInt32(&writingProcessingRequestPostingResponse), 99 | atomic.LoadInt32(&writingProcessingRequestDialingFirst), 100 | atomic.LoadInt32(&writingProcessingRequestRedialing), 101 | atomic.LoadInt32(&writingDoingWrite), 102 | atomic.LoadInt32(&writingPostingResponse), 103 | atomic.LoadInt32(&writingWritingEmpty), 104 | atomic.LoadInt32(&writingFinishingBody), 105 | atomic.LoadInt32(&writingFinishing), 106 | atomic.LoadInt32(&requesting), 107 | atomic.LoadInt32(&requestingFinishing), 108 | ) 109 | } 110 | 111 | // Increment a metric 112 | func increment(val *int32) { 113 | atomic.AddInt32(val, 1) 114 | } 115 | 116 | // Decrement a metric 117 | func decrement(val *int32) { 118 | atomic.AddInt32(val, -1) 119 | } 120 | -------------------------------------------------------------------------------- /conn_impl_requests.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "fmt" 5 | "net/http" 6 | ) 7 | 8 | // processRequests handles writing outbound requests to the proxy. Note - this 9 | // is not pipelined, because we cannot be sure that intervening proxies will 10 | // deliver requests to the enproxy server in order. In-order delivery is 11 | // required because we are encapsulating a stream of data inside the bodies of 12 | // successive requests. 13 | func (c *conn) processRequests(proxyConn *connInfo) { 14 | increment(&requesting) 15 | 16 | var resp *http.Response 17 | 18 | first := true 19 | defer c.finishRequesting(resp, first) 20 | 21 | defer func() { 22 | // If there's a proxyConn at the time that processRequests() exits, 23 | // close it. 24 | if !first && proxyConn != nil { 25 | if err := proxyConn.conn.Close(); err != nil { 26 | log.Debugf("Unable to close proxy connection: %v", err) 27 | } 28 | } 29 | }() 30 | 31 | var err error 32 | var proxyHost string 33 | 34 | mkerror := func(text string, err error) error { 35 | return fmt.Errorf("Dest: %s ProxyHost: %s %s: %s", c.addr, proxyHost, text, err) 36 | } 37 | 38 | for request := range c.requestOutCh { 39 | decrement(&writingRequestPending) 40 | increment(&writingProcessingRequestRedialing) 41 | proxyConn, err = c.redialProxyIfNecessary(proxyConn) 42 | decrement(&writingProcessingRequestRedialing) 43 | if err != nil { 44 | c.fail(mkerror("Unable to redial proxy", err)) 45 | return 46 | } 47 | 48 | // Then issue new request 49 | increment(&writingProcessingRequest) 50 | resp, err = c.doRequest(proxyConn, proxyHost, OP_WRITE, request) 51 | decrement(&writingProcessingRequest) 52 | log.Debugf("Issued write request with result: %v", err) 53 | increment(&writingProcessingRequestPostingRequestFinished) 54 | c.requestFinishedCh <- err 55 | decrement(&writingProcessingRequestPostingRequestFinished) 56 | if err != nil { 57 | c.fail(mkerror("Unable to issue write request", err)) 58 | return 59 | } 60 | 61 | if !first { 62 | if err := resp.Body.Close(); err != nil { 63 | log.Debugf("Unable to close response body: %v", err) 64 | } 65 | } else { 66 | // On our first request, find out what host we're actually 67 | // talking to and remember that for future requests. 68 | proxyHost = resp.Header.Get(X_ENPROXY_PROXY_HOST) 69 | if c.config.OnFirstResponse != nil { 70 | c.config.OnFirstResponse(resp) 71 | } 72 | 73 | // Also post it to initialResponseCh so that the processReads() 74 | // routine knows which proxyHost to use and gets the initial 75 | // response data 76 | increment(&writingProcessingRequestPostingResponse) 77 | c.initialResponseCh <- hostWithResponse{ 78 | proxyHost: proxyHost, 79 | proxyConn: proxyConn, 80 | resp: resp, 81 | } 82 | decrement(&writingProcessingRequestPostingResponse) 83 | 84 | first = false 85 | 86 | // Dial again because our old proxyConn is now being used by the 87 | // reader goroutine 88 | increment(&writingProcessingRequestDialingFirst) 89 | proxyConn, err = c.dialProxy() 90 | decrement(&writingProcessingRequestDialingFirst) 91 | if err != nil { 92 | c.fail(mkerror("Unable to dial proxy for 2nd request", err)) 93 | return 94 | } 95 | } 96 | } 97 | } 98 | 99 | // submitRequest submits a request to the processRequests goroutine, returning 100 | // true if the request was accepted or false if requests are no longer being 101 | // accepted 102 | func (c *conn) submitRequest(request *request) bool { 103 | c.closingMutex.RLock() 104 | defer c.closingMutex.RUnlock() 105 | if c.closing { 106 | return false 107 | } else { 108 | increment(&writingRequestPending) 109 | c.requestOutCh <- request 110 | return true 111 | } 112 | } 113 | 114 | func (c *conn) finishRequesting(resp *http.Response, first bool) { 115 | increment(&requestingFinishing) 116 | close(c.initialResponseCh) 117 | if !first && resp != nil { 118 | if err := resp.Body.Close(); err != nil { 119 | log.Debugf("Unable to close response body: %v", err) 120 | } 121 | } 122 | // Drain requestsOutCh 123 | for req := range c.requestOutCh { 124 | decrement(&writingRequestPending) 125 | if err := req.body.Close(); err != nil { 126 | log.Debugf("Unable to close request body: %v", err) 127 | } 128 | } 129 | c.doneRequestingCh <- true 130 | decrement(&requestingFinishing) 131 | decrement(&requesting) 132 | return 133 | } 134 | -------------------------------------------------------------------------------- /request_strategy.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | ) 7 | 8 | // request is an outgoing request to the upstream proxy 9 | type request struct { 10 | body io.ReadCloser 11 | length int 12 | } 13 | 14 | // requestStrategy encapsulates a strategy for making requests upstream (either 15 | // buffered or streaming) 16 | type requestStrategy interface { 17 | write(b []byte) (int, error) 18 | 19 | finishBody() error 20 | } 21 | 22 | // bufferingRequestStrategy is an implementation of requestStrategy that buffers 23 | // requests upstream. 24 | type bufferingRequestStrategy struct { 25 | c *conn 26 | currentBody []byte 27 | currentBytesWritten int 28 | } 29 | 30 | // streamingRequestStrategy is an implementation of requestStrategy that streams 31 | // requests upstream. 32 | type streamingRequestStrategy struct { 33 | c *conn 34 | writer *io.PipeWriter 35 | } 36 | 37 | // Writes the given buffer to the upstream proxy encapsulated in an HTTP 38 | // request. If b is bigger than bodySize (65K), then this will result in 39 | // multiple POST requests. 40 | func (brs *bufferingRequestStrategy) write(b []byte) (int, error) { 41 | // Consume writes as long as they keep coming in 42 | bytesWritten := 0 43 | 44 | // Copy from b into outbound body 45 | for { 46 | bytesRemaining := bodySize - brs.currentBytesWritten 47 | bytesToCopy := len(b) 48 | if bytesToCopy == 0 { 49 | break 50 | } else { 51 | if brs.currentBody == nil { 52 | brs.initBody() 53 | } 54 | dst := brs.currentBody[brs.currentBytesWritten:] 55 | if bytesToCopy <= bytesRemaining { 56 | // Copy the entire buffer to the destination 57 | copy(dst, b) 58 | brs.currentBytesWritten = brs.currentBytesWritten + bytesToCopy 59 | bytesWritten = bytesWritten + bytesToCopy 60 | break 61 | } else { 62 | // Copy as much as we can from the buffer to the destination 63 | copy(dst, b[:bytesRemaining]) 64 | // Set buffer to remaining bytes 65 | b = b[bytesRemaining:] 66 | brs.currentBytesWritten = brs.currentBytesWritten + bytesRemaining 67 | bytesWritten = bytesWritten + bytesRemaining 68 | // Write the body 69 | err := brs.finishBody() 70 | if err != nil { 71 | return 0, err 72 | } 73 | } 74 | } 75 | } 76 | 77 | if bodySize == brs.currentBytesWritten { 78 | // We've filled the body, write it 79 | err := brs.finishBody() 80 | if err != nil { 81 | return 0, err 82 | } 83 | } 84 | 85 | return bytesWritten, nil 86 | } 87 | 88 | // Writes the given buffer to the upstream proxy encapsulated in an HTTP 89 | // request. 90 | func (srs *streamingRequestStrategy) write(b []byte) (int, error) { 91 | if srs.writer == nil { 92 | // Lazily initialize our next request to the proxy 93 | // Construct a pipe for piping data to proxy 94 | reader, writer := io.Pipe() 95 | increment(&writePipeOpen) 96 | srs.writer = writer 97 | request := &request{ 98 | body: reader, 99 | length: 0, // forces chunked encoding 100 | } 101 | increment(&writingSubmittingRequest) 102 | if !srs.c.submitRequest(request) { 103 | decrement(&writingSubmittingRequest) 104 | return 0, io.EOF 105 | } 106 | decrement(&writingSubmittingRequest) 107 | go func() { 108 | // Drain the requestFinishedCh 109 | err := <-srs.c.requestFinishedCh 110 | if err := writer.Close(); err != nil { 111 | log.Debugf("Unable to close writer: %v", err) 112 | } 113 | if err != nil && err != io.EOF { 114 | srs.c.fail(err) 115 | } 116 | }() 117 | } 118 | 119 | increment(&writingDoingWrite) 120 | defer decrement(&writingDoingWrite) 121 | return srs.writer.Write(b) 122 | } 123 | 124 | func (brs *bufferingRequestStrategy) initBody() { 125 | brs.currentBody = make([]byte, bodySize) 126 | brs.currentBytesWritten = 0 127 | } 128 | 129 | func (brs *bufferingRequestStrategy) finishBody() error { 130 | if brs.currentBody == nil { 131 | return nil 132 | } 133 | 134 | body := brs.currentBody 135 | if brs.currentBytesWritten < len(brs.currentBody) { 136 | body = brs.currentBody[:brs.currentBytesWritten] 137 | } 138 | success := brs.c.submitRequest(&request{ 139 | body: &closer{bytes.NewReader(body)}, 140 | length: brs.currentBytesWritten, // forces identity encoding 141 | }) 142 | if success { 143 | err := <-brs.c.requestFinishedCh 144 | if err != nil { 145 | return err 146 | } 147 | } 148 | brs.currentBody = nil 149 | brs.currentBytesWritten = 0 150 | if !success { 151 | return io.EOF 152 | } 153 | 154 | return nil 155 | } 156 | 157 | func (srs *streamingRequestStrategy) finishBody() error { 158 | if srs.writer == nil { 159 | return nil 160 | } 161 | 162 | if err := srs.writer.Close(); err != nil { 163 | log.Debugf("Unable to close writer: %v", err) 164 | } 165 | srs.writer = nil 166 | decrement(&writePipeOpen) 167 | 168 | return nil 169 | } 170 | -------------------------------------------------------------------------------- /conn_impl.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "io" 7 | "net" 8 | "net/http" 9 | "net/http/httputil" 10 | 11 | "github.com/getlantern/idletiming" 12 | "github.com/pborman/uuid" 13 | ) 14 | 15 | // Dial creates a Conn, opens a connection to the proxy and starts processing 16 | // writes and reads on the Conn. 17 | // 18 | // addr: the host:port of the destination server that we're trying to reach 19 | // 20 | // config: configuration for this Conn 21 | func Dial(addr string, config *Config) (net.Conn, error) { 22 | c := &conn{ 23 | id: uuid.NewRandom().String(), 24 | addr: addr, 25 | config: config, 26 | } 27 | 28 | c.initDefaults() 29 | c.makeChannels() 30 | c.initRequestStrategy() 31 | 32 | // Dial proxy 33 | proxyConn, err := c.dialProxy() 34 | if err != nil { 35 | return nil, fmt.Errorf("Unable to dial proxy to %s: %s", addr, err) 36 | } 37 | 38 | go c.processWrites() 39 | go c.processReads() 40 | go c.processRequests(proxyConn) 41 | 42 | increment(&open) 43 | 44 | return idletiming.Conn(c, c.config.IdleTimeout, func() { 45 | log.Debugf("Proxy connection to %s via %s idle for %v, closing", addr, proxyConn.conn.RemoteAddr(), c.config.IdleTimeout) 46 | if err := c.Close(); err != nil { 47 | log.Debugf("Unable to close connection: %v", err) 48 | } 49 | // Close the initial proxyConn just in case 50 | if err := proxyConn.conn.Close(); err != nil { 51 | log.Debugf("Unable to close proxy connection: %v", err) 52 | } 53 | }), nil 54 | } 55 | 56 | func (c *conn) initDefaults() { 57 | if c.config.FlushTimeout == 0 { 58 | c.config.FlushTimeout = defaultWriteFlushTimeout 59 | } 60 | if c.config.IdleTimeout == 0 { 61 | c.config.IdleTimeout = defaultIdleTimeoutClient 62 | } 63 | } 64 | 65 | func (c *conn) makeChannels() { 66 | // All channels are buffered to prevent deadlocks 67 | c.initialResponseCh = make(chan hostWithResponse, 1) 68 | c.writeRequestsCh = make(chan []byte, 1) 69 | c.writeResponsesCh = make(chan rwResponse, 1) 70 | c.readRequestsCh = make(chan []byte, 1) 71 | c.readResponsesCh = make(chan rwResponse, 1) 72 | c.requestOutCh = make(chan *request, 1) 73 | c.requestFinishedCh = make(chan error, 1) 74 | 75 | // Buffered to depth 2 because we report async errors to the reading and 76 | // writing goroutines. 77 | c.asyncErrCh = make(chan error, 2) 78 | 79 | // Buffered so that even if conn.Close() hasn't been called, we can report 80 | // finished. 81 | c.doneWritingCh = make(chan bool, 1) 82 | c.doneReadingCh = make(chan bool, 1) 83 | c.doneRequestingCh = make(chan bool, 1) 84 | } 85 | 86 | func (c *conn) initRequestStrategy() { 87 | if c.config.BufferRequests { 88 | c.rs = &bufferingRequestStrategy{ 89 | c: c, 90 | } 91 | } else { 92 | c.rs = &streamingRequestStrategy{ 93 | c: c, 94 | } 95 | } 96 | } 97 | 98 | func (c *conn) dialProxy() (*connInfo, error) { 99 | conn, err := c.config.DialProxy(c.addr) 100 | if err != nil { 101 | msg := fmt.Errorf("Unable to dial proxy to %s: %s", c.addr, err) 102 | log.Debug(msg) 103 | return nil, msg 104 | } 105 | proxyConn := &connInfo{ 106 | bufReader: bufio.NewReader(conn), 107 | } 108 | proxyConn.conn = idletiming.Conn(conn, c.config.IdleTimeout, func() { 109 | // When the underlying connection times out, mark the connInfo closed 110 | proxyConn.closedMutex.Lock() 111 | defer proxyConn.closedMutex.Unlock() 112 | proxyConn.closed = true 113 | }) 114 | return proxyConn, nil 115 | } 116 | 117 | func (c *conn) redialProxyIfNecessary(proxyConn *connInfo) (*connInfo, error) { 118 | proxyConn.closedMutex.Lock() 119 | defer proxyConn.closedMutex.Unlock() 120 | if proxyConn.closed || proxyConn.conn.TimesOutIn() < oneSecond { 121 | if err := proxyConn.conn.Close(); err != nil { 122 | log.Debugf("Unable to close proxy connection: %v", err) 123 | } 124 | return c.dialProxy() 125 | } else { 126 | return proxyConn, nil 127 | } 128 | } 129 | 130 | func (c *conn) doRequest(proxyConn *connInfo, host string, op string, request *request) (resp *http.Response, err error) { 131 | var body io.Reader 132 | if request != nil { 133 | body = request.body 134 | } 135 | path := c.id + "/" + c.addr + "/" + op 136 | req, err := c.config.NewRequest(host, path, "POST", body) 137 | if err != nil { 138 | err = fmt.Errorf("Unable to construct request to %s via proxy %s: %s", c.addr, host, err) 139 | return 140 | } 141 | //req.Header.Set(X_ENPROXY_OP, op) 142 | // Always send our connection id 143 | //req.Header.Set(X_ENPROXY_ID, c.id) 144 | // Always send the address that we're trying to reach 145 | //req.Header.Set(X_ENPROXY_DEST_ADDR, c.addr) 146 | req.Header.Set("Content-type", "application/octet-stream") 147 | if request != nil && request.length > 0 { 148 | // Force identity encoding to appeas CDNs like Fastly that can't 149 | // handle chunked encoding on requests 150 | req.TransferEncoding = []string{"identity"} 151 | req.ContentLength = int64(request.length) 152 | } else { 153 | req.ContentLength = 0 154 | } 155 | 156 | err = req.Write(proxyConn.conn) 157 | if err != nil { 158 | err = fmt.Errorf("Error sending request to %s via proxy %s: %s", c.addr, host, err) 159 | return 160 | } 161 | 162 | resp, err = http.ReadResponse(proxyConn.bufReader, req) 163 | if err != nil { 164 | err = fmt.Errorf("Error reading response from proxy: %s", err) 165 | return 166 | } 167 | 168 | // Check response status 169 | responseOK := resp.StatusCode >= 200 && resp.StatusCode < 300 170 | if !responseOK { 171 | // This means we're getting something other than an OK response from the fronting provider 172 | // itself, which is odd. Try to log the entire response for easier debugging. 173 | full, er := httputil.DumpResponse(resp, true) 174 | if er == nil { 175 | err = fmt.Errorf("Bad response status for read from fronting provider: %s", string(full)) 176 | } else { 177 | log.Errorf("Could not dump response: %v", er) 178 | err = fmt.Errorf("Bad response status for read from fronting provider: %s", resp.Status) 179 | } 180 | if err := resp.Body.Close(); err != nil { 181 | log.Debugf("Unable to close response body: %v", err) 182 | } 183 | resp = nil 184 | } else { 185 | log.Debugf("Got OK from fronting provider") 186 | } 187 | 188 | return 189 | } 190 | 191 | type closer struct { 192 | io.Reader 193 | } 194 | 195 | func (r *closer) Close() error { 196 | return nil 197 | } 198 | -------------------------------------------------------------------------------- /conn_intf.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "bufio" 5 | "io" 6 | "net" 7 | "net/http" 8 | "sync" 9 | "time" 10 | 11 | "github.com/getlantern/golog" 12 | "github.com/getlantern/idletiming" 13 | ) 14 | 15 | const ( 16 | X_ENPROXY_ID = "X-Enproxy-Id" 17 | X_ENPROXY_DEST_ADDR = "X-Enproxy-Dest-Addr" 18 | X_ENPROXY_EOF = "X-Enproxy-EOF" 19 | X_ENPROXY_PROXY_HOST = "X-Enproxy-Proxy-Host" 20 | X_ENPROXY_OP = "X-Enproxy-Op" 21 | 22 | OP_WRITE = "write" 23 | OP_READ = "read" 24 | ) 25 | 26 | var ( 27 | log = golog.LoggerFor("enproxy") 28 | ) 29 | 30 | var ( 31 | defaultWriteFlushTimeout = 35 * time.Millisecond 32 | defaultReadFlushTimeout = 35 * time.Millisecond 33 | defaultIdleTimeoutClient = 30 * time.Second 34 | defaultIdleTimeoutServer = 70 * time.Second 35 | 36 | // closeChannelDepth: controls depth of channels used for close processing. 37 | // Doesn't need to be particularly big, as it's just used to prevent 38 | // deadlocks on multiple calls to Close(). 39 | closeChannelDepth = 20 40 | 41 | bodySize = 65536 // size of buffer used for request bodies 42 | 43 | oneSecond = 1 * time.Second 44 | ) 45 | 46 | // Conn is a net.Conn that tunnels its data via an httpconn.Proxy using HTTP 47 | // requests and responses. It assumes that streaming requests are not supported 48 | // by the underlying servers/proxies, and so uses a polling technique similar to 49 | // the one used by meek, but different in that data is not encoded as JSON. 50 | // https://trac.torproject.org/projects/tor/wiki/doc/AChildsGardenOfPluggableTransports#Undertheencryption. 51 | // 52 | // enproxy uses two parallel channels to send and receive data. One channel 53 | // handles writing data out by making sequential POST requests to the server 54 | // which encapsulate the outbound data in their request bodies, while the other 55 | // channel handles reading data by making GET requests and grabbing the data 56 | // encapsulated in the response bodies. 57 | // 58 | // Write Channel: 59 | // 60 | // 1. Accept writes, piping these to the proxy as the body of an http POST 61 | // 2. Continue to pipe the writes until the pause between consecutive writes 62 | // exceeds the IdleInterval, at which point we finish the request body. We 63 | // do this because it is assumed that intervening proxies (e.g. CloudFlare 64 | // CDN) do not allow streaming requests, so it is necessary to finish the 65 | // request for data to get flushed to the destination server. 66 | // 3. After receiving a response to the POST request, return to step 1 67 | // 68 | // Read Channel: 69 | // 70 | // 1. Accept reads, issuing a new GET request if one is not already ongoing 71 | // 2. Process read by grabbing data from the response to the GET request 72 | // 3. Continue to accept reads, grabbing these from the response of the 73 | // existing GET request 74 | // 4. Once the response to the GET request reaches EOF, return to step 1. This 75 | // will happen because the proxy periodically closes responses to make sure 76 | // intervening proxies don't time out. 77 | // 5. If a response is received with a special header indicating a true EOF 78 | // from the destination server, return EOF to the reader 79 | // 80 | type conn struct { 81 | // addr: the host:port of the destination server that we're trying to reach 82 | addr string 83 | 84 | // config: configuration of this Conn 85 | config *Config 86 | 87 | // initialResponseCh: Self-reported FQDN of the proxy serving this connection 88 | // plus initial response from proxy. 89 | // 90 | // This allows us to guarantee we reach the same server in subsequent 91 | // requests, even if it was initially reached through a FQDN that may 92 | // resolve to different IPs in different DNS lookups (e.g. as in DNS round 93 | // robin). 94 | initialResponseCh chan hostWithResponse 95 | 96 | // id: unique identifier for this connection. This is used by the Proxy to 97 | // associate requests from this connection to the corresponding outbound 98 | // connection on the Proxy side. It is populated using a type 4 UUID. 99 | id string 100 | 101 | /* Write processing */ 102 | writeRequestsCh chan []byte // requests to write 103 | writeResponsesCh chan rwResponse // responses for writes 104 | doneWritingCh chan bool 105 | rs requestStrategy 106 | 107 | /* Request processing (for writes) */ 108 | requestOutCh chan *request // channel for next outgoing request body 109 | requestFinishedCh chan error 110 | doneRequestingCh chan bool 111 | 112 | /* Read processing */ 113 | readRequestsCh chan []byte // requests to read 114 | readResponsesCh chan rwResponse // responses for reads 115 | doneReadingCh chan bool 116 | 117 | /* Fields for tracking error and closed status */ 118 | asyncErr error // error that occurred during asynchronous processing 119 | asyncErrMutex sync.RWMutex // mutex guarding asyncErr 120 | asyncErrCh chan error // channel used to interrupted any waiting reads/writes with an async error 121 | closing bool // whether or not this Conn is closing 122 | closingMutex sync.RWMutex // mutex controlling access to the closing flag 123 | 124 | /* Track current response */ 125 | resp *http.Response // the current response being used to read data 126 | } 127 | 128 | // Config configures a Conn 129 | type Config struct { 130 | // DialProxy: function to open a connection to the proxy 131 | DialProxy dialFunc 132 | 133 | // NewRequest: function to create a new request to the proxy 134 | NewRequest newRequestFunc 135 | 136 | // OnFirstResponse: optional callback that gets called on the first response 137 | // from the proxy. 138 | OnFirstResponse func(resp *http.Response) 139 | 140 | // FlushTimeout: how long to let writes idle before writing out a 141 | // request to the proxy. Defaults to 15 milliseconds. 142 | FlushTimeout time.Duration 143 | 144 | // IdleTimeout: how long to wait before closing an idle connection, defaults 145 | // to 30 seconds on the client and 70 seconds on the server proxy. 146 | // 147 | // For clients, the value should be set lower than the proxy's idle timeout 148 | // so that enproxy redials before the active connection is closed. The value 149 | // should be set higher than the maximum possible time between the proxy 150 | // receiving the last data from a request and the proxy returning the first 151 | // data of the response, otherwise the connection will be closed in the 152 | // middle of processing a request. 153 | IdleTimeout time.Duration 154 | 155 | // BufferRequests: if true, requests to the proxy will be buffered and sent 156 | // with identity encoding. If false, they'll be streamed with chunked 157 | // encoding. 158 | BufferRequests bool 159 | } 160 | 161 | // dialFunc is a function that dials an address (e.g. the upstream proxy) 162 | type dialFunc func(addr string) (net.Conn, error) 163 | 164 | // newRequestFunc is a function that builds a new request to the upstream proxy 165 | type newRequestFunc func(host, path, method string, body io.Reader) (*http.Request, error) 166 | 167 | // rwResponse is a response to a read or write 168 | type rwResponse struct { 169 | n int 170 | err error 171 | } 172 | 173 | type connInfo struct { 174 | conn *idletiming.IdleTimingConn 175 | bufReader *bufio.Reader 176 | closed bool 177 | closedMutex sync.Mutex 178 | } 179 | 180 | type hostWithResponse struct { 181 | proxyHost string 182 | proxyConn *connInfo 183 | resp *http.Response 184 | } 185 | 186 | // Write() implements the function from net.Conn 187 | func (c *conn) Write(b []byte) (n int, err error) { 188 | err = c.getAsyncErr() 189 | if err != nil { 190 | return 191 | } 192 | 193 | if c.submitWrite(b) { 194 | defer decrement(&blockedOnWrite) 195 | 196 | select { 197 | case res, ok := <-c.writeResponsesCh: 198 | if !ok { 199 | return 0, io.EOF 200 | } else { 201 | return res.n, res.err 202 | } 203 | case err := <-c.asyncErrCh: 204 | return 0, err 205 | } 206 | } else { 207 | return 0, io.EOF 208 | } 209 | } 210 | 211 | // Read() implements the function from net.Conn 212 | func (c *conn) Read(b []byte) (n int, err error) { 213 | err = c.getAsyncErr() 214 | if err != nil { 215 | return 216 | } 217 | 218 | if c.submitRead(b) { 219 | defer decrement(&blockedOnRead) 220 | 221 | select { 222 | case res, ok := <-c.readResponsesCh: 223 | if !ok { 224 | return 0, io.EOF 225 | } else { 226 | return res.n, res.err 227 | } 228 | case err := <-c.asyncErrCh: 229 | return 0, err 230 | } 231 | } else { 232 | return 0, io.EOF 233 | } 234 | } 235 | 236 | func (c *conn) fail(err error) { 237 | log.Debugf("Failing on %v", err) 238 | 239 | c.asyncErrMutex.Lock() 240 | if c.asyncErr != nil { 241 | c.asyncErr = err 242 | } 243 | c.asyncErrMutex.Unlock() 244 | 245 | // Let any waiting readers or writers know about the error 246 | for i := 0; i < 2; i++ { 247 | select { 248 | case c.asyncErrCh <- err: 249 | // submitted okay 250 | default: 251 | // channel full, continue 252 | } 253 | } 254 | 255 | go func() { 256 | if err := c.Close(); err != nil { 257 | log.Debugf("Unable to close connection: %v", err) 258 | } 259 | }() 260 | } 261 | 262 | func (c *conn) getAsyncErr() error { 263 | c.asyncErrMutex.RLock() 264 | err := c.asyncErr 265 | c.asyncErrMutex.RUnlock() 266 | return err 267 | } 268 | 269 | // Close() implements the function from net.Conn 270 | func (c *conn) Close() error { 271 | increment(&closing) 272 | defer decrement(&closing) 273 | 274 | c.closingMutex.Lock() 275 | wasClosing := c.closing 276 | c.closing = true 277 | c.closingMutex.Unlock() 278 | if !wasClosing { 279 | increment(&blockedOnClosing) 280 | close(c.writeRequestsCh) 281 | close(c.readRequestsCh) 282 | <-c.doneReadingCh 283 | <-c.doneWritingCh 284 | <-c.doneRequestingCh 285 | decrement(&blockedOnClosing) 286 | decrement(&open) 287 | } 288 | return nil 289 | } 290 | 291 | // LocalAddr() is not implemented 292 | func (c *conn) LocalAddr() net.Addr { 293 | panic("LocalAddr() not implemented") 294 | } 295 | 296 | // RemoteAddr() is not implemented 297 | func (c *conn) RemoteAddr() net.Addr { 298 | panic("RemoteAddr() not implemented") 299 | } 300 | 301 | // SetDeadline() is currently unimplemented. 302 | func (c *conn) SetDeadline(t time.Time) error { 303 | log.Tracef("SetDeadline not implemented") 304 | return nil 305 | } 306 | 307 | // SetReadDeadline() is currently unimplemented. 308 | func (c *conn) SetReadDeadline(t time.Time) error { 309 | log.Tracef("SetReadDeadline not implemented") 310 | return nil 311 | } 312 | 313 | // SetWriteDeadline() is currently unimplemented. 314 | func (c *conn) SetWriteDeadline(t time.Time) error { 315 | log.Tracef("SetWriteDeadline not implemented") 316 | return nil 317 | } 318 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2014 Brave New Software Project, Inc. 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /conn_test.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "crypto/tls" 7 | "io" 8 | "net" 9 | "net/http" 10 | "sync" 11 | "testing" 12 | "time" 13 | 14 | "github.com/getlantern/fdcount" 15 | "github.com/getlantern/keyman" 16 | "github.com/getlantern/testify/assert" 17 | . "github.com/getlantern/waitforserver" 18 | ) 19 | 20 | const ( 21 | TEXT = "Hello byte counting world" 22 | HR = "----------------------------" 23 | ) 24 | 25 | var ( 26 | pk *keyman.PrivateKey 27 | cert *keyman.Certificate 28 | 29 | proxyAddr = "" 30 | httpAddr = "" 31 | httpsAddr = "" 32 | bytesReceived = int64(0) 33 | bytesSent = int64(0) 34 | destsReceived = make(map[string]bool) 35 | destsSent = make(map[string]bool) 36 | statMutex sync.Mutex 37 | ) 38 | 39 | func TestPlainTextStreamingNoHostFn(t *testing.T) { 40 | doTestPlainText(false, false, t) 41 | } 42 | 43 | func TestPlainTextBufferedNoHostFn(t *testing.T) { 44 | doTestPlainText(true, false, t) 45 | } 46 | 47 | func TestPlainTextStreamingHostFn(t *testing.T) { 48 | doTestPlainText(false, true, t) 49 | } 50 | 51 | func TestPlainTextBufferedHostFn(t *testing.T) { 52 | doTestPlainText(true, true, t) 53 | } 54 | 55 | func TestTLSStreaming(t *testing.T) { 56 | doTestTLS(false, t) 57 | } 58 | 59 | func TestTLSBuffered(t *testing.T) { 60 | doTestTLS(true, t) 61 | } 62 | 63 | func TestBadStreaming(t *testing.T) { 64 | doTestBad(false, t) 65 | } 66 | 67 | func TestBadBuffered(t *testing.T) { 68 | doTestBad(true, t) 69 | } 70 | 71 | func TestIdle(t *testing.T) { 72 | idleTimeout := 100 * time.Millisecond 73 | 74 | _, counter, err := fdcount.Matching("TCP") 75 | if err != nil { 76 | t.Fatalf("Unable to get fdcount: %v", err) 77 | } 78 | 79 | _, err = Dial(httpAddr, &Config{ 80 | DialProxy: func(addr string) (net.Conn, error) { 81 | return net.Dial("tcp", proxyAddr) 82 | }, 83 | NewRequest: newRequest, 84 | IdleTimeout: idleTimeout, 85 | }) 86 | if assert.NoError(t, err, "Dialing should have succeeded") { 87 | time.Sleep(idleTimeout * 2) 88 | assert.NoError(t, counter.AssertDelta(2), "All file descriptors except the connection from proxy to destination site should have been closed") 89 | } 90 | } 91 | 92 | // This test stimulates a connection leak as seen in 93 | // https://github.com/getlantern/lantern/issues/2174. 94 | func TestHTTPRedirect(t *testing.T) { 95 | startProxy(t, false) 96 | 97 | client := &http.Client{ 98 | Transport: &http.Transport{ 99 | Dial: func(network, addr string) (net.Conn, error) { 100 | return Dial(addr, &Config{ 101 | DialProxy: func(addr string) (net.Conn, error) { 102 | return net.Dial("tcp", proxyAddr) 103 | }, 104 | NewRequest: newRequest, 105 | }) 106 | }, 107 | DisableKeepAlives: true, 108 | }, 109 | } 110 | 111 | _, counter, err := fdcount.Matching("TCP") 112 | if err != nil { 113 | t.Fatalf("Unable to get fdcount: %v", err) 114 | } 115 | 116 | resp, err := client.Head("http://www.facebook.com") 117 | if assert.NoError(t, err, "Head request to facebook should have succeeded") { 118 | if err := resp.Body.Close(); err != nil { 119 | log.Debugf("Unable to close response body: %v", err) 120 | } 121 | } 122 | 123 | assert.NoError(t, counter.AssertDelta(2), "All file descriptors except the connection from proxy to destination site should have been closed") 124 | } 125 | 126 | func doTestPlainText(buffered bool, useHostFn bool, t *testing.T) { 127 | var counter *fdcount.Counter 128 | var err error 129 | 130 | startServers(t, useHostFn) 131 | 132 | err = fdcount.WaitUntilNoneMatch("CLOSE_WAIT", 5*time.Second) 133 | if err != nil { 134 | t.Fatalf("Unable to wait until no more connections are in CLOSE_WAIT: %v", err) 135 | } 136 | 137 | _, counter, err = fdcount.Matching("TCP") 138 | if err != nil { 139 | t.Fatalf("Unable to get fdcount: %v", err) 140 | } 141 | 142 | var reportedHost string 143 | var reportedHostMutex sync.Mutex 144 | onResponse := func(resp *http.Response) { 145 | reportedHostMutex.Lock() 146 | reportedHost = resp.Header.Get(X_ENPROXY_PROXY_HOST) 147 | reportedHostMutex.Unlock() 148 | } 149 | 150 | conn, err := prepareConn(httpAddr, buffered, false, t, onResponse) 151 | if err != nil { 152 | t.Fatalf("Unable to prepareConn: %s", err) 153 | } 154 | defer func() { 155 | err := conn.Close() 156 | assert.Nil(t, err, "Closing conn should succeed") 157 | if !assert.NoError(t, counter.AssertDelta(2), "All file descriptors except the connection from proxy to destination site should have been closed") { 158 | DumpConnTrace() 159 | } 160 | }() 161 | 162 | doRequests(conn, t) 163 | 164 | assert.Equal(t, 208, bytesReceived, "Wrong number of bytes received") 165 | assert.Equal(t, 284, bytesSent, "Wrong number of bytes sent") 166 | assert.True(t, destsSent[httpAddr], "http address wasn't recorded as sent destination") 167 | assert.True(t, destsReceived[httpAddr], "http address wasn't recorded as received destination") 168 | 169 | reportedHostMutex.Lock() 170 | rh := reportedHost 171 | reportedHostMutex.Unlock() 172 | assert.Equal(t, "localhost", rh, "Didn't get correct reported host") 173 | } 174 | 175 | func doTestTLS(buffered bool, t *testing.T) { 176 | startServers(t, false) 177 | 178 | _, counter, err := fdcount.Matching("TCP") 179 | if err != nil { 180 | t.Fatalf("Unable to get fdcount: %v", err) 181 | } 182 | 183 | conn, err := prepareConn(httpsAddr, buffered, false, t, nil) 184 | if err != nil { 185 | t.Fatalf("Unable to prepareConn: %s", err) 186 | } 187 | 188 | tlsConn := tls.Client(conn, &tls.Config{ 189 | ServerName: "localhost", 190 | RootCAs: cert.PoolContainingCert(), 191 | }) 192 | defer func() { 193 | err := conn.Close() 194 | assert.Nil(t, err, "Closing conn should succeed") 195 | if !assert.NoError(t, counter.AssertDelta(2), "All file descriptors except the connection from proxy to destination site should have been closed") { 196 | DumpConnTrace() 197 | } 198 | }() 199 | 200 | err = tlsConn.Handshake() 201 | if err != nil { 202 | t.Fatalf("Unable to handshake: %s", err) 203 | } 204 | 205 | doRequests(tlsConn, t) 206 | 207 | assert.True(t, destsSent[httpsAddr], "https address wasn't recorded as sent destination") 208 | assert.True(t, destsReceived[httpsAddr], "https address wasn't recorded as received destination") 209 | } 210 | 211 | func doTestBad(buffered bool, t *testing.T) { 212 | startServers(t, false) 213 | 214 | conn, err := prepareConn(httpAddr, buffered, true, t, nil) 215 | if err == nil { 216 | defer func() { 217 | if err := conn.Close(); err != nil { 218 | log.Debugf("Unable to close connection: %v", err) 219 | } 220 | }() 221 | t.Error("Bad conn should have returned error on Connect()") 222 | } 223 | } 224 | 225 | func prepareConn(addr string, buffered bool, fail bool, t *testing.T, onResponse func(resp *http.Response)) (conn net.Conn, err error) { 226 | return Dial(addr, 227 | &Config{ 228 | DialProxy: func(addr string) (net.Conn, error) { 229 | proto := "tcp" 230 | if fail { 231 | proto = "fakebad" 232 | } 233 | return net.Dial(proto, proxyAddr) 234 | }, 235 | NewRequest: newRequest, 236 | BufferRequests: buffered, 237 | OnFirstResponse: onResponse, 238 | }) 239 | } 240 | 241 | func newRequest(host, path, method string, body io.Reader) (req *http.Request, err error) { 242 | return http.NewRequest(method, "http://"+proxyAddr+"/"+path+"/", body) 243 | } 244 | 245 | func doRequests(conn net.Conn, t *testing.T) { 246 | // Single request/response pair 247 | req := makeRequest(conn, t) 248 | readResponse(conn, req, t) 249 | 250 | // Consecutive request/response pairs 251 | req = makeRequest(conn, t) 252 | readResponse(conn, req, t) 253 | } 254 | 255 | func makeRequest(conn net.Conn, t *testing.T) *http.Request { 256 | req, err := http.NewRequest("GET", "http://www.google.com/humans.txt", nil) 257 | req.Header.Add("Testcdn", "Of course!") 258 | if err != nil { 259 | t.Fatalf("Unable to create request: %s", err) 260 | } 261 | 262 | go func() { 263 | err = req.Write(conn) 264 | if err != nil { 265 | t.Fatalf("Unable to write request: %s", err) 266 | } 267 | }() 268 | 269 | return req 270 | } 271 | 272 | func readResponse(conn net.Conn, req *http.Request, t *testing.T) { 273 | buffIn := bufio.NewReader(conn) 274 | resp, err := http.ReadResponse(buffIn, req) 275 | if err != nil { 276 | t.Fatalf("Unable to read response: %s", err) 277 | } 278 | 279 | buff := bytes.NewBuffer(nil) 280 | _, err = io.Copy(buff, resp.Body) 281 | if err != nil { 282 | t.Fatalf("Unable to read response body: %s", err) 283 | } 284 | text := string(buff.Bytes()) 285 | assert.Contains(t, text, TEXT, "Wrong text returned from server") 286 | } 287 | 288 | func startServers(t *testing.T, useHostFn bool) { 289 | startHttpServer(t) 290 | startHttpsServer(t) 291 | startProxy(t, useHostFn) 292 | } 293 | 294 | func startProxy(t *testing.T, useHostFn bool) { 295 | if proxyAddr != "" { 296 | statMutex.Lock() 297 | bytesReceived = 0 298 | bytesSent = 0 299 | destsReceived = make(map[string]bool) 300 | destsReceived = make(map[string]bool) 301 | statMutex.Unlock() 302 | return 303 | } 304 | 305 | l, err := net.Listen("tcp", "localhost:0") 306 | if err != nil { 307 | t.Fatalf("Proxy unable to listen: %v", err) 308 | } 309 | proxyAddr = l.Addr().String() 310 | 311 | var host string 312 | var hostFn func(*http.Request) string 313 | 314 | if useHostFn { 315 | hostFn = func(req *http.Request) string { 316 | if _, found := req.Header["Testcdn"]; found { 317 | return "localhost" 318 | } else { 319 | return "" 320 | } 321 | } 322 | } else { 323 | host = "localhost" 324 | } 325 | go func() { 326 | proxy := &Proxy{ 327 | OnBytesReceived: func(clientIp string, destAddr string, req *http.Request, bytes int64) { 328 | statMutex.Lock() 329 | bytesReceived += bytes 330 | destsReceived[destAddr] = true 331 | statMutex.Unlock() 332 | }, 333 | OnBytesSent: func(clientIp string, destAddr string, req *http.Request, bytes int64) { 334 | statMutex.Lock() 335 | bytesSent += bytes 336 | destsSent[destAddr] = true 337 | statMutex.Unlock() 338 | }, 339 | Host: host, 340 | HostFn: hostFn, 341 | } 342 | err := proxy.Serve(l) 343 | if err != nil { 344 | t.Fatalf("Proxy unable to serve: %s", err) 345 | } 346 | }() 347 | 348 | if err := WaitForServer("tcp", proxyAddr, 1*time.Second); err != nil { 349 | t.Fatal(err) 350 | } 351 | } 352 | 353 | func startHttpServer(t *testing.T) { 354 | if httpAddr != "" { 355 | return 356 | } 357 | 358 | l, err := net.Listen("tcp", "localhost:0") 359 | if err != nil { 360 | t.Fatalf("HTTP unable to listen: %v", err) 361 | } 362 | httpAddr = l.Addr().String() 363 | 364 | doStartServer(t, l) 365 | } 366 | 367 | func startHttpsServer(t *testing.T) { 368 | if httpsAddr != "" { 369 | return 370 | } 371 | 372 | var err error 373 | 374 | pk, err = keyman.GeneratePK(2048) 375 | if err != nil { 376 | t.Fatalf("Unable to generate key: %s", err) 377 | } 378 | 379 | // Generate self-signed certificate 380 | cert, err = pk.TLSCertificateFor(time.Now().Add(1*time.Hour), true, nil, "tlsdialer", "localhost") 381 | if err != nil { 382 | t.Fatalf("Unable to generate cert: %s", err) 383 | } 384 | 385 | keypair, err := tls.X509KeyPair(cert.PEMEncoded(), pk.PEMEncoded()) 386 | if err != nil { 387 | t.Fatalf("Unable to generate x509 key pair: %s", err) 388 | } 389 | 390 | l, err := tls.Listen("tcp", "localhost:0", &tls.Config{ 391 | Certificates: []tls.Certificate{keypair}, 392 | }) 393 | if err != nil { 394 | t.Fatalf("HTTP unable to listen: %v", err) 395 | } 396 | httpsAddr = l.Addr().String() 397 | 398 | doStartServer(t, l) 399 | } 400 | 401 | func doStartServer(t *testing.T, l net.Listener) { 402 | go func() { 403 | httpServer := &http.Server{ 404 | Handler: http.HandlerFunc(func(resp http.ResponseWriter, req *http.Request) { 405 | if _, err := resp.Write([]byte(TEXT)); err != nil { 406 | log.Debugf("Unable to write response: %v", err) 407 | } 408 | }), 409 | } 410 | err := httpServer.Serve(l) 411 | if err != nil { 412 | t.Fatalf("Unable to start http server: %s", err) 413 | } 414 | }() 415 | 416 | if err := WaitForServer("tcp", l.Addr().String(), 1*time.Second); err != nil { 417 | t.Fatal(err) 418 | } 419 | } 420 | -------------------------------------------------------------------------------- /proxy.go: -------------------------------------------------------------------------------- 1 | package enproxy 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "net" 7 | "net/http" 8 | "regexp" 9 | "strings" 10 | "sync" 11 | "time" 12 | ) 13 | 14 | const ( 15 | DEFAULT_BYTES_BEFORE_FLUSH = 1024768 16 | DEFAULT_READ_BUFFER_SIZE = 65536 17 | ) 18 | 19 | var ( 20 | r = regexp.MustCompile("/(.*)/(.*)/(.*)/") 21 | ) 22 | 23 | // Proxy is the server side to an enproxy.Client. Proxy implements the 24 | // http.Handler interface for plugging into an HTTP server, and it also 25 | // provides a convenience ListenAndServe() function for quickly starting up 26 | // a dedicated HTTP server using this Proxy as its handler. 27 | type Proxy struct { 28 | // Dial: function used to dial the destination server. If nil, a default 29 | // TCP dialer is used. 30 | Dial dialFunc 31 | 32 | // Host: (Deprecated; use HostFn instead) FQDN of this particular proxy. 33 | // Either this or HostFn is required if this server was originally reached 34 | // by DNS round robin. 35 | Host string 36 | 37 | // HostFn: given a http.Request, return the FQDN of this particular proxy, 38 | // hopefully through the same front. This is used to support multiple 39 | // domain fronts. Either this or Host is required if this server was 40 | // originally reached by DNS round robin. 41 | HostFn func(*http.Request) string 42 | 43 | // FlushTimeout: how long to let reads idle before writing out a 44 | // response to the client. Defaults to 35 milliseconds. 45 | FlushTimeout time.Duration 46 | 47 | // BytesBeforeFlush: how many bytes to read before flushing response to 48 | // client. Periodically flushing the response keeps the response buffer 49 | // from getting too big when processing big downloads. 50 | BytesBeforeFlush int 51 | 52 | // IdleTimeout: how long to wait before closing an idle connection, defaults 53 | // to 70 seconds 54 | IdleTimeout time.Duration 55 | 56 | // ReadBufferSize: size of read buffer in bytes 57 | ReadBufferSize int 58 | 59 | // OnBytesReceived is an optional callback for learning about bytes received 60 | // from a client 61 | OnBytesReceived statCallback 62 | 63 | // OnBytesSent is an optional callback for learning about bytes sent to a 64 | // client 65 | OnBytesSent statCallback 66 | 67 | // Allow: Optional function that checks whether the given request to the 68 | // given destAddr is allowed. If it is not allowed, this function should 69 | // return the HTTP error code and an error. 70 | Allow func(req *http.Request, destAddr string) (int, error) 71 | 72 | // connMap: map of outbound connections by their id 73 | connMap map[string]*lazyConn 74 | 75 | // connMapMutex: synchronizes access to connMap 76 | connMapMutex sync.RWMutex 77 | } 78 | 79 | // statCallback is a function for receiving stat information. 80 | // 81 | // clientIp: ip address of client 82 | // destAddr: the destination address to which we're proxying 83 | // req: the http.Request that's being served 84 | // countryCode: the country-code of the client (only available when using CloudFlare) 85 | // bytes: the number of bytes sent/received 86 | type statCallback func( 87 | clientIp string, 88 | destAddr string, 89 | req *http.Request, 90 | bytes int64) 91 | 92 | // Start() starts this proxy 93 | func (p *Proxy) Start() { 94 | if p.Dial == nil { 95 | p.Dial = func(addr string) (net.Conn, error) { 96 | return net.Dial("tcp", addr) 97 | } 98 | } 99 | if p.FlushTimeout == 0 { 100 | p.FlushTimeout = defaultReadFlushTimeout 101 | } 102 | if p.IdleTimeout == 0 { 103 | p.IdleTimeout = defaultIdleTimeoutServer 104 | } 105 | if p.ReadBufferSize == 0 { 106 | p.ReadBufferSize = DEFAULT_READ_BUFFER_SIZE 107 | } 108 | if p.BytesBeforeFlush == 0 { 109 | p.BytesBeforeFlush = DEFAULT_BYTES_BEFORE_FLUSH 110 | } 111 | p.connMap = make(map[string]*lazyConn) 112 | } 113 | 114 | // ListenAndServe: convenience function for quickly starting up a dedicated HTTP 115 | // server using this Proxy as its handler 116 | func (p *Proxy) ListenAndServe(addr string) error { 117 | l, err := net.Listen("tcp", addr) 118 | if err != nil { 119 | return fmt.Errorf("Unable to listen at %v: %v", addr, err) 120 | } 121 | return p.Serve(l) 122 | } 123 | 124 | // Serve: convenience function for quickly starting up a dedicated HTTP server 125 | // using this Proxy as its handler 126 | func (p *Proxy) Serve(l net.Listener) error { 127 | p.Start() 128 | httpServer := &http.Server{ 129 | Handler: p, 130 | ReadTimeout: 10 * time.Second, 131 | WriteTimeout: 10 * time.Second, 132 | } 133 | return httpServer.Serve(l) 134 | } 135 | 136 | func (p *Proxy) parseRequestPath(path string) (string, string, string, error) { 137 | log.Debugf("Path is %v", path) 138 | strs := r.FindStringSubmatch(path) 139 | if len(strs) < 4 { 140 | return "", "", "", fmt.Errorf("Unexpected request path: %v", path) 141 | } 142 | return strs[1], strs[2], strs[3], nil 143 | } 144 | 145 | func (p *Proxy) parseRequestProps(req *http.Request) (string, string, string, error) { 146 | // If it's a reasonably long path, it likely follows our new request URI format: 147 | // /X-Enproxy-Id/X-Enproxy-Dest-Addr/X-Enproxy-Op 148 | if len(req.URL.Path) > 5 { 149 | return p.parseRequestPath(req.URL.Path) 150 | } 151 | 152 | id := req.Header.Get(X_ENPROXY_ID) 153 | if id == "" { 154 | return "", "", "", fmt.Errorf("No id found in header %s", X_ENPROXY_ID) 155 | } 156 | 157 | addr := req.Header.Get(X_ENPROXY_DEST_ADDR) 158 | if addr == "" { 159 | return "", "", "", fmt.Errorf("No address found in header %s", X_ENPROXY_DEST_ADDR) 160 | } 161 | 162 | op := req.Header.Get(X_ENPROXY_OP) 163 | return id, addr, op, nil 164 | } 165 | 166 | // ServeHTTP: implements the http.Handler interface 167 | func (p *Proxy) ServeHTTP(resp http.ResponseWriter, req *http.Request) { 168 | resp.Header().Set("Lantern-IP", req.Header.Get("X-Forwarded-For")) 169 | resp.Header().Set("Lantern-Country", req.Header.Get("Cf-Ipcountry")) 170 | 171 | if req.Method == "HEAD" { 172 | // Just respond OK to HEAD requests (used for health checks) 173 | resp.WriteHeader(200) 174 | return 175 | } 176 | 177 | id, addr, op, er := p.parseRequestProps(req) 178 | if er != nil { 179 | respond(http.StatusBadRequest, resp, er.Error()) 180 | log.Errorf("Could not parse enproxy data: %v", er) 181 | return 182 | } 183 | log.Debugf("Parsed enproxy data id: %v, addr: %v, op: %v", id, addr, op) 184 | 185 | lc, isNew, err := p.getLazyConn(id, addr, req, resp) 186 | if err != nil { 187 | // Close the connection? 188 | return 189 | } 190 | connOut, err := lc.get() 191 | if err != nil { 192 | respond(http.StatusInternalServerError, resp, fmt.Sprintf("Unable to get outoing connection to destination server: %v", err)) 193 | return 194 | } 195 | 196 | if op == OP_WRITE { 197 | p.handleWrite(resp, req, lc, connOut, isNew) 198 | } else if op == OP_READ { 199 | p.handleRead(resp, req, lc, connOut, true) 200 | } else { 201 | respond(http.StatusInternalServerError, resp, fmt.Sprintf("Operation not supported: %v", op)) 202 | } 203 | } 204 | 205 | // handleWrite forwards the data from a POST to the outbound connection 206 | func (p *Proxy) handleWrite(resp http.ResponseWriter, req *http.Request, lc *lazyConn, connOut net.Conn, first bool) { 207 | // Pipe request 208 | n, err := io.Copy(connOut, req.Body) 209 | if p.OnBytesReceived != nil && n > 0 { 210 | clientIp := clientIpFor(req) 211 | if clientIp != "" { 212 | p.OnBytesReceived(clientIp, lc.addr, req, n) 213 | } 214 | } 215 | if err != nil && err != io.EOF { 216 | respond(http.StatusInternalServerError, resp, fmt.Sprintf("Unable to write to connOut: %s", err)) 217 | return 218 | } 219 | host := "" 220 | if p.HostFn != nil { 221 | host = p.HostFn(req) 222 | } 223 | // Falling back on deprecated mechanism for backwards compatibility 224 | if host == "" { 225 | host = p.Host 226 | } 227 | if host != "" { 228 | // Enable sticky routing (see the comment on HostFn above). 229 | resp.Header().Set(X_ENPROXY_PROXY_HOST, host) 230 | } 231 | if first { 232 | // On first write, immediately do some reading 233 | p.handleRead(resp, req, lc, connOut, false) 234 | } else { 235 | resp.WriteHeader(200) 236 | } 237 | } 238 | 239 | // handleRead streams the data from the outbound connection to the client as 240 | // a response body. If no data is read for more than FlushTimeout, then the 241 | // response is finished and client needs to make a new GET request. 242 | func (p *Proxy) handleRead(resp http.ResponseWriter, req *http.Request, lc *lazyConn, connOut net.Conn, waitForData bool) { 243 | if lc.hitEOF { 244 | // We hit EOF on the server while processing a previous request, 245 | // immediately return EOF to the client 246 | resp.Header().Set(X_ENPROXY_EOF, "true") 247 | // Echo back connection id (for debugging purposes) 248 | resp.Header().Set(X_ENPROXY_ID, lc.id) 249 | resp.WriteHeader(200) 250 | return 251 | } 252 | 253 | // Get clientIp for reporting stats 254 | clientIp := clientIpFor(req) 255 | 256 | b := make([]byte, p.ReadBufferSize) 257 | first := true 258 | haveRead := false 259 | bytesInBatch := 0 260 | lastReadTime := time.Now() 261 | for { 262 | readDeadline := time.Now().Add(p.FlushTimeout) 263 | if err := connOut.SetReadDeadline(readDeadline); err != nil { 264 | log.Debugf("Unable to set read deadline: %v", err) 265 | } 266 | 267 | // Read 268 | n, readErr := connOut.Read(b) 269 | if first { 270 | if readErr == io.EOF { 271 | // Reached EOF, tell client using a special header 272 | resp.Header().Set(X_ENPROXY_EOF, "true") 273 | } 274 | // Echo back connection id (for debugging purposes) 275 | resp.Header().Set(X_ENPROXY_ID, lc.id) 276 | // Always respond 200 OK 277 | resp.WriteHeader(200) 278 | first = false 279 | } 280 | 281 | // Write if necessary 282 | if n > 0 { 283 | if clientIp != "" && p.OnBytesSent != nil && n > 0 { 284 | p.OnBytesSent(clientIp, lc.addr, req, int64(n)) 285 | } 286 | 287 | haveRead = true 288 | lastReadTime = time.Now() 289 | bytesInBatch = bytesInBatch + n 290 | _, writeErr := resp.Write(b[:n]) 291 | if writeErr != nil { 292 | log.Errorf("Error writing to response: %s", writeErr) 293 | if err := connOut.Close(); err != nil { 294 | log.Debugf("Unable to close out connection: %v", err) 295 | } 296 | return 297 | } 298 | } 299 | 300 | // Inspect readErr to decide whether or not to continue reading 301 | if readErr != nil { 302 | switch e := readErr.(type) { 303 | case net.Error: 304 | if e.Timeout() { 305 | if n == 0 { 306 | // We didn't read anything, might be time to return to 307 | // client 308 | if !waitForData { 309 | // We're not supposed to wait for data, so just 310 | // return right away 311 | return 312 | } 313 | if haveRead { 314 | // We've read some data, so return right away so 315 | // that client doesn't have to wait 316 | return 317 | } 318 | } 319 | } else { 320 | return 321 | } 322 | default: 323 | if readErr == io.EOF { 324 | lc.hitEOF = true 325 | } else { 326 | log.Errorf("Unexpected error reading from upstream: %s", readErr) 327 | // TODO: probably want to close connOut right away 328 | } 329 | return 330 | } 331 | } 332 | 333 | if time.Now().Sub(lastReadTime) > 10*time.Second { 334 | // We've spent more than 10 seconds without reading, return so that 335 | // CloudFlare doesn't time us out 336 | // TODO: Fastly has much more configurable timeouts, might be able to bump this up 337 | return 338 | } 339 | 340 | if bytesInBatch > p.BytesBeforeFlush { 341 | // We've read a good chunk, flush the response to keep its buffer 342 | // from getting too big. 343 | resp.(http.Flusher).Flush() 344 | bytesInBatch = 0 345 | } 346 | } 347 | } 348 | 349 | // getLazyConn gets the lazyConn corresponding to the given id and addr, or 350 | // creates a new one and saves it to connMap. 351 | func (p *Proxy) getLazyConn(id string, addr string, req *http.Request, resp http.ResponseWriter) (l *lazyConn, isNew bool, err error) { 352 | p.connMapMutex.RLock() 353 | l = p.connMap[id] 354 | p.connMapMutex.RUnlock() 355 | if l != nil { 356 | return l, false, nil 357 | } 358 | return p.newOutgoingConn(id, addr, req, resp) 359 | } 360 | 361 | // newOutgoingConn creates a new outoing connection and stores it in the connection cache. 362 | func (p *Proxy) newOutgoingConn(id string, addr string, req *http.Request, resp http.ResponseWriter) (l *lazyConn, isNew bool, err error) { 363 | if p.Allow != nil { 364 | log.Trace("Checking if connection is allowed") 365 | code, err := p.Allow(req, addr) 366 | if err != nil { 367 | respond(code, resp, err.Error()) 368 | return nil, false, fmt.Errorf("Not allowed: %v", err) 369 | } 370 | } 371 | l = p.newLazyConn(id, addr) 372 | p.connMapMutex.Lock() 373 | p.connMap[id] = l 374 | p.connMapMutex.Unlock() 375 | return l, true, nil 376 | } 377 | 378 | func clientIpFor(req *http.Request) string { 379 | clientIp := req.Header.Get("X-Forwarded-For") 380 | if clientIp == "" { 381 | clientIp, _, err := net.SplitHostPort(req.RemoteAddr) 382 | if err != nil { 383 | log.Debugf("Unable to split RemoteAddr %v: %v", err) 384 | return "" 385 | } 386 | return clientIp 387 | } 388 | // clientIp may contain multiple ips, use the first 389 | ips := strings.Split(clientIp, ",") 390 | return strings.TrimSpace(ips[0]) 391 | } 392 | 393 | func respond(status int, resp http.ResponseWriter, msg string) { 394 | log.Errorf(msg) 395 | resp.WriteHeader(status) 396 | if _, err := resp.Write([]byte(msg)); err != nil { 397 | log.Debugf("Unable to write response: %v", err) 398 | } 399 | } 400 | --------------------------------------------------------------------------------