zig/lib/std/Io/net.zig
2025-10-29 06:20:48 -07:00

596 lines
18 KiB
Zig

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.
pub fn parse(name: []const u8, port: u16) !IpAddress {
if (Ip4Address.parse(name, port)) |ip4| {
return .{ .ip4 = ip4 };
} else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.NonCanonical,
=> {},
}
if (Ip6Address.parse(name, port)) |ip6| {
return .{ .ip6 = ip6 };
} else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.InvalidIpv4Mapping,
=> {},
}
return error.InvalidIpAddressFormat;
}
pub fn parseIp6(buffer: []const u8, port: u16) Ip6Address.ParseError!IpAddress {
return .{ .ip6 = try Ip6Address.parse(buffer, port) };
}
pub fn parseIp4(buffer: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
return .{ .ip4 = try Ip4Address.parse(buffer, 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,
}
}
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);
}
};
pub const Ip4Address = struct {
bytes: [4]u8,
port: u16,
pub fn localhost(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;
}
};
pub const Ip6Address = struct {
/// Native endian
port: u16,
/// Big endian
bytes: [16]u8,
flowinfo: u32 = 0,
scope_id: u32 = 0,
pub const ParseError = error{
Overflow,
InvalidCharacter,
InvalidEnd,
InvalidIpv4Mapping,
Incomplete,
};
pub const Policy = struct {
addr: [16]u8,
len: u8,
mask: u8,
prec: u8,
label: u8,
};
pub fn localhost(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 parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
var result: Ip6Address = .{
.port = port,
.bytes = undefined,
};
var ip_slice: *[16]u8 = &result.bytes;
var tail: [16]u8 = undefined;
var x: u16 = 0;
var saw_any_digits = false;
var index: u8 = 0;
var scope_id = false;
var abbrv = false;
for (buffer, 0..) |c, i| {
if (scope_id) {
if (c >= '0' and c <= '9') {
const digit = c - '0';
{
const ov = @mulWithOverflow(result.scope_id, 10);
if (ov[1] != 0) return error.Overflow;
result.scope_id = ov[0];
}
{
const ov = @addWithOverflow(result.scope_id, digit);
if (ov[1] != 0) return error.Overflow;
result.scope_id = ov[0];
}
} else {
return error.InvalidCharacter;
}
} else if (c == ':') {
if (!saw_any_digits) {
if (abbrv) return error.InvalidCharacter; // ':::'
if (i != 0) abbrv = true;
@memset(ip_slice[index..], 0);
ip_slice = tail[0..];
index = 0;
continue;
}
if (index == 14) {
return error.InvalidEnd;
}
ip_slice[index] = @as(u8, @truncate(x >> 8));
index += 1;
ip_slice[index] = @as(u8, @truncate(x));
index += 1;
x = 0;
saw_any_digits = false;
} else if (c == '%') {
if (!saw_any_digits) {
return error.InvalidCharacter;
}
scope_id = true;
saw_any_digits = false;
} else if (c == '.') {
if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
// must start with '::ffff:'
return error.InvalidIpv4Mapping;
}
const start_index = std.mem.lastIndexOfScalar(u8, buffer[0..i], ':').? + 1;
const addr = (Ip4Address.parse(buffer[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
}).bytes;
ip_slice = result.bytes[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
ip_slice[12] = addr[0];
ip_slice[13] = addr[1];
ip_slice[14] = addr[2];
ip_slice[15] = addr[3];
return result;
} else {
const digit = try std.fmt.charToDigit(c, 16);
{
const ov = @mulWithOverflow(x, 16);
if (ov[1] != 0) return error.Overflow;
x = ov[0];
}
{
const ov = @addWithOverflow(x, digit);
if (ov[1] != 0) return error.Overflow;
x = ov[0];
}
saw_any_digits = true;
}
}
if (!saw_any_digits and !abbrv) {
return error.Incomplete;
}
if (!abbrv and index < 14) {
return error.Incomplete;
}
if (index == 14) {
ip_slice[14] = @as(u8, @truncate(x >> 8));
ip_slice[15] = @as(u8, @truncate(x));
return result;
} else {
ip_slice[index] = @as(u8, @truncate(x >> 8));
index += 1;
ip_slice[index] = @as(u8, @truncate(x));
index += 1;
@memcpy(result.bytes[16 - index ..][0..index], ip_slice[0..index]);
return result;
}
}
pub fn format(a: Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
const bytes = &a.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}]:{d}", .{
bytes[12], bytes[13], bytes[14], bytes[15], a.port,
});
return;
}
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(":");
}
}
try w.print("]:{d}", .{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 Stream = struct {
/// Underlying platform-defined type which may or may not be
/// interchangeable with a file system file descriptor.
handle: Handle,
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);
}
};
pub const InterfaceIndexError = error{
InterfaceNotFound,
AccessDenied,
SystemResources,
} || Io.UnexpectedError || Io.Cancelable;
/// Otherwise known as "if_nametoindex".
pub fn interfaceIndex(io: Io, name: []const u8) InterfaceIndexError!u32 {
return io.vtable.netInterfaceIndex(io.userdata, name);
}