func (p *Proxy) handle(ctx *Context, conn net.Conn, brw *bufio.ReadWriter) error { log.Debugf("martian: waiting for request: %v", conn.RemoteAddr()) req, err := http.ReadRequest(brw.Reader) if err != nil { if isCloseable(err) { log.Debugf("martian: connection closed prematurely: %v", err) } else { log.Errorf("martian: failed to read request: %v", err) } // TODO: TCPConn.WriteClose() to avoid sending an RST to the client. return errClose } defer req.Body.Close() if h, pattern := p.mux.Handler(req); pattern != "" { defer brw.Flush() closing := req.Close || p.Closing() log.Infof("martian: intercepted configuration request: %s", req.URL) rw := newResponseWriter(brw, closing) defer rw.Close() h.ServeHTTP(rw, req) // Call WriteHeader to ensure a response is sent, since the handler isn't // required to call WriteHeader/Write. rw.WriteHeader(200) if closing { return errClose } return nil } ctx, err = withSession(ctx.Session()) if err != nil { log.Errorf("martian: failed to build new context: %v", err) return err } link(req, ctx) defer unlink(req) if tconn, ok := conn.(*tls.Conn); ok { ctx.Session().MarkSecure() cs := tconn.ConnectionState() req.TLS = &cs } req.URL.Scheme = "http" if ctx.Session().IsSecure() { log.Debugf("martian: forcing HTTPS inside secure session") req.URL.Scheme = "https" } req.RemoteAddr = conn.RemoteAddr().String() if req.URL.Host == "" { req.URL.Host = req.Host } log.Infof("martian: received request: %s", req.URL) if req.Method == "CONNECT" { if err := p.reqmod.ModifyRequest(req); err != nil { log.Errorf("martian: error modifying CONNECT request: %v", err) proxyutil.Warning(req.Header, err) } if p.mitm != nil { log.Debugf("martian: attempting MITM for connection: %s", req.Host) res := proxyutil.NewResponse(200, nil, req) if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying CONNECT response: %v", err) proxyutil.Warning(res.Header, err) } if err := res.Write(brw); err != nil { log.Errorf("martian: got error while writing response back to client: %v", err) } if err := brw.Flush(); err != nil { log.Errorf("martian: got error while flushing response back to client: %v", err) } log.Debugf("martian: completed MITM for connection: %s", req.Host) b := make([]byte, 1) if _, err := brw.Read(b); err != nil { log.Errorf("martian: error peeking message through CONNECT tunnel to determine type: %v", err) } // Drain all of the rest of the buffered data. buf := make([]byte, brw.Reader.Buffered()) brw.Read(buf) // 22 is the TLS handshake. // https://tools.ietf.org/html/rfc5246#section-6.2.1 if b[0] == 22 { // Prepend the previously read data to be read again by // http.ReadRequest. tlsconn := tls.Server(&peekedConn{conn, io.MultiReader(bytes.NewReader(b), bytes.NewReader(buf), conn)}, p.mitm.TLSForHost(req.Host)) if err := tlsconn.Handshake(); err != nil { p.mitm.HandshakeErrorCallback(req, err) return err } brw.Writer.Reset(tlsconn) brw.Reader.Reset(tlsconn) return p.handle(ctx, tlsconn, brw) } // Prepend the previously read data to be read again by http.ReadRequest. brw.Reader.Reset(io.MultiReader(bytes.NewReader(b), bytes.NewReader(buf), conn)) return p.handle(ctx, conn, brw) } log.Debugf("martian: attempting to establish CONNECT tunnel: %s", req.URL.Host) res, cconn, cerr := p.connect(req) if cerr != nil { log.Errorf("martian: failed to CONNECT: %v", err) res = proxyutil.NewResponse(502, nil, req) proxyutil.Warning(res.Header, cerr) if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying CONNECT response: %v", err) proxyutil.Warning(res.Header, err) } if err := res.Write(brw); err != nil { log.Errorf("martian: got error while writing response back to client: %v", err) } err := brw.Flush() if err != nil { log.Errorf("martian: got error while flushing response back to client: %v", err) } return err } defer res.Body.Close() defer cconn.Close() if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying CONNECT response: %v", err) proxyutil.Warning(res.Header, err) } if err := res.Write(brw); err != nil { log.Errorf("martian: got error while writing response back to client: %v", err) } if err := brw.Flush(); err != nil { log.Errorf("martian: got error while flushing response back to client: %v", err) } cbw := bufio.NewWriter(cconn) cbr := bufio.NewReader(cconn) defer cbw.Flush() copySync := func(w io.Writer, r io.Reader, donec chan<- bool) { if _, err := io.Copy(w, r); err != nil && err != io.EOF { log.Errorf("martian: failed to copy CONNECT tunnel: %v", err) } log.Debugf("martian: CONNECT tunnel finished copying") donec <- true } donec := make(chan bool, 2) go copySync(cbw, brw, donec) go copySync(brw, cbr, donec) log.Debugf("martian: established CONNECT tunnel, proxying traffic") <-donec <-donec log.Debugf("martian: closed CONNECT tunnel") return errClose } if err := p.reqmod.ModifyRequest(req); err != nil { log.Errorf("martian: error modifying request: %v", err) proxyutil.Warning(req.Header, err) } res, err := p.roundTrip(ctx, req) if err != nil { log.Errorf("martian: failed to round trip: %v", err) res = proxyutil.NewResponse(502, nil, req) proxyutil.Warning(res.Header, err) } defer res.Body.Close() if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying response: %v", err) proxyutil.Warning(res.Header, err) } var closing error if req.Close || res.Close || p.Closing() { log.Debugf("martian: received close request: %v", req.RemoteAddr) res.Close = true closing = errClose } log.Debugf("martian: sent response: %v", req.URL) if err := res.Write(brw); err != nil { log.Errorf("martian: got error while writing response back to client: %v", err) } if err := brw.Flush(); err != nil { log.Errorf("martian: got error while flushing response back to client: %v", err) } return closing }
func (p *Proxy) handle(ctx *session.Context, conn net.Conn, brw *bufio.ReadWriter) error { log.Debugf("martian: waiting for request: %v", conn.RemoteAddr()) req, err := http.ReadRequest(brw.Reader) if err != nil { if isCloseable(err) { log.Debugf("martian: connection closed prematurely: %v", err) } else { log.Errorf("martian: failed to read request: %v", err) } // TODO: TCPConn.WriteClose() to avoid sending an RST to the client. return errClose } defer req.Body.Close() if h, pattern := p.mux.Handler(req); pattern != "" { defer brw.Flush() closing := req.Close || p.Closing() log.Infof("martian: intercepted configuration request: %s", req.URL) rw := newResponseWriter(brw, closing) defer rw.Close() h.ServeHTTP(rw, req) // Call WriteHeader to ensure a response is sent, since the handler isn't // required to call WriteHeader/Write. rw.WriteHeader(200) if closing { return errClose } return nil } ctx, err = session.FromContext(ctx) if err != nil { log.Errorf("martian: failed to derive context: %v", err) return err } SetContext(req, ctx) defer RemoveContext(req) if tconn, ok := conn.(*tls.Conn); ok { ctx.GetSession().MarkSecure() cs := tconn.ConnectionState() req.TLS = &cs } req.URL.Scheme = "http" if ctx.GetSession().IsSecure() { log.Debugf("martian: forcing HTTPS inside secure session") req.URL.Scheme = "https" } req.RemoteAddr = conn.RemoteAddr().String() if req.URL.Host == "" { req.URL.Host = req.Host } log.Infof("martian: received request: %s", req.URL) if req.Method == "CONNECT" { if err := p.reqmod.ModifyRequest(req); err != nil { log.Errorf("martian: error modifying CONNECT request: %v", err) proxyutil.Warning(req.Header, err) } if p.mitm != nil { log.Debugf("martian: attempting MITM for connection: %s", req.Host) res := proxyutil.NewResponse(200, nil, req) if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying CONNECT response: %v", err) proxyutil.Warning(res.Header, err) } res.Write(brw) brw.Flush() log.Debugf("martian: completed MITM for connection: %s", req.Host) tlsconn := tls.Server(conn, p.mitm.TLSForHost(req.Host)) brw.Writer.Reset(tlsconn) brw.Reader.Reset(tlsconn) return p.handle(ctx, tlsconn, brw) } log.Debugf("martian: attempting to establish CONNECT tunnel: %s", req.URL.Host) res, cconn, cerr := p.connect(req) if cerr != nil { log.Errorf("martian: failed to CONNECT: %v", err) res = proxyutil.NewResponse(502, nil, req) proxyutil.Warning(res.Header, cerr) if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying CONNECT response: %v", err) proxyutil.Warning(res.Header, err) } res.Write(brw) return brw.Flush() } defer res.Body.Close() defer cconn.Close() if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying CONNECT response: %v", err) proxyutil.Warning(res.Header, err) } res.Write(brw) brw.Flush() cbw := bufio.NewWriter(cconn) cbr := bufio.NewReader(cconn) defer cbw.Flush() copySync := func(w io.Writer, r io.Reader, donec chan<- bool) { io.Copy(w, r) donec <- true } donec := make(chan bool, 2) go copySync(cbw, brw, donec) go copySync(brw, cbr, donec) log.Debugf("martian: established CONNECT tunnel, proxying traffic") <-donec <-donec log.Debugf("martian: closed CONNECT tunnel") return errClose } if err := p.reqmod.ModifyRequest(req); err != nil { log.Errorf("martian: error modifying request: %v", err) proxyutil.Warning(req.Header, err) } res, err := p.roundTrip(ctx, req) if err != nil { log.Errorf("martian: failed to round trip: %v", err) res = proxyutil.NewResponse(502, nil, req) proxyutil.Warning(res.Header, err) } defer res.Body.Close() if err := p.resmod.ModifyResponse(res); err != nil { log.Errorf("martian: error modifying response: %v", err) proxyutil.Warning(res.Header, err) } var closing error if req.Close || p.Closing() { log.Debugf("martian: received close request: %v", req.RemoteAddr) res.Header.Add("Connection", "close") closing = errClose } log.Debugf("martian: sent response: %v", req.URL) res.Write(brw) brw.Flush() return closing }