| 1 | module net |
| 2 | |
| 3 | import io |
| 4 | import time |
| 5 | import strings |
| 6 | |
| 7 | pub const tcp_default_read_timeout = 30 * time.second |
| 8 | pub const tcp_default_write_timeout = 30 * time.second |
| 9 | |
| 10 | // TCPDialer is a concrete instance of the Dialer interface, |
| 11 | // for creating tcp connections. |
| 12 | pub struct TCPDialer {} |
| 13 | |
| 14 | // dial will try to create a new abstract connection to the given address. |
| 15 | // It will return an error, if that is not possible. |
| 16 | pub fn (t TCPDialer) dial(address string) !Connection { |
| 17 | return dial_tcp(address)! |
| 18 | } |
| 19 | |
| 20 | // default_tcp_dialer will give you an instance of Dialer, that is suitable for making new tcp connections. |
| 21 | pub fn default_tcp_dialer() Dialer { |
| 22 | return &TCPDialer{} |
| 23 | } |
| 24 | |
| 25 | @[heap] |
| 26 | pub struct TcpConn { |
| 27 | pub mut: |
| 28 | sock TcpSocket |
| 29 | handle int |
| 30 | write_deadline time.Time |
| 31 | read_deadline time.Time |
| 32 | read_timeout time.Duration |
| 33 | write_timeout time.Duration |
| 34 | is_blocking bool = true |
| 35 | // last_write_sent is the exact number of bytes the most recent write_ptr |
| 36 | // call sent, valid even when it then returned an error (send() is |
| 37 | // byte-accurate), so a caller can tell a zero-byte failure — 0, safe to |
| 38 | // replay — from a partial write. (The TLS backends expose the same field but |
| 39 | // use -1 for the indeterminate case they cannot prove; plain TCP never does.) |
| 40 | last_write_sent int |
| 41 | } |
| 42 | |
| 43 | // dial_tcp will try to create a new TcpConn to the given address. |
| 44 | pub fn dial_tcp(oaddress string) !&TcpConn { |
| 45 | mut address := oaddress |
| 46 | $if windows { |
| 47 | // resolving 0.0.0.0 to localhost, works on linux and macos, but not on windows, so try to emulate it: |
| 48 | if address.starts_with(':::') { |
| 49 | address = address.replace_once(':::', 'localhost:') |
| 50 | } |
| 51 | if address.starts_with('0.0.0.0:') { |
| 52 | address = address.replace_once('0.0.0.0:', 'localhost:') |
| 53 | } |
| 54 | } |
| 55 | addrs := resolve_addrs_fuzzy(address, .tcp) or { |
| 56 | return error('${err.msg()}; could not resolve address ${address} in dial_tcp') |
| 57 | } |
| 58 | |
| 59 | // Keep track of dialing errors that take place |
| 60 | mut errs := []IError{} |
| 61 | |
| 62 | // Very simple dialer |
| 63 | for addr in addrs { |
| 64 | mut s := new_tcp_socket(addr.family()) or { |
| 65 | return error('${err.msg()}; could not create new tcp socket in dial_tcp') |
| 66 | } |
| 67 | s.connect(addr) or { |
| 68 | errs << err |
| 69 | // Connection failed |
| 70 | s.close() or { continue } |
| 71 | continue |
| 72 | } |
| 73 | |
| 74 | mut conn := &TcpConn{ |
| 75 | sock: s |
| 76 | read_timeout: tcp_default_read_timeout |
| 77 | write_timeout: tcp_default_write_timeout |
| 78 | } |
| 79 | // The blocking / non-blocking mode is determined before the connection is established. |
| 80 | $if net_nonblocking_sockets ? { |
| 81 | conn.is_blocking = false |
| 82 | } |
| 83 | return conn |
| 84 | } |
| 85 | |
| 86 | // Once we've failed now try and explain why we failed to connect |
| 87 | // to any of these addresses |
| 88 | mut err_builder := strings.new_builder(1024) |
| 89 | err_builder.write_string('dial_tcp failed for address ${address}\n') |
| 90 | err_builder.write_string('tried addrs:\n') |
| 91 | for i := 0; i < errs.len; i++ { |
| 92 | addr := addrs[i] |
| 93 | why := errs[i] |
| 94 | err_builder.write_string('\t${addr}: ${why}\n') |
| 95 | } |
| 96 | |
| 97 | // failed |
| 98 | return error(err_builder.str()) |
| 99 | } |
| 100 | |
| 101 | // dial_tcp_with_bind will bind the given local address `laddr` and dial. |
| 102 | pub fn dial_tcp_with_bind(saddr string, laddr string) !&TcpConn { |
| 103 | addrs := resolve_addrs_fuzzy(saddr, .tcp) or { |
| 104 | return error('${err.msg()}; could not resolve address ${saddr} in dial_tcp_with_bind') |
| 105 | } |
| 106 | |
| 107 | // Very simple dialer |
| 108 | for addr in addrs { |
| 109 | mut s := new_tcp_socket(addr.family()) or { |
| 110 | return error('${err.msg()}; could not create new tcp socket in dial_tcp_with_bind') |
| 111 | } |
| 112 | s.bind(laddr) or { |
| 113 | s.close() or { continue } |
| 114 | continue |
| 115 | } |
| 116 | s.connect(addr) or { |
| 117 | // Connection failed |
| 118 | s.close() or { continue } |
| 119 | continue |
| 120 | } |
| 121 | |
| 122 | mut conn := &TcpConn{ |
| 123 | sock: s |
| 124 | read_timeout: tcp_default_read_timeout |
| 125 | write_timeout: tcp_default_write_timeout |
| 126 | } |
| 127 | // The blocking / non-blocking mode is determined before the connection is established. |
| 128 | $if net_nonblocking_sockets ? { |
| 129 | conn.is_blocking = false |
| 130 | } |
| 131 | return conn |
| 132 | } |
| 133 | // failed |
| 134 | return error('dial_tcp_with_bind failed for address ${saddr}') |
| 135 | } |
| 136 | |
| 137 | // close closes the tcp connection |
| 138 | pub fn (mut c TcpConn) close() ! { |
| 139 | $if trace_tcp ? { |
| 140 | eprintln(' TcpConn.close | c.sock.handle: ${c.sock.handle:6}') |
| 141 | } |
| 142 | c.sock.close()! |
| 143 | } |
| 144 | |
| 145 | // read_ptr reads data from the tcp connection to the given buffer. It reads at most `len` bytes. |
| 146 | // It returns the number of actually read bytes, which can vary between 0 to `len`. |
| 147 | pub fn (c TcpConn) read_ptr(buf_ptr &u8, len int) !int { |
| 148 | mut res := 0 |
| 149 | mut ecode := 0 |
| 150 | $if is_coroutine ? { |
| 151 | res = C.photon_recv(c.sock.handle, voidptr(buf_ptr), len, 0, c.read_timeout) |
| 152 | ecode = error_code() |
| 153 | } $else { |
| 154 | if c.is_blocking { |
| 155 | // Honor read deadlines/timeouts first, then use a normal blocking recv. |
| 156 | // This avoids transient EAGAIN-style reads on newly accepted sockets. |
| 157 | c.wait_for_read()! |
| 158 | res = C.recv(c.sock.handle, voidptr(buf_ptr), len, 0) |
| 159 | } else { |
| 160 | res = C.recv(c.sock.handle, voidptr(buf_ptr), len, msg_dontwait) |
| 161 | } |
| 162 | ecode = error_code() |
| 163 | } |
| 164 | if res == 0 { |
| 165 | return io.Eof{} |
| 166 | } |
| 167 | if res > 0 { |
| 168 | $if trace_tcp ? { |
| 169 | eprintln( |
| 170 | '<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} | len: ${len} | res: ${res} |\n' + |
| 171 | unsafe { buf_ptr.vstring_with_len(len) }) |
| 172 | } |
| 173 | $if trace_tcp_data_read ? { |
| 174 | eprintln( |
| 175 | '<<< TcpConn.read_ptr | 1 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' + |
| 176 | unsafe { buf_ptr.vstring_with_len(res) }) |
| 177 | } |
| 178 | return res |
| 179 | } |
| 180 | if ecode in [int(error_ewouldblock), int(error_eagain), C.EINTR] { |
| 181 | c.wait_for_read()! |
| 182 | res = $if is_coroutine ? { |
| 183 | C.photon_recv(c.sock.handle, voidptr(buf_ptr), len, 0, c.read_timeout) |
| 184 | } $else { |
| 185 | if c.is_blocking { |
| 186 | C.recv(c.sock.handle, voidptr(buf_ptr), len, 0) |
| 187 | } else { |
| 188 | C.recv(c.sock.handle, voidptr(buf_ptr), len, msg_dontwait) |
| 189 | } |
| 190 | } |
| 191 | if res == 0 { |
| 192 | return io.Eof{} |
| 193 | } |
| 194 | $if trace_tcp ? { |
| 195 | eprintln( |
| 196 | '<<< TcpConn.read_ptr | c.sock.handle: ${c.sock.handle} | buf_ptr: ${ptr_str(buf_ptr)} | len: ${len} | res: ${res} | code: ${ecode} |\n' + |
| 197 | unsafe { buf_ptr.vstring_with_len(len) }) |
| 198 | } |
| 199 | $if trace_tcp_data_read ? { |
| 200 | if res > 0 { |
| 201 | eprintln( |
| 202 | '<<< TcpConn.read_ptr | 2 data.len: ${res:6} | hex: ${unsafe { buf_ptr.vbytes(res) }.hex()} | data: ' + |
| 203 | unsafe { buf_ptr.vstring_with_len(res) }) |
| 204 | } |
| 205 | } |
| 206 | return socket_error(res) |
| 207 | } else { |
| 208 | wrap_error(ecode)! |
| 209 | } |
| 210 | return error('none') |
| 211 | } |
| 212 | |
| 213 | // read reads data from the tcp connection into the mutable buffer `buf`. |
| 214 | // The number of bytes read is limited to the length of the buffer `buf.len`. |
| 215 | // The returned value is the number of read bytes (between 0 and `buf.len`). |
| 216 | pub fn (c TcpConn) read(mut buf []u8) !int { |
| 217 | return c.read_ptr(buf.data, buf.len)! |
| 218 | } |
| 219 | |
| 220 | pub fn (mut c TcpConn) read_deadline() !time.Time { |
| 221 | if c.read_deadline.unix() == 0 { |
| 222 | return c.read_deadline |
| 223 | } |
| 224 | return error('none') |
| 225 | } |
| 226 | |
| 227 | // write_ptr blocks and attempts to write all data |
| 228 | pub fn (mut c TcpConn) write_ptr(b &u8, len int) !int { |
| 229 | $if trace_tcp_sock_handle ? { |
| 230 | eprintln('>>> TcpConn.write_ptr | c: ${ptr_str(c)} | c.sock.handle: ${c.sock.handle} | b: ${ptr_str(b)} | len: ${len}') |
| 231 | } |
| 232 | $if trace_tcp ? { |
| 233 | eprintln( |
| 234 | '>>> TcpConn.write_ptr | c.sock.handle: ${c.sock.handle} | b: ${ptr_str(b)} len: ${len} |\n' + |
| 235 | unsafe { b.vstring_with_len(len) }) |
| 236 | } |
| 237 | $if trace_tcp_data_write ? { |
| 238 | eprintln( |
| 239 | '>>> TcpConn.write_ptr | data.len: ${len:6} | hex: ${unsafe { b.vbytes(len) }.hex()} | data: ' + |
| 240 | unsafe { b.vstring_with_len(len) }) |
| 241 | } |
| 242 | c.last_write_sent = 0 |
| 243 | unsafe { |
| 244 | mut ptr_base := &u8(b) |
| 245 | mut total_sent := 0 |
| 246 | for total_sent < len { |
| 247 | ptr := ptr_base + total_sent |
| 248 | remaining := len - total_sent |
| 249 | mut sent := $if is_coroutine ? { |
| 250 | C.photon_send(c.sock.handle, ptr, remaining, msg_nosignal, c.write_timeout) |
| 251 | } $else { |
| 252 | C.send(c.sock.handle, ptr, remaining, msg_nosignal) |
| 253 | } |
| 254 | code := error_code() |
| 255 | $if trace_tcp_data_write ? { |
| 256 | eprintln('>>> TcpConn.write_ptr | data chunk, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x} => sent: ${sent:6}') |
| 257 | } |
| 258 | if sent < 0 { |
| 259 | $if trace_tcp_send_failures ? { |
| 260 | eprintln('>>> TcpConn.write_ptr | send_failure, data.len: ${len:6}, total_sent: ${total_sent:6}, remaining: ${remaining:6}, ptr: ${voidptr(ptr):x}, c.write_timeout: ${c.write_timeout:3} => sent: ${sent:6}, error code: ${code:3}') |
| 261 | } |
| 262 | if code in [int(error_ewouldblock), int(error_eagain), C.EINTR] { |
| 263 | c.wait_for_write()! |
| 264 | continue |
| 265 | } else { |
| 266 | wrap_error(code)! |
| 267 | } |
| 268 | } |
| 269 | total_sent += sent |
| 270 | c.last_write_sent = total_sent |
| 271 | } |
| 272 | return total_sent |
| 273 | } |
| 274 | } |
| 275 | |
| 276 | // write blocks and attempts to write all data |
| 277 | pub fn (mut c TcpConn) write(bytes []u8) !int { |
| 278 | return c.write_ptr(bytes.data, bytes.len) |
| 279 | } |
| 280 | |
| 281 | // write_string blocks and attempts to write all data |
| 282 | pub fn (mut c TcpConn) write_string(s string) !int { |
| 283 | return c.write_ptr(s.str, s.len) |
| 284 | } |
| 285 | |
| 286 | pub fn (mut c TcpConn) set_read_deadline(deadline time.Time) { |
| 287 | c.read_deadline = deadline |
| 288 | } |
| 289 | |
| 290 | pub fn (mut c TcpConn) write_deadline() !time.Time { |
| 291 | if c.write_deadline.unix() == 0 { |
| 292 | return c.write_deadline |
| 293 | } |
| 294 | return error('none') |
| 295 | } |
| 296 | |
| 297 | pub fn (mut c TcpConn) set_write_deadline(deadline time.Time) { |
| 298 | c.write_deadline = deadline |
| 299 | } |
| 300 | |
| 301 | pub fn (c &TcpConn) read_timeout() time.Duration { |
| 302 | return c.read_timeout |
| 303 | } |
| 304 | |
| 305 | pub fn (mut c TcpConn) set_read_timeout(t time.Duration) { |
| 306 | c.read_timeout = t |
| 307 | } |
| 308 | |
| 309 | pub fn (c &TcpConn) write_timeout() time.Duration { |
| 310 | return c.write_timeout |
| 311 | } |
| 312 | |
| 313 | pub fn (mut c TcpConn) set_write_timeout(t time.Duration) { |
| 314 | c.write_timeout = t |
| 315 | } |
| 316 | |
| 317 | @[inline] |
| 318 | pub fn (c TcpConn) wait_for_read() ! { |
| 319 | return wait_for_read(c.sock.handle, c.read_deadline, c.read_timeout) |
| 320 | } |
| 321 | |
| 322 | @[inline] |
| 323 | pub fn (mut c TcpConn) wait_for_write() ! { |
| 324 | return wait_for_write(c.sock.handle, c.write_deadline, c.write_timeout) |
| 325 | } |
| 326 | |
| 327 | // set_sock initialises the c.sock field. It should be called after `.accept_only()!`. |
| 328 | // Note: just use `.accept()!`. In most cases it is simpler, and calls `.set_sock()!` for you. |
| 329 | pub fn (mut c TcpConn) set_sock() ! { |
| 330 | c.sock = tcp_socket_from_handle(c.handle)! |
| 331 | $if trace_tcp ? { |
| 332 | eprintln(' TcpListener.accept | << new_sock.handle: ${c.handle:6}') |
| 333 | } |
| 334 | } |
| 335 | |
| 336 | // peer_addr retrieves the ip address and port number used by the peer |
| 337 | pub fn (c &TcpConn) peer_addr() !Addr { |
| 338 | return peer_addr_from_socket_handle(c.sock.handle) |
| 339 | } |
| 340 | |
| 341 | // peer_ip retrieves the ip address used by the peer, and returns it as a string |
| 342 | pub fn (c &TcpConn) peer_ip() !string { |
| 343 | address := c.peer_addr()!.str() |
| 344 | if address.contains(']:') { |
| 345 | // ipv6 addresses similar to this: '[::1]:46098' |
| 346 | ip := address.all_before(']:').all_after('[') |
| 347 | return ip |
| 348 | } |
| 349 | // ipv4 addresses similar to '127.0.0.1:7346' |
| 350 | ip := address.all_before(':') |
| 351 | return ip |
| 352 | } |
| 353 | |
| 354 | pub fn (c &TcpConn) addr() !Addr { |
| 355 | return c.sock.address() |
| 356 | } |
| 357 | |
| 358 | pub fn (c TcpConn) str() string { |
| 359 | s := c.sock.str().replace('\n', ' ').replace(' ', ' ') |
| 360 | return 'TcpConn{ write_deadline: ${c.write_deadline}, read_deadline: ${c.read_deadline}, read_timeout: ${c.read_timeout}, write_timeout: ${c.write_timeout}, sock: ${s} }' |
| 361 | } |
| 362 | |
| 363 | pub struct TcpListener { |
| 364 | pub mut: |
| 365 | sock TcpSocket |
| 366 | accept_timeout time.Duration |
| 367 | accept_deadline time.Time |
| 368 | is_blocking bool = true |
| 369 | } |
| 370 | |
| 371 | @[params] |
| 372 | pub struct ListenOptions { |
| 373 | pub: |
| 374 | dualstack bool = true |
| 375 | backlog int = 128 |
| 376 | } |
| 377 | |
| 378 | pub fn listen_tcp(family AddrFamily, saddr string, options ListenOptions) !&TcpListener { |
| 379 | if family !in [.ip, .ip6] { |
| 380 | return error('listen_tcp only supports ip and ip6') |
| 381 | } |
| 382 | return listen_tcp_with_family(family, saddr, options) or { |
| 383 | if should_fallback_to_ipv4_listener(family, saddr, options, err.code()) { |
| 384 | fallback_saddr := ipv4_fallback_listen_addr(saddr) or { return err } |
| 385 | return listen_tcp_with_family(.ip, fallback_saddr, options) |
| 386 | } |
| 387 | return err |
| 388 | } |
| 389 | } |
| 390 | |
| 391 | fn should_fallback_to_ipv4_listener(family AddrFamily, saddr string, options ListenOptions, err_code int) bool { |
| 392 | // Treat an unspecified IPv6 listener as "dual stack if available, otherwise IPv4 only". |
| 393 | if family != .ip6 || !options.dualstack { |
| 394 | return false |
| 395 | } |
| 396 | if !is_unspecified_ip6_listen_addr(saddr) { |
| 397 | return false |
| 398 | } |
| 399 | return is_ipv6_unavailable_error(err_code) |
| 400 | } |
| 401 | |
| 402 | fn is_unspecified_ip6_listen_addr(saddr string) bool { |
| 403 | address, _ := split_address(saddr) or { return false } |
| 404 | return address in ['', '::'] |
| 405 | } |
| 406 | |
| 407 | fn is_ipv6_unavailable_error(err_code int) bool { |
| 408 | $if windows { |
| 409 | return err_code in [int(WsaError.wsaeafnosupport), int(WsaError.wsaeprotonosupport), |
| 410 | int(WsaError.wsaeaddrnotavail)] |
| 411 | } $else { |
| 412 | return err_code in [C.EAFNOSUPPORT, C.EPROTONOSUPPORT, C.EADDRNOTAVAIL] |
| 413 | } |
| 414 | } |
| 415 | |
| 416 | fn ipv4_fallback_listen_addr(saddr string) !string { |
| 417 | _, port := split_address(saddr)! |
| 418 | return ':${port}' |
| 419 | } |
| 420 | |
| 421 | fn listen_tcp_with_family(family AddrFamily, saddr string, options ListenOptions) !&TcpListener { |
| 422 | mut s := new_tcp_socket(family) or { return error('${err.msg()}; could not create new socket') } |
| 423 | s.set_dualstack(options.dualstack) or {} |
| 424 | |
| 425 | addrs := resolve_addrs(saddr, family, .tcp) or { |
| 426 | return error('${err.msg()}; could not resolve address ${saddr}') |
| 427 | } |
| 428 | // TODO(logic to pick here) |
| 429 | addr := addrs[0] |
| 430 | |
| 431 | // cast to the correct type |
| 432 | alen := addr.len() |
| 433 | socket_error_message(C.bind(s.handle, voidptr(&addr), alen), 'binding to ${saddr} failed')! |
| 434 | mut res := C.listen(s.handle, options.backlog) |
| 435 | if res == 0 { |
| 436 | mut listener := &TcpListener{ |
| 437 | sock: s |
| 438 | accept_deadline: no_deadline |
| 439 | accept_timeout: infinite_timeout |
| 440 | } |
| 441 | // The blocking / non-blocking mode is determined before the connection is established. |
| 442 | $if net_nonblocking_sockets ? { |
| 443 | listener.is_blocking = false |
| 444 | } |
| 445 | return listener |
| 446 | } |
| 447 | |
| 448 | $if !net_nonblocking_sockets ? { |
| 449 | socket_error_message(res, |
| 450 | 'listening on ${saddr} with maximum backlog pending queue of ${options.backlog}, failed')! |
| 451 | return &TcpListener(unsafe { nil }) // for compiler passed |
| 452 | } $else { |
| 453 | // non-blocking sockets may also not succeed immediately when they listen() and need to check the status and take action accordingly. |
| 454 | for { |
| 455 | code := error_code() |
| 456 | if code in [int(error_einprogress), int(error_ewouldblock), int(error_eagain), C.EINTR] { |
| 457 | select(s.handle, .read, connect_timeout)! |
| 458 | res = C.listen(s.handle, options.backlog) |
| 459 | if res == 0 { |
| 460 | break |
| 461 | } |
| 462 | } else { |
| 463 | socket_error_message(res, |
| 464 | 'listening on ${saddr} with maximum backlog pending queue of ${options.backlog}, failed')! |
| 465 | break // for compiler passed |
| 466 | } |
| 467 | } |
| 468 | mut listener := &TcpListener{ |
| 469 | sock: s |
| 470 | accept_deadline: no_deadline |
| 471 | accept_timeout: infinite_timeout |
| 472 | } |
| 473 | // The blocking / non-blocking mode is determined before the connection is established. |
| 474 | $if net_nonblocking_sockets ? { |
| 475 | listener.is_blocking = false |
| 476 | } |
| 477 | return listener |
| 478 | } |
| 479 | } |
| 480 | |
| 481 | // accept a tcp connection from an external source to the listener `l`. |
| 482 | pub fn (mut l TcpListener) accept() !&TcpConn { |
| 483 | mut res := l.accept_only()! |
| 484 | res.set_sock()! |
| 485 | return res |
| 486 | } |
| 487 | |
| 488 | // accept_only accepts a tcp connection from an external source to the listener `l`. |
| 489 | // Unlike `accept`, `accept_only` *will not call* `.set_sock()!` on the result, |
| 490 | // and is thus faster. |
| 491 | // |
| 492 | // Note: you *need* to call `.set_sock()!` manually, before using the |
| 493 | // connection after calling `.accept_only()!`, but that does not have to happen |
| 494 | // in the same thread that called `.accept_only()!`. |
| 495 | // The intention of this API, is to have a more efficient way to accept |
| 496 | // connections, that are later processed by a thread pool, while the main |
| 497 | // thread remains active, so that it can accept other connections. |
| 498 | // See also vlib/veb/veb.v . |
| 499 | // |
| 500 | // If you do not need that, just call `.accept()!` instead, which will call |
| 501 | // `.set_sock()!` for you. |
| 502 | pub fn (mut l TcpListener) accept_only() !&TcpConn { |
| 503 | $if trace_tcp ? { |
| 504 | eprintln(' TcpListener.accept | l.sock.handle: ${l.sock.handle:6}') |
| 505 | } |
| 506 | |
| 507 | // The blocking mode `accept()` does not support a timeout option, so `select` is used instead. |
| 508 | $if !is_coroutine ? { |
| 509 | if l.is_blocking { |
| 510 | l.wait_for_accept()! |
| 511 | } |
| 512 | } |
| 513 | |
| 514 | mut new_handle := $if is_coroutine ? { |
| 515 | C.photon_accept(l.sock.handle, 0, 0, tcp_default_read_timeout) |
| 516 | } $else { |
| 517 | C.accept(l.sock.handle, 0, 0) |
| 518 | } |
| 519 | code := error_code() |
| 520 | if !l.is_blocking && new_handle <= 0 { |
| 521 | if code in [int(error_einprogress), int(error_ewouldblock), int(error_eagain), C.EINTR] { |
| 522 | l.wait_for_accept()! |
| 523 | new_handle = $if is_coroutine ? { |
| 524 | C.photon_accept(l.sock.handle, 0, 0, tcp_default_read_timeout) |
| 525 | } $else { |
| 526 | C.accept(l.sock.handle, 0, 0) |
| 527 | } |
| 528 | } |
| 529 | } |
| 530 | if new_handle <= 0 { |
| 531 | return error('accept failed') |
| 532 | } |
| 533 | |
| 534 | return &TcpConn{ |
| 535 | handle: new_handle |
| 536 | read_timeout: tcp_default_read_timeout |
| 537 | write_timeout: tcp_default_write_timeout |
| 538 | is_blocking: l.is_blocking |
| 539 | } |
| 540 | } |
| 541 | |
| 542 | pub fn (c &TcpListener) accept_deadline() !time.Time { |
| 543 | if c.accept_deadline.unix() != 0 { |
| 544 | return c.accept_deadline |
| 545 | } |
| 546 | return error('invalid deadline') |
| 547 | } |
| 548 | |
| 549 | pub fn (mut c TcpListener) set_accept_deadline(deadline time.Time) { |
| 550 | c.accept_deadline = deadline |
| 551 | } |
| 552 | |
| 553 | pub fn (c &TcpListener) accept_timeout() time.Duration { |
| 554 | return c.accept_timeout |
| 555 | } |
| 556 | |
| 557 | pub fn (mut c TcpListener) set_accept_timeout(t time.Duration) { |
| 558 | c.accept_timeout = t |
| 559 | } |
| 560 | |
| 561 | pub fn (mut c TcpListener) wait_for_accept() ! { |
| 562 | return wait_for_read(c.sock.handle, c.accept_deadline, c.accept_timeout) |
| 563 | } |
| 564 | |
| 565 | pub fn (mut c TcpListener) close() ! { |
| 566 | c.sock.close()! |
| 567 | } |
| 568 | |
| 569 | pub fn (c &TcpListener) addr() !Addr { |
| 570 | return c.sock.address() |
| 571 | } |
| 572 | |
| 573 | pub struct TcpSocket { |
| 574 | Socket |
| 575 | } |
| 576 | |
| 577 | // This is a workaround for issue https://github.com/vlang/v/issues/20858 |
| 578 | // `noline` ensure that in `-prod` mode(CFLAG = `-O3 -flto`), gcc does not generate wrong instruction sequence |
| 579 | @[noinline] |
| 580 | pub fn new_tcp_socket(family AddrFamily) !TcpSocket { |
| 581 | handle := $if is_coroutine ? { |
| 582 | socket_error(C.photon_socket(i32(family), i32(SocketType.tcp), 0))! |
| 583 | } $else { |
| 584 | socket_error(C.socket(i32(family), i32(SocketType.tcp), 0))! |
| 585 | } |
| 586 | mut s := TcpSocket{ |
| 587 | handle: handle |
| 588 | } |
| 589 | $if trace_tcp ? { |
| 590 | eprintln(' new_tcp_socket | s.handle: ${s.handle:6}') |
| 591 | } |
| 592 | |
| 593 | // TODO(emily): |
| 594 | // we shouldn't be using ioctlsocket in the 21st century |
| 595 | // use the non-blocking socket option instead please :) |
| 596 | |
| 597 | // Some options need to be set before the connection is established, otherwise they will not work. |
| 598 | s.set_default_options(family)! |
| 599 | |
| 600 | // Set the desired "blocking/non-blocking" mode before the connection is established, |
| 601 | // and do not change it once the connection is successful. |
| 602 | $if net_nonblocking_sockets ? { |
| 603 | set_blocking(handle, false)! |
| 604 | } |
| 605 | return s |
| 606 | } |
| 607 | |
| 608 | fn tcp_socket_from_handle(sockfd int) !TcpSocket { |
| 609 | mut s := TcpSocket{ |
| 610 | handle: sockfd |
| 611 | } |
| 612 | $if trace_tcp ? { |
| 613 | eprintln(' tcp_socket_from_handle | s.handle: ${s.handle:6}') |
| 614 | } |
| 615 | |
| 616 | s.set_dualstack(true) or { |
| 617 | // Not ipv6, we dont care |
| 618 | } |
| 619 | addr := addr_from_socket_handle(sockfd) |
| 620 | s.set_default_options(addr.family())! |
| 621 | |
| 622 | return s |
| 623 | } |
| 624 | |
| 625 | // tcp_socket_from_handle_raw is similar to tcp_socket_from_handle, but it does not modify any socket options |
| 626 | pub fn tcp_socket_from_handle_raw(sockfd int) TcpSocket { |
| 627 | mut s := TcpSocket{ |
| 628 | handle: sockfd |
| 629 | } |
| 630 | $if trace_tcp ? { |
| 631 | eprintln(' tcp_socket_from_handle_raw | s.handle: ${s.handle:6}') |
| 632 | } |
| 633 | return s |
| 634 | } |
| 635 | |
| 636 | fn (mut s TcpSocket) set_option(level int, opt int, value int) ! { |
| 637 | socket_error(C.setsockopt(s.handle, level, opt, &value, sizeof(int)))! |
| 638 | } |
| 639 | |
| 640 | pub fn (mut s TcpSocket) set_option_bool(opt SocketOption, value bool) ! { |
| 641 | // TODO: reenable when this `in` operation works again |
| 642 | // if opt !in opts_can_set { |
| 643 | // return err_option_not_settable |
| 644 | // } |
| 645 | // if opt !in opts_bool { |
| 646 | // return err_option_wrong_type |
| 647 | // } |
| 648 | x := int(value) |
| 649 | s.set_option(C.SOL_SOCKET, int(opt), x)! |
| 650 | } |
| 651 | |
| 652 | pub fn (mut s TcpSocket) set_option_int(opt SocketOption, value int) ! { |
| 653 | s.set_option(C.SOL_SOCKET, int(opt), value)! |
| 654 | } |
| 655 | |
| 656 | pub fn (mut s TcpSocket) set_dualstack(on bool) ! { |
| 657 | x := int(!on) |
| 658 | s.set_option(C.IPPROTO_IPV6, int(SocketOption.ipv6_only), x)! |
| 659 | } |
| 660 | |
| 661 | fn (mut s TcpSocket) set_default_options(af AddrFamily) ! { |
| 662 | s.set_option_int(.reuse_addr, 1)! |
| 663 | |
| 664 | // At the socket level to ignore the exception signal (usually SIGNPIPE). |
| 665 | // In Linux, instead of using set_option(), specify the C.MSG_NOSIGNAL flag in c.send(). |
| 666 | // In Windows, there is no need to process this signal. |
| 667 | $if macos { |
| 668 | s.set_option(C.SOL_SOCKET, C.SO_NOSIGPIPE, 1)! |
| 669 | } |
| 670 | |
| 671 | // Enable the NODELAY option by default. |
| 672 | if af != .unix { |
| 673 | s.set_option(C.IPPROTO_TCP, C.TCP_NODELAY, 1)! |
| 674 | } |
| 675 | } |
| 676 | |
| 677 | // bind a local rddress for TcpSocket |
| 678 | pub fn (mut s TcpSocket) bind(addr string) ! { |
| 679 | addrs := resolve_addrs(addr, AddrFamily.ip, .tcp) or { |
| 680 | return error('${err.msg()}; could not resolve address ${addr}') |
| 681 | } |
| 682 | |
| 683 | // TODO(logic to pick here) |
| 684 | a := addrs[0] |
| 685 | |
| 686 | // cast to the correct type |
| 687 | alen := a.len() |
| 688 | socket_error_message(C.bind(s.handle, voidptr(&a), alen), 'binding to ${addr} failed') or { |
| 689 | return err |
| 690 | } |
| 691 | } |
| 692 | |
| 693 | fn (mut s TcpSocket) close() ! { |
| 694 | shutdown(s.handle) |
| 695 | return close(s.handle) |
| 696 | } |
| 697 | |
| 698 | const connect_timeout = 5 * time.second |
| 699 | |
| 700 | fn (mut s TcpSocket) connect(a Addr) ! { |
| 701 | $if net_nonblocking_sockets ? { |
| 702 | res := $if is_coroutine ? { |
| 703 | C.photon_connect(s.handle, voidptr(&a), a.len(), tcp_default_read_timeout) |
| 704 | } $else { |
| 705 | C.connect(s.handle, voidptr(&a), a.len()) |
| 706 | } |
| 707 | ecode := error_code() |
| 708 | if res == 0 { |
| 709 | return |
| 710 | } |
| 711 | // On nix non-blocking sockets we expect einprogress |
| 712 | // On windows we expect res == -1 && error_code() == ewouldblock |
| 713 | if (is_windows && ecode == int(error_ewouldblock)) || (!is_windows && res == -1 |
| 714 | && ecode in [int(error_einprogress), int(error_eagain), C.EINTR]) { |
| 715 | // The socket is nonblocking and the connection cannot be completed |
| 716 | // immediately. (UNIX domain sockets failed with EAGAIN instead.) |
| 717 | // It is possible to select(2) or poll(2) for completion by selecting |
| 718 | // the socket for writing. After select(2) indicates writability, |
| 719 | // use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to |
| 720 | // determine whether connect() completed successfully (SO_ERROR is zero) or |
| 721 | // unsuccessfully (SO_ERROR is one of the usual error codes listed here, |
| 722 | // ex‐ plaining the reason for the failure). |
| 723 | write_result := select(s.handle, .write, connect_timeout)! |
| 724 | err := 0 |
| 725 | len := sizeof(err) |
| 726 | xyz := C.getsockopt(s.handle, C.SOL_SOCKET, C.SO_ERROR, &err, &len) |
| 727 | if xyz == 0 && err == 0 { |
| 728 | return |
| 729 | } |
| 730 | if write_result { |
| 731 | if xyz == 0 { |
| 732 | wrap_error(err)! |
| 733 | return |
| 734 | } |
| 735 | return |
| 736 | } |
| 737 | return err_timed_out |
| 738 | } |
| 739 | wrap_error(ecode)! |
| 740 | return |
| 741 | } $else { |
| 742 | x := $if is_coroutine ? { |
| 743 | C.photon_connect(s.handle, voidptr(&a), a.len(), tcp_default_read_timeout) |
| 744 | } $else { |
| 745 | C.connect(s.handle, voidptr(&a), a.len()) |
| 746 | } |
| 747 | socket_error(x)! |
| 748 | } |
| 749 | } |
| 750 | |