diff --git a/chain.go b/chain.go index 9178067..779f5c4 100644 --- a/chain.go +++ b/chain.go @@ -42,7 +42,7 @@ func (c *ProxyChain) AddProxyNode(node ...ProxyNode) { func (c *ProxyChain) AddProxyNodeString(snode ...string) error { for _, sn := range snode { - node, err := ParseProxyNode(sn) + node, err := ParseProxyNode(sn, false) // isServeNode == false if err != nil { return err } diff --git a/cmd/gost/main.go b/cmd/gost/main.go index 6456987..0fd8175 100644 --- a/cmd/gost/main.go +++ b/cmd/gost/main.go @@ -60,7 +60,7 @@ func main() { var wg sync.WaitGroup for _, ns := range options.ServeNodes { - serverNode, err := gost.ParseProxyNode(ns) + serverNode, err := gost.ParseProxyNode(ns, true) // isServeNode == true if err != nil { glog.Fatal(err) } diff --git a/conn.go b/conn.go index a46a285..b5d41cb 100644 --- a/conn.go +++ b/conn.go @@ -84,6 +84,12 @@ func (c *ProxyConn) handshake() error { tlsUsed = true case "kcp": // kcp connection tlsUsed = true + case "obfs4": + conn, err := c.Node.Obfs4ClientConn(c.conn) + if err != nil { + return err + } + c.conn = conn default: } diff --git a/node.go b/node.go index e72d0bb..def140d 100644 --- a/node.go +++ b/node.go @@ -26,7 +26,7 @@ type ProxyNode struct { // The proxy node string pattern is [scheme://][user:pass@host]:port. // // Scheme can be devided into two parts by character '+', such as: http+tls. -func ParseProxyNode(s string) (node ProxyNode, err error) { +func ParseProxyNode(s string, isServeNode bool) (node ProxyNode, err error) { if !strings.Contains(s, "://") { s = "gost://" + s } @@ -79,6 +79,12 @@ func ParseProxyNode(s string) (node ProxyNode, err error) { node.Remote = strings.Trim(u.EscapedPath(), "/") case "rtcp", "rudp": // started from v2.1, rtcp and rudp are for remote port forwarding node.Remote = strings.Trim(u.EscapedPath(), "/") + case "obfs4": + err := node.Obfs4Init(isServeNode) + if err != nil { + glog.V(LDEBUG).Infoln("obfs4 init failed", err) + return node, err + } default: node.Transport = "" } diff --git a/obfs4.go b/obfs4.go new file mode 100644 index 0000000..c8aa5b6 --- /dev/null +++ b/obfs4.go @@ -0,0 +1,114 @@ +// obfs4 connection wrappers + +package gost + +import ( + "fmt" + "net" + "net/url" + "git.torproject.org/pluggable-transports/goptlib.git" // package pt + "git.torproject.org/pluggable-transports/obfs4.git/transports/base" + "git.torproject.org/pluggable-transports/obfs4.git/transports/obfs4" +) + +// Factories are stored per node since some arguments reside in them. +// For simplicity, c & s variables are packed together. + +type obfs4Context struct { + cf base.ClientFactory + cargs interface{} // type obfs4ClientArgs + sf base.ServerFactory + sargs *pt.Args +} + +var obfs4Map = make(map[string]obfs4Context) + +func (node *ProxyNode) obfs4GetContext() (obfs4Context, error) { + if node.Transport != "obfs4" { + return obfs4Context{}, fmt.Errorf("non-obfs4 node has no obfs4 context") + } + ctx, ok := obfs4Map[node.Addr] + if !ok { + return obfs4Context{}, fmt.Errorf("obfs4 context not inited") + } + return ctx, nil +} + +func (node *ProxyNode) Obfs4Init(isServeNode bool) error { + if _, ok := obfs4Map[node.Addr]; ok { + return fmt.Errorf("obfs4 context already inited") + } + + t := new(obfs4.Transport) + + stateDir := node.values.Get("state-dir") + if stateDir == "" { + stateDir = "." + } + + ptArgs := pt.Args(node.values) + + if !isServeNode { + cf, err := t.ClientFactory(stateDir) + if err != nil { + return err + } + + cargs, err := cf.ParseArgs(&ptArgs) + if err != nil { + return err + } + + obfs4Map[node.Addr] = obfs4Context{cf: cf, cargs: cargs} + } else { + sf, err := t.ServerFactory(stateDir, &ptArgs) + if err != nil { + return err + } + + sargs := sf.Args() + + obfs4Map[node.Addr] = obfs4Context{sf: sf, sargs: sargs} + + fmt.Println("[obfs4 server inited]", node.Obfs4ServerURL()) + } + + return nil +} + +func (node *ProxyNode) Obfs4ClientConn(conn net.Conn) (net.Conn, error) { + ctx, err := node.obfs4GetContext() + if err != nil { + return nil, err + } + + pseudoDial := func (a, b string) (net.Conn, error) {return conn, nil} + return ctx.cf.Dial("tcp", "", pseudoDial, ctx.cargs) +} + +func (node *ProxyNode) Obfs4ServerConn(conn net.Conn) (net.Conn, error) { + ctx, err := node.obfs4GetContext() + if err != nil { + return nil, err + } + + return ctx.sf.WrapConn(conn) +} + +func (node *ProxyNode) Obfs4ServerURL() string { + ctx, err := node.obfs4GetContext() + if err != nil { + return "" + } + + values := (*url.Values)(ctx.sargs) + query := values.Encode() + + return fmt.Sprintf( + "%s+%s://%s/?%s", //obfs4-cert=%s&iat-mode=%s", + node.Protocol, + node.Transport, + node.Addr, + query, + ) +} diff --git a/server.go b/server.go index 63ff9a0..10efd5e 100644 --- a/server.go +++ b/server.go @@ -141,6 +141,16 @@ func (s *ProxyServer) Serve() error { setKeepAlive(conn, KeepAliveTime) + if node.Transport == "obfs4" { + obfs4Conn, err := node.Obfs4ServerConn(conn) + if err != nil { + glog.V(LWARNING).Infoln(err) + conn.Close() + continue + } + conn = obfs4Conn + } + go s.handleConn(conn) } }