Commit 2ab588049e

lithdew <kenta@lithdew.net>
2021-04-30 11:17:39
x/os, x/net: layout tcp, ipv4/ipv6, and socket abstractions
The `Socket` abstraction was refactored to only comprise of methods that can be generically used/applied to all socket domains and protocols. A more comprehensive IPv4/IPv6 module derived from @LemonBoy's earlier work was implemented under `std.x.os.IPv4` and `std.x.os.IPv6`. Using this module, one can then combine them together into a union for example in order to optimize memory usage when dealing with socket addresses. A `TCP.Client` and `TCP.Listener` abstraction is introduced that is one layer over the `Socket` abstraction, which isolates methods that can only be applied to a "client socket" and a "listening socket". All prior tests from the `Socket` abstraction, which all previously operated assuming the socket is operating via. TCP/IP, were moved. All TCP socket options were also moved into the `TCP.Client` and `TCP.Listener` abstractions respectively away from the `Socket` abstraction. Some additional socket options from @LemonBoy's prior PR for Darwin were also moved in (i.e. SIGNOPIPE).
1 parent c47028c
Changed files (6)
lib/std/os/bits/darwin.zig
@@ -832,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006;
 pub const SO_ERROR = 0x1007;
 pub const SO_TYPE = 0x1008;
 
+pub const SO_NREAD = 0x1020;
+pub const SO_NKE = 0x1021;
+pub const SO_NOSIGPIPE = 0x1022;
+pub const SO_NOADDRERR = 0x1023;
+pub const SO_NWRITE = 0x1024;
+pub const SO_REUSESHAREUID = 0x1025;
+
 fn wstatus(x: u32) u32 {
     return x & 0o177;
 }
