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"); pub const ListenError = std.net.Address.ListenError || 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 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(name: []const u8, port: u16) !IpAddress { if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) { error.Overflow, error.InvalidEnd, error.InvalidCharacter, error.Incomplete, error.NonCanonical, => {}, } return parseIp6(name, 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: IpAddress, b: 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, }, }; } /// 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); } }; /// 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 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, }; } /// 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, }; pub fn parse(buffer: []const u8) Parsed { if (buffer.len < 2) return .unexpected_end; var parts: [8]u16 = @splat(0); var parts_i: usize = 0; var i: usize = 0; var digit_i: usize = 0; const State = union(enum) { digit, colon, end }; state: switch (State.digit) { .digit => c: switch (buffer[i]) { 'a'...'f' => |c| { const digit = c - 'a'; parts[parts_i] = parts[parts_i] * 16 + digit; if (digit_i == 3) { digit_i = 0; parts_i += 1; i += 1; if (parts.len - parts_i == 0) continue :state .end; continue :state .colon; } digit_i += 1; if (buffer.len - i == 0) return .unexpected_end; i += 1; continue :c buffer[i]; }, 'A'...'F' => |c| continue :c c + ('a' - 'A'), '0'...'9' => |c| continue :c c + ('a' - '0'), ':' => @panic("TODO"), else => return .{ .invalid_byte = i }, }, .colon => @panic("TODO"), .end => @panic("TODO"), } } 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; } try w.writeAll("["); 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 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; 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 u8, 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 socket connection with a network protocol that guarantees /// sequencing, delivery, and prevents repetition. Typically TCP or UNIX domain /// socket. pub const Stream = struct { handle: Handle, /// 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: Stream, io: Io) void { return io.vtable.close(io.userdata, s); } 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 { listen_address: IpAddress, stream: Stream, pub const Connection = struct { stream: Stream, address: IpAddress, }; pub fn deinit(s: *Server, io: Io) void { s.stream.close(io); s.* = undefined; } 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 { return io.vtable.accept(io, s); } }; test { _ = HostName; }