x/os, x/net: re-approach Address, rename namespace TCP -> tcp

Address comments from @ifreund and @MasterQ32 to address unsafeness and
ergonomics of the `Address` API.

Rename the `TCP` namespace to `tcp` as it does not contain any
top-level fields.

Fix missing reference to `sockaddr` which was identified by @kprotty in
os/bits/linux/arm64.zig.
This commit is contained in:
lithdew 2021-04-30 21:08:49 +09:00
parent 2ab588049e
commit 13068da43e
7 changed files with 266 additions and 167 deletions

View File

@ -150,23 +150,7 @@ pub const Mode = enum {
/// This data structure is used by the Zig language code generation and /// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation. /// therefore must be kept in sync with the compiler implementation.
pub const CallingConvention = enum { pub const CallingConvention = enum { Unspecified, C, Naked, Async, Inline, Interrupt, Signal, Stdcall, Fastcall, Vectorcall, Thiscall, APCS, AAPCS, AAPCSVFP, SysV };
Unspecified,
C,
Naked,
Async,
Inline,
Interrupt,
Signal,
Stdcall,
Fastcall,
Vectorcall,
Thiscall,
APCS,
AAPCS,
AAPCSVFP,
SysV
};
/// This data structure is used by the Zig language code generation and /// This data structure is used by the Zig language code generation and
/// therefore must be kept in sync with the compiler implementation. /// therefore must be kept in sync with the compiler implementation.

View File

@ -662,9 +662,7 @@ test "lengths overflow" {
// malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316) // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316)
// f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6 // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6
// 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11
const stream = [_]u8{ const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 };
0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110
};
const reader = std.io.fixedBufferStream(&stream).reader(); const reader = std.io.fixedBufferStream(&stream).reader();
var window: [0x8000]u8 = undefined; var window: [0x8000]u8 = undefined;

View File

@ -9,6 +9,7 @@
const std = @import("../../../std.zig"); const std = @import("../../../std.zig");
const linux = std.os.linux; const linux = std.os.linux;
const socklen_t = linux.socklen_t; const socklen_t = linux.socklen_t;
const sockaddr = linux.sockaddr;
const iovec = linux.iovec; const iovec = linux.iovec;
const iovec_const = linux.iovec_const; const iovec_const = linux.iovec_const;
const uid_t = linux.uid_t; const uid_t = linux.uid_t;

View File

@ -1,8 +1,22 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("std.zig");
pub const os = struct { pub const os = struct {
pub const Socket = @import("x/os/Socket.zig"); pub const Socket = @import("x/os/Socket.zig");
pub usingnamespace @import("x/os/net.zig"); pub usingnamespace @import("x/os/net.zig");
}; };
pub const net = struct { pub const net = struct {
pub const TCP = @import("x/net/TCP.zig"); pub const tcp = @import("x/net/tcp.zig");
}; };
test {
inline for (.{ os, net }) |module| {
std.testing.refAllDecls(module);
}
}

View File

@ -1,3 +1,9 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig"); const std = @import("../../std.zig");
const os = std.os; const os = std.os;
@ -10,20 +16,76 @@ const IPv6 = std.x.os.IPv6;
const Socket = std.x.os.Socket; const Socket = std.x.os.Socket;
/// A generic TCP socket abstraction. /// A generic TCP socket abstraction.
const TCP = @This(); const tcp = @This();
/// A union of all eligible types of socket addresses over TCP.
pub const Address = union(enum) {
ipv4: IPv4.Address,
ipv6: IPv6.Address,
/// Instantiate a new address with a IPv4 host and port.
pub fn initIPv4(host: IPv4, port: u16) Address {
return .{ .ipv4 = .{ .host = host, .port = port } };
}
/// Instantiate a new address with a IPv6 host and port.
pub fn initIPv6(host: IPv6, port: u16) Address {
return .{ .ipv6 = .{ .host = host, .port = port } };
}
/// Re-interpret a generic socket address into a TCP socket address.
pub fn from(address: Socket.Address) tcp.Address {
return switch (address) {
.ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
.ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
};
}
/// Re-interpret a TCP socket address into a generic socket address.
pub fn into(self: tcp.Address) Socket.Address {
return switch (self) {
.ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
.ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
};
}
/// Implements the `std.fmt.format` API.
pub fn format(
self: tcp.Address,
comptime layout: []const u8,
opts: fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};
/// A TCP client-address pair. /// A TCP client-address pair.
pub const Connection = struct { pub const Connection = struct {
client: TCP.Client, client: tcp.Client,
address: TCP.Address, address: tcp.Address,
/// Enclose a TCP client and address into a client-address pair. /// Enclose a TCP client and address into a client-address pair.
pub fn from(socket: Socket, address: TCP.Address) Connection { pub fn from(conn: Socket.Connection) tcp.Connection {
return .{ .client = TCP.Client.from(socket), .address = address }; return .{
.client = tcp.Client.from(conn.socket),
.address = tcp.Address.from(conn.address),
};
}
/// Unravel a TCP client-address pair into a socket-address pair.
pub fn into(self: tcp.Connection) Socket.Connection {
return .{
.socket = self.client.socket,
.address = self.address.into(),
};
} }
/// Closes the underlying client of the connection. /// Closes the underlying client of the connection.
pub fn deinit(self: TCP.Connection) void { pub fn deinit(self: tcp.Connection) void {
self.client.deinit(); self.client.deinit();
} }
}; };
@ -39,7 +101,7 @@ pub const Client = struct {
socket: Socket, socket: Socket,
/// Opens a new client. /// Opens a new client.
pub fn init(domain: TCP.Domain, flags: u32) !Client { pub fn init(domain: tcp.Domain, flags: u32) !Client {
return Client{ return Client{
.socket = try Socket.init( .socket = try Socket.init(
@enumToInt(domain), @enumToInt(domain),
@ -65,8 +127,8 @@ pub const Client = struct {
} }
/// Have the client attempt to the connect to an address. /// Have the client attempt to the connect to an address.
pub fn connect(self: Client, address: TCP.Address) !void { pub fn connect(self: Client, address: tcp.Address) !void {
return self.socket.connect(TCP.Address, address); return self.socket.connect(address.into());
} }
/// Read data from the socket into the buffer provided. It returns the /// Read data from the socket into the buffer provided. It returns the
@ -122,8 +184,8 @@ pub const Client = struct {
} }
/// Query the address that the client's socket is locally bounded to. /// Query the address that the client's socket is locally bounded to.
pub fn getLocalAddress(self: Client) !TCP.Address { pub fn getLocalAddress(self: Client) !tcp.Address {
return self.socket.getLocalAddress(TCP.Address); return tcp.Address.from(try self.socket.getLocalAddress());
} }
/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
@ -167,7 +229,7 @@ pub const Listener = struct {
socket: Socket, socket: Socket,
/// Opens a new listener. /// Opens a new listener.
pub fn init(domain: TCP.Domain, flags: u32) !Listener { pub fn init(domain: tcp.Domain, flags: u32) !Listener {
return Listener{ return Listener{
.socket = try Socket.init( .socket = try Socket.init(
@enumToInt(domain), @enumToInt(domain),
@ -190,8 +252,8 @@ pub const Listener = struct {
} }
/// Binds the listener's socket to an address. /// Binds the listener's socket to an address.
pub fn bind(self: Listener, address: TCP.Address) !void { pub fn bind(self: Listener, address: tcp.Address) !void {
return self.socket.bind(TCP.Address, address); return self.socket.bind(address.into());
} }
/// Start listening for incoming connections. /// Start listening for incoming connections.
@ -201,8 +263,8 @@ pub const Listener = struct {
/// Accept a pending incoming connection queued to the kernel backlog /// Accept a pending incoming connection queued to the kernel backlog
/// of the listener's socket. /// of the listener's socket.
pub fn accept(self: Listener, flags: u32) !TCP.Connection { pub fn accept(self: Listener, flags: u32) !tcp.Connection {
return self.socket.accept(TCP.Connection, TCP.Address, flags); return tcp.Connection.from(try self.socket.accept(flags));
} }
/// Query and return the latest cached error on the listener's underlying socket. /// Query and return the latest cached error on the listener's underlying socket.
@ -211,8 +273,8 @@ pub const Listener = struct {
} }
/// Query the address that the listener's socket is locally bounded to. /// Query the address that the listener's socket is locally bounded to.
pub fn getLocalAddress(self: Listener) !TCP.Address { pub fn getLocalAddress(self: Listener) !tcp.Address {
return self.socket.getLocalAddress(TCP.Address); return tcp.Address.from(try self.socket.getLocalAddress());
} }
/// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
@ -253,147 +315,69 @@ pub const Listener = struct {
} }
}; };
/// A TCP socket address designated by a host IP and port. A TCP socket
/// address comprises of 28 bytes. It may freely be used in place of
/// `sockaddr` when working with socket syscalls.
///
/// It is not recommended to touch the fields of an `Address`, but to
/// instead make use of its available accessor methods.
pub const Address = extern struct {
family: u16,
port: u16,
host: extern union {
ipv4: extern struct {
address: IPv4,
},
ipv6: extern struct {
flow_info: u32 = 0,
address: IPv6,
},
},
/// Instantiate a new TCP address with a IPv4 host and port.
pub fn initIPv4(host: IPv4, port: u16) Address {
return Address{
.family = os.AF_INET,
.port = mem.nativeToBig(u16, port),
.host = .{
.ipv4 = .{
.address = host,
},
},
};
}
/// Instantiate a new TCP address with a IPv6 host and port.
pub fn initIPv6(host: IPv6, port: u16) Address {
return Address{
.family = os.AF_INET6,
.port = mem.nativeToBig(u16, port),
.host = .{
.ipv6 = .{
.address = host,
},
},
};
}
/// Extract the host of the address.
pub fn getHost(self: Address) union(enum) { v4: IPv4, v6: IPv6 } {
return switch (self.family) {
os.AF_INET => .{ .v4 = self.host.ipv4.address },
os.AF_INET6 => .{ .v6 = self.host.ipv6.address },
else => unreachable,
};
}
/// Extract the port of the address.
pub fn getPort(self: Address) u16 {
return mem.nativeToBig(u16, self.port);
}
/// Set the port of the address.
pub fn setPort(self: *Address, port: u16) void {
self.port = mem.nativeToBig(u16, port);
}
/// Implements the `std.fmt.format` API.
pub fn format(
self: Address,
comptime layout: []const u8,
opts: fmt.FormatOptions,
writer: anytype,
) !void {
switch (self.getHost()) {
.v4 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
.v6 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
}
}
};
test { test {
testing.refAllDecls(@This()); testing.refAllDecls(@This());
} }
test "tcp: create non-blocking pair" { test "tcp: create non-blocking pair" {
const a = try TCP.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); const listener = try tcp.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
defer a.deinit(); defer listener.deinit();
try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
try a.listen(128); try listener.listen(128);
const binded_address = try a.getLocalAddress(); const binded_address = try listener.getLocalAddress();
const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); const client = try tcp.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
defer b.deinit(); defer client.deinit();
testing.expectError(error.WouldBlock, b.connect(binded_address)); testing.expectError(error.WouldBlock, client.connect(binded_address));
try b.getError(); try client.getError();
const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); const conn = try listener.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
defer ab.deinit(); defer conn.deinit();
} }
test "tcp/client: set read timeout of 1 millisecond on blocking client" { test "tcp/client: set read timeout of 1 millisecond on blocking client" {
const a = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC); const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
defer a.deinit(); defer listener.deinit();
try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
try a.listen(128); try listener.listen(128);
const binded_address = try a.getLocalAddress(); const binded_address = try listener.getLocalAddress();
const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC); const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
defer b.deinit(); defer client.deinit();
try b.connect(binded_address); try client.connect(binded_address);
try b.setReadTimeout(1); try client.setReadTimeout(1);
const ab = try a.accept(os.SOCK_CLOEXEC); const conn = try listener.accept(os.SOCK_CLOEXEC);
defer ab.deinit(); defer conn.deinit();
var buf: [1]u8 = undefined; var buf: [1]u8 = undefined;
testing.expectError(error.WouldBlock, b.read(&buf)); testing.expectError(error.WouldBlock, client.read(&buf));
} }
test "tcp/listener: bind to unspecified ipv4 address" { test "tcp/listener: bind to unspecified ipv4 address" {
const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC); const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
defer socket.deinit(); defer listener.deinit();
try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0));
try socket.listen(128); try listener.listen(128);
const address = try socket.getLocalAddress(); const address = try listener.getLocalAddress();
testing.expect(address.getHost() == .v4); testing.expect(address == .ipv4);
} }
test "tcp/listener: bind to unspecified ipv6 address" { test "tcp/listener: bind to unspecified ipv6 address" {
const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC); const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC);
defer socket.deinit(); defer listener.deinit();
try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0)); try listener.bind(tcp.Address.initIPv6(IPv6.unspecified, 0));
try socket.listen(128); try listener.listen(128);
const address = try socket.getLocalAddress(); const address = try listener.getLocalAddress();
testing.expect(address.getHost() == .v6); testing.expect(address == .ipv6);
} }

