zig/lib/std/Io/net.zig
2025-12-05 11:50:04 +01:00

1392 lines
50 KiB
Zig
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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,
};
/// Windows 10 added support for unix sockets in build 17063, redstone 4 is the
/// first release to support them.
pub const has_unix_sockets = switch (native_os) {
.windows => builtin.os.version_range.windows.isAtLeast(.win10_rs4) orelse false,
.wasi => false,
else => true,
};
pub const default_kernel_backlog = 128;
pub const IpAddress = union(enum) {
ip4: Ip4Address,
ip6: Ip6Address,
pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
pub const ParseLiteralError = error{ InvalidAddress, InvalidPort };
/// Parse an IP address which may include a port.
///
/// For IPv4, this is written `address:port`.
///
/// For IPv6, RFC 3986 defines this as an "IP literal", and the port is
/// differentiated from the address by surrounding the address part in
/// brackets "[addr]:port". Even if the port is not given, the brackets are
/// mandatory.
pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {
if (text.len == 0) return error.InvalidAddress;
if (text[0] == '[') {
const addr_end = std.mem.findScalar(u8, text, ']') orelse
return error.InvalidAddress;
const addr_text = text[1..addr_end];
const port: u16 = p: {
if (addr_end == text.len - 1) break :p 0;
if (text[addr_end + 1] != ':') return error.InvalidAddress;
break :p std.fmt.parseInt(u16, text[addr_end + 2 ..], 10) catch return error.InvalidPort;
};
return parseIp6(addr_text, port) catch error.InvalidAddress;
}
if (std.mem.findScalar(u8, text, ':')) |i| {
const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;
return .{ .ip4 = .{
.bytes = addr.bytes,
.port = std.fmt.parseInt(u16, text[i + 1 ..], 10) catch return error.InvalidPort,
} };
}
return parseIp4(text, 0) catch error.InvalidAddress;
}
/// 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,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
/// One of the `ListenOptions` is not supported by the Io
/// implementation.
OptionUnsupported,
} || 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 = default_kernel_backlog,
/// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
/// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
reuse_address: bool = false,
/// Only connection-oriented modes may be used here, which includes:
/// * `Socket.Mode.stream`
/// * `Socket.Mode.seqpacket`
mode: Socket.Mode = .stream,
/// Only connection-oriented protocols may be used here, which includes:
/// * `Protocol.tcp`
/// * `Protocol.tp`
/// * `Protocol.dccp`
/// * `Protocol.sctp`
protocol: Protocol = .tcp,
};
/// 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.netListenIp(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,
/// One of the `BindOptions` is not supported by the Io
/// implementation.
OptionUnsupported,
} || 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: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {
return io.vtable.netBindIp(io.userdata, address, options);
}
pub const ConnectError = error{
AddressUnavailable,
AddressFamilyUnsupported,
/// Insufficient memory or other resource internal to the operating system.
SystemResources,
ConnectionPending,
ConnectionRefused,
ConnectionResetByPeer,
HostUnreachable,
NetworkUnreachable,
Timeout,
/// One of the `ConnectOptions` is not supported by the Io
/// implementation.
OptionUnsupported,
/// 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,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
/// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
/// the connection request failed because of a local firewall rule.
AccessDenied,
/// Non-blocking was requested and the operation cannot return immediately.
WouldBlock,
NetworkDown,
} || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
pub const ConnectOptions = struct {
mode: Socket.Mode,
protocol: ?Protocol = null,
timeout: Io.Timeout = .none,
};
/// Initiates a connection-oriented network stream.
pub fn connect(address: IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
return io.vtable.netConnectIp(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,
/// Has not been checked to be a valid native interface name.
/// Externally managed memory.
interface_name: ?[]const u8,
pub const Parsed = union(enum) {
success: Unresolved,
invalid_byte: usize,
incomplete,
junk_after_end: usize,
interface_name_oversized: usize,
invalid_ip4_mapping: usize,
overflow: usize,
};
pub fn parse(text: []const u8) Parsed {
if (text.len < 2) return .incomplete;
const ip4_prefix = "::ffff:";
if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) {
const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch
return .{ .invalid_ip4_mapping = ip4_prefix.len };
const b = parsed.bytes;
return .{ .success = .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
.interface_name = null,
} };
}
// 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] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
.overflow = text_i,
}) + 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] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
.overflow = text_i,
}) + 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 .incomplete;
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 == 0) return .incomplete;
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 .incomplete;
}
// 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 = interface_name_text,
} };
},
}
}
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});
}
};
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, or the lower level `Unresolved` API may be used.
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,
/// The interface name is longer than the host operating system supports.
NameTooLong,
} || 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 = i: {
const text = p.interface_name orelse break :i .none;
const name: Interface.Name = try .fromSlice(text);
break :i try name.resolve(io);
},
},
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 UnixAddress = struct {
path: []const u8,
pub const max_len = 108;
pub const InitError = error{NameTooLong};
pub fn init(p: []const u8) InitError!UnixAddress {
if (p.len > max_len) return error.NameTooLong;
return .{ .path = p };
}
pub const ListenError = error{
AddressFamilyUnsupported,
AddressInUse,
NetworkDown,
SystemResources,
SymLinkLoop,
FileNotFound,
NotDir,
ReadOnlyFileSystem,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
AccessDenied,
PermissionDenied,
AddressUnavailable,
} || Io.Cancelable || Io.UnexpectedError;
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 = default_kernel_backlog,
};
pub fn listen(ua: *const UnixAddress, io: Io, options: ListenOptions) ListenError!Server {
assert(ua.path.len <= max_len);
return .{ .socket = .{
.handle = try io.vtable.netListenUnix(io.userdata, ua, options),
.address = .{ .ip4 = .loopback(0) },
} };
}
pub const ConnectError = error{
SystemResources,
ProcessFdQuotaExceeded,
SystemFdQuotaExceeded,
AddressFamilyUnsupported,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
AccessDenied,
PermissionDenied,
SymLinkLoop,
FileNotFound,
NotDir,
ReadOnlyFileSystem,
WouldBlock,
NetworkDown,
} || Io.Cancelable || Io.UnexpectedError;
pub fn connect(ua: *const UnixAddress, io: Io) ConnectError!Stream {
assert(ua.path.len <= max_len);
return .{ .socket = .{
.handle = try io.vtable.netConnectUnix(io.userdata, ua),
.address = .{ .ip4 = .loopback(0) },
} };
}
};
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,
/// Useful for initializing before calling `receiveManyTimeout`.
pub const init: IncomingMessage = .{
.from = undefined,
.data = undefined,
.control = &.{},
.flags = undefined,
};
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 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 = if (@TypeOf(std.posix.IFNAMESIZE) == void) 0 else 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.os.windows.ws2_32.SOCKET,
else => std.posix.fd_t,
};
/// Leaves `address` in a valid state.
pub fn close(s: *const Socket, io: Io) void {
io.vtable.netClose(io.userdata, s.handle);
}
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 session was unexpectedly closed by recipient.
ConnectionResetByPeer,
/// Local end has been shut down on a connection-oriented socket, or
/// the socket was never connected.
SocketUnconnected,
/// An attempt was made to send to a network/broadcast address as
/// though it was a unicast address.
AccessDenied,
} || 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 };
const err, const n = io.vtable.netSend(io.userdata, s.handle, (&message)[0..1], .{});
if (n != 1) return err.?;
if (message.data_len != data.len) return error.MessageOversize;
}
pub fn sendMany(s: *const Socket, io: Io, messages: []OutgoingMessage, flags: SendFlags) SendError!void {
const err, const n = io.vtable.netSend(io.userdata, s.handle, messages, flags);
if (n != messages.len) return err.?;
}
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 = .init;
const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none);
if (maybe_err) |err| switch (err) {
// No timeout is passed to `netReceieve`, so it must not return timeout related errors.
error.Timeout, error.UnsupportedClock => unreachable,
else => |e| return e,
};
assert(1 == count);
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 = .init;
const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, timeout);
if (maybe_err) |err| return err;
assert(1 == count);
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,
/// Function assumes each element has initialized `control` field.
/// Initializing with `IncomingMessage.init` may be helpful.
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,
const max_iovecs_len = 8;
pub fn close(s: *const Stream, io: Io) void {
io.vtable.netClose(io.userdata, s.socket.handle);
}
pub const Reader = struct {
io: Io,
interface: Io.Reader,
stream: Stream,
err: ?Error,
pub const Error = error{
SystemResources,
ConnectionResetByPeer,
Timeout,
SocketUnconnected,
/// The file descriptor does not hold the required rights to read
/// from it.
AccessDenied,
NetworkDown,
} || Io.Cancelable || Io.UnexpectedError;
pub fn init(stream: Stream, io: Io, buffer: []u8) Reader {
return .{
.io = io,
.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: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
const r: *Reader = @alignCast(@fieldParentPtr("interface", io_r));
const io = r.io;
var iovecs_buffer: [max_iovecs_len][]u8 = undefined;
const dest_n, const data_size = try io_r.writableVector(&iovecs_buffer, data);
const dest = iovecs_buffer[0..dest_n];
assert(dest[0].len > 0);
const n = io.vtable.netRead(io.userdata, r.stream.socket.handle, dest) catch |err| {
r.err = err;
return error.ReadFailed;
};
if (n == 0) {
return error.EndOfStream;
}
if (n > data_size) {
r.interface.end += n - data_size;
return data_size;
}
return n;
}
};
pub const Writer = struct {
io: Io,
interface: Io.Writer,
stream: Stream,
err: ?Error = null,
pub const Error = error{
/// Another TCP Fast Open is already in progress.
FastOpenAlreadyInProgress,
/// Network session was unexpectedly closed by recipient.
ConnectionResetByPeer,
/// 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 down.
NetworkDown,
/// The destination address is not listening.
ConnectionRefused,
/// The passed address didn't have the correct address family in its sa_family field.
AddressFamilyUnsupported,
/// Local end has been shut down on a connection-oriented socket, or
/// the socket was never connected.
SocketUnconnected,
SocketNotBound,
} || Io.UnexpectedError || Io.Cancelable;
pub fn init(stream: Stream, io: Io, buffer: []u8) Writer {
return .{
.io = io,
.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 handle = w.stream.socket.handle;
const n = io.vtable.netWrite(io.userdata, handle, buffered, data, splat) catch |err| {
w.err = err;
return error.WriteFailed;
};
return io_w.consume(n);
}
};
pub fn reader(stream: Stream, io: Io, buffer: []u8) Reader {
return .init(stream, io, buffer);
}
pub fn writer(stream: Stream, io: Io, buffer: []u8) Writer {
return .init(stream, io, buffer);
}
};
pub const Server = struct {
socket: Socket,
pub fn deinit(s: *Server, io: Io) void {
s.socket.close(io);
s.* = undefined;
}
pub const AcceptError = error{
/// The per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// The system-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
/// Not enough free memory. This often means that the memory allocation is limited
/// by the socket buffer limits, not by the system memory.
SystemResources,
/// Either `listen` was never called, or `shutdown` was called (possibly while
/// this call was blocking). This allows `shutdown` to be used as a concurrent
/// cancellation mechanism.
SocketNotListening,
/// The network subsystem has failed.
NetworkDown,
/// No connection is already queued and ready to be accepted, and
/// the socket is configured as non-blocking.
WouldBlock,
/// An incoming connection was indicated, but was subsequently terminated by the
/// remote peer prior to accepting the call.
ConnectionAborted,
/// Firewall rules forbid connection.
BlockedByFirewall,
ProtocolFailure,
} || Io.UnexpectedError || Io.Cancelable;
/// Blocks until a client connects to the server.
pub fn accept(s: *Server, io: Io) AcceptError!Stream {
return io.vtable.netAccept(io.userdata, s.socket.handle);
}
};
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;
_ = @import("net/test.zig");
}