diff --git a/README.md b/README.md index 09917cd..c863d2f 100644 --- a/README.md +++ b/README.md @@ -10,5 +10,5 @@ - [x] https handler - [ ] http2 - [x] logger -- [ ] 经内存转发 https 流量 +- [x] 经内存转发 https 流量 - [x] 忽略某些错误例如:broken pipe, reset by peer, timeout diff --git a/go.mod b/go.mod index 9501986..c368672 100644 --- a/go.mod +++ b/go.mod @@ -6,5 +6,6 @@ require ( github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e github.com/golang/protobuf v1.4.3 // indirect github.com/joho/godotenv v1.3.0 + github.com/jordwest/mock-conn v0.0.0-20180617021051-4896c6bd1641 github.com/sirupsen/logrus v1.7.0 ) diff --git a/go.sum b/go.sum index e976b32..adfba9b 100644 --- a/go.sum +++ b/go.sum @@ -13,6 +13,8 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/jordwest/mock-conn v0.0.0-20180617021051-4896c6bd1641 h1:ChkB2s4mFDekyUUmbNE7qNhennP0rfqF2YZUOGxbhFk= +github.com/jordwest/mock-conn v0.0.0-20180617021051-4896c6bd1641/go.mod h1:AJFEOPtj5Z5z3MAy+0uvjQAH02iRnQr6fnvuHYp/Jek= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= diff --git a/proxy/mitm_memory.go b/proxy/mitm_memory.go new file mode 100644 index 0000000..5c3c0cc --- /dev/null +++ b/proxy/mitm_memory.go @@ -0,0 +1,171 @@ +package proxy + +import ( + "crypto/tls" + "net" + "net/http" + "os" + "time" + + mock_conn "github.com/jordwest/mock-conn" + "github.com/lqqyt2423/go-mitmproxy/cert" +) + +// 模拟实现 net + +type listener struct { + connChan chan net.Conn +} + +func (l *listener) Accept() (net.Conn, error) { + return <-l.connChan, nil +} + +func (l *listener) Close() error { + return nil +} + +func (l *listener) Addr() net.Addr { + return nil +} + +type ioRes struct { + n int + err error +} + +type conn struct { + *mock_conn.End + + readErrChan chan error // Read 方法提前返回时的错误 +} + +func newConn(end *mock_conn.End) *conn { + return &conn{ + End: end, + readErrChan: make(chan error), + } +} + +// 当接收到 readErrChan 时,可提前返回 +func (c *conn) Read(data []byte) (int, error) { + select { + case err := <-c.readErrChan: + return 0, err + default: + } + + resChan := make(chan *ioRes) + done := make(chan bool) + defer close(done) + + go func() { + select { + case <-done: + return + default: + } + + n, err := c.End.Read(data) + select { + case resChan <- &ioRes{n, err}: + return + case <-done: + close(resChan) + } + }() + + select { + case res := <-resChan: + return res.n, res.err + case err := <-c.readErrChan: + return 0, err + } +} + +func (c *conn) SetDeadline(t time.Time) error { + log.Warnf("SetDeadline %v\n", t) + return nil +} + +// http server 会在连接快结束时调用此方法 +func (c *conn) SetReadDeadline(t time.Time) error { + if !t.Equal(time.Time{}) { + if !t.After(time.Now()) { + // 使当前 Read 尽快返回 + c.readErrChan <- os.ErrDeadlineExceeded + } else { + log.Warnf("SetReadDeadline %v\n", t) + } + } + + return nil +} + +func (c *conn) SetWriteDeadline(t time.Time) error { + log.Warnf("SetWriteDeadline %v\n", t) + return nil +} + +type MitmMemory struct { + Proxy *Proxy + CA *cert.CA + Listener net.Listener + Server *http.Server +} + +func NewMitmMemory(proxy *Proxy) (Mitm, error) { + ca, err := cert.NewCA("") + if err != nil { + return nil, err + } + + m := &MitmMemory{ + Proxy: proxy, + CA: ca, + } + + server := &http.Server{ + Handler: m, + TLSNextProto: make(map[string]func(*http.Server, *tls.Conn, http.Handler)), // disable http2 + TLSConfig: &tls.Config{ + GetCertificate: func(chi *tls.ClientHelloInfo) (*tls.Certificate, error) { + log.Debugf("MitmMemory GetCertificate ServerName: %v\n", chi.ServerName) + return ca.GetCert(chi.ServerName) + }, + }, + } + + // 每次连接尽快结束,因为连接并无开销 + server.SetKeepAlivesEnabled(false) + + m.Server = server + + return m, nil +} + +func (m *MitmMemory) Start() error { + ln := &listener{ + connChan: make(chan net.Conn), + } + m.Listener = ln + return m.Server.ServeTLS(ln, "", "") +} + +func (m *MitmMemory) Dial(host string) (net.Conn, error) { + pipes := mock_conn.NewConn() + m.Listener.(*listener).connChan <- newConn(pipes.Server) + return newConn(pipes.Client), nil +} + +func (m *MitmMemory) ServeHTTP(res http.ResponseWriter, req *http.Request) { + if req.URL.Scheme == "" { + req.URL.Scheme = "https" + } + + if req.URL.Host == "" { + req.URL.Host = req.Host + } + + m.Proxy.ServeHTTP(res, req) +} diff --git a/proxy/proxy.go b/proxy/proxy.go index 21f4245..3b90cc7 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -63,8 +63,9 @@ func (proxy *Proxy) ServeHTTP(res http.ResponseWriter, req *http.Request) { } log := log.WithFields(_log.Fields{ - "in": "ServeHTTP", - "url": req.URL, + "in": "ServeHTTP", + "url": req.URL, + "method": req.Method, }) if !req.URL.IsAbs() || req.URL.Host == "" { @@ -112,7 +113,7 @@ func (proxy *Proxy) ServeHTTP(res http.ResponseWriter, req *http.Request) { return } - log.Infof("%v %v %v - %v ms", req.Method, req.URL.String(), proxyRes.StatusCode, time.Since(start).Milliseconds()) + log.Infof("status code: %v cost %v ms\n", proxyRes.StatusCode, time.Since(start).Milliseconds()) } func (proxy *Proxy) handleConnect(res http.ResponseWriter, req *http.Request) { @@ -191,7 +192,7 @@ func NewProxy(opts *Options) (*Proxy, error) { }, } - mitm, err := NewMitmServer(proxy) + mitm, err := NewMitmMemory(proxy) if err != nil { return nil, err }