diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 2c87c865b1..359d1a496f 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -663,13 +663,14 @@ pub const VTable = struct { now: *const fn (?*anyopaque, clockid: std.posix.clockid_t) ClockGetTimeError!Timestamp, sleep: *const fn (?*anyopaque, clockid: std.posix.clockid_t, deadline: Deadline) SleepError!void, - listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.ListenOptions) net.ListenError!net.Server, - bind: *const fn (?*anyopaque, address: net.IpAddress, options: net.BindOptions) net.BindError!net.Socket, - accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Server.Connection, - netSend: *const fn (?*anyopaque, address: net.IpAddress, data: []const []const u8) net.SendError!void, + listen: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.ListenOptions) net.IpAddress.ListenError!net.Server, + accept: *const fn (?*anyopaque, server: *net.Server) net.Server.AcceptError!net.Stream, + ipBind: *const fn (?*anyopaque, address: net.IpAddress, options: net.IpAddress.BindOptions) net.IpAddress.BindError!net.Socket, + netSend: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, data: []const u8) net.Socket.SendError!void, + netReceive: *const fn (?*anyopaque, handle: net.Socket.Handle, address: net.IpAddress, buffer: []u8) net.Socket.ReceiveError!void, netRead: *const fn (?*anyopaque, src: net.Stream, data: [][]u8) net.Stream.Reader.Error!usize, netWrite: *const fn (?*anyopaque, dest: net.Stream, header: []const u8, data: []const []const u8, splat: usize) net.Stream.Writer.Error!usize, - netClose: *const fn (?*anyopaque, socket: net.Socket) void, + netClose: *const fn (?*anyopaque, handle: net.Socket.Handle) void, netInterfaceNameResolve: *const fn (?*anyopaque, *const net.Interface.Name) net.Interface.Name.ResolveError!net.Interface, netInterfaceName: *const fn (?*anyopaque, net.Interface) net.Interface.NameError!net.Interface.Name, }; @@ -711,6 +712,10 @@ pub const Duration = struct { pub fn ms(x: u64) Duration { return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_ms }; } + + pub fn seconds(x: u64) Duration { + return .{ .nanoseconds = @as(i96, x) * std.time.ns_per_s }; + } }; pub const Deadline = union(enum) { duration: Duration, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c9b0810dac..07f87fdf0c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -124,8 +124,19 @@ pub fn io(pool: *Pool) Io { .now = now, .sleep = sleep, - .listen = listen, - .accept = accept, + .listen = switch (builtin.os.tag) { + .windows => @panic("TODO"), + else => listenPosix, + }, + .accept = switch (builtin.os.tag) { + .windows => @panic("TODO"), + else => acceptPosix, + }, + .ipBind = switch (builtin.os.tag) { + .windows => @panic("TODO"), + else => ipBindPosix, + }, + .netClose = netClose, .netRead = switch (builtin.os.tag) { .windows => @panic("TODO"), else => netReadPosix, @@ -134,7 +145,8 @@ pub fn io(pool: *Pool) Io { .windows => @panic("TODO"), else => netWritePosix, }, - .netClose = netClose, + .netSend = netSend, + .netReceive = netReceive, .netInterfaceNameResolve = netInterfaceNameResolve, .netInterfaceName = netInterfaceName, }, @@ -460,7 +472,7 @@ fn asyncDetached( fn await( userdata: ?*anyopaque, - any_future: *std.Io.AnyFuture, + any_future: *Io.AnyFuture, result: []u8, result_alignment: std.mem.Alignment, ) void { @@ -984,59 +996,228 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { return result.?; } -fn listen(userdata: ?*anyopaque, address: Io.net.IpAddress, options: Io.net.ListenOptions) Io.net.ListenError!Io.net.Server { +fn listenPosix( + userdata: ?*anyopaque, + address: Io.net.IpAddress, + options: Io.net.IpAddress.ListenOptions, +) Io.net.IpAddress.ListenError!Io.net.Server { const pool: *Pool = @ptrCast(@alignCast(userdata)); - try pool.checkCancel(); - - const nonblock: u32 = if (options.force_nonblocking) posix.SOCK.NONBLOCK else 0; - const sock_flags = posix.SOCK.STREAM | posix.SOCK.CLOEXEC | nonblock; - const proto: u32 = posix.IPPROTO.TCP; - const family = posixAddressFamily(address); - const sockfd = try posix.socket(family, sock_flags, proto); - const stream: std.net.Stream = .{ .handle = sockfd }; - errdefer stream.close(); + const family = posixAddressFamily(&address); + const protocol: u32 = posix.IPPROTO.TCP; + const socket_fd = while (true) { + try pool.checkCancel(); + const flags: u32 = posix.SOCK.STREAM | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; + const socket_rc = posix.system.socket(family, flags, protocol); + switch (posix.errno(socket_rc)) { + .SUCCESS => { + const fd: posix.fd_t = @intCast(socket_rc); + errdefer posix.close(fd); + if (socket_flags_unsupported) while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) { + .SUCCESS => break, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + }; + break fd; + }, + .INTR => continue, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }; + errdefer posix.close(socket_fd); if (options.reuse_address) { - try posix.setsockopt( - sockfd, - posix.SOL.SOCKET, - posix.SO.REUSEADDR, - &std.mem.toBytes(@as(c_int, 1)), - ); - if (@hasDecl(posix.SO, "REUSEPORT") and family != posix.AF.UNIX) { - try posix.setsockopt( - sockfd, - posix.SOL.SOCKET, - posix.SO.REUSEPORT, - &std.mem.toBytes(@as(c_int, 1)), - ); - } + try setSocketOption(pool, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); + if (@hasDecl(posix.SO, "REUSEPORT")) + try setSocketOption(pool, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); } var storage: PosixAddress = undefined; var socklen = addressToPosix(address, &storage); - try posix.bind(sockfd, &storage.any, socklen); - try posix.listen(sockfd, options.kernel_backlog); - try posix.getsockname(sockfd, &storage.any, &socklen); + try posixBind(pool, socket_fd, &storage.any, socklen); + + while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { + .SUCCESS => break, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + } + + try posixGetSockName(pool, socket_fd, &storage.any, &socklen); return .{ - .listen_address = addressFromPosix(&storage), - .stream = .{ .handle = stream.handle }, + .socket = .{ + .handle = socket_fd, + .address = addressFromPosix(&storage), + }, }; } -fn accept(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Server.Connection { +fn posixBind(pool: *Pool, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { + while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { + .SUCCESS => break, + .INTR => continue, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // always a race condition if this error is returned + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .ADDRNOTAVAIL => return error.AddressUnavailable, + .FAULT => |err| return errnoBug(err), // invalid `addr` pointer + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +fn posixGetSockName(pool: *Pool, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void { + while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { + .SUCCESS => break, + .INTR => continue, + .BADF => |err| return errnoBug(err), // always a race condition + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // always a race condition + .NOBUFS => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + } +} + +fn setSocketOption(pool: *Pool, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { + const o: []const u8 = @ptrCast(&option); + while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { + .SUCCESS => return, + .INTR => continue, + .BADF => |err| return errnoBug(err), // always a race condition + .NOTSOCK => |err| return errnoBug(err), // always a race condition + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + } +} + +fn ipBindPosix( + userdata: ?*anyopaque, + address: Io.net.IpAddress, + options: Io.net.IpAddress.BindOptions, +) Io.net.IpAddress.BindError!Io.net.Socket { const pool: *Pool = @ptrCast(@alignCast(userdata)); - try pool.checkCancel(); + const mode = posixSocketMode(options.mode); + const family = posixAddressFamily(&address); + const protocol = posixProtocol(options.protocol); + const socket_fd = while (true) { + try pool.checkCancel(); + const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; + const socket_rc = posix.system.socket(family, flags, protocol); + switch (posix.errno(socket_rc)) { + .SUCCESS => { + const fd: posix.fd_t = @intCast(socket_rc); + errdefer posix.close(fd); + if (socket_flags_unsupported) while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) { + .SUCCESS => break, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + }; + break fd; + }, + .INTR => continue, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .INVAL => return error.ProtocolUnsupportedBySystem, + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, + .PROTOTYPE => return error.SocketModeUnsupported, + else => |err| return posix.unexpectedErrno(err), + } + }; + + if (options.ip6_only) { + try setSocketOption(pool, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); + } var storage: PosixAddress = undefined; - var addr_len: posix.socklen_t = @sizeOf(PosixAddress); - const fd = try posix.accept(server.stream.handle, &storage.any, &addr_len, posix.SOCK.CLOEXEC); + var socklen = addressToPosix(address, &storage); + try posixBind(pool, socket_fd, &storage.any, socklen); + try posixGetSockName(pool, socket_fd, &storage.any, &socklen); return .{ - .stream = .{ .handle = fd }, + .handle = socket_fd, .address = addressFromPosix(&storage), }; } +const socket_flags_unsupported = builtin.os.tag.isDarwin() or native_os == .haiku; // 💩💩 +const have_accept4 = !socket_flags_unsupported; + +fn acceptPosix(userdata: ?*anyopaque, server: *Io.net.Server) Io.net.Server.AcceptError!Io.net.Stream { + const pool: *Pool = @ptrCast(@alignCast(userdata)); + const listen_fd = server.socket.handle; + var storage: PosixAddress = undefined; + var addr_len: posix.socklen_t = @sizeOf(PosixAddress); + const fd = while (true) { + try pool.checkCancel(); + const rc = if (have_accept4) + posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC) + else + posix.system.accept(listen_fd, &storage.any, &addr_len); + switch (posix.errno(rc)) { + .SUCCESS => { + const fd: posix.fd_t = @intCast(rc); + errdefer posix.close(fd); + if (!have_accept4) while (true) { + try pool.checkCancel(); + switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, posix.FD_CLOEXEC))) { + .SUCCESS => break, + .INTR => continue, + else => |err| return posix.unexpectedErrno(err), + } + }; + break fd; + }, + .INTR => continue, + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // always a race condition + .CONNABORTED => return error.ConnectionAborted, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.SocketNotListening, + .NOTSOCK => |err| return errnoBug(err), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .OPNOTSUPP => |err| return errnoBug(err), + .PROTO => return error.ProtocolFailure, + .PERM => return error.BlockedByFirewall, + else => |err| return posix.unexpectedErrno(err), + } + }; + return .{ .socket = .{ + .handle = fd, + .address = addressFromPosix(&storage), + } }; +} + fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.net.Stream.Reader.Error!usize { const pool: *Pool = @ptrCast(@alignCast(userdata)); try pool.checkCancel(); @@ -1052,11 +1233,41 @@ fn netReadPosix(userdata: ?*anyopaque, stream: Io.net.Stream, data: [][]u8) Io.n } const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - const n = try posix.readv(stream.handle, dest); + const n = try posix.readv(stream.socket.handle, dest); if (n == 0) return error.EndOfStream; return n; } +fn netSend( + userdata: ?*anyopaque, + handle: Io.net.Socket.Handle, + address: Io.net.IpAddress, + data: []const u8, +) Io.net.Socket.SendError!void { + const pool: *Pool = @ptrCast(@alignCast(userdata)); + try pool.checkCancel(); + + _ = handle; + _ = address; + _ = data; + @panic("TODO"); +} + +fn netReceive( + userdata: ?*anyopaque, + handle: Io.net.Socket.Handle, + address: Io.net.IpAddress, + buffer: []u8, +) Io.net.Socket.ReceiveError!void { + const pool: *Pool = @ptrCast(@alignCast(userdata)); + try pool.checkCancel(); + + _ = handle; + _ = address; + _ = buffer; + @panic("TODO"); +} + fn netWritePosix( userdata: ?*anyopaque, stream: Io.net.Stream, @@ -1106,7 +1317,7 @@ fn netWritePosix( }, }; const flags = posix.MSG.NOSIGNAL; - return posix.sendmsg(stream.handle, &msg, flags); + return posix.sendmsg(stream.socket.handle, &msg, flags); } fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), bytes: []const u8) void { @@ -1117,11 +1328,13 @@ fn addBuf(v: []posix.iovec_const, i: *@FieldType(posix.msghdr_const, "iovlen"), i.* += 1; } -fn netClose(userdata: ?*anyopaque, stream: Io.net.Stream) void { +fn netClose(userdata: ?*anyopaque, handle: Io.net.Socket.Handle) void { const pool: *Pool = @ptrCast(@alignCast(userdata)); _ = pool; - const net_stream: std.net.Stream = .{ .handle = stream.handle }; - return net_stream.close(); + switch (native_os) { + .windows => windows.closesocket(handle) catch recoverableOsBugDetected(), + else => posix.close(handle), + } } fn netInterfaceNameResolve( @@ -1153,13 +1366,13 @@ fn netInterfaceNameResolve( try pool.checkCancel(); switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) }, - .INVAL => |err| return badErrno(err), // Bad parameters. - .NOTTY => |err| return badErrno(err), - .NXIO => |err| return badErrno(err), - .BADF => |err| return badErrno(err), // Always a race condition. - .FAULT => |err| return badErrno(err), // Bad pointer parameter. + .INVAL => |err| return errnoBug(err), // Bad parameters. + .NOTTY => |err| return errnoBug(err), + .NXIO => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // Always a race condition. + .FAULT => |err| return errnoBug(err), // Bad pointer parameter. .INTR => continue, - .IO => |err| return badErrno(err), // sock_fd is not a file descriptor + .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor .NODEV => return error.InterfaceNotFound, else => |err| return posix.unexpectedErrno(err), } @@ -1207,8 +1420,8 @@ const PosixAddress = extern union { in6: posix.sockaddr.in6, }; -fn posixAddressFamily(a: Io.net.IpAddress) posix.sa_family_t { - return switch (a) { +fn posixAddressFamily(a: *const Io.net.IpAddress) posix.sa_family_t { + return switch (a.*) { .ip4 => posix.AF.INET, .ip6 => posix.AF.INET6, }; @@ -1267,9 +1480,27 @@ fn address6ToPosix(a: Io.net.Ip6Address) posix.sockaddr.in6 { }; } -fn badErrno(err: posix.E) Io.UnexpectedError { +fn errnoBug(err: posix.E) Io.UnexpectedError { switch (builtin.mode) { .Debug => std.debug.panic("programmer bug caused syscall error: {t}", .{err}), else => return error.Unexpected, } } + +fn posixSocketMode(mode: Io.net.Socket.Mode) u32 { + return switch (mode) { + .stream => posix.SOCK.STREAM, + .dgram => posix.SOCK.DGRAM, + .seqpacket => posix.SOCK.SEQPACKET, + .raw => posix.SOCK.RAW, + .rdm => posix.SOCK.RDM, + }; +} + +fn posixProtocol(protocol: ?Io.net.Protocol) u32 { + return @intFromEnum(protocol orelse return 0); +} + +fn recoverableOsBugDetected() void { + if (builtin.mode == .Debug) unreachable; +} diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index c9dc1d619a..eca6cdd1f7 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -6,26 +6,41 @@ const assert = std.debug.assert; pub const HostName = @import("net/HostName.zig"); -pub const ListenError = std.net.Address.ListenError || Io.Cancelable; - -pub const BindError = std.net.Address.BindError || Io.Cancelable; - -pub const ListenOptions = struct { - /// How many connections the kernel will accept on the application's behalf. - /// If more than this many connections pool in the kernel, clients will start - /// seeing "Connection refused". - kernel_backlog: u31 = 128, - /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX. - /// Sets SO_REUSEADDR on Windows, which is roughly equivalent. - reuse_address: bool = false, - force_nonblocking: bool = false, -}; - -pub const BindOptions = struct { - /// The socket is restricted to sending and receiving IPv6 packets only. - /// In this case, an IPv4 and an IPv6 application can bind to a single port - /// at the same time. - ip6_only: bool = false, +/// Source of truth: Internet Assigned Numbers Authority (IANA) +pub const Protocol = enum(u32) { + hopopts = 0, + icmp = 1, + igmp = 2, + ipip = 4, + tcp = 6, + egp = 8, + pup = 12, + udp = 17, + idp = 22, + tp = 29, + dccp = 33, + ipv6 = 41, + routing = 43, + fragment = 44, + rsvp = 46, + gre = 47, + esp = 50, + ah = 51, + icmpv6 = 58, + none = 59, + dstopts = 60, + mtp = 92, + beetph = 94, + encap = 98, + pim = 103, + comp = 108, + sctp = 132, + mh = 135, + udplite = 136, + mpls = 137, + ethernet = 143, + raw = 255, + mptcp = 262, }; pub const IpAddress = union(enum) { @@ -132,12 +147,70 @@ pub const IpAddress = union(enum) { }; } + pub const ListenError = error{ + /// The address is already taken. Can occur when bound port is 0 but + /// all ephemeral ports are already in use. + AddressInUse, + /// A nonexistent interface was requested or the requested address was not local. + AddressUnavailable, + /// The local network interface used to reach the destination is offline. + NetworkSubsystemDown, + /// Insufficient memory or other resource internal to the operating system. + SystemResources, + /// Per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + /// System-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + /// The requested address family (IPv4 or IPv6) is not supported by the operating system. + AddressFamilyUnsupported, + } || Io.UnexpectedError || Io.Cancelable; + + pub const ListenOptions = struct { + /// How many connections the kernel will accept on the application's behalf. + /// If more than this many connections pool in the kernel, clients will start + /// seeing "Connection refused". + kernel_backlog: u31 = 128, + /// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX. + /// Sets SO_REUSEADDR on Windows, which is roughly equivalent. + reuse_address: bool = false, + }; + /// Waits for a TCP connection. When using this API, `bind` does not need /// to be called. The returned `Server` has an open `stream`. pub fn listen(address: IpAddress, io: Io, options: ListenOptions) ListenError!Server { - return io.vtable.listen(io.userdata, address, options); + return io.vtable.tcpListen(io.userdata, address, options); } + pub const BindError = error{ + /// The address is already taken. Can occur when bound port is 0 but + /// all ephemeral ports are already in use. + AddressInUse, + /// A nonexistent interface was requested or the requested address was not local. + AddressUnavailable, + /// The address is not valid for the address family of socket. + AddressFamilyUnsupported, + /// Insufficient memory or other resource internal to the operating system. + SystemResources, + /// The local network interface used to reach the destination is offline. + NetworkSubsystemDown, + ProtocolUnsupportedBySystem, + ProtocolUnsupportedByAddressFamily, + /// Per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + /// System-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + SocketModeUnsupported, + } || Io.UnexpectedError || Io.Cancelable; + + pub const BindOptions = struct { + /// The socket is restricted to sending and receiving IPv6 packets only. + /// In this case, an IPv4 and an IPv6 application can bind to a single port + /// at the same time. + ip6_only: bool = false, + mode: Socket.Mode, + protocol: ?Protocol = null, + }; + /// Associates an address with a `Socket` which can be used to receive UDP /// packets and other kinds of non-streaming messages. See `listen` for a /// streaming alternative. @@ -145,7 +218,7 @@ pub const IpAddress = union(enum) { /// One bound `Socket` can be used to receive messages from multiple /// different addresses. pub fn bind(address: IpAddress, io: Io, options: BindOptions) BindError!Socket { - return io.vtable.bind(io.userdata, address, options); + return io.vtable.ipBind(io.userdata, address, options); } }; @@ -255,7 +328,7 @@ pub const Ip6Address = struct { pub fn fromIp4(ip4: Ip4Address) Ip6Address { const b = &ip4.bytes; return .{ - .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] }, + .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] }, .port = ip4.port, }; } @@ -682,7 +755,25 @@ pub const Interface = struct { pub const Socket = struct { handle: Handle, /// Contains the resolved ephemeral port number if requested. - bind_address: IpAddress, + address: IpAddress, + + pub const Mode = enum { + /// Provides sequenced, reliable, two-way, connection-based byte + /// streams. An out-of-band data transmission mechanism may be + /// supported. + stream, + /// Supports datagrams (connectionless, unreliable messages of a fixed + /// maximum length). + dgram, + /// Provides a sequenced, reliable, two-way connection-based data + /// transmission path for datagrams of fixed maximum length; a consumer + /// is required to read an entire packet with each input system call. + seqpacket, + /// Provides raw network protocol access. + raw, + /// Provides a reliable datagram layer that does not guarantee ordering. + rdm, + }; /// Underlying platform-defined type which may or may not be /// interchangeable with a file system file descriptor. @@ -691,8 +782,48 @@ pub const Socket = struct { else => std.posix.fd_t, }; - pub fn close(s: Socket, io: Io) void { - return io.vtable.netClose(io.userdata, s); + pub fn close(s: *Socket, io: Io) void { + io.vtable.netClose(io.userdata, s.handle); + s.handle = undefined; + } + + pub const SendError = error{ + /// The socket type requires that message be sent atomically, and the size of the message + /// to be sent made this impossible. The message is not transmitted. + MessageTooBig, + /// The output queue for a network interface was full. This generally indicates that the + /// interface has stopped sending, but may be caused by transient congestion. (Normally, + /// this does not occur in Linux. Packets are just silently dropped when a device queue + /// overflows.) + /// + /// This is also caused when there is not enough kernel memory available. + SystemResources, + /// No route to network. + NetworkUnreachable, + /// Network reached but no route to host. + HostUnreachable, + /// The local network interface used to reach the destination is offline. + NetworkSubsystemDown, + /// The destination address is not listening. Can still occur for + /// connectionless messages. + ConnectionRefused, + /// Operating system or protocol does not support the address family. + AddressFamilyUnsupported, + } || Io.UnexpectedError || Io.Cancelable; + + /// Transfers `data` to `dest`, connectionless. + pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void { + return io.vtable.netSend(io.userdata, s.handle, dest, data); + } + + pub const ReceiveError = error{} || Io.Cancelable; + + /// Transfers `data` from `source`, connectionless. + /// + /// Returned slice has same pointer as `buffer` with possibly shorter length. + pub fn receive(s: *const Socket, io: Io, source: *const IpAddress, buffer: []u8) ReceiveError![]u8 { + const n = try io.vtable.netReceive(io.userdata, s.handle, source, buffer); + return buffer[0..n]; } }; @@ -784,11 +915,6 @@ pub const Stream = struct { pub const Server = struct { socket: Socket, - pub const Connection = struct { - stream: Stream, - address: IpAddress, - }; - pub fn deinit(s: *Server, io: Io) void { s.socket.close(io); s.* = undefined; @@ -796,9 +922,8 @@ pub const Server = struct { pub const AcceptError = std.posix.AcceptError || Io.Cancelable; - /// Blocks until a client connects to the server. The returned `Connection` has - /// an open stream. - pub fn accept(s: *Server, io: Io) AcceptError!Connection { + /// Blocks until a client connects to the server. + pub fn accept(s: *Server, io: Io) AcceptError!Stream { return io.vtable.accept(io, s); } }; diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 5e41e12b7d..091c6e2b96 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -539,7 +539,7 @@ pub const ResolvConf = struct { .search_buffer = undefined, .search_len = 0, .ndots = 1, - .timeout = 5, + .timeout = .seconds(5), .attempts = 2, }; @@ -589,7 +589,7 @@ pub const ResolvConf = struct { switch (std.meta.stringToEnum(Option, name) orelse continue) { .ndots => rc.ndots = @min(value, 15), .attempts => rc.attempts = @min(value, 10), - .timeout => rc.timeout = @min(value, 60), + .timeout => rc.timeout = .seconds(@min(value, 60)), } }, .nameserver => { @@ -638,14 +638,15 @@ pub const ResolvConf = struct { const socket = s: { if (any_ip6) ip6: { const ip6_addr: IpAddress = .{ .ip6 = .unspecified(0) }; - const socket = ip6_addr.bind(io, .{ .ip6_only = true }) catch |err| switch (err) { - error.AddressFamilyNotSupported => break :ip6, + const socket = ip6_addr.bind(io, .{ .ip6_only = true, .mode = .dgram }) catch |err| switch (err) { + error.AddressFamilyUnsupported => break :ip6, + else => |e| return e, }; break :s socket; } any_ip6 = false; const ip4_addr: IpAddress = .{ .ip4 = .unspecified(0) }; - const socket = try ip4_addr.bind(io, .{}); + const socket = try ip4_addr.bind(io, .{ .mode = .dgram }); break :s socket; }; defer socket.close(); diff --git a/lib/std/net.zig b/lib/std/net.zig index 331806f203..d1192c0182 100644 --- a/lib/std/net.zig +++ b/lib/std/net.zig @@ -2383,6 +2383,7 @@ pub const Stream = struct { } }; +/// A bound, listening TCP socket, ready to accept new connections. pub const Server = struct { listen_address: Address, stream: Stream,