diff --git a/server/socks5/socks5_request_handle.go b/server/socks5/socks5_request_handle.go new file mode 100644 index 0000000..40cff4b --- /dev/null +++ b/server/socks5/socks5_request_handle.go @@ -0,0 +1,171 @@ +package socks5 + +import ( + "context" + "encoding/binary" + "errors" + "github.com/cnlh/nps/core" + "io" + "net" + "strconv" +) + +type Request struct { + clientConn net.Conn + ctx context.Context +} + +const ( + ipV4 = 1 + domainName = 3 + ipV6 = 4 + connectMethod = 1 + bindMethod = 2 + associateMethod = 3 + // The maximum packet size of any udp Associate packet, based on ethernet's max size, + // minus the IP and UDP headerrequest. IPv4 has a 20 byte header, UDP adds an + // additional 4 byterequest. This is a total overhead of 24 byterequest. Ethernet's + // max packet size is 1500 bytes, 1500 - 24 = 1476. + maxUDPPacketSize = 1476 + commandNotSupported = 7 + addrTypeNotSupported = 8 +) + +func (request *Request) GetConfigName() []*core.Config { + c := make([]*core.Config, 0) + c = append(c, &core.Config{ConfigName: "socks5_check_request", Description: "need check the permission?"}) + c = append(c, &core.Config{ConfigName: "socks5_request_username", Description: "auth username"}) + c = append(c, &core.Config{ConfigName: "socks5_request_password", Description: "auth password"}) + return nil +} + +func (request *Request) GetStage() core.Stage { + return core.STAGE_RUN +} + +func (request *Request) Start(ctx context.Context, config map[string]string) error { + return nil +} +func (request *Request) End(ctx context.Context, config map[string]string) error { + return nil +} + +func (request *Request) Run(ctx context.Context, config map[string]string) error { + clientCtxConn := ctx.Value("clientConn") + if clientCtxConn == nil { + return errors.New("the client request.clientConnection is not exist") + } + request.clientConn = clientCtxConn.(net.Conn) + request.ctx = ctx + + /* + The SOCKS request is formed as follows: + +----+-----+-------+------+----------+----------+ + |VER | CMD | RSV | ATYP | DST.ADDR | DST.PORT | + +----+-----+-------+------+----------+----------+ + | 1 | 1 | X'00' | 1 | Variable | 2 | + +----+-----+-------+------+----------+----------+ + */ + header := make([]byte, 3) + + _, err := io.ReadFull(request.clientConn, header) + + if err != nil { + return errors.New("illegal request" + err.Error()) + } + + switch header[1] { + case connectMethod: + context.WithValue(request.ctx, "socks5_target_type", "tcp") + return request.doConnect() + case bindMethod: + return request.handleBind() + case associateMethod: + context.WithValue(request.ctx, "socks5_target_type", "udp") + return request.handleUDP() + default: + request.sendReply(commandNotSupported) + return errors.New("command not supported") + } + return nil +} + +func (request *Request) sendReply(rep uint8) error { + reply := []byte{ + 5, + rep, + 0, + 1, + } + + localAddr := request.clientConn.LocalAddr().String() + localHost, localPort, _ := net.SplitHostPort(localAddr) + ipBytes := net.ParseIP(localHost).To4() + nPort, _ := strconv.Atoi(localPort) + reply = append(reply, ipBytes...) + portBytes := make([]byte, 2) + binary.BigEndian.PutUint16(portBytes, uint16(nPort)) + reply = append(reply, portBytes...) + _, err := request.clientConn.Write(reply) + return err +} + +//do conn +func (request *Request) doConnect() error { + addrType := make([]byte, 1) + request.clientConn.Read(addrType) + + var host string + switch addrType[0] { + case ipV4: + ipv4 := make(net.IP, net.IPv4len) + request.clientConn.Read(ipv4) + host = ipv4.String() + case ipV6: + ipv6 := make(net.IP, net.IPv6len) + request.clientConn.Read(ipv6) + host = ipv6.String() + case domainName: + var domainLen uint8 + binary.Read(request.clientConn, binary.BigEndian, &domainLen) + domain := make([]byte, domainLen) + request.clientConn.Read(domain) + host = string(domain) + default: + request.sendReply(addrTypeNotSupported) + return errors.New("target address type is not support") + } + + var port uint16 + binary.Read(request.clientConn, binary.BigEndian, &port) + + context.WithValue(request.ctx, "socks5_target_host", host) + context.WithValue(request.ctx, "socks5_target_port", port) + return nil +} + +// passive mode +func (request *Request) handleBind() error { + return nil +} + +//udp +func (request *Request) handleUDP() error { + /* + +----+------+------+----------+----------+----------+ + |RSV | FRAG | ATYP | DST.ADDR | DST.PORT | DATA | + +----+------+------+----------+----------+----------+ + | 2 | 1 | 1 | Variable | 2 | Variable | + +----+------+------+----------+----------+----------+ + */ + buf := make([]byte, 3) + request.clientConn.Read(buf) + // relay udp datagram silently, without any notification to the requesting client + if buf[2] != 0 { + // does not support fragmentation, drop it + dummy := make([]byte, maxUDPPacketSize) + request.clientConn.Read(dummy) + return errors.New("does not support fragmentation, drop") + } + return request.doConnect() +}