View File

@ -1,4 +1,11 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig"); const std = @import("../../std.zig");
const net = @import("net.zig");
const os = std.os; const os = std.os;
const mem = std.mem; const mem = std.mem;
@ -7,6 +14,98 @@ const time = std.time;
/// A generic socket abstraction. /// A generic socket abstraction.
const Socket = @This(); const Socket = @This();
/// A socket-address pair.
pub const Connection = struct {
socket: Socket,
address: Socket.Address,
/// Enclose a socket and address into a socket-address pair.
pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
return .{ .socket = socket, .address = address };
}
};
/// A generic socket address abstraction. It is safe to directly access and modify
/// the fields of a `Socket.Address`.
pub const Address = union(enum) {
ipv4: net.IPv4.Address,
ipv6: net.IPv6.Address,
/// Instantiate a new address with a IPv4 host and port.
pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
return .{ .ipv4 = .{ .host = host, .port = port } };
}
/// Instantiate a new address with a IPv6 host and port.
pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
return .{ .ipv6 = .{ .host = host, .port = port } };
}
/// Parses a `sockaddr` into a generic socket address.
pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
switch (address.family) {
os.AF_INET => {
const info = @ptrCast(*const os.sockaddr_in, address);
const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
const port = mem.bigToNative(u16, info.port);
return Socket.Address.initIPv4(host, port);
},
os.AF_INET6 => {
const info = @ptrCast(*const os.sockaddr_in6, address);
const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
const port = mem.bigToNative(u16, info.port);
return Socket.Address.initIPv6(host, port);
},
else => unreachable,
}
}
/// Encodes a generic socket address into an extern union that may be reliably
/// casted into a `sockaddr` which may be passed into socket syscalls.
pub fn toNative(self: Socket.Address) extern union {
ipv4: os.sockaddr_in,
ipv6: os.sockaddr_in6,
} {
return switch (self) {
.ipv4 => |address| .{
.ipv4 = .{
.addr = @bitCast(u32, address.host.octets),
.port = mem.nativeToBig(u16, address.port),
},
},
.ipv6 => |address| .{
.ipv6 = .{
.addr = address.host.octets,
.port = mem.nativeToBig(u16, address.port),
.scope_id = address.host.scope_id,
.flowinfo = 0,
},
},
};
}
/// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
pub fn getNativeSize(self: Socket.Address) u32 {
return switch (self) {
.ipv4 => @sizeOf(os.sockaddr_in),
.ipv6 => @sizeOf(os.sockaddr_in6),
};
}
/// Implements the `std.fmt.format` API.
pub fn format(
self: Socket.Address,
comptime layout: []const u8,
opts: fmt.FormatOptions,
writer: anytype,
) !void {
switch (self) {
.ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
.ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
}
}
};
/// The underlying handle of a socket. /// The underlying handle of a socket.
fd: os.socket_t, fd: os.socket_t,
@ -31,8 +130,8 @@ pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
} }
/// Binds the socket to an address. /// Binds the socket to an address.
pub fn bind(self: Socket, comptime Address: type, address: Address) !void { pub fn bind(self: Socket, address: Socket.Address) !void {
return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
} }
/// Start listening for incoming connections on the socket. /// Start listening for incoming connections on the socket.
@ -41,19 +140,20 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {
} }
/// Have the socket attempt to the connect to an address. /// Have the socket attempt to the connect to an address.
pub fn connect(self: Socket, comptime Address: type, address: Address) !void { pub fn connect(self: Socket, address: Socket.Address) !void {
return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
} }
/// Accept a pending incoming connection queued to the kernel backlog /// Accept a pending incoming connection queued to the kernel backlog
/// of the socket. /// of the socket.
pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection { pub fn accept(self: Socket, flags: u32) !Socket.Connection {
var address: Address = undefined; var address: os.sockaddr = undefined;
var address_len: u32 = @sizeOf(Address); var address_len: u32 = @sizeOf(os.sockaddr);
const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags); const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) };
const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
return Connection.from(.{ .fd = fd }, address); return Socket.Connection.from(socket, socket_address);
} }
/// Read data from the socket into the buffer provided. It returns the /// Read data from the socket into the buffer provided. It returns the
@ -94,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
} }
/// Query the address that the socket is locally bounded to. /// Query the address that the socket is locally bounded to.
pub fn getLocalAddress(self: Socket, comptime Address: type) !Address { pub fn getLocalAddress(self: Socket) !Socket.Address {
var address: Address = undefined; var address: os.sockaddr = undefined;
var address_len: u32 = @sizeOf(Address); var address_len: u32 = @sizeOf(os.sockaddr);
try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len); try os.getsockname(self.fd, &address, &address_len);
return address; return Socket.Address.fromNative(@alignCast(4, &address));
} }
/// Query and return the latest cached error on the socket. /// Query and return the latest cached error on the socket.

View File

@ -1,3 +1,9 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("../../std.zig"); const std = @import("../../std.zig");
const os = std.os; const os = std.os;
@ -27,6 +33,12 @@ pub fn resolveScopeID(name: []const u8) !u32 {
/// An IPv4 address comprised of 4 bytes. /// An IPv4 address comprised of 4 bytes.
pub const IPv4 = extern struct { pub const IPv4 = extern struct {
/// A IPv4 host-port pair.
pub const Address = extern struct {
host: IPv4,
port: u16,
};
/// Octets of a IPv4 address designating the local host. /// Octets of a IPv4 address designating the local host.
pub const localhost_octets = [_]u8{ 127, 0, 0, 1 }; pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
@ -200,6 +212,12 @@ pub const IPv4 = extern struct {
/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes /// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
/// for a scope ID; cumulatively summing to 20 bytes in total. /// for a scope ID; cumulatively summing to 20 bytes in total.
pub const IPv6 = extern struct { pub const IPv6 = extern struct {
/// A IPv6 host-port pair.
pub const Address = extern struct {
host: IPv6,
port: u16,
};
/// Octets of a IPv6 address designating the local host. /// Octets of a IPv6 address designating the local host.
pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01}; pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};