lib/std/x/net/TCP.zig
@@ -0,0 +1,399 @@
+const std = @import("../../std.zig");
+
+const os = std.os;
+const fmt = std.fmt;
+const mem = std.mem;
+const testing = std.testing;
+
+const IPv4 = std.x.os.IPv4;
+const IPv6 = std.x.os.IPv6;
+const Socket = std.x.os.Socket;
+
+/// A generic TCP socket abstraction.
+const TCP = @This();
+
+/// A TCP client-address pair.
+pub const Connection = struct {
+    client: TCP.Client,
+    address: TCP.Address,
+
+    /// Enclose a TCP client and address into a client-address pair.
+    pub fn from(socket: Socket, address: TCP.Address) Connection {
+        return .{ .client = TCP.Client.from(socket), .address = address };
+    }
+
+    /// Closes the underlying client of the connection.
+    pub fn deinit(self: TCP.Connection) void {
+        self.client.deinit();
+    }
+};
+
+/// Possible domains that a TCP client/listener may operate over.
+pub const Domain = extern enum(u16) {
+    ip = os.AF_INET,
+    ipv6 = os.AF_INET6,
+};
+
+/// A TCP client.
+pub const Client = struct {
+    socket: Socket,
+
+    /// Opens a new client.
+    pub fn init(domain: TCP.Domain, flags: u32) !Client {
+        return Client{
+            .socket = try Socket.init(
+                @enumToInt(domain),
+                os.SOCK_STREAM | flags,
+                os.IPPROTO_TCP,
+            ),
+        };
+    }
+
+    /// Enclose a TCP client over an existing socket.
+    pub fn from(socket: Socket) Client {
+        return Client{ .socket = socket };
+    }
+
+    /// Closes the client.
+    pub fn deinit(self: Client) void {
+        self.socket.deinit();
+    }
+
+    /// Shutdown either the read side, write side, or all sides of the client's underlying socket.
+    pub fn shutdown(self: Client, how: os.ShutdownHow) !void {
+        return self.socket.shutdown(how);
+    }
+
+    /// Have the client attempt to the connect to an address.
+    pub fn connect(self: Client, address: TCP.Address) !void {
+        return self.socket.connect(TCP.Address, address);
+    }
+
+    /// Read data from the socket into the buffer provided. It returns the
+    /// number of bytes read into the buffer provided.
+    pub fn read(self: Client, buf: []u8) !usize {
+        return self.socket.read(buf);
+    }
+
+    /// Read data from the socket into the buffer provided with a set of flags
+    /// specified. It returns the number of bytes read into the buffer provided.
+    pub fn recv(self: Client, buf: []u8, flags: u32) !usize {
+        return self.socket.recv(buf, flags);
+    }
+
+    /// Write a buffer of data provided to the socket. It returns the number
+    /// of bytes that are written to the socket.
+    pub fn write(self: Client, buf: []const u8) !usize {
+        return self.socket.write(buf);
+    }
+
+    /// Writes multiple I/O vectors to the socket. It returns the number
+    /// of bytes that are written to the socket.
+    pub fn writev(self: Client, buffers: []const os.iovec_const) !usize {
+        return self.socket.writev(buffers);
+    }
+
+    /// Write a buffer of data provided to the socket with a set of flags specified.
+    /// It returns the number of bytes that are written to the socket.
+    pub fn send(self: Client, buf: []const u8, flags: u32) !usize {
+        return self.socket.send(buf, flags);
+    }
+
+    /// Writes multiple I/O vectors with a prepended message header to the socket
+    /// with a set of flags specified. It returns the number of bytes that are
+    /// written to the socket.
+    pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize {
+        return self.socket.sendmsg(msg, flags);
+    }
+
+    /// Query and return the latest cached error on the client's underlying socket.
+    pub fn getError(self: Client) !void {
+        return self.socket.getError();
+    }
+
+    /// Query the read buffer size of the client's underlying socket.
+    pub fn getReadBufferSize(self: Client) !u32 {
+        return self.socket.getReadBufferSize();
+    }
+
+    /// Query the write buffer size of the client's underlying socket.
+    pub fn getWriteBufferSize(self: Client) !u32 {
+        return self.socket.getWriteBufferSize();
+    }
+
+    /// Query the address that the client's socket is locally bounded to.
+    pub fn getLocalAddress(self: Client) !TCP.Address {
+        return self.socket.getLocalAddress(TCP.Address);
+    }
+
+    /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
+    /// the host does not support sockets disabling Nagle's algorithm.
+    pub fn setNoDelay(self: Client, enabled: bool) !void {
+        if (comptime @hasDecl(os, "TCP_NODELAY")) {
+            const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
+            return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes);
+        }
+        return error.UnsupportedSocketOption;
+    }
+
+    /// Set the write buffer size of the socket.
+    pub fn setWriteBufferSize(self: Client, size: u32) !void {
+        return self.socket.setWriteBufferSize(size);
+    }
+
+    /// Set the read buffer size of the socket.
+    pub fn setReadBufferSize(self: Client, size: u32) !void {
+        return self.socket.setReadBufferSize(size);
+    }
+
+    /// Set a timeout on the socket that is to occur if no messages are successfully written
+    /// to its bound destination after a specified number of milliseconds. A subsequent write
+    /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
+    pub fn setWriteTimeout(self: Client, milliseconds: usize) !void {
+        return self.socket.setWriteTimeout(milliseconds);
+    }
+
+    /// Set a timeout on the socket that is to occur if no messages are successfully read
+    /// from its bound destination after a specified number of milliseconds. A subsequent
+    /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
+    /// exceeded.
+    pub fn setReadTimeout(self: Client, milliseconds: usize) !void {
+        return self.socket.setReadTimeout(milliseconds);
+    }
+};
+
+/// A TCP listener.
+pub const Listener = struct {
+    socket: Socket,
+
+    /// Opens a new listener.
+    pub fn init(domain: TCP.Domain, flags: u32) !Listener {
+        return Listener{
+            .socket = try Socket.init(
+                @enumToInt(domain),
+                os.SOCK_STREAM | flags,
+                os.IPPROTO_TCP,
+            ),
+        };
+    }
+
+    /// Closes the listener.
+    pub fn deinit(self: Listener) void {
+        self.socket.deinit();
+    }
+
+    /// Shuts down the underlying listener's socket. The next subsequent call, or
+    /// a current pending call to accept() after shutdown is called will return
+    /// an error.
+    pub fn shutdown(self: Listener) !void {
+        return self.socket.shutdown(.recv);
+    }
+
+    /// Binds the listener's socket to an address.
+    pub fn bind(self: Listener, address: TCP.Address) !void {
+        return self.socket.bind(TCP.Address, address);
+    }
+
+    /// Start listening for incoming connections.
+    pub fn listen(self: Listener, max_backlog_size: u31) !void {
+        return self.socket.listen(max_backlog_size);
+    }
+
+    /// Accept a pending incoming connection queued to the kernel backlog
+    /// of the listener's socket.
+    pub fn accept(self: Listener, flags: u32) !TCP.Connection {
+        return self.socket.accept(TCP.Connection, TCP.Address, flags);
+    }
+
+    /// Query and return the latest cached error on the listener's underlying socket.
+    pub fn getError(self: Client) !void {
+        return self.socket.getError();
+    }
+
+    /// Query the address that the listener's socket is locally bounded to.
+    pub fn getLocalAddress(self: Listener) !TCP.Address {
+        return self.socket.getLocalAddress(TCP.Address);
+    }
+
+    /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
+    /// the host does not support sockets listening the same address.
+    pub fn setReuseAddress(self: Listener, enabled: bool) !void {
+        return self.socket.setReuseAddress(enabled);
+    }
+
+    /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
+    /// the host does not supports sockets listening on the same port.
+    pub fn setReusePort(self: Listener, enabled: bool) !void {
+        return self.socket.setReusePort(enabled);
+    }
+
+    /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
+    /// support TCP Fast Open.
+    pub fn setFastOpen(self: Listener, enabled: bool) !void {
+        if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
+            return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
+        }
+        return error.UnsupportedSocketOption;
+    }
+
+    /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
+    /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
+    pub fn setQuickACK(self: Listener, enabled: bool) !void {
+        if (comptime @hasDecl(os, "TCP_QUICKACK")) {
+            return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
+        }
+        return error.UnsupportedSocketOption;
+    }
+
+    /// Set a timeout on the listener that is to occur if no new incoming connections come in
+    /// after a specified number of milliseconds. A subsequent accept call to the listener
+    /// will thereafter return `error.WouldBlock` should the timeout be exceeded.
+    pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void {
+        return self.socket.setReadTimeout(milliseconds);
+    }
+};
+
+/// 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 {
+    testing.refAllDecls(@This());
+}
+
+test "tcp: create non-blocking pair" {
+    const a = try TCP.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
+    defer a.deinit();
+
+    try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
+    try a.listen(128);
+
+    const binded_address = try a.getLocalAddress();
+
+    const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
+    defer b.deinit();
+
+    testing.expectError(error.WouldBlock, b.connect(binded_address));
+    try b.getError();
+
+    const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
+    defer ab.deinit();
+}
+
+test "tcp/client: set read timeout of 1 millisecond on blocking client" {
+    const a = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
+    defer a.deinit();
+
+    try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
+    try a.listen(128);
+
+    const binded_address = try a.getLocalAddress();
+
+    const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC);
+    defer b.deinit();
+
+    try b.connect(binded_address);
+    try b.setReadTimeout(1);
+
+    const ab = try a.accept(os.SOCK_CLOEXEC);
+    defer ab.deinit();
+
+    var buf: [1]u8 = undefined;
+    testing.expectError(error.WouldBlock, b.read(&buf));
+}
+
+test "tcp/listener: bind to unspecified ipv4 address" {
+    const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
+    defer socket.deinit();
+
+    try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
+    try socket.listen(128);
+
+    const address = try socket.getLocalAddress();
+    testing.expect(address.getHost() == .v4);
+}
+
+test "tcp/listener: bind to unspecified ipv6 address" {
+    const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC);
+    defer socket.deinit();
+
+    try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0));
+    try socket.listen(128);
+
+    const address = try socket.getLocalAddress();
+    testing.expect(address.getHost() == .v6);
+}
lib/std/x/os/net.zig
@@ -0,0 +1,532 @@
+const std = @import("../../std.zig");
+
+const os = std.os;
+const fmt = std.fmt;
+const mem = std.mem;
+const math = std.math;
+const builtin = std.builtin;
+const testing = std.testing;
+
+/// Resolves a network interface name into a scope/zone ID. It returns
+/// an error if either resolution fails, or if the interface name is
+/// too long.
+pub fn resolveScopeID(name: []const u8) !u32 {
+    if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong;
+
+    const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0);
+    defer os.closeSocket(fd);
+
+    var f: os.ifreq = undefined;
+    mem.copy(u8, &f.ifrn.name, name);
+    f.ifrn.name[name.len] = 0;
+
+    try os.ioctl_SIOCGIFINDEX(fd, &f);
+
+    return @bitCast(u32, f.ifru.ivalue);
+}
+
+/// An IPv4 address comprised of 4 bytes.
+pub const IPv4 = extern struct {
+    /// Octets of a IPv4 address designating the local host.
+    pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
+
+    /// The IPv4 address of the local host.
+    pub const localhost: IPv4 = .{ .octets = localhost_octets };
+
+    /// Octets of an unspecified IPv4 address.
+    pub const unspecified_octets = [_]u8{0} ** 4;
+
+    /// An unspecified IPv4 address.
+    pub const unspecified: IPv4 = .{ .octets = unspecified_octets };
+
+    /// Octets of a broadcast IPv4 address.
+    pub const broadcast_octets = [_]u8{255} ** 4;
+
+    /// An IPv4 broadcast address.
+    pub const broadcast: IPv4 = .{ .octets = broadcast_octets };
+
+    /// The prefix octet pattern of a link-local IPv4 address.
+    pub const link_local_prefix = [_]u8{ 169, 254 };
+
+    /// The prefix octet patterns of IPv4 addresses intended for
+    /// documentation.
+    pub const documentation_prefixes = [_][]const u8{
+        &[_]u8{ 192, 0, 2 },
+        &[_]u8{ 198, 51, 100 },
+        &[_]u8{ 203, 0, 113 },
+    };
+
+    octets: [4]u8,
+
+    /// Returns whether or not the two addresses are equal to, less than, or
+    /// greater than each other.
+    pub fn cmp(self: IPv4, other: IPv4) math.Order {
+        return mem.order(u8, &self.octets, &other.octets);
+    }
+
+    /// Returns true if both addresses are semantically equivalent.
+    pub fn eql(self: IPv4, other: IPv4) bool {
+        return mem.eql(u8, &self.octets, &other.octets);
+    }
+
+    /// Returns true if the address is a loopback address.
+    pub fn isLoopback(self: IPv4) bool {
+        return self.octets[0] == 127;
+    }
+
+    /// Returns true if the address is an unspecified IPv4 address.
+    pub fn isUnspecified(self: IPv4) bool {
+        return mem.eql(u8, &self.octets, &unspecified_octets);
+    }
+
+    /// Returns true if the address is a private IPv4 address.
+    pub fn isPrivate(self: IPv4) bool {
+        return self.octets[0] == 10 or
+            (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or
+            (self.octets[0] == 192 and self.octets[1] == 168);
+    }
+
+    /// Returns true if the address is a link-local IPv4 address.
+    pub fn isLinkLocal(self: IPv4) bool {
+        return mem.startsWith(u8, &self.octets, &link_local_prefix);
+    }
+
+    /// Returns true if the address is a multicast IPv4 address.
+    pub fn isMulticast(self: IPv4) bool {
+        return self.octets[0] >= 224 and self.octets[0] <= 239;
+    }
+
+    /// Returns true if the address is a IPv4 broadcast address.
+    pub fn isBroadcast(self: IPv4) bool {
+        return mem.eql(u8, &self.octets, &broadcast_octets);
+    }
+
+    /// Returns true if the address is in a range designated for documentation. Refer
+    /// to IETF RFC 5737 for more details.
+    pub fn isDocumentation(self: IPv4) bool {
+        inline for (documentation_prefixes) |prefix| {
+            if (mem.startsWith(u8, &self.octets, prefix)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /// Implements the `std.fmt.format` API.
+    pub fn format(
+        self: IPv4,
+        comptime layout: []const u8,
+        opts: fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        if (comptime layout.len != 0 and layout[0] != 's') {
+            @compileError("Unsupported format specifier for IPv4 type '" ++ layout ++ "'.");
+        }
+
+        try fmt.format(writer, "{}.{}.{}.{}", .{
+            self.octets[0],
+            self.octets[1],
+            self.octets[2],
+            self.octets[3],
+        });
+    }
+
+    /// Set of possible errors that may encountered when parsing an IPv4
+    /// address.
+    pub const ParseError = error{
+        UnexpectedEndOfOctet,
+        TooManyOctets,
+        OctetOverflow,
+        UnexpectedToken,
+        IncompleteAddress,
+    };
+
+    /// Parses an arbitrary IPv4 address.
+    pub fn parse(buf: []const u8) ParseError!IPv4 {
+        var octets: [4]u8 = undefined;
+        var octet: u8 = 0;
+
+        var index: u8 = 0;
+        var saw_any_digits: bool = false;
+
+        for (buf) |c| {
+            switch (c) {
+                '.' => {
+                    if (!saw_any_digits) return error.UnexpectedEndOfOctet;
+                    if (index == 3) return error.TooManyOctets;
+                    octets[index] = octet;
+                    index += 1;
+                    octet = 0;
+                    saw_any_digits = false;
+                },
+                '0'...'9' => {
+                    saw_any_digits = true;
+                    octet = math.mul(u8, octet, 10) catch return error.OctetOverflow;
+                    octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow;
+                },
+                else => return error.UnexpectedToken,
+            }
+        }
+
+        if (index == 3 and saw_any_digits) {
+            octets[index] = octet;
+            return IPv4{ .octets = octets };
+        }
+
+        return error.IncompleteAddress;
+    }
+
+    /// Maps the address to its IPv6 equivalent. In most cases, you would
+    /// want to map the address to its IPv6 equivalent rather than directly
+    /// re-interpreting the address.
+    pub fn mapToIPv6(self: IPv4) IPv6 {
+        var octets: [16]u8 = undefined;
+        mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix);
+        mem.copy(u8, octets[12..], &self.octets);
+        return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
+    }
+
+    /// Directly re-interprets the address to its IPv6 equivalent. In most
+    /// cases, you would want to map the address to its IPv6 equivalent rather
+    /// than directly re-interpreting the address.
+    pub fn toIPv6(self: IPv4) IPv6 {
+        var octets: [16]u8 = undefined;
+        mem.set(u8, octets[0..12], 0);
+        mem.copy(u8, octets[12..], &self.octets);
+        return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
+    }
+};
+
+/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
+/// for a scope ID; cumulatively summing to 20 bytes in total.
+pub const IPv6 = extern struct {
+    /// Octets of a IPv6 address designating the local host.
+    pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};
+
+    /// The IPv6 address of the local host.
+    pub const localhost: IPv6 = .{
+        .octets = localhost_octets,
+        .scope_id = no_scope_id,
+    };
+
+    /// Octets of an unspecified IPv6 address.
+    pub const unspecified_octets = [_]u8{0} ** 16;
+
+    /// An unspecified IPv6 address.
+    pub const unspecified: IPv6 = .{
+        .octets = unspecified_octets,
+        .scope_id = no_scope_id,
+    };
+
+    /// The prefix of a IPv6 address that is mapped to a IPv4 address.
+    pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2;
+
+    /// A marker value used to designate an IPv6 address with no
+    /// associated scope ID.
+    pub const no_scope_id = math.maxInt(u32);
+
+    octets: [16]u8,
+    scope_id: u32,
+
+    /// Returns whether or not the two addresses are equal to, less than, or
+    /// greater than each other.
+    pub fn cmp(self: IPv6, other: IPv6) math.Order {
+        return switch (mem.order(u8, self.octets, other.octets)) {
+            .eq => math.order(self.scope_id, other.scope_id),
+            else => |order| order,
+        };
+    }
+
+    /// Returns true if both addresses are semantically equivalent.
+    pub fn eql(self: IPv6, other: IPv6) bool {
+        return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets);
+    }
+
+    /// Returns true if the address is an unspecified IPv6 address.
+    pub fn isUnspecified(self: IPv6) bool {
+        return mem.eql(u8, &self.octets, &unspecified_octets);
+    }
+
+    /// Returns true if the address is a loopback address.
+    pub fn isLoopback(self: IPv6) bool {
+        return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and
+            mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 });
+    }
+
+    /// Returns true if the address maps to an IPv4 address.
+    pub fn mapsToIPv4(self: IPv6) bool {
+        return mem.startsWith(u8, &self.octets, &v4_mapped_prefix);
+    }
+
+    /// Returns an IPv4 address representative of the address should
+    /// it the address be mapped to an IPv4 address. It returns null
+    /// otherwise.
+    pub fn toIPv4(self: IPv6) ?IPv4 {
+        if (!self.mapsToIPv4()) return null;
+        return IPv4{ .octets = self.octets[12..][0..4].* };
+    }
+
+    /// Returns true if the address is a multicast IPv6 address.
+    pub fn isMulticast(self: IPv6) bool {
+        return self.octets[0] == 0xFF;
+    }
+
+    /// Returns true if the address is a unicast link local IPv6 address.
+    pub fn isLinkLocal(self: IPv6) bool {
+        return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80;
+    }
+
+    /// Returns true if the address is a deprecated unicast site local
+    /// IPv6 address. Refer to IETF RFC 3879 for more details as to
+    /// why they are deprecated.
+    pub fn isSiteLocal(self: IPv6) bool {
+        return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0;
+    }
+
+    /// IPv6 multicast address scopes.
+    pub const Scope = enum(u8) {
+        interface = 1,
+        link = 2,
+        realm = 3,
+        admin = 4,
+        site = 5,
+        organization = 8,
+        global = 14,
+        unknown = 0xFF,
+    };
+
+    /// Returns the multicast scope of the address.
+    pub fn scope(self: IPv6) Scope {
+        if (!self.isMulticast()) return .unknown;
+
+        return switch (self.octets[0] & 0x0F) {
+            1 => .interface,
+            2 => .link,
+            3 => .realm,
+            4 => .admin,
+            5 => .site,
+            8 => .organization,
+            14 => .global,
+            else => .unknown,
+        };
+    }
+
+    /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the
+    /// address lower-cased octets, while specifying 'X' or 'S' formats the
+    /// address using upper-cased ASCII octets.
+    ///
+    /// The default specifier is 'x'.
+    pub fn format(
+        self: IPv6,
+        comptime layout: []const u8,
+        opts: fmt.FormatOptions,
+        writer: anytype,
+    ) !void {
+        comptime const specifier = &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) {
+            'x', 'X' => |specifier| specifier,
+            's' => 'x',
+            'S' => 'X',
+            else => @compileError("Unsupported format specifier for IPv6 type '" ++ layout ++ "'."),
+        }};
+
+        if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) {
+            return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{
+                0xFF,
+                0xFF,
+                self.octets[12],
+                self.octets[13],
+                self.octets[14],
+                self.octets[15],
+            });
+        }
+
+        const zero_span = span: {
+            var i: usize = 0;
+            while (i < self.octets.len) : (i += 2) {
+                if (self.octets[i] == 0 and self.octets[i + 1] == 0) break;
+            } else break :span .{ .from = 0, .to = 0 };
+
+            const from = i;
+
+            while (i < self.octets.len) : (i += 2) {
+                if (self.octets[i] != 0 or self.octets[i + 1] != 0) break;
+            }
+
+            break :span .{ .from = from, .to = i };
+        };
+
+        var i: usize = 0;
+        while (i != 16) : (i += 2) {
+            if (zero_span.from != zero_span.to and i == zero_span.from) {
+                try writer.writeAll("::");
+            } else if (i >= zero_span.from and i < zero_span.to) {} else {
+                if (i != 0 and i != zero_span.to) try writer.writeAll(":");
+
+                const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1];
+                try fmt.formatIntValue(val, specifier, .{}, writer);
+            }
+        }
+
+        if (self.scope_id != no_scope_id and self.scope_id != 0) {
+            try fmt.format(writer, "%{d}", .{self.scope_id});
+        }
+    }
+
+    /// Set of possible errors that may encountered when parsing an IPv6
+    /// address.
+    pub const ParseError = error{
+        MalformedV4Mapping,
+        BadScopeID,
+    } || IPv4.ParseError;
+
+    /// Parses an arbitrary IPv6 address, including link-local addresses.
+    pub fn parse(buf: []const u8) ParseError!IPv6 {
+        if (mem.lastIndexOfScalar(u8, buf, '%')) |index| {
+            const ip_slice = buf[0..index];
+            const scope_id_slice = buf[index + 1 ..];
+
+            if (scope_id_slice.len == 0) return error.BadScopeID;
+
+            const scope_id: u32 = switch (scope_id_slice[0]) {
+                '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10),
+                else => resolveScopeID(scope_id_slice),
+            } catch return error.BadScopeID;
+
+            return parseWithScopeID(ip_slice, scope_id);
+        }
+
+        return parseWithScopeID(buf, no_scope_id);
+    }
+
+    /// Parses an IPv6 address with a pre-specified scope ID. Presumes
+    /// that the address is not a link-local address.
+    pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 {
+        var octets: [16]u8 = undefined;
+        var octet: u16 = 0;
+        var tail: [16]u8 = undefined;
+
+        var out: []u8 = &octets;
+        var index: u8 = 0;
+
+        var saw_any_digits: bool = false;
+        var abbrv: bool = false;
+
+        for (buf) |c, i| {
+            switch (c) {
+                ':' => {
+                    if (!saw_any_digits) {
+                        if (abbrv) return error.UnexpectedToken;
+                        if (i != 0) abbrv = true;
+                        mem.set(u8, out[index..], 0);
+                        out = &tail;
+                        index = 0;
+                        continue;
+                    }
+                    if (index == 14) return error.TooManyOctets;
+
+                    out[index] = @truncate(u8, octet >> 8);
+                    index += 1;
+                    out[index] = @truncate(u8, octet);
+                    index += 1;
+
+                    octet = 0;
+                    saw_any_digits = false;
+                },
+                '.' => {
+                    if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) {
+                        return error.MalformedV4Mapping;
+                    }
+                    const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
+                    const v4 = try IPv4.parse(buf[start_index..]);
+                    octets[10] = 0xFF;
+                    octets[11] = 0xFF;
+                    mem.copy(u8, octets[12..], &v4.octets);
+
+                    return IPv6{ .octets = octets, .scope_id = scope_id };
+                },
+                else => {
+                    saw_any_digits = true;
+                    const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken;
+                    octet = math.mul(u16, octet, 16) catch return error.OctetOverflow;
+                    octet = math.add(u16, octet, digit) catch return error.OctetOverflow;
+                },
+            }
+        }
+
+        if (!saw_any_digits and !abbrv) {
+            return error.IncompleteAddress;
+        }
+
+        if (index == 14) {
+            out[14] = @truncate(u8, octet >> 8);
+            out[15] = @truncate(u8, octet);
+        } else {
+            out[index] = @truncate(u8, octet >> 8);
+            index += 1;
+            out[index] = @truncate(u8, octet);
+            index += 1;
+            mem.copy(u8, octets[16 - index ..], out[0..index]);
+        }
+
+        return IPv6{ .octets = octets, .scope_id = scope_id };
+    }
+};
+
+test {
+    testing.refAllDecls(@This());
+}
+
+test "ip: convert to and from ipv6" {
+    try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()});
+    testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4());
+
+    try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()});
+    testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4());
+
+    testing.expect(IPv4.localhost.toIPv6().toIPv4() == null);
+    try testing.expectFmt("127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6().toIPv4()});
+}
+
+test "ipv4: parse & format" {
+    const cases = [_][]const u8{
+        "0.0.0.0",
+        "255.255.255.255",
+        "1.2.3.4",
+        "123.255.0.91",
+        "127.0.0.1",
+    };
+
+    for (cases) |case| {
+        try testing.expectFmt(case, "{}", .{try IPv4.parse(case)});
+    }
+}
+
+test "ipv6: parse & format" {
+    const inputs = [_][]const u8{
+        "FF01:0:0:0:0:0:0:FB",
+        "FF01::Fb",
+        "::1",
+        "::",
+        "2001:db8::",
+        "::1234:5678",
+        "2001:db8::1234:5678",
+        "::ffff:123.5.123.5",
+        "FF01::FB%lo",
+    };
+
+    const outputs = [_][]const u8{
+        "ff01::fb",
+        "ff01::fb",
+        "::1",
+        "::",
+        "2001:db8::",
+        "::1234:5678",
+        "2001:db8::1234:5678",
+        "::ffff:123.5.123.5",
+        "ff01::fb%1",
+    };
+
+    for (inputs) |input, i| {
+        try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)});
+    }
+}
lib/std/x/os/os.zig
@@ -1,9 +0,0 @@
-const std = @import("../../std.zig");
-
-const testing = std.testing;
-
-pub const Socket = @import("Socket.zig");
-
-test {
-    testing.refAllDecls(@This());
-}
lib/std/x/os/Socket.zig
@@ -2,19 +2,11 @@ const std = @import("../../std.zig");
 
 const os = std.os;
 const mem = std.mem;
