const builtin = @import("builtin"); const native_os = builtin.os.tag; const std = @import("../std.zig"); const Io = std.Io; const assert = std.debug.assert; pub const HostName = @import("net/HostName.zig"); /// 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) { ip4: Ip4Address, ip6: Ip6Address, pub const Family = @typeInfo(IpAddress).@"union".tag_type.?; /// Parse the given IP address string into an `IpAddress` value. /// /// This is a pure function but it cannot handle IPv6 addresses that have /// scope ids ("%foo" at the end). To also handle those, `resolve` must be /// called instead. pub fn parse(text: []const u8, port: u16) !IpAddress { if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) { error.Overflow, error.InvalidEnd, error.InvalidCharacter, error.Incomplete, error.NonCanonical, => {}, } return parseIp6(text, port); } pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress { return .{ .ip4 = try Ip4Address.parse(text, port) }; } /// This is a pure function but it cannot handle IPv6 addresses that have /// scope ids ("%foo" at the end). To also handle those, `resolveIp6` must be /// called instead. pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress { return .{ .ip6 = try Ip6Address.parse(text, port) }; } /// This function requires an `Io` parameter because it must query the operating /// system to convert interface name to index. For example, in /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by /// creating a socket and then using an `ioctl` syscall. /// /// For a pure function that cannot handle scopes, see `parse`. pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress { if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) { error.Overflow, error.InvalidEnd, error.InvalidCharacter, error.Incomplete, error.NonCanonical, => {}, } return resolveIp6(io, text, port); } pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress { return .{ .ip6 = try Ip6Address.resolve(io, text, port) }; } /// Returns the port in native endian. pub fn getPort(a: IpAddress) u16 { return switch (a) { inline .ip4, .ip6 => |x| x.port, }; } /// `port` is native-endian. pub fn setPort(a: *IpAddress, port: u16) void { switch (a) { inline .ip4, .ip6 => |*x| x.port = port, } } /// Includes the optional scope ("%foo" at the end) in IPv6 addresses. /// /// See `format` for an alternative that omits scopes and does /// not require an `Io` parameter. pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void { switch (a) { .ip4 => |x| return x.format(w), .ip6 => |x| return x.formatResolved(io, w), } } /// See `formatResolved` for an alternative that additionally prints the optional /// scope at the end of IPv6 addresses and requires an `Io` parameter. pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void { switch (a) { inline .ip4, .ip6 => |x| return x.format(w), } } pub fn eql(a: *const IpAddress, b: *const IpAddress) bool { return switch (a.*) { .ip4 => |a_ip4| switch (b.*) { .ip4 => |b_ip4| a_ip4.eql(b_ip4), else => false, }, .ip6 => |a_ip6| switch (b.*) { .ip6 => |b_ip6| a_ip6.eql(b_ip6), else => false, }, }; } 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. NetworkDown, /// 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.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. NetworkDown, 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. /// /// 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.ipBind(io.userdata, address, options); } }; /// An IPv4 address in binary memory layout. pub const Ip4Address = struct { bytes: [4]u8, port: u16, pub fn loopback(port: u16) Ip4Address { return .{ .bytes = .{ 127, 0, 0, 1 }, .port = port, }; } pub fn unspecified(port: u16) Ip4Address { return .{ .bytes = .{ 0, 0, 0, 0 }, .port = port, }; } pub const ParseError = error{ Overflow, InvalidEnd, InvalidCharacter, Incomplete, NonCanonical, }; pub fn parse(buffer: []const u8, port: u16) ParseError!Ip4Address { var bytes: [4]u8 = @splat(0); var index: u8 = 0; var saw_any_digits = false; var has_zero_prefix = false; for (buffer) |c| switch (c) { '.' => { if (!saw_any_digits) return error.InvalidCharacter; if (index == 3) return error.InvalidEnd; index += 1; saw_any_digits = false; has_zero_prefix = false; }, '0'...'9' => { if (c == '0' and !saw_any_digits) { has_zero_prefix = true; } else if (has_zero_prefix) { return error.NonCanonical; } saw_any_digits = true; bytes[index] = try std.math.mul(u8, bytes[index], 10); bytes[index] = try std.math.add(u8, bytes[index], c - '0'); }, else => return error.InvalidCharacter, }; if (index == 3 and saw_any_digits) return .{ .bytes = bytes, .port = port, }; return error.Incomplete; } pub fn format(a: Ip4Address, w: *Io.Writer) Io.Writer.Error!void { const bytes = &a.bytes; try w.print("{d}.{d}.{d}.{d}:{d}", .{ bytes[0], bytes[1], bytes[2], bytes[3], a.port }); } pub fn eql(a: Ip4Address, b: Ip4Address) bool { const a_int: u32 = @bitCast(a.bytes); const b_int: u32 = @bitCast(b.bytes); return a.port == b.port and a_int == b_int; } }; /// An IPv6 address in binary memory layout. pub const Ip6Address = struct { /// Native endian port: u16, /// Big endian bytes: [16]u8, flow: u32 = 0, interface: Interface = .none, pub const Policy = struct { addr: [16]u8, len: u8, mask: u8, prec: u8, label: u8, }; pub fn loopback(port: u16) Ip6Address { return .{ .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }, .port = port, }; } pub fn unspecified(port: u16) Ip6Address { return .{ .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, .port = port, }; } /// Constructs an IPv4-mapped IPv6 address. pub fn fromIp4(ip4: Ip4Address) Ip6Address { const b = &ip4.bytes; return .{ .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] }, .port = ip4.port, }; } /// Given an `IpAddress`, converts it to an `Ip6Address` directly, or via /// constructing an IPv4-mapped IPv6 address. pub fn fromAny(addr: IpAddress) Ip6Address { return switch (addr) { .ip4 => |ip4| fromIp4(ip4), .ip6 => |ip6| ip6, }; } /// An IPv6 address but with `Interface` as a name rather than index. pub const Unresolved = struct { /// Big endian bytes: [16]u8, interface_name: ?Interface.Name, pub const Parsed = union(enum) { success: Unresolved, invalid_byte: usize, unexpected_end, junk_after_end: usize, interface_name_oversized: usize, }; pub fn parse(text: []const u8) Parsed { if (text.len < 2) return .unexpected_end; // Has to be u16 elements to handle 3-digit hex numbers from compression. var parts: [8]u16 = @splat(0); var parts_i: u8 = 0; var text_i: u8 = 0; var digit_i: u8 = 0; var compress_start: ?u8 = null; var interface_name_text: ?[]const u8 = null; const State = union(enum) { digit, end }; state: switch (State.digit) { .digit => c: switch (text[text_i]) { 'a'...'f' => |c| { const digit = c - 'a' + 10; parts[parts_i] = parts[parts_i] * 16 + digit; if (digit_i == 4) return .{ .invalid_byte = text_i }; digit_i += 1; text_i += 1; if (text.len - text_i == 0) { parts_i += 1; continue :state .end; } continue :c text[text_i]; }, 'A'...'F' => |c| continue :c c - 'A' + 'a', '0'...'9' => |c| { const digit = c - '0'; parts[parts_i] = parts[parts_i] * 16 + digit; if (digit_i == 4) return .{ .invalid_byte = text_i }; digit_i += 1; text_i += 1; if (text.len - text_i == 0) { parts_i += 1; continue :state .end; } continue :c text[text_i]; }, ':' => { if (digit_i == 0) { if (compress_start != null) return .{ .invalid_byte = text_i }; if (text_i == 0) { text_i += 1; if (text[text_i] != ':') return .{ .invalid_byte = text_i }; assert(parts_i == 0); } compress_start = parts_i; text_i += 1; if (text.len - text_i == 0) continue :state .end; continue :c text[text_i]; } else { parts_i += 1; if (parts.len - parts_i == 0) continue :state .end; digit_i = 0; text_i += 1; if (text.len - text_i == 0) return .unexpected_end; continue :c text[text_i]; } }, '%' => { if (digit_i == 0) return .{ .invalid_byte = text_i }; parts_i += 1; text_i += 1; const name = text[text_i..]; if (name.len > Interface.Name.max_len) return .{ .interface_name_oversized = text_i }; interface_name_text = name; text_i = @intCast(text.len); continue :state .end; }, else => return .{ .invalid_byte = text_i }, }, .end => { if (text.len - text_i != 0) return .{ .junk_after_end = text_i }; const remaining = parts.len - parts_i; if (compress_start) |s| { const src = parts[s..parts_i]; @memmove(parts[parts.len - src.len ..], src); @memset(parts[s..][0..remaining], 0); } else { if (remaining != 0) return .unexpected_end; } // Workaround that can be removed when this proposal is // implemented https://github.com/ziglang/zig/issues/19755 if ((comptime @import("builtin").cpu.arch.endian()) != .big) { for (&parts) |*part| part.* = @byteSwap(part.*); } return .{ .success = .{ .bytes = @bitCast(parts), .interface_name = if (interface_name_text) |t| .fromSliceUnchecked(t) else null, } }; }, } } pub const FromAddressError = Interface.NameError; pub fn fromAddress(a: *const Ip6Address, io: Io) FromAddressError!Unresolved { if (a.interface.isNone()) return .{ .bytes = a.bytes, .interface_name = null, }; return .{ .bytes = a.bytes, .interface_name = try a.interface.name(io), }; } pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void { const bytes = &u.bytes; if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) { try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] }); } else { const parts: [8]u16 = .{ std.mem.readInt(u16, bytes[0..2], .big), std.mem.readInt(u16, bytes[2..4], .big), std.mem.readInt(u16, bytes[4..6], .big), std.mem.readInt(u16, bytes[6..8], .big), std.mem.readInt(u16, bytes[8..10], .big), std.mem.readInt(u16, bytes[10..12], .big), std.mem.readInt(u16, bytes[12..14], .big), std.mem.readInt(u16, bytes[14..16], .big), }; // Find the longest zero run var longest_start: usize = 8; var longest_len: usize = 0; var current_start: usize = 0; var current_len: usize = 0; for (parts, 0..) |part, i| { if (part == 0) { if (current_len == 0) { current_start = i; } current_len += 1; if (current_len > longest_len) { longest_start = current_start; longest_len = current_len; } } else { current_len = 0; } } // Only compress if the longest zero run is 2 or more if (longest_len < 2) { longest_start = 8; longest_len = 0; } var i: usize = 0; var abbrv = false; while (i < parts.len) : (i += 1) { if (i == longest_start) { // Emit "::" for the longest zero run if (!abbrv) { try w.writeAll(if (i == 0) "::" else ":"); abbrv = true; } i += longest_len - 1; // Skip the compressed range continue; } if (abbrv) { abbrv = false; } try w.print("{x}", .{parts[i]}); if (i != parts.len - 1) { try w.writeAll(":"); } } } if (u.interface_name) |n| try w.print("%{s}", .{n.toSlice()}); } }; pub const ParseError = error{ /// If this is returned, more detailed diagnostics can be obtained by /// calling `Ip6Address.Parsed.init`. ParseFailed, /// If this is returned, the IPv6 address had a scope id on it ("%foo" /// at the end) which requires calling `resolve`. UnresolvedScope, }; /// This is a pure function but it cannot handle IPv6 addresses that have /// scope ids ("%foo" at the end). To also handle those, `resolve` must be /// called instead. pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address { switch (Unresolved.parse(buffer)) { .success => |p| return .{ .bytes = p.bytes, .port = port, .interface = if (p.interface_name != null) return error.UnresolvedScope else .none, }, else => return error.ParseFailed, } return .{ .ip6 = try Ip6Address.parse(buffer, port) }; } pub const ResolveError = error{ /// If this is returned, more detailed diagnostics can be obtained by /// calling the `Parsed.init` function. ParseFailed, } || Interface.Name.ResolveError; /// This function requires an `Io` parameter because it must query the operating /// system to convert interface name to index. For example, in /// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by /// creating a socket and then using an `ioctl` syscall. pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address { return switch (Unresolved.parse(buffer)) { .success => |p| return .{ .bytes = p.bytes, .port = port, .interface = if (p.interface_name) |n| try n.resolve(io) else .none, }, else => return error.ParseFailed, }; } pub const FormatError = Io.Writer.Error || Unresolved.FromAddressError; /// Includes the optional scope ("%foo" at the end). /// /// See `format` for an alternative that omits scopes and does /// not require an `Io` parameter. pub fn formatResolved(a: Ip6Address, io: Io, w: *Io.Writer) FormatError!void { const u: Unresolved = try .fromAddress(io); try w.print("[{f}]:{d}", .{ u, a.port }); } /// See `formatResolved` for an alternative that additionally prints the optional /// scope at the end of addresses and requires an `Io` parameter. pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void { const u: Unresolved = .{ .bytes = a.bytes, .interface_name = null, }; try w.print("[{f}]:{d}", .{ u, a.port }); } pub fn eql(a: Ip6Address, b: Ip6Address) bool { return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes); } pub fn isMultiCast(a: Ip6Address) bool { return a.bytes[0] == 0xff; } pub fn isLinkLocal(a: Ip6Address) bool { const b = &a.bytes; return b[0] == 0xfe and (b[1] & 0xc0) == 0x80; } pub fn isLoopBack(a: Ip6Address) bool { const b = &a.bytes; return b[0] == 0 and b[1] == 0 and b[2] == 0 and b[12] == 0 and b[13] == 0 and b[14] == 0 and b[15] == 1; } pub fn isSiteLocal(a: Ip6Address) bool { const b = &a.bytes; return b[0] == 0xfe and (b[1] & 0xc0) == 0xc0; } pub fn policy(a: Ip6Address) *const Policy { const b = &a.bytes; for (&defined_policies) |*p| { if (!std.mem.eql(u8, b[0..p.len], p.addr[0..p.len])) continue; if ((b[p.len] & p.mask) != p.addr[p.len]) continue; return p; } unreachable; } pub fn scope(a: Ip6Address) u8 { if (isMultiCast(a)) return a.bytes[1] & 15; if (isLinkLocal(a)) return 2; if (isLoopBack(a)) return 2; if (isSiteLocal(a)) return 5; return 14; } const defined_policies = [_]Policy{ .{ .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*, .len = 15, .mask = 0xff, .prec = 50, .label = 0, }, .{ .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*, .len = 11, .mask = 0xff, .prec = 35, .label = 4, }, .{ .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, .len = 1, .mask = 0xff, .prec = 30, .label = 2, }, .{ .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, .len = 3, .mask = 0xff, .prec = 5, .label = 5, }, .{ .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, .len = 0, .mask = 0xfe, .prec = 3, .label = 13, }, // These are deprecated and/or returned to the address // pool, so despite the RFC, treating them as special // is probably wrong. // { "", 11, 0xff, 1, 3 }, // { "\xfe\xc0", 1, 0xc0, 1, 11 }, // { "\x3f\xfe", 1, 0xff, 1, 12 }, // Last rule must match all addresses to stop loop. .{ .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*, .len = 0, .mask = 0, .prec = 40, .label = 1, }, }; }; pub const ReceiveFlags = packed struct(u8) { oob: bool = false, peek: bool = false, trunc: bool = false, _: u5 = 0, }; pub const IncomingMessage = struct { /// Populated by receive functions. from: IpAddress, /// Populated by receive functions, points into the caller-supplied buffer. data: []u8, /// Supplied by caller before calling receive functions; mutated by receive /// functions. control: []u8 = &.{}, /// Populated by receive functions. flags: Flags, pub const Flags = packed struct(u8) { /// indicates end-of-record; the data returned completed a record /// (generally used with sockets of type SOCK_SEQPACKET). eor: bool, /// indicates that the trailing portion of a datagram was discarded /// because the datagram was larger than the buffer supplied. trunc: bool, /// indicates that some control data was discarded due to lack of /// space in the buffer for ancil‐ lary data. ctrunc: bool, /// indicates expedited or out-of-band data was received. oob: bool, /// indicates that no data was received but an extended error from the /// socket error queue. errqueue: bool, _: u3 = 0, }; }; pub const OutgoingMessage = struct { address: *const IpAddress, data_ptr: [*]const u8, /// Initialized with how many bytes of `data_ptr` to send. After sending /// succeeds, replaced with how many bytes were actually sent. data_len: usize, control: []const u8 = &.{}, }; pub const SendFlags = packed struct(u8) { confirm: bool = false, dont_route: bool = false, eor: bool = false, oob: bool = false, fastopen: bool = false, _: u3 = 0, }; pub const SendResult = union(enum) { success, fail: struct { err: Socket.SendError, sent: usize, }, }; pub const Interface = struct { /// Value 0 indicates `none`. index: u32, pub const none: Interface = .{ .index = 0 }; pub const Name = struct { bytes: [max_len:0]u8, pub const max_len = std.posix.IFNAMESIZE - 1; pub fn toSlice(n: *const Name) []const u8 { return std.mem.sliceTo(&n.bytes, 0); } pub fn fromSlice(bytes: []const u8) error{NameTooLong}!Name { if (bytes.len > max_len) return error.NameTooLong; return .fromSliceUnchecked(bytes); } /// Asserts bytes.len fits in `max_len`. pub fn fromSliceUnchecked(bytes: []const u8) Name { assert(bytes.len <= max_len); var result: Name = undefined; @memcpy(result.bytes[0..bytes.len], bytes); result.bytes[bytes.len] = 0; return result; } pub const ResolveError = error{ InterfaceNotFound, AccessDenied, SystemResources, } || Io.UnexpectedError || Io.Cancelable; /// Corresponds to "if_nametoindex" in libc. pub fn resolve(n: *const Name, io: Io) ResolveError!Interface { return io.vtable.netInterfaceNameResolve(io.userdata, n); } }; pub const NameError = Io.UnexpectedError || Io.Cancelable; /// Asserts not `none`. /// /// Corresponds to "if_indextoname" in libc. pub fn name(i: Interface, io: Io) NameError!Name { assert(i.index != 0); return io.vtable.netInterfaceName(io.userdata, i); } pub fn isNone(i: Interface) bool { return i.index == 0; } }; /// An open port with unspecified protocol. pub const Socket = struct { handle: Handle, /// Contains the resolved ephemeral port number if requested. 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. pub const Handle = switch (native_os) { .windows => std.windows.ws2_32.SOCKET, else => std.posix.fd_t, }; 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 /// was not transmitted, or was partially transmitted. MessageOversize, /// 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. NetworkDown, /// The destination address is not listening. Can still occur for /// connectionless messages. ConnectionRefused, /// Operating system or protocol does not support the address family. AddressFamilyUnsupported, /// Another TCP Fast Open is already in progress. FastOpenAlreadyInProgress, /// Network connection was unexpectedly closed by recipient. ConnectionResetByPeer, /// Local end has been shut down on a connection-oriented socket, or /// the socket was never connected. SocketUnconnected, } || Io.UnexpectedError || Io.Cancelable; /// Transfers `data` to `dest`, connectionless, in one packet. pub fn send(s: *const Socket, io: Io, dest: *const IpAddress, data: []const u8) SendError!void { var message: OutgoingMessage = .{ .address = dest, .data_ptr = data.ptr, .data_len = data.len }; try io.vtable.netSend(io.userdata, s.handle, &message, .{}); if (message.data_len != data.len) return error.MessageOversize; } pub fn sendMany(s: *const Socket, io: Io, messages: []OutgoingMessage, flags: SendFlags) SendError!void { return io.vtable.netSend(io.userdata, s.handle, messages, flags); } pub const ReceiveError = error{ /// 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, /// Local end has been shut down on a connection-oriented socket, or /// the socket was never connected. SocketUnconnected, /// The socket type requires that message be sent atomically, and the /// size of the message to be sent made this impossible. The message /// was not transmitted, or was partially transmitted. MessageOversize, /// Network connection was unexpectedly closed by sender. ConnectionResetByPeer, /// The local network interface used to reach the destination is offline. NetworkDown, } || Io.UnexpectedError || Io.Cancelable; /// Waits for data. Connectionless. /// /// See also: /// * `receiveTimeout` pub fn receive(s: *const Socket, io: Io, buffer: []u8) ReceiveError!IncomingMessage { var message: IncomingMessage = undefined; assert(1 == try io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none)); return message; } pub const ReceiveTimeoutError = ReceiveError || Io.Timeout.Error; /// Waits for data. Connectionless. /// /// Returns `error.Timeout` if no message arrives early enough. /// /// See also: /// * `receive` /// * `receiveManyTimeout` pub fn receiveTimeout( s: *const Socket, io: Io, buffer: []u8, timeout: Io.Timeout, ) ReceiveTimeoutError!IncomingMessage { var message: IncomingMessage = undefined; assert(1 == try io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, timeout)); return message; } /// Waits until at least one message is delivered, possibly returning more /// than one message. Connectionless. /// /// Returns number of messages received, or `error.Timeout` if no message /// arrives early enough. /// /// See also: /// * `receive` /// * `receiveTimeout` pub fn receiveManyTimeout( s: *const Socket, io: Io, message_buffer: []IncomingMessage, data_buffer: []u8, flags: ReceiveFlags, timeout: Io.Timeout, ) struct { ?ReceiveTimeoutError, usize } { return io.vtable.netReceive(io.userdata, s.handle, message_buffer, data_buffer, flags, timeout); } }; /// An open socket connection with a network protocol that guarantees /// sequencing, delivery, and prevents repetition. Typically TCP or UNIX domain /// socket. pub const Stream = struct { socket: Socket, pub fn close(s: Stream, io: Io) void { return io.vtable.netClose(io.userdata, s.socket); } pub const Reader = struct { io: Io, interface: Io.Reader, stream: Stream, err: ?Error, pub const Error = std.net.Stream.ReadError || Io.Cancelable || Io.Writer.Error || error{EndOfStream}; pub fn init(stream: Stream, buffer: []u8) Reader { return .{ .interface = .{ .vtable = &.{ .stream = streamImpl, .readVec = readVec, }, .buffer = buffer, .seek = 0, .end = 0, }, .stream = stream, .err = null, }; } fn streamImpl(io_r: *Io.Reader, io_w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { const dest = limit.slice(try io_w.writableSliceGreedy(1)); var data: [1][]u8 = .{dest}; const n = try readVec(io_r, &data); io_w.advance(n); return n; } fn readVec(io_r: *Reader, data: [][]u8) Io.Reader.Error!usize { const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r)); const io = r.io; return io.vtable.netReadVec(io.vtable.userdata, r.stream, io_r, data); } }; pub const Writer = struct { io: Io, interface: Io.Writer, stream: Stream, err: ?Error = null, pub const Error = std.net.Stream.WriteError || Io.Cancelable; pub fn init(stream: Stream, buffer: []u8) Writer { return .{ .stream = stream, .interface = .{ .vtable = &.{ .drain = drain }, .buffer = buffer, }, }; } fn drain(io_w: *Io.Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const w: *Writer = @alignCast(@fieldParentPtr("interface", io_w)); const io = w.io; const buffered = io_w.buffered(); const n = try io.vtable.netWrite(io.vtable.userdata, w.stream, buffered, data, splat); return io_w.consume(n); } }; pub fn reader(stream: Stream, buffer: []u8) Reader { return .init(stream, buffer); } pub fn writer(stream: Stream, buffer: []u8) Writer { return .init(stream, buffer); } }; pub const Server = struct { socket: Socket, pub fn deinit(s: *Server, io: Io) void { s.socket.close(io); s.* = undefined; } pub const AcceptError = std.posix.AcceptError || Io.Cancelable; /// Blocks until a client connects to the server. pub fn accept(s: *Server, io: Io) AcceptError!Stream { return io.vtable.accept(io, s); } }; test "parsing IPv6 addresses" { try testIp6Parse("fe80::e0e:76ff:fed4:cf22%eno1"); try testIp6Parse("2001:db8::1"); try testIp6ParseTransform("2001:db8::1", "2001:0db8:0000:0000:0000:0000:0000:0001"); try testIp6Parse("::1"); try testIp6Parse("::"); try testIp6Parse("fe80::1"); try testIp6Parse("fe80::abcd:ef12%3"); try testIp6Parse("ff02::"); try testIp6Parse("ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff"); } fn testIp6Parse(input: []const u8) !void { return testIp6ParseTransform(input, input); } fn testIp6ParseTransform(expected: []const u8, input: []const u8) !void { const ua = switch (Ip6Address.Unresolved.parse(input)) { .success => |p| p, else => |x| { std.debug.print("failed to parse \"{s}\": {any}\n", .{ input, x }); return error.TestFailed; }, }; var buffer: [100]u8 = undefined; const result = try std.fmt.bufPrint(&buffer, "{f}", .{ua}); try std.testing.expectEqualStrings(expected, result); } test { _ = HostName; }