|  | package socksproxy | 
|  |  | 
|  | import ( | 
|  | "encoding/binary" | 
|  | "errors" | 
|  | "fmt" | 
|  | "io" | 
|  | "net" | 
|  | ) | 
|  |  | 
|  | // readMethods implements RFC1928 3. “Procedure for TCP-based clients”, | 
|  | // paragraph 3. It receives a 'version identifier/method selection message' from | 
|  | // r and returns the methods supported by the client. | 
|  | func readMethods(r io.Reader) ([]method, error) { | 
|  | var ver uint8 | 
|  | if err := binary.Read(r, binary.BigEndian, &ver); err != nil { | 
|  | return nil, fmt.Errorf("when reading ver: %w", err) | 
|  | } | 
|  | if ver != 5 { | 
|  | return nil, fmt.Errorf("unimplemented version %d", ver) | 
|  | } | 
|  | var nmethods uint8 | 
|  | if err := binary.Read(r, binary.BigEndian, &nmethods); err != nil { | 
|  | return nil, fmt.Errorf("when reading nmethods: %w", err) | 
|  | } | 
|  | methodBytes := make([]byte, nmethods) | 
|  | if _, err := io.ReadFull(r, methodBytes); err != nil { | 
|  | return nil, fmt.Errorf("while reading methods: %w", err) | 
|  | } | 
|  | methods := make([]method, nmethods) | 
|  | for i, m := range methodBytes { | 
|  | methods[i] = method(m) | 
|  | } | 
|  | return methods, nil | 
|  | } | 
|  |  | 
|  | // writeMethod implements RFC1928 3. “Procedure for TCP-based clients”, | 
|  | // paragraph 5. It sends a selected method to w. | 
|  | func writeMethod(w io.Writer, m method) error { | 
|  | if err := binary.Write(w, binary.BigEndian, uint8(5)); err != nil { | 
|  | return fmt.Errorf("while writing version: %w", err) | 
|  | } | 
|  | if err := binary.Write(w, binary.BigEndian, uint8(m)); err != nil { | 
|  | return fmt.Errorf("while writing method: %w", err) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | // method is an RFC1928 authentication method. | 
|  | type method uint8 | 
|  |  | 
|  | const ( | 
|  | methodNoAuthenticationRequired method = 0 | 
|  | methodNoAcceptableMethods      method = 0xff | 
|  | ) | 
|  |  | 
|  | // negotiateMethod implements the entire flow RFC1928 3. “Procedure for | 
|  | // TCP-based clients” by negotiating for the 'NO AUTHENTICATION REQUIRED' | 
|  | // authentication method, and failing otherwise. | 
|  | func negotiateMethod(rw io.ReadWriter) error { | 
|  | methods, err := readMethods(rw) | 
|  | if err != nil { | 
|  | return fmt.Errorf("could not read methods: %w", err) | 
|  | } | 
|  |  | 
|  | found := false | 
|  | for _, m := range methods { | 
|  | if m == methodNoAuthenticationRequired { | 
|  | found = true | 
|  | break | 
|  | } | 
|  | } | 
|  | if !found { | 
|  | // Discard error, as this connection is failed anyway. | 
|  | writeMethod(rw, methodNoAcceptableMethods) | 
|  | return fmt.Errorf("no acceptable methods found") | 
|  | } | 
|  | if err := writeMethod(rw, methodNoAuthenticationRequired); err != nil { | 
|  | return fmt.Errorf("could not respond with method: %w", err) | 
|  | } | 
|  | return nil | 
|  | } | 
|  |  | 
|  | var ( | 
|  | // errNotConnect is returned by readRequest when the request contained some | 
|  | // other request than CONNECT. | 
|  | errNotConnect = errors.New("not CONNECT") | 
|  | // errUnsupportedAddressType is returned by readRequest when the request | 
|  | // contained some unsupported address type (not IPv4 or IPv6). | 
|  | errUnsupportedAddressType = errors.New("unsupported address type") | 
|  | ) | 
|  |  | 
|  | // readRequest implements RFC1928 4. “Requests” by reading a SOCKS request from | 
|  | // r and ensuring it's an IPv4/IPv6 CONNECT request. The parsed address/port | 
|  | // pair is then returned. | 
|  | func readRequest(r io.Reader) (*connectRequest, error) { | 
|  | header := struct { | 
|  | Ver  uint8 | 
|  | Cmd  uint8 | 
|  | Rsv  uint8 | 
|  | Atyp uint8 | 
|  | }{} | 
|  | if err := binary.Read(r, binary.BigEndian, &header); err != nil { | 
|  | return nil, fmt.Errorf("when reading request header: %w", err) | 
|  | } | 
|  |  | 
|  | if header.Ver != 5 { | 
|  | return nil, fmt.Errorf("invalid version %d", header.Ver) | 
|  | } | 
|  | if header.Cmd != 1 { | 
|  | return nil, errNotConnect | 
|  | } | 
|  |  | 
|  | var addrBytes []byte | 
|  | switch header.Atyp { | 
|  | case 1: | 
|  | addrBytes = make([]byte, 4) | 
|  | case 4: | 
|  | addrBytes = make([]byte, 4) | 
|  | default: | 
|  | return nil, errUnsupportedAddressType | 
|  | } | 
|  | if _, err := io.ReadFull(r, addrBytes); err != nil { | 
|  | return nil, fmt.Errorf("when reading address: %w", err) | 
|  | } | 
|  |  | 
|  | var port uint16 | 
|  | if err := binary.Read(r, binary.BigEndian, &port); err != nil { | 
|  | return nil, fmt.Errorf("when reading port: %w", err) | 
|  | } | 
|  |  | 
|  | return &connectRequest{ | 
|  | address: addrBytes, | 
|  | port:    port, | 
|  | }, nil | 
|  | } | 
|  |  | 
|  | type connectRequest struct { | 
|  | address net.IP | 
|  | port    uint16 | 
|  | } | 
|  |  | 
|  | // Reply is an RFC1928 6. “Replies” reply field value. It's returned to the | 
|  | // client by internal socksproxy code or a Handler to signal a success or error | 
|  | // condition within an RFC1928 reply. | 
|  | type Reply uint8 | 
|  |  | 
|  | const ( | 
|  | ReplySucceeded               Reply = 0 | 
|  | ReplyGeneralFailure          Reply = 1 | 
|  | ReplyConnectionNotAllowed    Reply = 2 | 
|  | ReplyNetworkUnreachable      Reply = 3 | 
|  | ReplyHostUnreachable         Reply = 4 | 
|  | ReplyConnectionRefused       Reply = 5 | 
|  | ReplyTTLExpired              Reply = 6 | 
|  | ReplyCommandNotSupported     Reply = 7 | 
|  | ReplyAddressTypeNotSupported Reply = 8 | 
|  | ) | 
|  |  | 
|  | // writeReply implements RFC1928 6. “Replies” by sending a given Reply, bind | 
|  | // address and bind port to w. An error is returned if the given bind address is | 
|  | // invaild, or if a communication error occurred. | 
|  | func writeReply(w io.Writer, r Reply, bindAddr net.IP, bindPort uint16) error { | 
|  | var atyp uint8 | 
|  | switch len(bindAddr) { | 
|  | case 4: | 
|  | atyp = 1 | 
|  | case 16: | 
|  | atyp = 4 | 
|  | default: | 
|  | return fmt.Errorf("unsupported bind address type") | 
|  | } | 
|  |  | 
|  | header := struct { | 
|  | Ver   uint8 | 
|  | Reply uint8 | 
|  | Rsv   uint8 | 
|  | Atyp  uint8 | 
|  | }{ | 
|  | Ver:   5, | 
|  | Reply: uint8(r), | 
|  | Rsv:   0, | 
|  | Atyp:  atyp, | 
|  | } | 
|  | if err := binary.Write(w, binary.BigEndian, &header); err != nil { | 
|  | return fmt.Errorf("when writing reply header: %w", err) | 
|  | } | 
|  | if _, err := w.Write(bindAddr); err != nil { | 
|  | return fmt.Errorf("when writing reply bind address: %w", err) | 
|  | } | 
|  | if err := binary.Write(w, binary.BigEndian, bindPort); err != nil { | 
|  | return fmt.Errorf("when writing reply bind port: %w", err) | 
|  | } | 
|  | return nil | 
|  | } |