-const net = std.net;
 const time = std.time;
-const builtin = std.builtin;
-const testing = std.testing;
 
+/// A generic socket abstraction.
 const Socket = @This();
 
-/// A socket-address pair.
-pub const Connection = struct {
-    socket: Socket,
-    address: net.Address,
-};
-
 /// The underlying handle of a socket.
 fd: os.socket_t,
 
@@ -23,19 +15,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
     return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
 }
 
+/// Enclose a socket abstraction over an existing socket file descriptor.
+pub fn from(fd: os.socket_t) Socket {
+    return Socket{ .fd = fd };
+}
+
 /// Closes the socket.
 pub fn deinit(self: Socket) void {
     os.closeSocket(self.fd);
 }
 
-/// Shutdown either the read side, or write side, or the entirety of a socket.
+/// Shutdown either the read side, write side, or all side of the socket.
 pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
     return os.shutdown(self.fd, how);
 }
 
 /// Binds the socket to an address.
-pub fn bind(self: Socket, address: net.Address) !void {
-    return os.bind(self.fd, &address.any, address.getOsSockLen());
+pub fn bind(self: Socket, comptime Address: type, address: Address) !void {
+    return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));
 }
 
 /// Start listening for incoming connections on the socket.
@@ -44,22 +41,19 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {
 }
 
 /// Have the socket attempt to the connect to an address.
