From afb424193ebd039d6b11609c19f33fa95a54ea78 Mon Sep 17 00:00:00 2001 From: zhengrui Date: Sat, 3 Nov 2018 18:52:23 +0800 Subject: [PATCH] fix server example --- client/client.go | 24 ++++++ client/selector.go | 52 ++++++++++++ examples/server.go | 112 ++++---------------------- server/handler.go | 195 +++++++++++++++++++++++++++++++++++++++++++++ server/selector.go | 70 ++++++++++++++++ server/server.go | 66 +++++++++++++++ 6 files changed, 424 insertions(+), 95 deletions(-) create mode 100644 client/client.go create mode 100644 client/selector.go create mode 100644 server/handler.go create mode 100644 server/selector.go create mode 100644 server/server.go diff --git a/client/client.go b/client/client.go new file mode 100644 index 0000000..91247ad --- /dev/null +++ b/client/client.go @@ -0,0 +1,24 @@ +package client + +import ( + "net" + "time" + + "github.com/ginuerzh/gosocks5" +) + +type Client struct { + selector gosocks5.Selector +} + +func (c *Client) Dial(addr string, options ...DialOption) (net.Conn, error) { + return nil, nil +} + +// DialOptions describes the options for Transporter.Dial. +type DialOptions struct { + Timeout time.Duration +} + +// DialOption allows a common way to set dial options. +type DialOption func(opts *DialOptions) diff --git a/client/selector.go b/client/selector.go new file mode 100644 index 0000000..cab92c7 --- /dev/null +++ b/client/selector.go @@ -0,0 +1,52 @@ +package client + +import ( + "net" + "net/url" + + "github.com/ginuerzh/gosocks5" +) + +type clientSelector struct { + methods []uint8 + User *url.Userinfo +} + +func (selector *clientSelector) Methods() []uint8 { + return selector.methods +} + +func (selector *clientSelector) AddMethod(methods ...uint8) { + selector.methods = append(selector.methods, methods...) +} + +func (selector *clientSelector) Select(methods ...uint8) (method uint8) { + return +} + +func (selector *clientSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { + switch method { + case gosocks5.MethodUserPass: + var username, password string + if selector.User != nil { + username = selector.User.Username() + password, _ = selector.User.Password() + } + + req := gosocks5.NewUserPassRequest(gosocks5.UserPassVer, username, password) + if err := req.Write(conn); err != nil { + return nil, err + } + resp, err := gosocks5.ReadUserPassResponse(conn) + if err != nil { + return nil, err + } + if resp.Status != gosocks5.Succeeded { + return nil, gosocks5.ErrAuthFailure + } + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + } + + return conn, nil +} diff --git a/examples/server.go b/examples/server.go index 5b76aef..8169098 100644 --- a/examples/server.go +++ b/examples/server.go @@ -1,110 +1,32 @@ package main import ( - "github.com/ginuerzh/gosocks5" - "io" + "flag" "log" "net" + + "github.com/ginuerzh/gosocks5/server" ) +var ( + laddr string +) + +func init() { + flag.StringVar(&laddr, "l", ":1080", "SOCKS5 server address") + flag.Parse() +} + func main() { log.SetFlags(log.Lshortfile | log.LstdFlags) - server := &gosocks5.Server{ - Addr: ":9999", - Handle: handle, - } - - server.ListenAndServe() -} - -func handle(conn net.Conn, method uint8) error { - defer conn.Close() - - req, err := gosocks5.ReadRequest(conn) + ln, err := net.Listen("tcp", laddr) if err != nil { - log.Println(err) - return err + log.Fatal(err) } - tconn, err := Connect(req.Addr.String()) - if err != nil { - log.Println(err) - return err - } - defer tconn.Close() - - rep := gosocks5.NewReply(gosocks5.Succeeded, nil) - if err := rep.Write(conn); err != nil { - return err + srv := &server.Server{ + Listener: ln, } - if err := Transport(conn, tconn); err != nil { - log.Println(err) - } - - return nil -} - -func Connect(addr string) (net.Conn, error) { - taddr, err := net.ResolveTCPAddr("tcp", addr) - if err != nil { - log.Println(err) - return nil, err - } - return net.DialTCP("tcp", nil, taddr) -} - -func Copy(dst io.Writer, src io.Reader) (written int64, err error) { - buf := make([]byte, 32*1024) - for { - nr, er := src.Read(buf) - //log.Println("cp r", nr, er) - if nr > 0 { - nw, ew := dst.Write(buf[:nr]) - //log.Println("cp w", nw, ew) - if nw > 0 { - written += int64(nw) - } - if ew != nil { - err = ew - break - } - /* - if nr != nw { - err = io.ErrShortWrite - break - } - */ - } - if er == io.EOF { - break - } - if er != nil { - err = er - break - } - } - return -} - -func Pipe(src io.Reader, dst io.Writer, c chan<- error) { - _, err := Copy(dst, src) - c <- err -} - -func Transport(conn, conn2 net.Conn) (err error) { - rChan := make(chan error, 1) - wChan := make(chan error, 1) - - go Pipe(conn, conn2, wChan) - go Pipe(conn2, conn, rChan) - - select { - case err = <-wChan: - //log.Println("w exit", err) - case err = <-rChan: - //log.Println("r exit", err) - } - - return + log.Fatal(srv.Serve(server.DefaultHandler)) } diff --git a/server/handler.go b/server/handler.go new file mode 100644 index 0000000..798bc37 --- /dev/null +++ b/server/handler.go @@ -0,0 +1,195 @@ +package server + +import ( + "fmt" + "io" + "net" + "strconv" + "sync" + + "github.com/ginuerzh/gosocks5" +) + +var ( + // DefaultHandler is the default server handler. + DefaultHandler Handler +) + +func init() { + DefaultHandler = &serverHandler{ + selector: &serverSelector{}, + } +} + +// Handler is interface for server handler. +type Handler interface { + Handle(conn net.Conn) error +} + +type serverHandler struct { + selector gosocks5.Selector +} + +func (h *serverHandler) Handle(conn net.Conn) error { + conn = gosocks5.ServerConn(conn, h.selector) + req, err := gosocks5.ReadRequest(conn) + if err != nil { + return err + } + + switch req.Cmd { + case gosocks5.CmdConnect: + return h.handleConnect(conn, req) + + case gosocks5.CmdBind: + return h.handleBind(conn, req) + + // case gosocks5.CmdUdp: + // h.handleUDPRelay(conn, req) + + default: + return fmt.Errorf("%d: unsupported command", gosocks5.CmdUnsupported) + } +} + +func (h *serverHandler) handleConnect(conn net.Conn, req *gosocks5.Request) error { + cc, err := net.Dial("tcp", req.Addr.String()) + if err != nil { + rep := gosocks5.NewReply(gosocks5.HostUnreachable, nil) + rep.Write(conn) + return err + } + defer cc.Close() + + rep := gosocks5.NewReply(gosocks5.Succeeded, nil) + if err := rep.Write(conn); err != nil { + return err + } + + return transport(conn, cc) +} + +var ( + trPool = sync.Pool{ + New: func() interface{} { + return make([]byte, 1500) + }, + } +) + +func (h *serverHandler) handleBind(conn net.Conn, req *gosocks5.Request) error { + addr := req.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 { + gosocks5.NewReply(gosocks5.Failure, nil).Write(conn) + return err + } + + 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 { + ln.Close() + return err + } + + 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 { + return err + } + defer pconn.Close() + + reply := gosocks5.NewReply(gosocks5.Succeeded, toSocksAddr(pconn.RemoteAddr())) + if err := reply.Write(pc2); err != nil { + return err + } + + if err = transport(pc2, pconn); err != nil { + } + + return err + case err := <-pipe(): + ln.Close() + return err + } + } +} + +func transport(rw1, rw2 io.ReadWriter) error { + errc := make(chan error, 1) + go func() { + buf := trPool.Get().([]byte) + defer trPool.Put(buf) + + _, err := io.CopyBuffer(rw1, rw2, buf) + errc <- err + }() + + go func() { + buf := trPool.Get().([]byte) + defer trPool.Put(buf) + + _, err := io.CopyBuffer(rw2, rw1, buf) + errc <- err + }() + + err := <-errc + if err != nil && err == io.EOF { + err = nil + } + return err +} + +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), + } +} diff --git a/server/selector.go b/server/selector.go new file mode 100644 index 0000000..149fbe4 --- /dev/null +++ b/server/selector.go @@ -0,0 +1,70 @@ +package server + +import ( + "net" + "net/url" + + "github.com/ginuerzh/gosocks5" +) + +type serverSelector struct { + methods []uint8 + Users []*url.Userinfo +} + +func (selector *serverSelector) Methods() []uint8 { + return selector.methods +} + +func (selector *serverSelector) AddMethod(methods ...uint8) { + selector.methods = append(selector.methods, methods...) +} + +func (selector *serverSelector) Select(methods ...uint8) (method uint8) { + method = gosocks5.MethodNoAuth + + // when user/pass is set, auth is mandatory + if len(selector.Users) > 0 { + method = gosocks5.MethodUserPass + } + + return +} + +func (selector *serverSelector) OnSelected(method uint8, conn net.Conn) (net.Conn, error) { + switch method { + case gosocks5.MethodUserPass: + req, err := gosocks5.ReadUserPassRequest(conn) + if err != nil { + return nil, err + } + + valid := false + for _, user := range selector.Users { + username := user.Username() + password, _ := user.Password() + if (req.Username == username && req.Password == password) || + (req.Username == username && password == "") || + (username == "" && req.Password == password) { + valid = true + break + } + } + if len(selector.Users) > 0 && !valid { + resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Failure) + if err := resp.Write(conn); err != nil { + return nil, err + } + return nil, gosocks5.ErrAuthFailure + } + + resp := gosocks5.NewUserPassResponse(gosocks5.UserPassVer, gosocks5.Succeeded) + if err := resp.Write(conn); err != nil { + return nil, err + } + case gosocks5.MethodNoAcceptable: + return nil, gosocks5.ErrBadMethod + } + + return conn, nil +} diff --git a/server/server.go b/server/server.go new file mode 100644 index 0000000..693a3f7 --- /dev/null +++ b/server/server.go @@ -0,0 +1,66 @@ +package server + +import ( + "net" + "time" +) + +// Server is a SOCKS5 server. +type Server struct { + Listener net.Listener +} + +// Addr returns the address of the server +func (s *Server) Addr() net.Addr { + return s.Listener.Addr() +} + +// Serve serves incoming requests. +func (s *Server) Serve(h Handler, options ...ServerOption) error { + if s.Listener == nil { + ln, err := net.ListenTCP("tcp", nil) + if err != nil { + return err + } + s.Listener = ln + } + if h == nil { + h = DefaultHandler + } + + l := s.Listener + var tempDelay time.Duration + for { + conn, e := l.Accept() + if e != nil { + if ne, ok := e.(net.Error); ok && ne.Temporary() { + if tempDelay == 0 { + tempDelay = 5 * time.Millisecond + } else { + tempDelay *= 2 + } + if max := 1 * time.Second; tempDelay > max { + tempDelay = max + } + time.Sleep(tempDelay) + continue + } + return e + } + tempDelay = 0 + + go h.Handle(conn) + } +} + +// Close closes the socks5 server +func (s *Server) Close() error { + return s.Listener.Close() +} + +// ServerOptions is options for server. +type ServerOptions struct { +} + +// ServerOption allows a common way to set server options. +type ServerOption func(opts *ServerOptions)