From 684ccca25253152977c5942c873840a97517e4bf Mon Sep 17 00:00:00 2001 From: "rui.zheng" Date: Sat, 22 Jul 2017 17:55:29 +0800 Subject: [PATCH] add socks4/5 server handlers --- gost/chain.go | 72 +++++- gost/cli/cli.go | 61 +++-- gost/client.go | 14 ++ gost/gost.go | 12 + gost/handler.go | 37 +++ gost/node.go | 1 + gost/server.go | 63 ++--- gost/socks.go | 627 +++++++++++++++++++++++++++++++++++++++++++++++- gost/srv/srv.go | 50 ++-- tcp/server.go | 41 ---- 10 files changed, 837 insertions(+), 141 deletions(-) create mode 100644 gost/handler.go delete mode 100644 tcp/server.go diff --git a/gost/chain.go b/gost/chain.go index b8bc832..2d48e80 100644 --- a/gost/chain.go +++ b/gost/chain.go @@ -1,25 +1,80 @@ package gost import ( + "errors" "net" ) +var ( + // ErrEmptyChain is an error that implies the chain is empty + ErrEmptyChain = errors.New("empty chain") +) + +// Chain is a proxy chain that holds a list of proxy nodes. type Chain struct { - Nodes []Node + nodes []Node } +// NewChain creates a proxy chain with proxy nodes nodes. func NewChain(nodes ...Node) *Chain { return &Chain{ - Nodes: nodes, + nodes: nodes, } } +// Nodes returns the proxy nodes that the chain holds. +func (c *Chain) Nodes() []Node { + return c.nodes +} + +// LastNode returns the last node of the node list. +// If the chain is empty, an empty node is returns. +func (c *Chain) LastNode() Node { + if c.IsEmpty() { + return Node{} + } + return c.nodes[len(c.nodes)-1] +} + +// AddNode appends the node(s) to the chain. +func (c *Chain) AddNode(nodes ...Node) { + c.nodes = append(c.nodes, nodes...) +} + +// IsEmpty checks if the chain is empty. +// An empty chain means that there is no proxy node in the chain. +func (c *Chain) IsEmpty() bool { + return len(c.nodes) == 0 +} + +// Dial connects to the target address addr through the chain. +// If the chain is empty, it will use the net.Dial directly. func (c *Chain) Dial(addr string) (net.Conn, error) { - if len(c.Nodes) == 0 { + if c.IsEmpty() { return net.Dial("tcp", addr) } - nodes := c.Nodes + conn, err := c.Conn() + if err != nil { + return nil, err + } + + cc, err := c.LastNode().Client.Connect(conn, addr) + if err != nil { + conn.Close() + return nil, err + } + return cc, nil +} + +// Conn obtains a handshaked connection to the last node of the chain. +// If the chain is empty, it returns an ErrEmptyChain error. +func (c *Chain) Conn() (net.Conn, error) { + if c.IsEmpty() { + return nil, ErrEmptyChain + } + + nodes := c.nodes conn, err := nodes[0].Client.Dial(nodes[0].Addr) if err != nil { return nil, err @@ -46,14 +101,7 @@ func (c *Chain) Dial(addr string) (net.Conn, error) { conn.Close() return nil, err } - conn = cc } - - cc, err := nodes[len(nodes)-1].Client.Connect( conn, addr) - if err != nil { - conn.Close() - return nil, err - } - return cc, nil + return conn, nil } diff --git a/gost/cli/cli.go b/gost/cli/cli.go index b46539b..d183827 100644 --- a/gost/cli/cli.go +++ b/gost/cli/cli.go @@ -2,11 +2,9 @@ package main import ( "bufio" - "crypto/tls" "log" "net/http" "net/http/httputil" - "net/url" "github.com/ginuerzh/gost/gost" @@ -20,16 +18,48 @@ func init() { func main() { chain := gost.NewChain( /* - // http+ws + // http+tcp gost.Node{ - Addr: "127.0.0.1:8000", + Addr: "127.0.0.1:8080", Client: gost.NewClient( gost.HTTPConnector(url.UserPassword("admin", "123456")), - gost.WSTransporter("127.0.0.1:8000", nil), + gost.TCPTransporter(), ), }, */ + // socks5+tcp + gost.Node{ + Addr: "127.0.0.1:1080", + Client: gost.NewClient( + gost.SOCKS5Connector(url.UserPassword("admin", "123456")), + gost.TCPTransporter(), + ), + }, + + /* + // ss+tcp + gost.Node{ + Addr: "127.0.0.1:8338", + Client: gost.NewClient( + gost.ShadowConnector(url.UserPassword("chacha20", "123456")), + gost.TCPTransporter(), + ), + }, + */ + + /* + // http+ws + gost.Node{ + Addr: "127.0.0.1:8000", + Client: gost.NewClient( + gost.HTTPConnector(url.UserPassword("admin", "123456")), + gost.WSTransporter("127.0.0.1:8000", nil), + ), + }, + */ + + /* // http+wss gost.Node{ Addr: "127.0.0.1:8443", @@ -41,15 +71,6 @@ func main() { ), ), }, - /* - // http+tcp - gost.Node{ - Addr: "127.0.0.1:1080", - Client: gost.NewClient( - gost.HTTPConnector(url.UserPassword("admin", "123456")), - gost.TCPTransporter(), - ), - }, */ /* @@ -62,18 +83,8 @@ func main() { ), }, */ - - /* - // ss+tcp - gost.Node{ - Addr: "127.0.0.1:8338", - Client: gost.NewClient( - gost.ShadowConnector(url.UserPassword("chacha20", "123456")), - gost.TCPTransporter(), - ), - }, - */ ) + conn, err := chain.Dial("localhost:10000") if err != nil { log.Fatal(err) diff --git a/gost/client.go b/gost/client.go index 96ab013..b285e09 100644 --- a/gost/client.go +++ b/gost/client.go @@ -4,11 +4,16 @@ import ( "net" ) +// Client is a proxy client. type Client struct { Connector Connector Transporter Transporter } +// NewClient creates a proxy client. +// A client is divided into two layers: connector and transporter. +// Connector is responsible for connecting to the destination address through this proxy. +// Transporter performs a handshake with this proxy. func NewClient(c Connector, tr Transporter) *Client { return &Client{ Connector: c, @@ -21,10 +26,14 @@ func (c *Client) Dial(addr string) (net.Conn, error) { return net.Dial(c.Transporter.Network(), addr) } +// Handshake performs a handshake with the proxy. +// The conn should be an connection to this proxy. func (c *Client) Handshake(conn net.Conn) (net.Conn, error) { return c.Transporter.Handshake(conn) } +// Connect connects to the address addr via the proxy. +// The conn should be an connection to this proxy. func (c *Client) Connect(conn net.Conn, addr string) (net.Conn, error) { return c.Connector.Connect(conn, addr) } @@ -32,22 +41,27 @@ func (c *Client) Connect(conn net.Conn, addr string) (net.Conn, error) { // DefaultClient is a standard HTTP proxy client var DefaultClient = NewClient(HTTPConnector(nil), TCPTransporter()) +// Dial connects to the address addr via the DefaultClient. func Dial(addr string) (net.Conn, error) { return DefaultClient.Dial(addr) } +// Handshake performs a handshake via the DefaultClient func Handshake(conn net.Conn) (net.Conn, error) { return DefaultClient.Handshake(conn) } +// Connect connects to the address addr via the DefaultClient. func Connect(conn net.Conn, addr string) (net.Conn, error) { return DefaultClient.Connect(conn, addr) } +// Connector is responsible for connecting to the destination address type Connector interface { Connect(conn net.Conn, addr string) (net.Conn, error) } +// Transporter is responsible for handshaking with the proxy server. type Transporter interface { Network() string Handshake(conn net.Conn) (net.Conn, error) diff --git a/gost/gost.go b/gost/gost.go index b3f7974..b8bd2ce 100644 --- a/gost/gost.go +++ b/gost/gost.go @@ -1,6 +1,8 @@ package gost import ( + "time" + "github.com/go-log/log" ) @@ -9,11 +11,21 @@ const Version = "2.4-dev20170722" var Debug bool var ( + TinyBufferSize = 128 SmallBufferSize = 1 * 1024 // 1KB small buffer MediumBufferSize = 8 * 1024 // 8KB medium buffer LargeBufferSize = 32 * 1024 // 32KB large buffer ) +var ( + KeepAliveTime = 180 * time.Second + DialTimeout = 30 * time.Second + ReadTimeout = 90 * time.Second + WriteTimeout = 90 * time.Second + + DefaultTTL = 60 // default udp node TTL in second for udp port forwarding +) + func init() { log.DefaultLogger = &logger{} } diff --git a/gost/handler.go b/gost/handler.go new file mode 100644 index 0000000..5b7612f --- /dev/null +++ b/gost/handler.go @@ -0,0 +1,37 @@ +package gost + +import ( + "crypto/tls" + "net" + "net/url" +) + +type Handler interface { + Handle(net.Conn) +} + +type HandlerOptions struct { + Chain *Chain + Users []*url.Userinfo + TLSConfig *tls.Config +} + +type HandlerOption func(opts *HandlerOptions) + +func ChainHandlerOption(chain *Chain) HandlerOption { + return func(opts *HandlerOptions) { + opts.Chain = chain + } +} + +func UsersHandlerOption(users ...*url.Userinfo) HandlerOption { + return func(opts *HandlerOptions) { + opts.Users = users + } +} + +func TLSConfigHandlerOption(config *tls.Config) HandlerOption { + return func(opts *HandlerOptions) { + opts.TLSConfig = config + } +} diff --git a/gost/node.go b/gost/node.go index 8c0aaa9..10fbd93 100644 --- a/gost/node.go +++ b/gost/node.go @@ -1,5 +1,6 @@ package gost +// Node is a proxy node, mainly used to construct a proxy chain type Node struct { Addr string Protocol string diff --git a/gost/server.go b/gost/server.go index 4ff03b7..eff8606 100644 --- a/gost/server.go +++ b/gost/server.go @@ -1,25 +1,25 @@ package gost import ( - "crypto/tls" "io" "net" "time" - "net/url" - "github.com/go-log/log" ) +// Server is a proxy server. type Server struct { l net.Listener handler Handler } +// Handle sets a handler for the server func (s *Server) Handle(h Handler) { s.handler = h } +// Serve serves as a proxy server. func (s *Server) Serve(l net.Listener) error { defer l.Close() @@ -48,6 +48,7 @@ func (s *Server) Serve(l net.Listener) error { } +// Listener is a proxy server listener, just like a net.Listener. type Listener interface { net.Listener } @@ -64,34 +65,18 @@ func TCPListener(addr string) (Listener, error) { return &tcpListener{Listener: &tcpKeepAliveListener{ln.(*net.TCPListener)}}, nil } -type Handler interface { - Handle(net.Conn) +type tcpKeepAliveListener struct { + *net.TCPListener } -type HandlerOptions struct { - Chain *Chain - Users []*url.Userinfo - TLSConfig *tls.Config -} - -type HandlerOption func(opts *HandlerOptions) - -func ChainHandlerOption(chain *Chain) HandlerOption { - return func(opts *HandlerOptions) { - opts.Chain = chain - } -} - -func UsersHandlerOption(users ...*url.Userinfo) HandlerOption { - return func(opts *HandlerOptions) { - opts.Users = users - } -} - -func TLSConfigHandlerOption(config *tls.Config) HandlerOption { - return func(opts *HandlerOptions) { - opts.TLSConfig = config +func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { + tc, err := ln.AcceptTCP() + if err != nil { + return } + tc.SetKeepAlive(true) + tc.SetKeepAlivePeriod(KeepAliveTime) + return tc, nil } func transport(rw1, rw2 io.ReadWriter) error { @@ -106,23 +91,9 @@ func transport(rw1, rw2 io.ReadWriter) error { errc <- err }() - return <-errc -} - -// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted -// connections. It's used by ListenAndServe and ListenAndServeTLS so -// dead TCP connections (e.g. closing laptop mid-download) eventually -// go away. -type tcpKeepAliveListener struct { - *net.TCPListener -} - -func (ln tcpKeepAliveListener) Accept() (c net.Conn, err error) { - tc, err := ln.AcceptTCP() - if err != nil { - return + err := <-errc + if err != nil && err == io.EOF { + err = nil } - tc.SetKeepAlive(true) - tc.SetKeepAlivePeriod(3 * time.Minute) - return tc, nil + return err } diff --git a/gost/socks.go b/gost/socks.go index 5b0cb06..afb558a 100644 --- a/gost/socks.go +++ b/gost/socks.go @@ -1,12 +1,16 @@ package gost import ( + "bytes" "crypto/tls" "errors" "fmt" "net" "net/url" "strconv" + "time" + + "io" "github.com/ginuerzh/gosocks4" "github.com/ginuerzh/gosocks5" @@ -327,6 +331,7 @@ type socks5Handler struct { options *HandlerOptions } +// SOCKS5Handler returns a SOCKS5 server handler func SOCKS5Handler(opts ...HandlerOption) Handler { options := &HandlerOptions{ Chain: new(Chain), @@ -374,11 +379,11 @@ func (h *socks5Handler) Handle(conn net.Conn) { case gosocks5.CmdUdp: log.Logf("[socks5-udp] %s - %s", conn.RemoteAddr(), req.Addr) - //s.handleUDPRelay(req) + h.handleUDPRelay(conn, req) case CmdUdpTun: log.Logf("[socks5-rudp] %s - %s", conn.RemoteAddr(), req.Addr) - //s.handleUDPTunnel(req) + h.handleUDPTunnel(conn, req) default: log.Log("[socks5] Unrecognized request:", req.Cmd) @@ -421,5 +426,621 @@ func (h *socks5Handler) handleConnect(conn net.Conn, req *gosocks5.Request) { } func (h *socks5Handler) handleBind(conn net.Conn, req *gosocks5.Request) { - // TODO: socks5 bind + if h.options.Chain.IsEmpty() { + + //! if !s.Base.Node.Can("rtcp", addr) { + //! glog.Errorf("Unauthorized to tcp bind to %s", addr) + //! return + //! } + + h.bindOn(conn, req.Addr.String()) + return + } + + cc, err := h.options.Chain.Conn() + if err != nil { + log.Logf("[socks5-bind] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(conn) + if Debug { + log.Logf("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, reply) + } + return + } + + // forward request + // note: this type of request forwarding is defined when starting server, + // so we don't need to authenticate it, as it's as explicit as whitelisting + defer cc.Close() + req.Write(cc) + log.Logf("[socks5-bind] %s <-> %s", conn.RemoteAddr(), cc.RemoteAddr()) + transport(conn, cc) + log.Logf("[socks5-bind] %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) +} + +func (h *socks5Handler) bindOn(conn net.Conn, addr string) { + bindAddr, _ := net.ResolveTCPAddr("tcp", addr) + ln, err := net.ListenTCP("tcp", bindAddr) // strict mode: if the port already in use, it will return error + if err != nil { + log.Logf("[socks5-bind] %s -> %s : %s", conn.RemoteAddr(), addr, err) + gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) + return + } + + socksAddr := toSocksAddr(ln.Addr()) + // Issue: may not reachable when host has multi-interface + socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) + if err := reply.Write(conn); err != nil { + log.Logf("[socks5-bind] %s <- %s : %s", conn.RemoteAddr(), addr, err) + ln.Close() + return + } + if Debug { + log.Logf("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), addr, reply) + } + log.Logf("[socks5-bind] %s - %s BIND ON %s OK", conn.RemoteAddr(), addr, socksAddr) + + var pconn net.Conn + accept := func() <-chan error { + errc := make(chan error, 1) + + go func() { + defer close(errc) + defer ln.Close() + + c, err := ln.AcceptTCP() + if err != nil { + errc <- err + return + } + pconn = c + }() + + return errc + } + + pc1, pc2 := net.Pipe() + pipe := func() <-chan error { + errc := make(chan error, 1) + + go func() { + defer close(errc) + defer pc1.Close() + + errc <- transport(conn, pc1) + }() + + return errc + } + + defer pc2.Close() + + for { + select { + case err := <-accept(): + if err != nil || pconn == nil { + log.Logf("[socks5-bind] %s <- %s : %v", conn.RemoteAddr(), addr, err) + return + } + defer pconn.Close() + + reply := gosocks5.NewReply(gosocks5.Succeeded, toSocksAddr(pconn.RemoteAddr())) + if err := reply.Write(pc2); err != nil { + log.Logf("[socks5-bind] %s <- %s : %v", conn.RemoteAddr(), addr, err) + } + if Debug { + log.Logf("[socks5-bind] %s <- %s\n%s", conn.RemoteAddr(), addr, reply) + } + log.Logf("[socks5-bind] %s <- %s PEER %s ACCEPTED", conn.RemoteAddr(), socksAddr, pconn.RemoteAddr()) + + log.Logf("[socks5-bind] %s <-> %s", conn.RemoteAddr(), pconn.RemoteAddr()) + if err = transport(pc2, pconn); err != nil { + log.Logf("[socks5-bind] %s - %s : %v", conn.RemoteAddr(), pconn.RemoteAddr(), err) + } + log.Logf("[socks5-bind] %s >-< %s", conn.RemoteAddr(), pconn.RemoteAddr()) + return + case err := <-pipe(): + if err != nil { + log.Logf("[socks5-bind] %s -> %s : %v", conn.RemoteAddr(), addr, err) + } + ln.Close() + return + } + } +} + +func (h *socks5Handler) handleUDPRelay(conn net.Conn, req *gosocks5.Request) { + //! addr := req.Addr.String() + //! + //! if !s.Base.Node.Can("udp", addr) { + //! glog.Errorf("Unauthorized to udp connect to %s", addr) + //! rep := gosocks5.NewReply(gosocks5.NotAllowed, nil) + //! rep.Write(s.conn) + //! return + //! } + + relay, err := net.ListenUDP("udp", nil) + if err != nil { + log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), relay.LocalAddr(), err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(conn) + if Debug { + log.Logf("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), relay.LocalAddr(), reply) + } + return + } + defer relay.Close() + + socksAddr := toSocksAddr(relay.LocalAddr()) + socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) // replace the IP to the out-going interface's + reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) + if err := reply.Write(conn); err != nil { + log.Logf("[socks5-udp] %s <- %s : %s", conn.RemoteAddr(), relay.LocalAddr(), err) + return + } + if Debug { + log.Logf("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), reply.Addr, reply) + } + log.Logf("[socks5-udp] %s - %s BIND ON %s OK", conn.RemoteAddr(), relay.LocalAddr(), socksAddr) + + // serve as standard socks5 udp relay local <-> remote + if h.options.Chain.IsEmpty() { + peer, er := net.ListenUDP("udp", nil) + if er != nil { + log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), socksAddr, er) + return + } + defer peer.Close() + + go h.transportUDP(relay, peer) + log.Logf("[socks5-udp] %s <-> %s", conn.RemoteAddr(), socksAddr) + if err := h.discardClientData(conn); err != nil { + log.Logf("[socks5-udp] %s - %s : %s", conn.RemoteAddr(), socksAddr, err) + } + log.Logf("[socks5-udp] %s >-< %s", conn.RemoteAddr(), socksAddr) + return + } + + cc, err := h.options.Chain.Conn() + // connection error + if err != nil { + log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), socksAddr, err) + return + } + + // forward udp local <-> tunnel + defer cc.Close() + + cc.SetWriteDeadline(time.Now().Add(WriteTimeout)) + r := gosocks5.NewRequest(CmdUdpTun, nil) + if err := r.Write(cc); err != nil { + log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), cc.RemoteAddr(), err) + return + } + cc.SetWriteDeadline(time.Time{}) + if Debug { + log.Logf("[socks5-udp] %s -> %s\n%s", conn.RemoteAddr(), cc.RemoteAddr(), r) + } + cc.SetReadDeadline(time.Now().Add(ReadTimeout)) + reply, err = gosocks5.ReadReply(cc) + if err != nil { + log.Logf("[socks5-udp] %s -> %s : %s", conn.RemoteAddr(), cc.RemoteAddr(), err) + return + } + if Debug { + log.Logf("[socks5-udp] %s <- %s\n%s", conn.RemoteAddr(), cc.RemoteAddr(), reply) + } + + if reply.Rep != gosocks5.Succeeded { + log.Logf("[socks5-udp] %s <- %s : udp associate failed", conn.RemoteAddr(), cc.RemoteAddr()) + return + } + cc.SetReadDeadline(time.Time{}) + log.Logf("[socks5-udp] %s <-> %s [tun: %s]", conn.RemoteAddr(), socksAddr, reply.Addr) + + go h.tunnelClientUDP(relay, cc) + log.Logf("[socks5-udp] %s <-> %s", conn.RemoteAddr(), socksAddr) + if err := h.discardClientData(conn); err != nil { + log.Logf("[socks5-udp] %s - %s : %s", conn.RemoteAddr(), socksAddr, err) + } + log.Logf("[socks5-udp] %s >-< %s", conn.RemoteAddr(), socksAddr) +} + +func (s *socks5Handler) discardClientData(conn net.Conn) (err error) { + b := make([]byte, TinyBufferSize) + n := 0 + for { + n, err = conn.Read(b) // discard any data from tcp connection + if err != nil { + if err == io.EOF { // disconnect normally + err = nil + } + break // client disconnected + } + log.Logf("[socks5-udp] read %d UNEXPECTED TCP data from client", n) + } + return +} + +func (s *socks5Handler) transportUDP(relay, peer *net.UDPConn) (err error) { + errc := make(chan error, 2) + + var clientAddr *net.UDPAddr + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, laddr, err := relay.ReadFromUDP(b) + if err != nil { + errc <- err + return + } + if clientAddr == nil { + clientAddr = laddr + } + dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n])) + if err != nil { + errc <- err + return + } + + raddr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) + if err != nil { + continue // drop silently + } + if _, err := peer.WriteToUDP(dgram.Data, raddr); err != nil { + errc <- err + return + } + if Debug { + log.Logf("[socks5-udp] %s >>> %s length: %d", relay.LocalAddr(), raddr, len(dgram.Data)) + } + } + }() + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, raddr, err := peer.ReadFromUDP(b) + if err != nil { + errc <- err + return + } + if clientAddr == nil { + continue + } + buf := bytes.Buffer{} + dgram := gosocks5.NewUDPDatagram(gosocks5.NewUDPHeader(0, 0, toSocksAddr(raddr)), b[:n]) + dgram.Write(&buf) + if _, err := relay.WriteToUDP(buf.Bytes(), clientAddr); err != nil { + errc <- err + return + } + if Debug { + log.Logf("[socks5-udp] %s <<< %s length: %d", relay.LocalAddr(), raddr, len(dgram.Data)) + } + } + }() + + select { + case err = <-errc: + //log.Println("w exit", err) + } + + return +} + +func (h *socks5Handler) tunnelClientUDP(uc *net.UDPConn, cc net.Conn) (err error) { + errc := make(chan error, 2) + + var clientAddr *net.UDPAddr + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, addr, err := uc.ReadFromUDP(b) + if err != nil { + log.Logf("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), addr, err) + errc <- err + return + } + + // glog.V(LDEBUG).Infof("read udp %d, % #x", n, b[:n]) + // pipe from relay to tunnel + dgram, err := gosocks5.ReadUDPDatagram(bytes.NewReader(b[:n])) + if err != nil { + errc <- err + return + } + if clientAddr == nil { + clientAddr = addr + } + dgram.Header.Rsv = uint16(len(dgram.Data)) + if err := dgram.Write(cc); err != nil { + errc <- err + return + } + if Debug { + log.Logf("[udp-tun] %s >>> %s length: %d", uc.LocalAddr(), dgram.Header.Addr, len(dgram.Data)) + } + } + }() + + go func() { + for { + dgram, err := gosocks5.ReadUDPDatagram(cc) + if err != nil { + log.Logf("[udp-tun] %s -> 0 : %s", cc.RemoteAddr(), err) + errc <- err + return + } + + // pipe from tunnel to relay + if clientAddr == nil { + continue + } + dgram.Header.Rsv = 0 + + buf := bytes.Buffer{} + dgram.Write(&buf) + if _, err := uc.WriteToUDP(buf.Bytes(), clientAddr); err != nil { + errc <- err + return + } + if Debug { + log.Logf("[udp-tun] %s <<< %s length: %d", uc.LocalAddr(), dgram.Header.Addr, len(dgram.Data)) + } + } + }() + + select { + case err = <-errc: + } + + return +} + +func (h *socks5Handler) handleUDPTunnel(conn net.Conn, req *gosocks5.Request) { + // serve tunnel udp, tunnel <-> remote, handle tunnel udp request + if h.options.Chain.IsEmpty() { + addr := req.Addr.String() + + //! if !s.Base.Node.Can("rudp", addr) { + //! glog.Errorf("Unauthorized to udp bind to %s", addr) + //! return + //! } + + bindAddr, _ := net.ResolveUDPAddr("udp", addr) + uc, err := net.ListenUDP("udp", bindAddr) + if err != nil { + log.Logf("[socks5-rudp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + return + } + defer uc.Close() + + socksAddr := toSocksAddr(uc.LocalAddr()) + socksAddr.Host, _, _ = net.SplitHostPort(conn.LocalAddr().String()) + reply := gosocks5.NewReply(gosocks5.Succeeded, socksAddr) + if err := reply.Write(conn); err != nil { + log.Logf("[socks5-rudp] %s <- %s : %s", conn.RemoteAddr(), socksAddr, err) + return + } + if Debug { + log.Logf("[socks5-rudp] %s <- %s\n%s", conn.RemoteAddr(), socksAddr, reply) + } + log.Logf("[socks5-rudp] %s <-> %s", conn.RemoteAddr(), socksAddr) + h.tunnelServerUDP(conn, uc) + log.Logf("[socks5-rudp] %s >-< %s", conn.RemoteAddr(), socksAddr) + return + } + + cc, err := h.options.Chain.Conn() + // connection error + if err != nil { + log.Logf("[socks5-rudp] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + reply := gosocks5.NewReply(gosocks5.Failure, nil) + reply.Write(conn) + log.Logf("[socks5-rudp] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, reply) + return + } + + defer cc.Close() + + // tunnel <-> tunnel, direct forwarding + // note: this type of request forwarding is defined when starting server + // so we don't need to authenticate it, as it's as explicit as whitelisting + req.Write(cc) + + log.Logf("[socks5-rudp] %s <-> %s [tun]", conn.RemoteAddr(), cc.RemoteAddr()) + transport(conn, cc) + log.Logf("[socks5-rudp] %s >-< %s [tun]", conn.RemoteAddr(), cc.RemoteAddr()) +} + +func (h *socks5Handler) tunnelServerUDP(cc net.Conn, uc *net.UDPConn) (err error) { + errc := make(chan error, 2) + + go func() { + b := make([]byte, LargeBufferSize) + + for { + n, addr, err := uc.ReadFromUDP(b) + if err != nil { + log.Logf("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), addr, err) + errc <- err + return + } + + // pipe from peer to tunnel + dgram := gosocks5.NewUDPDatagram( + gosocks5.NewUDPHeader(uint16(n), 0, toSocksAddr(addr)), b[:n]) + if err := dgram.Write(cc); err != nil { + log.Logf("[udp-tun] %s <- %s : %s", cc.RemoteAddr(), dgram.Header.Addr, err) + errc <- err + return + } + if Debug { + log.Logf("[udp-tun] %s <<< %s length: %d", cc.RemoteAddr(), dgram.Header.Addr, len(dgram.Data)) + } + } + }() + + go func() { + for { + dgram, err := gosocks5.ReadUDPDatagram(cc) + if err != nil { + log.Logf("[udp-tun] %s -> 0 : %s", cc.RemoteAddr(), err) + errc <- err + return + } + + // pipe from tunnel to peer + addr, err := net.ResolveUDPAddr("udp", dgram.Header.Addr.String()) + if err != nil { + continue // drop silently + } + if _, err := uc.WriteToUDP(dgram.Data, addr); err != nil { + log.Logf("[udp-tun] %s -> %s : %s", cc.RemoteAddr(), addr, err) + errc <- err + return + } + if Debug { + log.Logf("[udp-tun] %s >>> %s length: %d", cc.RemoteAddr(), addr, len(dgram.Data)) + } + } + }() + + select { + case err = <-errc: + } + + return +} + +func toSocksAddr(addr net.Addr) *gosocks5.Addr { + host := "0.0.0.0" + port := 0 + if addr != nil { + h, p, _ := net.SplitHostPort(addr.String()) + host = h + port, _ = strconv.Atoi(p) + } + return &gosocks5.Addr{ + Type: gosocks5.AddrIPv4, + Host: host, + Port: uint16(port), + } +} + +type socks4Handler struct { + options *HandlerOptions +} + +// SOCKS4Handler returns a SOCKS4 server handler +func SOCKS4Handler(opts ...HandlerOption) Handler { + options := &HandlerOptions{ + Chain: new(Chain), + } + for _, opt := range opts { + opt(options) + } + return &socks4Handler{ + options: options, + } +} + +func (h *socks4Handler) Handle(conn net.Conn) { + req, err := gosocks4.ReadRequest(conn) + if err != nil { + log.Log("[socks4]", err) + return + } + + if Debug { + log.Logf("[socks4] %s -> %s\n%s", conn.RemoteAddr(), req.Addr, req) + } + + switch req.Cmd { + case gosocks4.CmdConnect: + log.Logf("[socks4-connect] %s -> %s", conn.RemoteAddr(), req.Addr) + h.handleConnect(conn, req) + + case gosocks4.CmdBind: + log.Logf("[socks4-bind] %s - %s", conn.RemoteAddr(), req.Addr) + h.handleBind(conn, req) + + default: + log.Logf("[socks4] Unrecognized request: %d", req.Cmd) + } +} + +func (h *socks4Handler) handleConnect(conn net.Conn, req *gosocks4.Request) { + addr := req.Addr.String() + + //! if !s.Base.Node.Can("tcp", addr) { + //! glog.Errorf("Unauthorized to tcp connect to %s", addr) + //! rep := gosocks5.NewReply(gosocks4.Rejected, nil) + //! rep.Write(s.conn) + //! return + //! } + + cc, err := h.options.Chain.Dial(addr) + if err != nil { + log.Logf("[socks4-connect] %s -> %s : %s", conn.RemoteAddr(), req.Addr, err) + rep := gosocks4.NewReply(gosocks4.Failed, nil) + rep.Write(conn) + if Debug { + log.Logf("[socks4-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + } + return + } + defer cc.Close() + + rep := gosocks4.NewReply(gosocks4.Granted, nil) + if err := rep.Write(conn); err != nil { + log.Logf("[socks4-connect] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + return + } + if Debug { + log.Logf("[socks4-connect] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, rep) + } + + log.Logf("[socks4-connect] %s <-> %s", conn.RemoteAddr(), req.Addr) + transport(conn, cc) + log.Logf("[socks4-connect] %s >-< %s", conn.RemoteAddr(), req.Addr) +} + +func (h *socks4Handler) handleBind(conn net.Conn, req *gosocks4.Request) { + // TODO: serve socks4 bind + if h.options.Chain.IsEmpty() { + reply := gosocks4.NewReply(gosocks4.Rejected, nil) + reply.Write(conn) + if Debug { + log.Logf("[socks4-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, reply) + } + return + } + + cc, err := h.options.Chain.Conn() + // connection error + if err != nil && err != ErrEmptyChain { + log.Logf("[socks4-bind] %s <- %s : %s", conn.RemoteAddr(), req.Addr, err) + reply := gosocks4.NewReply(gosocks4.Failed, nil) + reply.Write(conn) + if Debug { + log.Logf("[socks4-bind] %s <- %s\n%s", conn.RemoteAddr(), req.Addr, reply) + } + return + } + + defer cc.Close() + // forward request + req.Write(cc) + + log.Logf("[socks4-bind] %s <-> %s", conn.RemoteAddr(), cc.RemoteAddr()) + transport(conn, cc) + log.Logf("[socks4-bind] %s >-< %s", conn.RemoteAddr(), cc.RemoteAddr()) } diff --git a/gost/srv/srv.go b/gost/srv/srv.go index 15ce218..91fb036 100644 --- a/gost/srv/srv.go +++ b/gost/srv/srv.go @@ -21,6 +21,8 @@ func main() { wg.Add(1) go httpServer(&wg) wg.Add(1) + go socks5Server(&wg) + wg.Add(1) go tlsServer(&wg) wg.Add(1) go shadowServer(&wg) @@ -38,6 +40,26 @@ func httpServer(wg *sync.WaitGroup) { s.Handle(gost.HTTPHandler( gost.UsersHandlerOption(url.UserPassword("admin", "123456")), )) + ln, err := gost.TCPListener(":8080") + if err != nil { + log.Fatal(err) + } + log.Fatal(s.Serve(ln)) +} + +func socks5Server(wg *sync.WaitGroup) { + defer wg.Done() + + cert, err := tls.LoadX509KeyPair("cert.pem", "key.pem") + if err != nil { + log.Fatal(err) + } + + s := &gost.Server{} + s.Handle(gost.SOCKS5Handler( + gost.UsersHandlerOption(url.UserPassword("admin", "123456")), + gost.TLSConfigHandlerOption(&tls.Config{Certificates: []tls.Certificate{cert}}), + )) ln, err := gost.TCPListener(":1080") if err != nil { log.Fatal(err) @@ -45,6 +67,20 @@ func httpServer(wg *sync.WaitGroup) { log.Fatal(s.Serve(ln)) } +func shadowServer(wg *sync.WaitGroup) { + defer wg.Done() + + s := &gost.Server{} + s.Handle(gost.ShadowHandler( + gost.UsersHandlerOption(url.UserPassword("chacha20", "123456")), + )) + ln, err := gost.TCPListener(":8338") + if err != nil { + log.Fatal(err) + } + log.Fatal(s.Serve(ln)) +} + func tlsServer(wg *sync.WaitGroup) { defer wg.Done() @@ -95,17 +131,3 @@ func wssServer(wg *sync.WaitGroup) { } log.Fatal(s.Serve(ln)) } - -func shadowServer(wg *sync.WaitGroup) { - defer wg.Done() - - s := &gost.Server{} - s.Handle(gost.ShadowHandler( - gost.UsersHandlerOption(url.UserPassword("chacha20", "123456")), - )) - ln, err := gost.TCPListener(":8338") - if err != nil { - log.Fatal(err) - } - log.Fatal(s.Serve(ln)) -} diff --git a/tcp/server.go b/tcp/server.go deleted file mode 100644 index d9dc8f9..0000000 --- a/tcp/server.go +++ /dev/null @@ -1,41 +0,0 @@ -package tcp - -import ( - "net" - - "github.com/ginuerzh/gost" - "github.com/ginuerzh/gost/server" -) - -type nodeServer struct { - options *server.Server -} - -func (s *nodeServer) Init(opts ...server.Option) { - for _, opt := range opts { - opt(s.options) - } -} - -func (s *nodeServer) Options() *server.Options { - return s.options -} - -func (s *nodeServer) Run() error { - ln, err := net.Listen("tcp", s.options.Addr) - if err != nil { - return err - } - defer ln.Close() - - for { - conn, err := ln.Accept() - if err != nil { - return err - } - go func(c net.Conn) { - defer c.Close() - gost.DefaultHandler(s).Handle(conn) - }(conn) - } -}