From 2ab588049e41a96337a4fa8c2d9507320bc4278b Mon Sep 17 00:00:00 2001 From: lithdew Date: Fri, 30 Apr 2021 18:17:39 +0900 Subject: [PATCH 01/12] 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). --- lib/std/os/bits/darwin.zig | 7 + lib/std/x.zig | 9 +- lib/std/x/net/TCP.zig | 399 ++++++++++++++++++++++++++++ lib/std/x/os/Socket.zig | 124 ++------- lib/std/x/os/net.zig | 532 +++++++++++++++++++++++++++++++++++++ lib/std/x/os/os.zig | 9 - 6 files changed, 967 insertions(+), 113 deletions(-) create mode 100644 lib/std/x/net/TCP.zig create mode 100644 lib/std/x/os/net.zig delete mode 100644 lib/std/x/os/os.zig diff --git a/lib/std/os/bits/darwin.zig b/lib/std/os/bits/darwin.zig index aca24b1c0c..e8a477c5d6 100644 --- a/lib/std/os/bits/darwin.zig +++ b/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; } diff --git a/lib/std/x.zig b/lib/std/x.zig index df7debf121..02742f29ec 100644 --- a/lib/std/x.zig +++ b/lib/std/x.zig @@ -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"); +}; diff --git a/lib/std/x/net/TCP.zig b/lib/std/x/net/TCP.zig new file mode 100644 index 0000000000..5e81dbc060 --- /dev/null +++ b/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); +} diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig index 3f1971a877..96e64c97ef 100644 --- a/lib/std/x/os/Socket.zig +++ b/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(); -} diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig new file mode 100644 index 0000000000..dddb3b73e0 --- /dev/null +++ b/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)}); + } +} diff --git a/lib/std/x/os/os.zig b/lib/std/x/os/os.zig deleted file mode 100644 index 1d1b8edc7c..0000000000 --- a/lib/std/x/os/os.zig +++ /dev/null @@ -1,9 +0,0 @@ -const std = @import("../../std.zig"); - -const testing = std.testing; - -pub const Socket = @import("Socket.zig"); - -test { - testing.refAllDecls(@This()); -} From 13068da43e8fbd0ae5d03aff27fb4e8802e1218c Mon Sep 17 00:00:00 2001 From: lithdew Date: Fri, 30 Apr 2021 21:08:49 +0900 Subject: [PATCH 02/12] 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. --- lib/std/builtin.zig | 18 +-- lib/std/compress/deflate.zig | 6 +- lib/std/os/bits/linux/arm64.zig | 1 + lib/std/x.zig | 16 +- lib/std/x/net/{TCP.zig => tcp.zig} | 246 ++++++++++++++--------------- lib/std/x/os/Socket.zig | 128 +++++++++++++-- lib/std/x/os/net.zig | 18 +++ 7 files changed, 266 insertions(+), 167 deletions(-) rename lib/std/x/net/{TCP.zig => tcp.zig} (67%) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index a4da9ba2f3..c5fa77bb7c 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -150,23 +150,7 @@ pub const Mode = enum { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. -pub const CallingConvention = enum { - Unspecified, - C, - Naked, - Async, - Inline, - Interrupt, - Signal, - Stdcall, - Fastcall, - Vectorcall, - Thiscall, - APCS, - AAPCS, - AAPCSVFP, - SysV -}; +pub const CallingConvention = enum { 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 /// therefore must be kept in sync with the compiler implementation. diff --git a/lib/std/compress/deflate.zig b/lib/std/compress/deflate.zig index 09e162933c..88b9ec8672 100644 --- a/lib/std/compress/deflate.zig +++ b/lib/std/compress/deflate.zig @@ -662,14 +662,12 @@ test "lengths overflow" { // 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 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11 - const stream = [_]u8{ - 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 - }; + const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 }; const reader = std.io.fixedBufferStream(&stream).reader(); var window: [0x8000]u8 = undefined; var inflate = inflateStream(reader, &window); var buf: [1]u8 = undefined; - std.testing.expectError(error.InvalidLength, inflate.read(&buf)); + std.testing.expectError(error.InvalidLength, inflate.read(&buf)); } diff --git a/lib/std/os/bits/linux/arm64.zig b/lib/std/os/bits/linux/arm64.zig index e373d978e1..9737a68de1 100644 --- a/lib/std/os/bits/linux/arm64.zig +++ b/lib/std/os/bits/linux/arm64.zig @@ -9,6 +9,7 @@ const std = @import("../../../std.zig"); const linux = std.os.linux; const socklen_t = linux.socklen_t; +const sockaddr = linux.sockaddr; const iovec = linux.iovec; const iovec_const = linux.iovec_const; const uid_t = linux.uid_t; diff --git a/lib/std/x.zig b/lib/std/x.zig index 02742f29ec..0c23f3f035 100644 --- a/lib/std/x.zig +++ b/lib/std/x.zig @@ -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 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"); + pub const tcp = @import("x/net/tcp.zig"); }; + +test { + inline for (.{ os, net }) |module| { + std.testing.refAllDecls(module); + } +} diff --git a/lib/std/x/net/TCP.zig b/lib/std/x/net/tcp.zig similarity index 67% rename from lib/std/x/net/TCP.zig rename to lib/std/x/net/tcp.zig index 5e81dbc060..4c65310f51 100644 --- a/lib/std/x/net/TCP.zig +++ b/lib/std/x/net/tcp.zig @@ -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 os = std.os; @@ -10,20 +16,76 @@ const IPv6 = std.x.os.IPv6; const Socket = std.x.os.Socket; /// 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. pub const Connection = struct { - client: TCP.Client, - address: TCP.Address, + 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 }; + pub fn from(conn: Socket.Connection) tcp.Connection { + 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. - pub fn deinit(self: TCP.Connection) void { + pub fn deinit(self: tcp.Connection) void { self.client.deinit(); } }; @@ -39,7 +101,7 @@ pub const Client = struct { socket: Socket, /// Opens a new client. - pub fn init(domain: TCP.Domain, flags: u32) !Client { + pub fn init(domain: tcp.Domain, flags: u32) !Client { return Client{ .socket = try Socket.init( @enumToInt(domain), @@ -65,8 +127,8 @@ pub const Client = struct { } /// 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); + pub fn connect(self: Client, address: tcp.Address) !void { + return self.socket.connect(address.into()); } /// 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. - pub fn getLocalAddress(self: Client) !TCP.Address { - return self.socket.getLocalAddress(TCP.Address); + pub fn getLocalAddress(self: Client) !tcp.Address { + return tcp.Address.from(try self.socket.getLocalAddress()); } /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if @@ -167,7 +229,7 @@ pub const Listener = struct { socket: Socket, /// Opens a new listener. - pub fn init(domain: TCP.Domain, flags: u32) !Listener { + pub fn init(domain: tcp.Domain, flags: u32) !Listener { return Listener{ .socket = try Socket.init( @enumToInt(domain), @@ -190,8 +252,8 @@ pub const Listener = struct { } /// Binds the listener's socket to an address. - pub fn bind(self: Listener, address: TCP.Address) !void { - return self.socket.bind(TCP.Address, address); + pub fn bind(self: Listener, address: tcp.Address) !void { + return self.socket.bind(address.into()); } /// Start listening for incoming connections. @@ -201,8 +263,8 @@ pub const Listener = struct { /// 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); + pub fn accept(self: Listener, flags: u32) !tcp.Connection { + return tcp.Connection.from(try self.socket.accept(flags)); } /// 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. - pub fn getLocalAddress(self: Listener) !TCP.Address { - return self.socket.getLocalAddress(TCP.Address); + pub fn getLocalAddress(self: Listener) !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 @@ -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 { 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(); + const listener = try tcp.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer listener.deinit(); - try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); - try a.listen(128); + try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); + 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); - defer b.deinit(); + const client = try tcp.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer client.deinit(); - testing.expectError(error.WouldBlock, b.connect(binded_address)); - try b.getError(); + testing.expectError(error.WouldBlock, client.connect(binded_address)); + try client.getError(); - const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); - defer ab.deinit(); + const conn = try listener.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + defer conn.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(); + const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); + defer listener.deinit(); - try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); - try a.listen(128); + try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); + 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); - defer b.deinit(); + const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC); + defer client.deinit(); - try b.connect(binded_address); - try b.setReadTimeout(1); + try client.connect(binded_address); + try client.setReadTimeout(1); - const ab = try a.accept(os.SOCK_CLOEXEC); - defer ab.deinit(); + const conn = try listener.accept(os.SOCK_CLOEXEC); + defer conn.deinit(); 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" { - const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC); - defer socket.deinit(); + const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); + defer listener.deinit(); - try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0)); - try socket.listen(128); + try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); + try listener.listen(128); - const address = try socket.getLocalAddress(); - testing.expect(address.getHost() == .v4); + const address = try listener.getLocalAddress(); + testing.expect(address == .ipv4); } test "tcp/listener: bind to unspecified ipv6 address" { - const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC); - defer socket.deinit(); + const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC); + defer listener.deinit(); - try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0)); - try socket.listen(128); + try listener.bind(tcp.Address.initIPv6(IPv6.unspecified, 0)); + try listener.listen(128); - const address = try socket.getLocalAddress(); - testing.expect(address.getHost() == .v6); + const address = try listener.getLocalAddress(); + testing.expect(address == .ipv6); } diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig index 96e64c97ef..33e463e731 100644 --- a/lib/std/x/os/Socket.zig +++ b/lib/std/x/os/Socket.zig @@ -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 net = @import("net.zig"); const os = std.os; const mem = std.mem; @@ -7,6 +14,98 @@ const time = std.time; /// A generic socket abstraction. 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. fd: os.socket_t, @@ -31,8 +130,8 @@ pub fn shutdown(self: Socket, how: os.ShutdownHow) !void { } /// Binds the socket to an address. -pub fn bind(self: Socket, comptime Address: type, address: Address) !void { - return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); +pub fn bind(self: Socket, address: Socket.Address) !void { + return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize()); } /// 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. -pub fn connect(self: Socket, comptime Address: type, address: Address) !void { - return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address)); +pub fn connect(self: Socket, address: Socket.Address) !void { + return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize()); } /// Accept a pending incoming connection queued to the kernel backlog /// of the socket. -pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection { - var address: Address = undefined; - var address_len: u32 = @sizeOf(Address); +pub fn accept(self: Socket, flags: u32) !Socket.Connection { + var address: os.sockaddr = undefined; + 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 @@ -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. -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; +pub fn getLocalAddress(self: Socket) !Socket.Address { + var address: os.sockaddr = undefined; + var address_len: u32 = @sizeOf(os.sockaddr); + try os.getsockname(self.fd, &address, &address_len); + return Socket.Address.fromNative(@alignCast(4, &address)); } /// Query and return the latest cached error on the socket. diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig index dddb3b73e0..78de60e6c1 100644 --- a/lib/std/x/os/net.zig +++ b/lib/std/x/os/net.zig @@ -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 os = std.os; @@ -27,6 +33,12 @@ pub fn resolveScopeID(name: []const u8) !u32 { /// An IPv4 address comprised of 4 bytes. 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. 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 /// for a scope ID; cumulatively summing to 20 bytes in total. 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. pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01}; From 76304a36afb6d2cc77902a0e60906382b53d7baa Mon Sep 17 00:00:00 2001 From: lithdew Date: Fri, 30 Apr 2021 21:10:36 +0900 Subject: [PATCH 03/12] std/builtin: add missing comma to CallingConvention --- lib/std/builtin.zig | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index c5fa77bb7c..53c27f0bf5 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -150,7 +150,23 @@ pub const Mode = enum { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. -pub const CallingConvention = enum { Unspecified, C, Naked, Async, Inline, Interrupt, Signal, Stdcall, Fastcall, Vectorcall, Thiscall, APCS, AAPCS, AAPCSVFP, SysV }; +pub const CallingConvention = enum { + 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 /// therefore must be kept in sync with the compiler implementation. From 8ef9d98e6dd24bf9c3def1e938e3eab66b39667c Mon Sep 17 00:00:00 2001 From: lithdew Date: Fri, 30 Apr 2021 21:59:02 +0900 Subject: [PATCH 04/12] x/os: fix compile errors on mac and linux Use i32 instead of isize for os.timeval's for socket read/write timeouts. Add a comptime check to resolveScopeID to see if `IFNAMESIZE` is available on the host. If it is not available, return an error indicating that resolving the scope ID of a IPv6 address is not yet supported on the host platform. --- lib/std/x/os/Socket.zig | 8 ++++---- lib/std/x/os/net.zig | 19 +++++++++++-------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/lib/std/x/os/Socket.zig b/lib/std/x/os/Socket.zig index 33e463e731..bb984af97a 100644 --- a/lib/std/x/os/Socket.zig +++ b/lib/std/x/os/Socket.zig @@ -273,8 +273,8 @@ pub fn setReadBufferSize(self: Socket, size: u32) !void { /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded. pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { const timeout = os.timeval{ - .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), - .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), + .tv_sec = @intCast(i32, milliseconds / time.ms_per_s), + .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms), }; return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout)); @@ -286,8 +286,8 @@ pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void { /// exceeded. pub fn setReadTimeout(self: Socket, milliseconds: usize) !void { const timeout = os.timeval{ - .tv_sec = @intCast(isize, milliseconds / time.ms_per_s), - .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms), + .tv_sec = @intCast(i32, milliseconds / time.ms_per_s), + .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms), }; return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout)); diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig index 78de60e6c1..2534570c53 100644 --- a/lib/std/x/os/net.zig +++ b/lib/std/x/os/net.zig @@ -17,18 +17,21 @@ const testing = std.testing; /// 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; + if (comptime @hasDecl(os, "IFNAMESIZE")) { + 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); + 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; + var f: os.ifreq = undefined; + mem.copy(u8, &f.ifrn.name, name); + f.ifrn.name[name.len] = 0; - try os.ioctl_SIOCGIFINDEX(fd, &f); + try os.ioctl_SIOCGIFINDEX(fd, &f); - return @bitCast(u32, f.ifru.ivalue); + return @bitCast(u32, f.ifru.ivalue); + } + return error.Unsupported; } /// An IPv4 address comprised of 4 bytes. From 9a7c57144da9057c80059a3a1ec37aec44dcdc7a Mon Sep 17 00:00:00 2001 From: lithdew Date: Fri, 30 Apr 2021 22:09:12 +0900 Subject: [PATCH 05/12] x/net: disable tcp tests on wasi --- lib/std/x/net/tcp.zig | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/std/x/net/tcp.zig b/lib/std/x/net/tcp.zig index 4c65310f51..70cbc52f4e 100644 --- a/lib/std/x/net/tcp.zig +++ b/lib/std/x/net/tcp.zig @@ -9,6 +9,7 @@ const std = @import("../../std.zig"); const os = std.os; const fmt = std.fmt; const mem = std.mem; +const builtin = std.builtin; const testing = std.testing; const IPv4 = std.x.os.IPv4; @@ -315,11 +316,9 @@ pub const Listener = struct { } }; -test { - testing.refAllDecls(@This()); -} - test "tcp: create non-blocking pair" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const listener = try tcp.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); defer listener.deinit(); @@ -339,6 +338,8 @@ test "tcp: create non-blocking pair" { } test "tcp/client: set read timeout of 1 millisecond on blocking client" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); defer listener.deinit(); @@ -361,6 +362,8 @@ test "tcp/client: set read timeout of 1 millisecond on blocking client" { } test "tcp/listener: bind to unspecified ipv4 address" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); defer listener.deinit(); @@ -372,6 +375,8 @@ test "tcp/listener: bind to unspecified ipv4 address" { } test "tcp/listener: bind to unspecified ipv6 address" { + if (builtin.os.tag == .wasi) return error.SkipZigTest; + const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC); defer listener.deinit(); From aa15699269b416f46f3c10469d842b930ac26fc9 Mon Sep 17 00:00:00 2001 From: lithdew Date: Fri, 30 Apr 2021 22:45:10 +0900 Subject: [PATCH 06/12] x/os/net: skip ipv6 scope id parse test on unsupported platforms --- lib/std/x/os/net.zig | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/std/x/os/net.zig b/lib/std/x/os/net.zig index 2534570c53..a8d9f095d3 100644 --- a/lib/std/x/os/net.zig +++ b/lib/std/x/os/net.zig @@ -532,7 +532,6 @@ test "ipv6: parse & format" { "::1234:5678", "2001:db8::1234:5678", "::ffff:123.5.123.5", - "FF01::FB%lo", }; const outputs = [_][]const u8{ @@ -544,6 +543,21 @@ test "ipv6: parse & format" { "::1234:5678", "2001:db8::1234:5678", "::ffff:123.5.123.5", + }; + + for (inputs) |input, i| { + try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)}); + } +} + +test "ipv6: parse & format addresses with scope ids" { + if (!@hasDecl(os, "IFNAMESIZE")) return error.SkipZigTest; + + const inputs = [_][]const u8{ + "FF01::FB%lo", + }; + + const outputs = [_][]const u8{ "ff01::fb%1", }; From a799ad05b37c4b04f73ad79f9e3c67c4036138ec Mon Sep 17 00:00:00 2001 From: lithdew Date: Sun, 2 May 2021 16:50:56 +0900 Subject: [PATCH 07/12] x/net/tcp: make tcp tests blocking to avoid unit test races --- lib/std/x/net/tcp.zig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/lib/std/x/net/tcp.zig b/lib/std/x/net/tcp.zig index 70cbc52f4e..9804699f48 100644 --- a/lib/std/x/net/tcp.zig +++ b/lib/std/x/net/tcp.zig @@ -316,10 +316,10 @@ pub const Listener = struct { } }; -test "tcp: create non-blocking pair" { +test "tcp: create client/listener pair" { if (builtin.os.tag == .wasi) return error.SkipZigTest; - const listener = try tcp.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); defer listener.deinit(); try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); @@ -327,13 +327,12 @@ test "tcp: create non-blocking pair" { const binded_address = try listener.getLocalAddress(); - const client = try tcp.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC); defer client.deinit(); - testing.expectError(error.WouldBlock, client.connect(binded_address)); - try client.getError(); + try client.connect(binded_address); - const conn = try listener.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC); + const conn = try listener.accept(os.SOCK_CLOEXEC); defer conn.deinit(); } From cc6714a92923075d32adc44ade226ededc5da7db Mon Sep 17 00:00:00 2001 From: lithdew Date: Sun, 2 May 2021 18:41:48 +0900 Subject: [PATCH 08/12] std/os/bits: add timeval struct for riscv64 linux --- lib/std/os/bits/linux/riscv64.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/os/bits/linux/riscv64.zig b/lib/std/os/bits/linux/riscv64.zig index 0cbdea415c..192a2a4f71 100644 --- a/lib/std/os/bits/linux/riscv64.zig +++ b/lib/std/os/bits/linux/riscv64.zig @@ -376,6 +376,11 @@ pub const timespec = extern struct { tv_nsec: isize, }; +pub const timeval = extern struct { + tv_sec: time_t, + tv_usec: i64, +}; + pub const Flock = extern struct { l_type: i16, l_whence: i16, From 5c4fbc40140ec6e30aa2954c13a5799b40e7afe4 Mon Sep 17 00:00:00 2001 From: lithdew Date: Sun, 2 May 2021 18:51:32 +0900 Subject: [PATCH 09/12] x/net: generalize `tcp.Address` into `ip.Address` Generalize `tcp.Address` into `ip.Address` given that multiple transport protocols apart from TCP (i.e. UDP) operate solely over IP. --- lib/std/x.zig | 1 + lib/std/x/net/ip.zig | 52 +++++++++++++++++++++++++++++++ lib/std/x/net/tcp.zig | 71 +++++++++---------------------------------- 3 files changed, 67 insertions(+), 57 deletions(-) create mode 100644 lib/std/x/net/ip.zig diff --git a/lib/std/x.zig b/lib/std/x.zig index 0c23f3f035..a123591470 100644 --- a/lib/std/x.zig +++ b/lib/std/x.zig @@ -12,6 +12,7 @@ pub const os = struct { }; pub const net = struct { + pub const ip = @import("x/net/ip.zig"); pub const tcp = @import("x/net/tcp.zig"); }; diff --git a/lib/std/x/net/ip.zig b/lib/std/x/net/ip.zig new file mode 100644 index 0000000000..5f6bf5ac48 --- /dev/null +++ b/lib/std/x/net/ip.zig @@ -0,0 +1,52 @@ +const std = @import("../../std.zig"); + +const IPv4 = std.x.os.IPv4; +const IPv6 = std.x.os.IPv6; +const Socket = std.x.os.Socket; + +const ip = @This(); + +/// A union of all eligible types of IP addresses. +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 an IP address. + pub fn from(address: Socket.Address) ip.Address { + return switch (address) { + .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address }, + .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address }, + }; + } + + /// Re-interpret an IP address into a generic socket address. + pub fn into(self: ip.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: ip.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 }), + } + } +}; diff --git a/lib/std/x/net/tcp.zig b/lib/std/x/net/tcp.zig index 9804699f48..8d6254fd0d 100644 --- a/lib/std/x/net/tcp.zig +++ b/lib/std/x/net/tcp.zig @@ -7,6 +7,8 @@ const std = @import("../../std.zig"); const os = std.os; +const ip = std.x.net.ip; + const fmt = std.fmt; const mem = std.mem; const builtin = std.builtin; @@ -19,61 +21,16 @@ const Socket = std.x.os.Socket; /// A generic TCP socket abstraction. 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. pub const Connection = struct { client: tcp.Client, - address: tcp.Address, + address: ip.Address, /// Enclose a TCP client and address into a client-address pair. pub fn from(conn: Socket.Connection) tcp.Connection { return .{ .client = tcp.Client.from(conn.socket), - .address = tcp.Address.from(conn.address), + .address = ip.Address.from(conn.address), }; } @@ -128,7 +85,7 @@ pub const Client = struct { } /// 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: ip.Address) !void { return self.socket.connect(address.into()); } @@ -185,8 +142,8 @@ pub const Client = struct { } /// Query the address that the client's socket is locally bounded to. - pub fn getLocalAddress(self: Client) !tcp.Address { - return tcp.Address.from(try self.socket.getLocalAddress()); + pub fn getLocalAddress(self: Client) !ip.Address { + return ip.Address.from(try self.socket.getLocalAddress()); } /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if @@ -253,7 +210,7 @@ pub const Listener = struct { } /// Binds the listener's socket to an address. - pub fn bind(self: Listener, address: tcp.Address) !void { + pub fn bind(self: Listener, address: ip.Address) !void { return self.socket.bind(address.into()); } @@ -274,8 +231,8 @@ pub const Listener = struct { } /// Query the address that the listener's socket is locally bounded to. - pub fn getLocalAddress(self: Listener) !tcp.Address { - return tcp.Address.from(try self.socket.getLocalAddress()); + pub fn getLocalAddress(self: Listener) !ip.Address { + return ip.Address.from(try self.socket.getLocalAddress()); } /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if @@ -322,7 +279,7 @@ test "tcp: create client/listener pair" { const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); defer listener.deinit(); - try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); + try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); try listener.listen(128); const binded_address = try listener.getLocalAddress(); @@ -342,7 +299,7 @@ test "tcp/client: set read timeout of 1 millisecond on blocking client" { const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); defer listener.deinit(); - try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); + try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); try listener.listen(128); const binded_address = try listener.getLocalAddress(); @@ -366,7 +323,7 @@ test "tcp/listener: bind to unspecified ipv4 address" { const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC); defer listener.deinit(); - try listener.bind(tcp.Address.initIPv4(IPv4.unspecified, 0)); + try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0)); try listener.listen(128); const address = try listener.getLocalAddress(); @@ -379,7 +336,7 @@ test "tcp/listener: bind to unspecified ipv6 address" { const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC); defer listener.deinit(); - try listener.bind(tcp.Address.initIPv6(IPv6.unspecified, 0)); + try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0)); try listener.listen(128); const address = try listener.getLocalAddress(); From af057c888b99316808165926847577869dae32e8 Mon Sep 17 00:00:00 2001 From: lithdew Date: Sun, 2 May 2021 18:54:35 +0900 Subject: [PATCH 10/12] x/net/ip: add copyright header --- lib/std/x/net/ip.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/x/net/ip.zig b/lib/std/x/net/ip.zig index 5f6bf5ac48..6d1632a50d 100644 --- a/lib/std/x/net/ip.zig +++ b/lib/std/x/net/ip.zig @@ -1,9 +1,16 @@ +// 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 IPv4 = std.x.os.IPv4; const IPv6 = std.x.os.IPv6; const Socket = std.x.os.Socket; +/// A generic IP abstraction. const ip = @This(); /// A union of all eligible types of IP addresses. From 16fc1b904c115d37dc335c35177b8433d91fa271 Mon Sep 17 00:00:00 2001 From: lithdew Date: Sun, 2 May 2021 20:32:22 +0900 Subject: [PATCH 11/12] std/os/linux/bits: correct socket option codes for ppc/ppc64 --- lib/std/os/bits/linux.zig | 39 +++++++++++++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/lib/std/os/bits/linux.zig b/lib/std/os/bits/linux.zig index 94da5cc99a..74f9b14ca2 100644 --- a/lib/std/os/bits/linux.zig +++ b/lib/std/os/bits/linux.zig @@ -32,6 +32,7 @@ pub usingnamespace @import("linux/prctl.zig"); pub usingnamespace @import("linux/securebits.zig"); const is_mips = builtin.arch.isMIPS(); +const is_ppc = builtin.arch.isPPC(); const is_ppc64 = builtin.arch.isPPC64(); const is_sparc = builtin.arch.isSPARC(); @@ -458,7 +459,39 @@ pub const AF_QIPCRTR = PF_QIPCRTR; pub const AF_SMC = PF_SMC; pub const AF_MAX = PF_MAX; -pub usingnamespace if (!is_mips) +pub usingnamespace if (is_mips) + struct {} +else if (is_ppc or is_ppc64) + struct { + pub const SO_DEBUG = 1; + pub const SO_REUSEADDR = 2; + pub const SO_TYPE = 3; + pub const SO_ERROR = 4; + pub const SO_DONTROUTE = 5; + pub const SO_BROADCAST = 6; + pub const SO_SNDBUF = 7; + pub const SO_RCVBUF = 8; + pub const SO_KEEPALIVE = 9; + pub const SO_OOBINLINE = 10; + pub const SO_NO_CHECK = 11; + pub const SO_PRIORITY = 12; + pub const SO_LINGER = 13; + pub const SO_BSDCOMPAT = 14; + pub const SO_REUSEPORT = 15; + pub const SO_RCVLOWAT = 16; + pub const SO_SNDLOWAT = 17; + pub const SO_RCVTIMEO = 18; + pub const SO_SNDTIMEO = 19; + pub const SO_PASSCRED = 20; + pub const SO_PEERCRED = 21; + pub const SO_ACCEPTCONN = 30; + pub const SO_PEERSEC = 31; + pub const SO_SNDBUFFORCE = 32; + pub const SO_RCVBUFFORCE = 33; + pub const SO_PROTOCOL = 38; + pub const SO_DOMAIN = 39; + } +else struct { pub const SO_DEBUG = 1; pub const SO_REUSEADDR = 2; @@ -487,9 +520,7 @@ pub usingnamespace if (!is_mips) pub const SO_RCVBUFFORCE = 33; pub const SO_PROTOCOL = 38; pub const SO_DOMAIN = 39; - } -else - struct {}; + }; pub const SO_SECURITY_AUTHENTICATION = 22; pub const SO_SECURITY_ENCRYPTION_TRANSPORT = 23; From 96fe49ef620cfd8423e878262420f58d5983d58c Mon Sep 17 00:00:00 2001 From: lithdew Date: Mon, 3 May 2021 14:48:51 +0900 Subject: [PATCH 12/12] std/os/bits/windows: add `timeval` extern struct --- lib/std/os/bits/windows.zig | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/std/os/bits/windows.zig b/lib/std/os/bits/windows.zig index 28a6a251f8..00ca2a1532 100644 --- a/lib/std/os/bits/windows.zig +++ b/lib/std/os/bits/windows.zig @@ -22,6 +22,11 @@ pub const timespec = extern struct { tv_nsec: c_long, }; +pub const timeval = extern struct { + tv_sec: c_long, + tv_usec: c_long, +}; + pub const sig_atomic_t = c_int; /// maximum signal number + 1