blob: cb9ae0a97af5b2d776b198a29f1a81099cedab6d [file] [log] [blame]
Serge Bazanskife7134b2022-04-01 15:46:29 +02001package socksproxy
2
3import (
4 "encoding/binary"
5 "errors"
6 "fmt"
7 "io"
8 "net"
9)
10
11// readMethods implements RFC1928 3. “Procedure for TCP-based clients”,
12// paragraph 3. It receives a 'version identifier/method selection message' from
13// r and returns the methods supported by the client.
14func readMethods(r io.Reader) ([]method, error) {
15 var ver uint8
16 if err := binary.Read(r, binary.BigEndian, &ver); err != nil {
17 return nil, fmt.Errorf("when reading ver: %w", err)
18 }
19 if ver != 5 {
20 return nil, fmt.Errorf("unimplemented version %d", ver)
21 }
22 var nmethods uint8
23 if err := binary.Read(r, binary.BigEndian, &nmethods); err != nil {
24 return nil, fmt.Errorf("when reading nmethods: %w", err)
25 }
26 methodBytes := make([]byte, nmethods)
27 if _, err := io.ReadFull(r, methodBytes); err != nil {
28 return nil, fmt.Errorf("while reading methods: %w", err)
29 }
30 methods := make([]method, nmethods)
31 for i, m := range methodBytes {
32 methods[i] = method(m)
33 }
34 return methods, nil
35}
36
37// writeMethod implements RFC1928 3. “Procedure for TCP-based clients”,
38// paragraph 5. It sends a selected method to w.
39func writeMethod(w io.Writer, m method) error {
40 if err := binary.Write(w, binary.BigEndian, uint8(5)); err != nil {
41 return fmt.Errorf("while writing version: %w", err)
42 }
43 if err := binary.Write(w, binary.BigEndian, uint8(m)); err != nil {
44 return fmt.Errorf("while writing method: %w", err)
45 }
46 return nil
47}
48
49// method is an RFC1928 authentication method.
50type method uint8
51
52const (
53 methodNoAuthenticationRequired method = 0
54 methodNoAcceptableMethods method = 0xff
55)
56
57// negotiateMethod implements the entire flow RFC1928 3. “Procedure for
58// TCP-based clients” by negotiating for the 'NO AUTHENTICATION REQUIRED'
59// authentication method, and failing otherwise.
60func negotiateMethod(rw io.ReadWriter) error {
61 methods, err := readMethods(rw)
62 if err != nil {
63 return fmt.Errorf("could not read methods: %w", err)
64 }
65
66 found := false
67 for _, m := range methods {
68 if m == methodNoAuthenticationRequired {
69 found = true
70 break
71 }
72 }
73 if !found {
74 // Discard error, as this connection is failed anyway.
75 writeMethod(rw, methodNoAcceptableMethods)
76 return fmt.Errorf("no acceptable methods found")
77 }
78 if err := writeMethod(rw, methodNoAuthenticationRequired); err != nil {
79 return fmt.Errorf("could not respond with method: %w", err)
80 }
81 return nil
82}
83
84var (
85 // errNotConnect is returned by readRequest when the request contained some
86 // other request than CONNECT.
87 errNotConnect = errors.New("not CONNECT")
88 // errUnsupportedAddressType is returned by readRequest when the request
89 // contained some unsupported address type (not IPv4 or IPv6).
90 errUnsupportedAddressType = errors.New("unsupported address type")
91)
92
93// readRequest implements RFC1928 4. “Requests” by reading a SOCKS request from
94// r and ensuring it's an IPv4/IPv6 CONNECT request. The parsed address/port
95// pair is then returned.
96func readRequest(r io.Reader) (*connectRequest, error) {
97 header := struct {
98 Ver uint8
99 Cmd uint8
100 Rsv uint8
101 Atyp uint8
102 }{}
103 if err := binary.Read(r, binary.BigEndian, &header); err != nil {
104 return nil, fmt.Errorf("when reading request header: %w", err)
105 }
106
107 if header.Ver != 5 {
108 return nil, fmt.Errorf("invalid version %d", header.Ver)
109 }
110 if header.Cmd != 1 {
111 return nil, errNotConnect
112 }
113
114 var addrBytes []byte
115 switch header.Atyp {
116 case 1:
117 addrBytes = make([]byte, 4)
118 case 4:
119 addrBytes = make([]byte, 4)
120 default:
121 return nil, errUnsupportedAddressType
122 }
123 if _, err := io.ReadFull(r, addrBytes); err != nil {
124 return nil, fmt.Errorf("when reading address: %w", err)
125 }
126
127 var port uint16
128 if err := binary.Read(r, binary.BigEndian, &port); err != nil {
129 return nil, fmt.Errorf("when reading port: %w", err)
130 }
131
132 return &connectRequest{
133 address: addrBytes,
134 port: port,
135 }, nil
136}
137
138type connectRequest struct {
139 address net.IP
140 port uint16
141}
142
143// Reply is an RFC1928 6. “Replies” reply field value. It's returned to the
144// client by internal socksproxy code or a Handler to signal a success or error
145// condition within an RFC1928 reply.
146type Reply uint8
147
148const (
149 ReplySucceeded Reply = 0
150 ReplyGeneralFailure Reply = 1
151 ReplyConnectionNotAllowed Reply = 2
152 ReplyNetworkUnreachable Reply = 3
153 ReplyHostUnreachable Reply = 4
154 ReplyConnectionRefused Reply = 5
155 ReplyTTLExpired Reply = 6
156 ReplyCommandNotSupported Reply = 7
157 ReplyAddressTypeNotSupported Reply = 8
158)
159
160// writeReply implements RFC1928 6. “Replies” by sending a given Reply, bind
161// address and bind port to w. An error is returned if the given bind address is
162// invaild, or if a communication error occurred.
163func writeReply(w io.Writer, r Reply, bindAddr net.IP, bindPort uint16) error {
164 var atyp uint8
165 switch len(bindAddr) {
166 case 4:
167 atyp = 1
168 case 16:
169 atyp = 4
170 default:
171 return fmt.Errorf("unsupported bind address type")
172 }
173
174 header := struct {
175 Ver uint8
176 Reply uint8
177 Rsv uint8
178 Atyp uint8
179 }{
180 Ver: 5,
181 Reply: uint8(r),
182 Rsv: 0,
183 Atyp: atyp,
184 }
185 if err := binary.Write(w, binary.BigEndian, &header); err != nil {
186 return fmt.Errorf("when writing reply header: %w", err)
187 }
188 if _, err := w.Write(bindAddr); err != nil {
189 return fmt.Errorf("when writing reply bind address: %w", err)
190 }
191 if err := binary.Write(w, binary.BigEndian, bindPort); err != nil {
192 return fmt.Errorf("when writing reply bind port: %w", err)
193 }
194 return nil
195}