diff --git a/cmd/gost/route.go b/cmd/gost/route.go index 3ae526f..ac840cf 100644 --- a/cmd/gost/route.go +++ b/cmd/gost/route.go @@ -481,6 +481,12 @@ func (r *route) GenRouters() ([]router, error) { TLSConfig: tlsCfg, }, ) + case "redu", "redirectu": + ln, err = gost.UDPRedirectListener(node.Addr, &gost.UDPListenConfig{ + TTL: ttl, + Backlog: node.GetInt("backlog"), + QueueSize: node.GetInt("queue"), + }) default: ln, err = gost.TCPListener(node.Addr) } @@ -512,8 +518,10 @@ func (r *route) GenRouters() ([]router, error) { handler = gost.UDPRemoteForwardHandler(node.Remote) case "forward": handler = gost.SSHForwardHandler() - case "redirect": + case "red", "redirect": handler = gost.TCPRedirectHandler() + case "redu", "redirectu": + handler = gost.UDPRedirectHandler() case "ssu": handler = gost.ShadowUDPHandler() case "sni": diff --git a/forward.go b/forward.go index 0c40518..326f87e 100644 --- a/forward.go +++ b/forward.go @@ -189,7 +189,7 @@ func (h *udpDirectForwardHandler) Handle(conn net.Conn) { raddr, err := net.ResolveUDPAddr("udp", node.Addr) if err != nil { node.MarkDead() - log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err) + log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), node.Addr, err) return } @@ -198,7 +198,7 @@ func (h *udpDirectForwardHandler) Handle(conn net.Conn) { cc, err = net.DialUDP("udp", nil, raddr) if err != nil { node.MarkDead() - log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err) + log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), node.Addr, err) return } } else if h.options.Chain.LastNode().Protocol == "ssu" { @@ -208,14 +208,14 @@ func (h *udpDirectForwardHandler) Handle(conn net.Conn) { ) if err != nil { node.MarkDead() - log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err) + log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), node.Addr, err) return } } else { var err error cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil) if err != nil { - log.Logf("[udp] %s - %s : %s", conn.LocalAddr(), node.Addr, err) + log.Logf("[udp] %s - %s : %s", conn.RemoteAddr(), node.Addr, err) return } diff --git a/go.mod b/go.mod index 199366f..1bd18df 100644 --- a/go.mod +++ b/go.mod @@ -5,6 +5,7 @@ go 1.13 require ( git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e + github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 // indirect github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da // indirect github.com/bifurcation/mint v0.0.0-20181105071958-a14404e9a861 // indirect diff --git a/go.sum b/go.sum index a62f2a4..98cc74b 100644 --- a/go.sum +++ b/go.sum @@ -2,6 +2,8 @@ git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec git.torproject.org/pluggable-transports/goptlib.git v0.0.0-20180321061416-7d56ec4f381e/go.mod h1:YT4XMSkuEXbtqlydr9+OxqFAyspUv0Gr9qhM3B++o/Q= git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e h1:c8h60PKrRxEB5debIHBmP7T+s/EUNXTklXqlmJfYiJQ= git.torproject.org/pluggable-transports/obfs4.git v0.0.0-20181103133120-08f4d470188e/go.mod h1:jRZbfRcLIgFQoCw6tRmsnETVyIj54jOmXhHCYYa0jbs= +github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed h1:eqa6queieK8SvoszxCu0WwH7lSVeL4/N/f1JwOMw1G4= +github.com/LiamHaworth/go-tproxy v0.0.0-20190726054950-ef7efd7f24ed/go.mod h1:rA52xkgZwql9LRZXWb2arHEFP6qSR48KY2xOfWzEciQ= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63/go.mod h1:nf+Komq6fVP4SwmKEaVGxHTyQGKREVlwjQKpvOV39yE= github.com/aead/chacha20 v0.0.0-20180709150244-8b13a72661da h1:KjTM2ks9d14ZYCvmHS9iAKVt9AyzRSqNU1qabPih5BY= diff --git a/node.go b/node.go index 9b8d8db..839cb6f 100644 --- a/node.go +++ b/node.go @@ -90,6 +90,7 @@ func ParseNode(s string) (node Node, err error) { case "tun", "tap": // tun/tap device case "ftcp": // fake TCP case "dns": + case "redu", "redirectu": // UDP tproxy default: node.Transport = "tcp" } @@ -105,7 +106,7 @@ func ParseNode(s string) (node Node, err error) { case "sni": case "tcp", "udp", "rtcp", "rudp": // port forwarding case "direct", "remote", "forward": // forwarding - case "redirect": // TCP transparent proxy + case "red", "redirect", "redu", "redirectu": // TCP,UDP transparent proxy case "tun", "tap": // tun/tap device case "ftcp": // fake TCP case "dns", "dot", "doh": diff --git a/redirect.go b/redirect.go index c36db97..afac627 100644 --- a/redirect.go +++ b/redirect.go @@ -8,6 +8,7 @@ import ( "net" "syscall" + "github.com/LiamHaworth/go-tproxy" "github.com/go-log/log" ) @@ -15,7 +16,7 @@ type tcpRedirectHandler struct { options *HandlerOptions } -// TCPRedirectHandler creates a server Handler for TCP redirect server. +// TCPRedirectHandler creates a server Handler for TCP transparent server. func TCPRedirectHandler(opts ...HandlerOption) Handler { h := &tcpRedirectHandler{} h.Init(opts...) @@ -97,3 +98,194 @@ func (h *tcpRedirectHandler) getOriginalDstAddr(conn *net.TCPConn) (addr net.Add } return } + +type udpRedirectHandler struct { + options *HandlerOptions +} + +// UDPRedirectHandler creates a server Handler for UDP transparent server. +func UDPRedirectHandler(opts ...HandlerOption) Handler { + h := &udpRedirectHandler{} + h.Init(opts...) + + return h +} + +func (h *udpRedirectHandler) Init(options ...HandlerOption) { + if h.options == nil { + h.options = &HandlerOptions{} + } + + for _, opt := range options { + opt(h.options) + } +} + +func (h *udpRedirectHandler) Handle(c net.Conn) { + defer c.Close() + + conn, ok := c.(*udpRedirectServerConn) + if !ok { + log.Log("wrong connection type") + return + } + + raddr := conn.DstAddr() + + var cc net.Conn + var err error + if h.options.Chain.IsEmpty() { + cc, err = net.DialUDP("udp", nil, raddr) + if err != nil { + log.Logf("[red-udp] %s - %s : %s", conn.RemoteAddr(), raddr, err) + return + } + } else if h.options.Chain.LastNode().Protocol == "ssu" { + cc, err = h.options.Chain.Dial(raddr.String(), + RetryChainOption(h.options.Retries), + TimeoutChainOption(h.options.Timeout), + ) + if err != nil { + log.Logf("[red-udp] %s - %s : %s", conn.RemoteAddr(), raddr, err) + return + } + } else { + var err error + cc, err = getSOCKS5UDPTunnel(h.options.Chain, nil) + if err != nil { + log.Logf("[red-udp] %s - %s : %s", conn.RemoteAddr(), raddr, err) + return + } + + cc = &udpTunnelConn{Conn: cc, raddr: raddr} + } + defer cc.Close() + + log.Logf("[red-udp] %s <-> %s", conn.RemoteAddr(), raddr) + transport(conn, cc) + log.Logf("[red-udp] %s >-< %s", conn.RemoteAddr(), raddr) +} + +type udpRedirectListener struct { + ln *net.UDPConn + connChan chan net.Conn + errChan chan error + connMap udpConnMap + config *UDPListenConfig +} + +// UDPRedirectListener creates a Listener for UDP transparent proxy server. +func UDPRedirectListener(addr string, cfg *UDPListenConfig) (Listener, error) { + laddr, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return nil, err + } + + ln, err := tproxy.ListenUDP("udp", laddr) + if err != nil { + return nil, err + } + + if cfg == nil { + cfg = &UDPListenConfig{} + } + + backlog := cfg.Backlog + if backlog <= 0 { + backlog = defaultBacklog + } + + l := &udpRedirectListener{ + ln: ln, + connChan: make(chan net.Conn, backlog), + errChan: make(chan error, 1), + config: cfg, + } + go l.listenLoop() + return l, nil +} + +func (l *udpRedirectListener) listenLoop() { + for { + b := make([]byte, mediumBufferSize) + n, raddr, dstAddr, err := tproxy.ReadFromUDP(l.ln, b) + if err != nil { + log.Logf("[red-udp] peer -> %s : %s", l.Addr(), err) + l.Close() + l.errChan <- err + close(l.errChan) + return + } + + conn, ok := l.connMap.Get(raddr.String()) + if !ok { + conn = newUDPServerConn(l.ln, raddr, &udpServerConnConfig{ + ttl: l.config.TTL, + qsize: l.config.QueueSize, + onClose: func() { + l.connMap.Delete(raddr.String()) + log.Logf("[red-udp] %s closed (%d)", raddr, l.connMap.Size()) + }, + }) + + cc := udpRedirectServerConn{ + udpServerConn: conn, + dstAddr: dstAddr, + } + select { + case l.connChan <- cc: + l.connMap.Set(raddr.String(), conn) + log.Logf("[red-udp] %s -> %s (%d)", raddr, l.Addr(), l.connMap.Size()) + default: + conn.Close() + log.Logf("[red-udp] %s - %s: connection queue is full (%d)", + raddr, l.Addr(), cap(l.connChan)) + } + } + + select { + case conn.rChan <- b[:n]: + if Debug { + log.Logf("[red-udp] %s >>> %s : length %d", raddr, l.Addr(), n) + } + default: + log.Logf("[red-udp] %s -> %s : recv queue is full (%d)", + raddr, l.Addr(), cap(conn.rChan)) + } + } +} + +func (l *udpRedirectListener) 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 *udpRedirectListener) Addr() net.Addr { + return l.ln.LocalAddr() +} + +func (l *udpRedirectListener) Close() error { + err := l.ln.Close() + l.connMap.Range(func(k interface{}, v *udpServerConn) bool { + v.Close() + return true + }) + + return err +} + +type udpRedirectServerConn struct { + *udpServerConn + dstAddr *net.UDPAddr +} + +func (c *udpRedirectServerConn) DstAddr() *net.UDPAddr { + return c.dstAddr +}