-pub fn connect(self: Socket, address: net.Address) !void {
-    return os.connect(self.fd, &address.any, address.getOsSockLen());
+pub fn connect(self: Socket, comptime Address: type, address: Address) !void {
+    return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));
 }
 
 /// Accept a pending incoming connection queued to the kernel backlog
 /// of the socket.
-pub fn accept(self: Socket, flags: u32) !Socket.Connection {
-    var address: os.sockaddr = undefined;
-    var address_len: u32 = @sizeOf(os.sockaddr);
+pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection {
+    var address: Address = undefined;
+    var address_len: u32 = @sizeOf(Address);
 
-    const fd = try os.accept(self.fd, &address, &address_len, flags);
+    const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags);
 
-    return Connection{
-        .socket = Socket{ .fd = fd },
-        .address = net.Address.initPosix(@alignCast(4, &address)),
-    };
+    return Connection.from(.{ .fd = fd }, address);
 }
 
 /// Read data from the socket into the buffer provided. It returns the
@@ -100,11 +94,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
 }
 
 /// Query the address that the socket is locally bounded to.
-pub fn getLocalAddress(self: Socket) !net.Address {
-    var address: os.sockaddr = undefined;
-    var address_len: u32 = @sizeOf(os.sockaddr);
-    try os.getsockname(self.fd, &address, &address_len);
-    return net.Address.initPosix(@alignCast(4, &address));
+pub fn getLocalAddress(self: Socket, comptime Address: type) !Address {
+    var address: Address = undefined;
+    var address_len: u32 = @sizeOf(Address);
+    try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
+    return address;
 }
 
 /// Query and return the latest cached error on the socket.
@@ -164,33 +158,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void {
     return error.UnsupportedSocketOption;
 }
 
-/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support
-/// sockets disabling Nagle's algorithm.
-pub fn setNoDelay(self: Socket, enabled: bool) !void {
-    if (comptime @hasDecl(os, "TCP_NODELAY")) {
-        return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled))));
-    }
-    return error.UnsupportedSocketOption;
-}
-
-/// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
-/// support TCP Fast Open.
-pub fn setFastOpen(self: Socket, enabled: bool) !void {
-    if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
-        return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
-    }
-    return error.UnsupportedSocketOption;
-}
-
-/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
-/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
-pub fn setQuickACK(self: Socket, enabled: bool) !void {
-    if (comptime @hasDecl(os, "TCP_QUICKACK")) {
-        return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
-    }
-    return error.UnsupportedSocketOption;
-}
-
 /// Set the write buffer size of the socket.
 pub fn setWriteBufferSize(self: Socket, size: u32) !void {
     return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size));
