1 | module net |
2 | |
3 | import time |
4 | import io |
5 | import strings |
6 | |
7 | pub const ( |
8 | tcp_default_read_timeout = 30 * time.second |
9 | tcp_default_write_timeout = 30 * time.second |
10 | ) |
11 | |
12 | [heap] |
13 | pub struct TcpConn { |
14 | pub mut: |
15 | sock TcpSocket |
16 | mut: |
17 | handle int |
18 | write_deadline time.Time |
19 | read_deadline time.Time |
20 | read_timeout time.Duration |
21 | write_timeout time.Duration |
22 | is_blocking bool |
23 | } |
24 | |
25 | pub fn dial_tcp(address string) !&TcpConn { |
26 | addrs := resolve_addrs_fuzzy(address, .tcp) or { |
27 | return error('${err.msg()}; could not resolve address ${address} in dial_tcp') |
28 | } |
29 | |
30 | // Keep track of dialing errors that take place |
31 | mut errs := []IError{} |
32 | |
33 | // Very simple dialer |
34 | for addr in addrs { |
35 | mut s := new_tcp_socket(addr.family()) or { |
36 | return error('${err.msg()}; could not create new tcp socket in dial_tcp') |
37 | } |
38 | s.connect(addr) or { |
39 | errs << err |
40 | // Connection failed |
41 | s.close() or { continue } |
42 | continue |
43 | } |
44 | |
45 | return &TcpConn{ |
46 | sock: s |
47 | read_timeout: net.tcp_default_read_timeout |
48 | write_timeout: net.tcp_default_write_timeout |
49 | } |
50 | } |
51 | |
52 | // Once we've failed now try and explain why we failed to connect |
53 | // to any of these addresses |
54 | mut err_builder := strings.new_builder(1024) |
55 | err_builder.write_string('dial_tcp failed for address ${address}\n') |
56 | err_builder.write_string('tried addrs:\n') |
57 | for i := 0; i < errs.len; i++ { |
58 | addr := addrs[i] |
59 | why := errs[i] |
60 | err_builder.write_string('\t${addr}: ${why}\n') |
61 | } |
62 | |
63 | // failed |
64 | return error(err_builder.str()) |
65 | } |
66 | |
67 | // bind local address and dial. |
68 | pub fn dial_tcp_with_bind(saddr string, laddr string) !&TcpConn { |
69 | addrs := resolve_addrs_fuzzy(saddr, .tcp) or { |
70 | return error('${err.msg()}; could not resolve address ${saddr} in dial_tcp_with_bind') |
71 | } |
72 | |
73 | // Very simple dialer |
74 | for addr in addrs { |
75 | mut s := new_tcp_socket(addr.family()) or { |
76 | return error('${err.msg()}; could not create new tcp socket in dial_tcp_with_bind') |
77 | } |
78 | s.bind(laddr) or { |
79 | s.close() or { continue } |
80 | continue |
81 | } |
82 | s.connect(addr) or { |
83 | // Connection failed |
84 | s.close() or { continue } |
85 | continue |
86 | } |
87 | |
88 | return &TcpConn{ |
89 | sock: s |
90 | read_timeout: net.tcp_default_read_timeout |
91 | write_timeout: net.tcp_default_write_timeout |
92 | } |
93 | } |
94 | // failed |
95 | return error('dial_tcp_with_bind failed for address ${saddr}') |
96 | } |
97 | |
98 | pub fn (mut c TcpConn) close() ! { |
99 | $if trace_tcp ? { |
100 | eprintln(' TcpConn.close | c.sock.handle: ${c.sock.handle:6}') |
101 | } |
102 | c.sock.close()! |
103 | } |
104 | |
105 | pub fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int { |
106 | mut res := wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0))! |
107 | $if trace_tcp ? { |
108 | eprintln('<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} len: ${len} | res: ${res}') |
109 | } |
110 | if res > 0 { |
111 | $if trace_tcp_data_read ? { |
112 | eprintln( |
113 | '<<< TcpConn.read_ptr | 1 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' + |
114 | unsafe { buf_ptr.vstring_with_len(res) }) |
115 | } |
116 | return res |
117 | } |
118 | code := error_code() |
119 | if code == int(error_ewouldblock) { |
120 | c.wait_for_read()! |
121 | res = wrap_read_result(C.recv(c.sock.handle, voidptr(buf_ptr), len, 0))! |
122 | $if trace_tcp ? { |
123 | eprintln('<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} len: ${len} | res: ${res}') |
124 | } |
125 | $if trace_tcp_data_read ? { |
126 | if res > 0 { |
127 | eprintln( |
128 | '<<< TcpConn.read_ptr | 2 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' + |
129 | unsafe { buf_ptr.vstring_with_len(res) }) |
130 | } |
131 | } |
132 | return socket_error(res) |
133 | } else { |
134 | wrap_error(code)! |
135 | } |
136 | return error('none') |
137 | } |
138 | |
139 | pub fn (c TcpConn) read(mut buf []u8) !int { |
140 | return c.read_ptr(buf.data, buf.len) or { |
141 | return io.NotExpected{ |
142 | cause: 'unexpected error in `read_ptr` function' |
143 | code: -1 |
144 | } |
145 | } |
146 | } |
147 | |
148 | pub fn (mut c TcpConn) read_deadline() !time.Time { |
149 | if c.read_deadline.unix == 0 { |
150 | return c.read_deadline |
151 | } |
152 | return error('none') |
153 | } |
154 | |
155 | // write_ptr blocks and attempts to write all data |
156 | pub fn (mut c TcpConn) write_ptr(b &u8, len int) !int { |
157 | $if trace_tcp_sock_handle ? { |
158 | eprintln('>>> TcpConn.write_ptr | c: ${ptr_str(c)} | c.sock.handle: ${c.sock.handle} | b: ${ptr_str(b)} | len: ${len}') |
159 | } |
160 | $if trace_tcp ? { |
161 | eprintln( |
162 | '>>> TcpConn.write_ptr | c.sock.handle: ${c.sock.handle} | b: ${ptr_str(b)} len: ${len} |\n' + |
163 | unsafe { b.vstring_with_len(len) }) |
164 | } |
165 | $if trace_tcp_data_write ? { |
166 | eprintln( |
167 | '>>> TcpConn.write_ptr | data.len: ${len:6} | hex: ${unsafe { b.vbytes(len) }.hex()} | data: ' + |
168 | unsafe { b.vstring_with_len(len) }) |
169 | } |
170 | unsafe { |
171 | mut ptr_base := &u8(b) |
172 | mut total_sent := 0 |
173 | for total_sent < len { |
174 | ptr := ptr_base + total_sent |
175 | remaining := len - total_sent |
176 | mut sent := C.send(c.sock.handle, ptr, remaining, msg_nosignal) |
177 | $if trace_tcp_data_write ? { |
178 | eprintln('>>> TcpConn.write_ptr | data chunk, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x} => sent: ${sent:6}') |
179 | } |
180 | if sent < 0 { |
181 | code := error_code() |
182 | if code == int(error_ewouldblock) { |
183 | c.wait_for_write()! |
184 | continue |
185 | } else { |
186 | wrap_error(code)! |
187 | } |
188 | } |
189 | total_sent += sent |
190 | } |
191 | return total_sent |
192 | } |
193 | } |
194 | |
195 | // write blocks and attempts to write all data |
196 | pub fn (mut c TcpConn) write(bytes []u8) !int { |
197 | return c.write_ptr(bytes.data, bytes.len) |
198 | } |
199 | |
200 | // write_string blocks and attempts to write all data |
201 | pub fn (mut c TcpConn) write_string(s string) !int { |
202 | return c.write_ptr(s.str, s.len) |
203 | } |
204 | |
205 | pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) { |
206 | c.read_deadline = deadline |
207 | } |
208 | |
209 | pub fn (mut c TcpConn) write_deadline() !time.Time { |
210 | if c.write_deadline.unix == 0 { |
211 | return c.write_deadline |
212 | } |
213 | return error('none') |
214 | } |
215 | |
216 | pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) { |
217 | c.write_deadline = deadline |
218 | } |
219 | |
220 | pub fn (c &TcpConn) read_timeout() time.Duration { |
221 | return c.read_timeout |
222 | } |
223 | |
224 | pub fn (mut c TcpConn) set_read_timeout(t time.Duration) { |
225 | c.read_timeout = t |
226 | } |
227 | |
228 | pub fn (c &TcpConn) write_timeout() time.Duration { |
229 | return c.write_timeout |
230 | } |
231 | |
232 | pub fn (mut c TcpConn) set_write_timeout(t time.Duration) { |
233 | c.write_timeout = t |
234 | } |
235 | |
236 | [inline] |
237 | pub fn (c TcpConn) wait_for_read() ! { |
238 | return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) |
239 | } |
240 | |
241 | [inline] |
242 | pub fn (mut c TcpConn) wait_for_write() ! { |
243 | return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) |
244 | } |
245 | |
246 | // set_sock initialises the c.sock field. It should be called after `.accept_only()!`. |
247 | // Note: just use `.accept()!`. In most cases it is simpler, and calls `.set_sock()!` for you. |
248 | pub fn (mut c TcpConn) set_sock() ! { |
249 | c.sock = tcp_socket_from_handle(c.handle)! |
250 | $if trace_tcp ? { |
251 | eprintln(' TcpListener.accept | << new_sock.handle: ${c.handle:6}') |
252 | } |
253 | } |
254 | |
255 | // peer_addr retrieves the ip address and port number used by the peer |
256 | pub fn (c &TcpConn) peer_addr() !Addr { |
257 | return peer_addr_from_socket_handle(c.sock.handle) |
258 | } |
259 | |
260 | // peer_ip retrieves the ip address used by the peer, and returns it as a string |
261 | pub fn (c &TcpConn) peer_ip() !string { |
262 | return c.peer_addr()!.str() |
263 | } |
264 | |
265 | pub fn (c &TcpConn) addr() !Addr { |
266 | return c.sock.address() |
267 | } |
268 | |
269 | pub fn (c TcpConn) str() string { |
270 | s := c.sock.str().replace('\n', ' ').replace(' ', ' ') |
271 | return 'TcpConn{ write_deadline: ${c.write_deadline}, read_deadline: ${c.read_deadline}, read_timeout: ${c.read_timeout}, write_timeout: ${c.write_timeout}, sock: ${s} }' |
272 | } |
273 | |
274 | pub struct TcpListener { |
275 | pub mut: |
276 | sock TcpSocket |
277 | mut: |
278 | accept_timeout time.Duration |
279 | accept_deadline time.Time |
280 | } |
281 | |
282 | [params] |
283 | pub struct ListenOptions { |
284 | pub: |
285 | dualstack bool = true |
286 | backlog int = 128 |
287 | } |
288 | |
289 | pub fn listen_tcp(family AddrFamily, saddr string, options ListenOptions) !&TcpListener { |
290 | mut s := new_tcp_socket(family) or { return error('${err.msg()}; could not create new socket') } |
291 | s.set_dualstack(options.dualstack) or {} |
292 | |
293 | addrs := resolve_addrs(saddr, family, .tcp) or { |
294 | return error('${err.msg()}; could not resolve address ${saddr}') |
295 | } |
296 | // TODO(logic to pick here) |
297 | addr := addrs[0] |
298 | |
299 | // cast to the correct type |
300 | alen := addr.len() |
301 | socket_error_message(C.bind(s.handle, voidptr(&addr), alen), 'binding to ${saddr} failed')! |
302 | socket_error_message(C.listen(s.handle, options.backlog), 'listening on ${saddr} with maximum backlog pending queue of ${options.backlog}, failed')! |
303 | return &TcpListener{ |
304 | sock: s |
305 | accept_deadline: no_deadline |
306 | accept_timeout: infinite_timeout |
307 | } |
308 | } |
309 | |
310 | // accept a tcp connection from an external source to the listener `l`. |
311 | pub fn (mut l TcpListener) accept() !&TcpConn { |
312 | mut res := l.accept_only()! |
313 | res.set_sock()! |
314 | return res |
315 | } |
316 | |
317 | // accept_only accepts a tcp connection from an external source to the listener `l`. |
318 | // Unlike `accept`, `accept_only` *will not call* `.set_sock()!` on the result, |
319 | // and is thus faster. |
320 | // |
321 | // Note: you *need* to call `.set_sock()!` manually, before using the |
322 | // connection after calling `.accept_only()!`, but that does not have to happen |
323 | // in the same thread that called `.accept_only()!`. |
324 | // The intention of this API, is to have a more efficient way to accept |
325 | // connections, that are later processed by a thread pool, while the main |
326 | // thread remains active, so that it can accept other connections. |
327 | // See also vlib/vweb/vweb.v . |
328 | // |
329 | // If you do not need that, just call `.accept()!` instead, which will call |
330 | // `.set_sock()!` for you. |
331 | pub fn (mut l TcpListener) accept_only() !&TcpConn { |
332 | $if trace_tcp ? { |
333 | eprintln(' TcpListener.accept | l.sock.handle: ${l.sock.handle:6}') |
334 | } |
335 | |
336 | mut new_handle := C.accept(l.sock.handle, 0, 0) |
337 | if new_handle <= 0 { |
338 | l.wait_for_accept()! |
339 | new_handle = C.accept(l.sock.handle, 0, 0) |
340 | if new_handle == -1 || new_handle == 0 { |
341 | return error('accept failed') |
342 | } |
343 | } |
344 | |
345 | return &TcpConn{ |
346 | handle: new_handle |
347 | read_timeout: net.tcp_default_read_timeout |
348 | write_timeout: net.tcp_default_write_timeout |
349 | } |
350 | } |
351 | |
352 | pub fn (c &TcpListener) accept_deadline() !time.Time { |
353 | if c.accept_deadline.unix != 0 { |
354 | return c.accept_deadline |
355 | } |
356 | return error('invalid deadline') |
357 | } |
358 | |
359 | pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) { |
360 | c.accept_deadline = deadline |
361 | } |
362 | |
363 | pub fn (c &TcpListener) accept_timeout() time.Duration { |
364 | return c.accept_timeout |
365 | } |
366 | |
367 | pub fn (mut c TcpListener) set_accept_timeout(t time.Duration) { |
368 | c.accept_timeout = t |
369 | } |
370 | |
371 | pub fn (mut c TcpListener) wait_for_accept() ! { |
372 | return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout) |
373 | } |
374 | |
375 | pub fn (mut c TcpListener) close() ! { |
376 | c.sock.close()! |
377 | } |
378 | |
379 | pub fn (c &TcpListener) addr() !Addr { |
380 | return c.sock.address() |
381 | } |
382 | |
383 | struct TcpSocket { |
384 | Socket |
385 | } |
386 | |
387 | fn new_tcp_socket(family AddrFamily) !TcpSocket { |
388 | handle := socket_error(C.socket(family, SocketType.tcp, 0))! |
389 | mut s := TcpSocket{ |
390 | handle: handle |
391 | } |
392 | $if trace_tcp ? { |
393 | eprintln(' new_tcp_socket | s.handle: ${s.handle:6}') |
394 | } |
395 | |
396 | // TODO(emily): |
397 | // we shouldnt be using ioctlsocket in the 21st century |
398 | // use the non-blocking socket option instead please :) |
399 | |
400 | // TODO(emily): |
401 | // Move this to its own function on the socket |
402 | s.set_option_int(.reuse_addr, 1)! |
403 | |
404 | $if !net_blocking_sockets ? { |
405 | $if windows { |
406 | t := u32(1) // true |
407 | socket_error(C.ioctlsocket(handle, fionbio, &t))! |
408 | } $else { |
409 | socket_error(C.fcntl(handle, C.F_SETFL, C.fcntl(handle, C.F_GETFL) | C.O_NONBLOCK))! |
410 | } |
411 | } |
412 | return s |
413 | } |
414 | |
415 | fn tcp_socket_from_handle(sockfd int) !TcpSocket { |
416 | mut s := TcpSocket{ |
417 | handle: sockfd |
418 | } |
419 | $if trace_tcp ? { |
420 | eprintln(' tcp_socket_from_handle | s.handle: ${s.handle:6}') |
421 | } |
422 | // s.set_option_bool(.reuse_addr, true)? |
423 | s.set_option_int(.reuse_addr, 1)! |
424 | s.set_dualstack(true) or { |
425 | // Not ipv6, we dont care |
426 | } |
427 | $if !net_blocking_sockets ? { |
428 | $if windows { |
429 | t := u32(1) // true |
430 | socket_error(C.ioctlsocket(sockfd, fionbio, &t))! |
431 | } $else { |
432 | socket_error(C.fcntl(sockfd, C.F_SETFL, C.fcntl(sockfd, C.F_GETFL) | C.O_NONBLOCK))! |
433 | } |
434 | } |
435 | return s |
436 | } |
437 | |
438 | // tcp_socket_from_handle_raw is similar to tcp_socket_from_handle, but it does not modify any socket options |
439 | pub fn tcp_socket_from_handle_raw(sockfd int) TcpSocket { |
440 | mut s := TcpSocket{ |
441 | handle: sockfd |
442 | } |
443 | $if trace_tcp ? { |
444 | eprintln(' tcp_socket_from_handle_raw | s.handle: ${s.handle:6}') |
445 | } |
446 | return s |
447 | } |
448 | |
449 | pub fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) ! { |
450 | // TODO reenable when this `in` operation works again |
451 | // if opt !in opts_can_set { |
452 | // return err_option_not_settable |
453 | // } |
454 | // if opt !in opts_bool { |
455 | // return err_option_wrong_type |
456 | // } |
457 | x := int(value) |
458 | socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &x, sizeof(int)))! |
459 | } |
460 | |
461 | pub fn (mut s TcpSocket) set_dualstack(on bool) ! { |
462 | x := int(!on) |
463 | socket_error(C.setsockopt(s.handle, C.IPPROTO_IPV6, int(SocketOption.ipv6_only), &x, |
464 | sizeof(int)))! |
465 | } |
466 | |
467 | pub fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) ! { |
468 | socket_error(C.setsockopt(s.handle, C.SOL_SOCKET, int(opt), &value, sizeof(int)))! |
469 | } |
470 | |
471 | // bind a local rddress for TcpSocket |
472 | pub fn (mut s TcpSocket) bind(addr string) ! { |
473 | addrs := resolve_addrs(addr, AddrFamily.ip, .tcp) or { |
474 | return error('${err.msg()}; could not resolve address ${addr}') |
475 | } |
476 | |
477 | // TODO(logic to pick here) |
478 | a := addrs[0] |
479 | |
480 | // cast to the correct type |
481 | alen := a.len() |
482 | socket_error_message(C.bind(s.handle, voidptr(&a), alen), 'binding to ${addr} failed') or { |
483 | return err |
484 | } |
485 | } |
486 | |
487 | fn (mut s TcpSocket) close() ! { |
488 | shutdown(s.handle) |
489 | return close(s.handle) |
490 | } |
491 | |
492 | fn (mut s TcpSocket) @select(test Select, timeout time.Duration) !bool { |
493 | return @select(s.handle, test, timeout) |
494 | } |
495 | |
496 | const ( |
497 | connect_timeout = 5 * time.second |
498 | ) |
499 | |
500 | fn (mut s TcpSocket) connect(a Addr) ! { |
501 | $if !net_blocking_sockets ? { |
502 | res := C.connect(s.handle, voidptr(&a), a.len()) |
503 | if res == 0 { |
504 | return |
505 | } |
506 | ecode := error_code() |
507 | // On nix non-blocking sockets we expect einprogress |
508 | // On windows we expect res == -1 && error_code() == ewouldblock |
509 | if (is_windows && ecode == int(error_ewouldblock)) |
510 | || (!is_windows && res == -1 && ecode == int(error_einprogress)) { |
511 | // The socket is nonblocking and the connection cannot be completed |
512 | // immediately. (UNIX domain sockets failed with EAGAIN instead.) |
513 | // It is possible to select(2) or poll(2) for completion by selecting |
514 | // the socket for writing. After select(2) indicates writability, |
515 | // use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to |
516 | // determine whether connect() completed successfully (SO_ERROR is zero) or |
517 | // unsuccessfully (SO_ERROR is one of the usual error codes listed here, |
518 | // ex‐ plaining the reason for the failure). |
519 | write_result := s.@select(.write, net.connect_timeout)! |
520 | err := 0 |
521 | len := sizeof(err) |
522 | xyz := C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len) |
523 | if xyz == 0 && err == 0 { |
524 | return |
525 | } |
526 | if write_result { |
527 | if xyz == 0 { |
528 | wrap_error(err)! |
529 | return |
530 | } |
531 | return |
532 | } |
533 | return err_timed_out |
534 | } |
535 | wrap_error(ecode)! |
536 | return |
537 | } $else { |
538 | x := C.connect(s.handle, voidptr(&a), a.len()) |
539 | socket_error(x)! |
540 | } |
541 | } |