// NewLogger returns a logger that logs requests and responses, optionally // logging the body. Log function defaults to martian.Infof. func NewLogger() *Logger { return &Logger{ log: func(line string) { log.Infof(line) }, } }
// NewLogger returns a logger that logs requests and responses, optionally // logging the body. Log function defaults to martian.Infof. func NewLogger() *Logger { return &Logger{ log: func(line string) { log.Infof(line) }, conf: &proxyutil.ViewConfig{}, } }
func (p *Proxy) handleLoop(conn net.Conn) { p.conns.Add(1) defer p.conns.Done() defer conn.Close() ctx := session.FromContext(nil) brw := bufio.NewReadWriter(bufio.NewReader(conn), bufio.NewWriter(conn)) for { deadline := time.Now().Add(p.timeout) conn.SetDeadline(deadline) if err := p.handle(ctx, conn, brw); isCloseable(err) { log.Infof("martian: closing connection: %v", conn.RemoteAddr()) return } } }
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 }
// Close sets the proxying to the closing state and waits for all connections // to resolve. func (p *Proxy) Close() { log.Infof("martian: closing down proxy") atomic.StoreInt32(&p.closing, 1) p.conns.Wait() }
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 }
func TestIntegrationHTTP100Continue(t *testing.T) { t.Parallel() l, err := net.Listen("tcp", "[::1]:0") if err != nil { t.Fatalf("net.Listen(): got %v, want no error", err) } p := NewProxy() defer p.Close() p.SetTimeout(2 * time.Second) sl, err := net.Listen("tcp", "[::1]:0") if err != nil { t.Fatalf("net.Listen(): got %v, want no error", err) } go func() { conn, err := sl.Accept() if err != nil { log.Errorf("proxy_test: failed to accept connection: %v", err) return } defer conn.Close() log.Infof("proxy_test: accepted connection: %s", conn.RemoteAddr()) req, err := http.ReadRequest(bufio.NewReader(conn)) if err != nil { log.Errorf("proxy_test: failed to read request: %v", err) return } if req.Header.Get("Expect") == "100-continue" { log.Infof("proxy_test: received 100-continue request") conn.Write([]byte("HTTP/1.1 100 Continue\r\n\r\n")) log.Infof("proxy_test: sent 100-continue response") } else { log.Infof("proxy_test: received non 100-continue request") res := proxyutil.NewResponse(417, nil, req) res.Header.Set("Connection", "close") res.Write(conn) return } res := proxyutil.NewResponse(200, req.Body, req) res.Header.Set("Connection", "close") res.Write(conn) log.Infof("proxy_test: sent 200 response") }() tm := martiantest.NewModifier() p.SetRequestModifier(tm) p.SetResponseModifier(tm) go p.Serve(l) conn, err := net.Dial("tcp", l.Addr().String()) if err != nil { t.Fatalf("net.Dial(): got %v, want no error", err) } defer conn.Close() host := sl.Addr().String() raw := fmt.Sprintf("POST http://%s/ HTTP/1.1\r\n"+ "Host: %s\r\n"+ "Content-Length: 12\r\n"+ "Expect: 100-continue\r\n\r\n", host, host) if _, err := conn.Write([]byte(raw)); err != nil { t.Fatalf("conn.Write(headers): got %v, want no error", err) } go func() { select { case <-time.After(time.Second): conn.Write([]byte("body content")) } }() res, err := http.ReadResponse(bufio.NewReader(conn), nil) if err != nil { t.Fatalf("http.ReadResponse(): got %v, want no error", err) } defer res.Body.Close() if got, want := res.StatusCode, 200; got != want { t.Fatalf("res.StatusCode: got %d, want %d", got, want) } got, err := ioutil.ReadAll(res.Body) if err != nil { t.Fatalf("ioutil.ReadAll(): got %v, want no error", err) } if want := []byte("body content"); !bytes.Equal(got, want) { t.Errorf("res.Body: got %q, want %q", got, want) } if !tm.RequestModified() { t.Error("tm.RequestModified(): got false, want true") } if !tm.ResponseModified() { t.Error("tm.ResponseModified(): got false, want true") } }
// StartWithCertificate runs a proxy on addr and configures a cert for MITM func StartWithCertificate(proxyAddr string, cert string, key string) (*Martian, error) { flag.Set("logtostderr", "true") signal.Ignore(syscall.SIGPIPE) l, err := net.Listen("tcp", proxyAddr) if err != nil { return nil, err } mlog.Debugf("mobileproxy: started listener: %v", l.Addr()) p := martian.NewProxy() mux := http.NewServeMux() p.SetMux(mux) if cert != "" && key != "" { tlsc, err := tls.X509KeyPair([]byte(cert), []byte(key)) if err != nil { log.Fatal(err) } mlog.Debugf("mobileproxy: loaded cert and key") x509c, err := x509.ParseCertificate(tlsc.Certificate[0]) if err != nil { log.Fatal(err) } mlog.Debugf("mobileproxy: parsed cert") mc, err := mitm.NewConfig(x509c, tlsc.PrivateKey) if err != nil { log.Fatal(err) } mc.SetValidity(12 * time.Hour) mc.SetOrganization("Martian Proxy") p.SetMITM(mc) mux.Handle("martian.proxy/authority.cer", martianhttp.NewAuthorityHandler(x509c)) mlog.Debugf("mobileproxy: install cert from http://martian.proxy/authority.cer") } stack, fg := httpspec.NewStack("martian.mobileproxy") p.SetRequestModifier(stack) p.SetResponseModifier(stack) // add HAR logger hl := har.NewLogger() stack.AddRequestModifier(hl) stack.AddResponseModifier(hl) m := martianhttp.NewModifier() fg.AddRequestModifier(m) fg.AddResponseModifier(m) mlog.Debugf("mobileproxy: set martianhttp modifier") // Proxy specific handlers. // These handlers take precendence over proxy traffic and will not be intercepted. // Retrieve HAR logs mux.Handle("martian.proxy/logs", har.NewExportHandler(hl)) mux.Handle("martian.proxy/logs/reset", har.NewResetHandler(hl)) // Update modifiers. mux.Handle("martian.proxy/configure", m) mlog.Debugf("mobileproxy: configure with requests to http://martian.proxy/configure") // Verify assertions. vh := verify.NewHandler() vh.SetRequestVerifier(m) vh.SetResponseVerifier(m) mux.Handle("martian.proxy/verify", vh) mlog.Debugf("mobileproxy: check verifications with requests to http://martian.proxy/verify") // Reset verifications. rh := verify.NewResetHandler() rh.SetRequestVerifier(m) rh.SetResponseVerifier(m) mux.Handle("martian.proxy/verify/reset", rh) mlog.Debugf("mobileproxy: reset verifications with requests to http://martian.proxy/verify/reset") go p.Serve(l) mlog.Infof("mobileproxy: started proxy on listener") return &Martian{ proxy: p, listener: l, mux: mux, }, nil }
// Shutdown tells the Proxy to close. The proxy will stay alive until all connections through it // have closed or timed out. func (p *Martian) Shutdown() { mlog.Infof("mobileproxy: telling proxy to close") p.proxy.Close() mlog.Infof("mobileproxy: proxy closed") }