diff --git a/cmd/gost/route.go b/cmd/gost/route.go index eb4f5d5..21a34b7 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -171,6 +171,8 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) { case "ohttp": host = node.Get("host") tr = gost.ObfsHTTPTransporter() + case "ftcp": + tr = gost.FakeTCPTransporter() default: tr = gost.TCPTransporter() } @@ -411,6 +413,15 @@ func (r *route) GenRouters() ([]router, error) { Gateway: node.Get("gw"), } ln, err = gost.TapListener(cfg) + case "ftcp": + ln, err = gost.FakeTCPListener( + node.Addr, + &gost.FakeTCPListenConfig{ + TTL: ttl, + Backlog: node.GetInt("backlog"), + QueueSize: node.GetInt("queue"), + }, + ) default: ln, err = gost.TCPListener(node.Addr) } diff --git a/forward.go b/forward.go index 23c4d64..e378b6c 100644 --- a/forward.go +++ b/forward.go @@ -534,7 +534,7 @@ func (c *udpServerConn) Write(b []byte) (n int, err error) { if n > 0 { if Debug { - log.Logf("[udp] %s <<< %s : length %d", c.RemoteAddr(), c.LocalAddr(), n) + log.Logf("[udp] %s <<< %s : length %d", c.raddr, c.LocalAddr(), n) } select { diff --git a/ftcp.go b/ftcp.go new file mode 100644 index 0000000..281255b --- /dev/null +++ b/ftcp.go @@ -0,0 +1,203 @@ +package gost + +import ( + "errors" + "net" + "time" + + "github.com/go-log/log" + "github.com/xtaci/tcpraw" +) + +type fakeTCPTransporter struct{} + +// FakeTCPTransporter creates a Transporter that is used by fake tcp client. +func FakeTCPTransporter() Transporter { + return &fakeTCPTransporter{} +} + +func (tr *fakeTCPTransporter) Dial(addr string, options ...DialOption) (conn net.Conn, err error) { + opts := &DialOptions{} + for _, option := range options { + option(opts) + } + + raddr, er := net.ResolveTCPAddr("tcp", addr) + if er != nil { + return nil, er + } + c, err := tcpraw.Dial("tcp", addr) + if err != nil { + return + } + conn = &fakeTCPConn{ + raddr: raddr, + PacketConn: c, + } + return conn, nil +} + +func (tr *fakeTCPTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) { + return conn, nil +} + +func (tr *fakeTCPTransporter) Multiplex() bool { + return false +} + +type FakeTCPListenConfig struct { + TTL time.Duration + Backlog int + QueueSize int +} + +type fakeTCPListener struct { + ln net.PacketConn + connChan chan net.Conn + errChan chan error + connMap udpConnMap + config *FakeTCPListenConfig +} + +// FakeTCPListener creates a Listener for fake TCP server. +func FakeTCPListener(addr string, cfg *FakeTCPListenConfig) (Listener, error) { + ln, err := tcpraw.Listen("tcp", addr) + if err != nil { + return nil, err + } + + if cfg == nil { + cfg = &FakeTCPListenConfig{} + } + + backlog := cfg.Backlog + if backlog <= 0 { + backlog = defaultBacklog + } + + l := &fakeTCPListener{ + ln: ln, + connChan: make(chan net.Conn, backlog), + errChan: make(chan error, 1), + config: cfg, + } + go l.listenLoop() + return l, nil +} + +func (l *fakeTCPListener) listenLoop() { + for { + b := make([]byte, mediumBufferSize) + n, raddr, err := l.ln.ReadFrom(b) + if err != nil { + log.Logf("[ftcp] peer -> %s : %s", l.Addr(), err) + l.Close() + l.errChan <- err + close(l.errChan) + return + } + + conn, ok := l.connMap.Get(raddr.String()) + if !ok { + cc := &fakeTCPConn{ + raddr: raddr, + PacketConn: l.ln, + } + conn = newUDPServerConn(cc, raddr, l.config.TTL, l.config.QueueSize) + conn.onClose = func() { + l.connMap.Delete(raddr.String()) + log.Logf("[ftcp] %s closed (%d)", raddr, l.connMap.Size()) + } + + select { + case l.connChan <- conn: + l.connMap.Set(raddr.String(), conn) + log.Logf("[ftcp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size()) + default: + conn.Close() + log.Logf("[ftcp] %s - %s: connection queue is full (%d)", raddr, l.Addr(), cap(l.connChan)) + } + } + + select { + case conn.rChan <- b[:n]: + if Debug { + log.Logf("[ftcp] %s >>> %s : length %d", raddr, l.Addr(), n) + } + default: + log.Logf("[ftcp] %s -> %s : recv queue is full (%d)", raddr, l.Addr(), cap(conn.rChan)) + } + } +} + +func (l *fakeTCPListener) Accept() (conn net.Conn, err error) { + var ok bool + select { + case conn = <-l.connChan: + case err, ok = <-l.errChan: + if !ok { + err = errors.New("accpet on closed listener") + } + } + return +} + +func (l *fakeTCPListener) Addr() net.Addr { + return l.ln.LocalAddr() +} + +func (l *fakeTCPListener) Close() error { + err := l.ln.Close() + l.connMap.Range(func(k interface{}, v *udpServerConn) bool { + v.Close() + return true + }) + + return err +} + +type fakeTCPConn struct { + mss int + raddr net.Addr + net.PacketConn +} + +func (c *fakeTCPConn) Read(b []byte) (n int, err error) { + n, _, err = c.ReadFrom(b) + return +} + +func (c *fakeTCPConn) Write(b []byte) (n int, err error) { + return c.WriteTo(b, c.raddr) +} + +func (c *fakeTCPConn) WriteTo(b []byte, addr net.Addr) (n int, err error) { + mss := c.mss + if mss <= 0 { + mss = 1460 + } + + for { + if len(b) == 0 { + break + } + var nn int + if len(b) <= mss { + nn, err = c.PacketConn.WriteTo(b, addr) + n += nn + break + } + nn, err = c.PacketConn.WriteTo(b[:mss], addr) + n += nn + if err != nil { + break + } + b = b[mss:] + } + + return +} + +func (c *fakeTCPConn) RemoteAddr() net.Addr { + return c.raddr +} diff --git a/node.go b/node.go index f5d8512..3305667 100644 --- a/node.go +++ b/node.go @@ -83,6 +83,7 @@ func ParseNode(s string) (node Node, err error) { case "rtcp", "rudp": // rtcp and rudp are for remote port forwarding case "ohttp": // obfs-http case "tun", "tap": // tun/tap device + case "ftcp": // fake TCP default: node.Transport = "tcp" } @@ -95,6 +96,7 @@ func ParseNode(s string) (node Node, err error) { case "direct", "remote", "forward": // forwarding case "redirect": // TCP transparent proxy case "tun", "tap": // tun/tap device + case "ftcp": // fake TCP default: node.Protocol = "" }