@@ -225,52 +192,3 @@ pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
 
     return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
 }
-
-test {
-    testing.refAllDecls(@This());
-}
-
-test "socket/linux: set read timeout of 1 millisecond on blocking socket" {
-    if (builtin.os.tag != .linux) return error.SkipZigTest;
-
-    const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
-    defer a.deinit();
-
-    try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0));
-    try a.listen(128);
-
-    const binded_address = try a.getLocalAddress();
-
-    const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
-    defer b.deinit();
-
-    try b.connect(binded_address);
-    try b.setReadTimeout(1);
-
-    const ab = try a.accept(os.SOCK_CLOEXEC);
-    defer ab.socket.deinit();
-
-    var buf: [1]u8 = undefined;
-    testing.expectError(error.WouldBlock, b.read(&buf));
-}
-
-test "socket/linux: create non-blocking socket pair" {
-    if (builtin.os.tag != .linux) return error.SkipZigTest;
-
-    const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
-    defer a.deinit();
-
-    try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0));
-    try a.listen(128);
-
-    const binded_address = try a.getLocalAddress();
-
-    const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
-    defer b.deinit();
-
-    testing.expectError(error.WouldBlock, b.connect(binded_address));
-    try b.getError();
-
-    const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
-    defer ab.socket.deinit();
-}
lib/std/x.zig
@@ -1,1 +1,8 @@
-pub const os = @import("x/os/os.zig");
+pub const os = struct {
+    pub const Socket = @import("x/os/Socket.zig");
+    pub usingnamespace @import("x/os/net.zig");
+};
+
+pub const net = struct {
+    pub const TCP = @import("x/net/TCP.zig");
+};