コード例 #1
0
ファイル: proxy.go プロジェクト: eolexe/martian
// handleRequest runs the request and response modifiers and performs the roundtrip to the destination server.
func (p *Proxy) handleRequest(ctx *Context, rw *bufio.ReadWriter, req *http.Request) (closing bool) {
	if err := proxyutil.FixBadFraming(req.Header); err != nil {
		Errorf("proxyutil.FixBadFraming(): %v", err)
		proxyutil.NewErrorResponse(400, err, req).Write(rw)
	}

	proxyutil.SetForwardedHeaders(req)
	proxyutil.SetViaHeader(req.Header, "1.1 martian")

	if err := p.ModifyRequest(ctx, req); err != nil {
		Errorf("martian.ModifyRequest(): %v", err)
		proxyutil.NewErrorResponse(400, err, req).Write(rw)
		return
	}

	if shouldCloseAfterReply(req.Header) {
		Debugf("closing after reply")
		closing = true
	}

	proxyutil.RemoveHopByHopHeaders(req.Header)

	var res *http.Response
	var err error
	if !ctx.SkipRoundTrip {
		Debugf("proceed to round trip for %s", req.URL)

		res, err = p.RoundTripper.RoundTrip(req)
		if err != nil {
			Errorf("RoundTripper.RoundTrip(%s): %v", req.URL, err)
			proxyutil.NewErrorResponse(502, err, req).Write(rw)
			return
		}
	} else {
		Debugf("skipped round trip for %s", req.URL)
		res = proxyutil.NewResponse(200, nil, req)
	}

	proxyutil.RemoveHopByHopHeaders(res.Header)

	if err := p.ModifyResponse(ctx, res); err != nil {
		Errorf("martian.ModifyResponse(): %v", err)
		proxyutil.NewErrorResponse(400, err, req).Write(rw)
		return
	}

	if closing {
		res.Header.Set("Connection", "close")
		res.Close = true
	}

	if err := res.Write(rw); err != nil {
		Errorf("res.Write(): %v", err)
	}

	return
}
コード例 #2
0
ファイル: proxy.go プロジェクト: eolexe/martian
// connectResponse builds the CONNECT response and runs the CONNECT request and
// response modifiers.
func (p *Proxy) connectResponse(ctx *Context, req *http.Request) (*http.Response, error) {
	res := proxyutil.NewResponse(200, nil, req)

	if p.creqmod != nil {
		if err := p.creqmod.ModifyRequest(ctx, req); err != nil {
			Errorf("ModifyConnectRequest(%s): %v", req.URL, err)
			return proxyutil.NewErrorResponse(400, err, req), err
		}
	}

	if p.cresmod != nil {
		if err := p.cresmod.ModifyResponse(ctx, res); err != nil {
			Errorf("ModifyConnectResponse(%s): %v", res.Request.URL, err)
			return proxyutil.NewErrorResponse(400, err, req), err
		}
	}

	return res, nil
}
コード例 #3
0
ファイル: proxy.go プロジェクト: eolexe/martian
// ServeHTTP handles requests from a connection and writes responses.
//
// If a MITM config was provided and a CONNECT request is received, the proxy
// will generate a fake TLS certificate using the given authority certificate
// and perform the TLS handshake. The request will then be decrypted and
// modifiers will be run, followed by the request being re-encrypted and sent
// to the destination server.
//
// If no MITM config was provided and a CONNECT request is received, the proxy
// will open a connection to the destination server and copy the encrypted bytes
// directly, as per normal CONNECT semantics.
func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := NewContext()

	hj, ok := w.(http.Hijacker)
	if !ok {
		Errorf("w.(http.Hijacker): !ok")
		http.Error(w, "error unsupported http.Hijacker", 500)
		return
	}

	// Take over the connection immediately. We technically don't need to do this
	// in a non-CONNECT request, but it's easier to have all cases share the same
	// logic for request handling.
	conn, rw, err := hj.Hijack()
	if err != nil {
		Errorf("hj.Hijack(): %v", err)
		return
	}
	defer conn.Close()

	var closing bool
	switch r.Method {
	case "CONNECT":
		if r.URL.Host == "" {
			r.URL.Host = r.Host
		}

		// Run the CONNECT modifiers and handle errors.
		res, err := p.connectResponse(ctx, r)
		if err != nil {
			Errorf("connectResponse(%s): %v", r.URL, err)
			res.Write(rw)
			break
		}

		var tlsconn net.Conn
		var tlsrw *bufio.ReadWriter
		if p.mitm != nil {
			// Drop the port when building the MITM certificate.
			host, _, err := net.SplitHostPort(r.Host)
			if err != nil {
				Errorf("net.SplitHostPort(%s): %v", r.Host, err)
				proxyutil.NewErrorResponse(400, err, r).Write(rw)
				break
			}

			// Build MITM certificate and wrap connection.
			tlsconn, tlsrw, err = p.mitm.Hijack(conn, host)
			if err != nil {
				Errorf("mitm.Hijack(conn, %s): %v", host, err)
				proxyutil.NewErrorResponse(400, err, r).Write(rw)
				break
			}
			defer tlsconn.Close()

			Debugf("Hijacked TLS connection for %s", host)
		}

		res.Write(rw)
		rw.Flush()

		if tlsconn != nil && tlsrw != nil {
			conn = tlsconn
			rw = tlsrw
		}

		// Proxy is not configured for man-in-the-middle and we have received a
		// CONNECT request. Proxy the request as normal CONNECT.
		if p.mitm == nil {
			p.handleNonMITMConnect(ctx, conn, r.Host)
			return
		}
	default:
		Debugf("received non-CONNECT request %s", r.URL)
		// Run the request and response modifiers.
		closing = p.handleRequest(ctx, rw, r)
	}

	Debugf("rw.Flush(): flushing response: %s", r.URL)
	if err := rw.Flush(); err != nil {
		Errorf("rw.Flush(): %v", err)
	}

	if closing {
		Debugf("closing connection")
		return
	}

	// We continue looping until the connection has been closed by the client.
	for {
		// Each request has its own timeout of p.Timeout. Reset after each request.
		deadline := time.Now().Add(p.Timeout)

		if err := conn.SetDeadline(deadline); err != nil {
			Errorf("conn.SetDeadline(%s): %v", deadline.Format(time.RFC3339), err)
			return
		}

		Debugf("Waiting for request...")
		req, err := http.ReadRequest(rw.Reader)
		if err != nil {
			// We have encountered a timeout error, do not attempt to send an error
			// response, just close the connection.
			neterr, ok := err.(net.Error)
			switch {
			case ok && neterr.Timeout():
			case err == io.EOF:
			case err == io.ErrClosedPipe:
			default:
				Errorf("http.ReadRequest(): %v", err)
				return
			}

			Debugf("http.ReadRequest(): timeout error %v", err)
			return
		}
		// Scheme will be empty in the case of a CONNECT request,
		// default to HTTPS if we don't have an original scheme.
		req.URL.Scheme = r.URL.Scheme
		if req.URL.Scheme == "" {
			req.URL.Scheme = "https"
		}

		// For requests received during MITM the URL.Host will not be set as it
		// does not appear in the Request-URI line. The http package will fill the
		// Host field with either the value from URL.Host or the Host header.
		req.URL.Host = req.Host

		req.RemoteAddr = conn.RemoteAddr().String()

		// Run the request and response modifiers.
		closing = p.handleRequest(ctx, rw, req)

		if err := rw.Flush(); err != nil {
			Errorf("rw.Flush(): %v", err)
		}

		if closing {
			Debugf("closing connection")
			return
		}
	}
}