diff --git a/cmd/gost/main.go b/cmd/gost/main.go index a3559ec..7274c95 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -171,6 +171,8 @@ func initChain() (*gost.Chain, error) { return nil, err } tr = gost.Obfs4Transporter() + case "ohttp": + tr = gost.ObfsHTTPTransporter() default: tr = gost.TCPTransporter() } @@ -288,11 +290,6 @@ func serve(chain *gost.Chain) error { ln, err = gost.H2Listener(node.Addr, tlsCfg) case "h2c": ln, err = gost.H2CListener(node.Addr) - case "obfs4": - if err = gost.Obfs4Init(node, true); err != nil { - return err - } - ln, err = gost.Obfs4Listener(node.Addr) case "tcp": ln, err = gost.TCPListener(node.Addr) case "rtcp": @@ -311,6 +308,13 @@ func serve(chain *gost.Chain) error { case "ssu": ttl, _ := strconv.Atoi(node.Values.Get("ttl")) ln, err = gost.ShadowUDPListener(node.Addr, node.User, time.Duration(ttl)*time.Second) + case "obfs4": + if err = gost.Obfs4Init(node, true); err != nil { + return err + } + ln, err = gost.Obfs4Listener(node.Addr) + case "ohttp": + ln, err = gost.ObfsHTTPListener(node.Addr) default: ln, err = gost.TCPListener(node.Addr) } diff --git a/node.go b/node.go index 88b0245..324dd12 100644 --- a/node.go +++ b/node.go @@ -58,6 +58,7 @@ func ParseNode(s string) (node Node, err error) { node.Transport = "tls" case "tcp", "udp": // started from v2.1, tcp and udp are for local port forwarding case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding + case "ohttp": // obfs-http default: node.Transport = "tcp" } diff --git a/obfs4.go b/obfs.go similarity index 52% rename from obfs4.go rename to obfs.go index 64f58ff..6b789ed 100644 --- a/obfs4.go +++ b/obfs.go @@ -3,9 +3,15 @@ package gost import ( + "bufio" + "bytes" + "errors" "fmt" "net" + "net/http" + "net/http/httputil" "net/url" + "sync" "github.com/go-log/log" @@ -14,6 +20,129 @@ import ( "git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4" ) +type obfsHTTPTransporter struct { + tcpTransporter +} + +// ObfsHTTPTransporter creates a Transporter that is used by HTTP obfuscating tunnel client. +func ObfsHTTPTransporter() Transporter { + return &obfsHTTPTransporter{} +} + +func (tr *obfsHTTPTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { + opts := &HandshakeOptions{} + for _, option := range options { + option(opts) + } + return &obfsHTTPConn{Conn: conn}, nil +} + +type obfsHTTPListener struct { + net.Listener +} + +// ObfsHTTPListener creates a Listener for HTTP obfuscating tunnel server. +func ObfsHTTPListener(addr string) (Listener, error) { + laddr, err := net.ResolveTCPAddr("tcp", addr) + if err != nil { + return nil, err + } + ln, err := net.ListenTCP("tcp", laddr) + if err != nil { + return nil, err + } + return &obfsHTTPListener{Listener: tcpKeepAliveListener{ln}}, nil +} + +func (l *obfsHTTPListener) Accept() (net.Conn, error) { + conn, err := l.Listener.Accept() + if err != nil { + return nil, err + } + + return &obfsHTTPConn{Conn: conn, isServer: true}, nil +} + +type obfsHTTPConn struct { + net.Conn + r *http.Request + isServer bool + handshaked bool + handshakeMutex sync.Mutex +} + +func (c *obfsHTTPConn) Handshake() (err error) { + c.handshakeMutex.Lock() + defer c.handshakeMutex.Unlock() + + if c.handshaked { + return nil + } + + if c.isServer { + c.r, err = http.ReadRequest(bufio.NewReader(c.Conn)) + if err != nil { + return + } + if Debug { + dump, _ := httputil.DumpRequest(c.r, false) + log.Logf("[ohttp] %s -> %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), string(dump)) + } + b := bytes.NewBufferString("HTTP/1.1 200 OK\r\nContent-Type: text/html; charset=utf-8\r\n\r\n") + if Debug { + log.Logf("[ohttp] %s <- %s\n%s", c.Conn.RemoteAddr(), c.Conn.LocalAddr(), b.String()) + } + if _, err = b.WriteTo(c.Conn); err != nil { + return + } + + } else { + r := c.r + if r == nil { + r, err = http.NewRequest(http.MethodPost, "http://www.baidu.com/", nil) + if err != nil { + return + } + r.Header.Set("User-Agent", DefaultUserAgent) + } + if err = r.Write(c.Conn); err != nil { + return + } + if Debug { + dump, _ := httputil.DumpRequest(r, false) + log.Logf("[ohttp] %s -> %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump)) + } + var resp *http.Response + resp, err = http.ReadResponse(bufio.NewReader(c.Conn), r) + if err != nil { + return + } + if Debug { + dump, _ := httputil.DumpResponse(resp, false) + log.Logf("[ohttp] %s <- %s\n%s", c.Conn.LocalAddr(), c.Conn.RemoteAddr(), string(dump)) + } + if resp.StatusCode != http.StatusOK { + return errors.New(resp.Status) + } + } + c.handshaked = true + return nil +} + +func (c *obfsHTTPConn) Read(b []byte) (n int, err error) { + if err = c.Handshake(); err != nil { + return + } + return c.Conn.Read(b) +} + +func (c *obfsHTTPConn) Write(b []byte) (n int, err error) { + if err = c.Handshake(); err != nil { + return + } + return c.Conn.Write(b) +} + type obfs4Context struct { cf base.ClientFactory cargs interface{} // type obfs4ClientArgs diff --git a/tls.go b/tls.go index 4bc55dd..3c617be 100644 --- a/tls.go +++ b/tls.go @@ -12,7 +12,6 @@ type tlsTransporter struct { } // TLSTransporter creates a Transporter that is used by TLS proxy client. -// It accepts a TLS config for TLS handshake. func TLSTransporter() Transporter { return &tlsTransporter{} }