pub const IpAddress = union(enum)
pub const IpAddress = union(enum) {
ip4: Ip4Address,
ip6: Ip6Address,
pub const Family = @typeInfo(IpAddress).@"union".tag_type.?;
pub const ParseLiteralError = error{ InvalidAddress, InvalidPort };
/// Parse an IP address which may include a port.
///
/// For IPv4, this is written `address:port`.
///
/// For IPv6, RFC 3986 defines this as an "IP literal", and the port is
/// differentiated from the address by surrounding the address part in
/// brackets "[addr]:port". Even if the port is not given, the brackets are
/// mandatory.
pub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddress {
if (text.len == 0) return error.InvalidAddress;
if (text[0] == '[') {
const addr_end = std.mem.findScalar(u8, text, ']') orelse
return error.InvalidAddress;
const addr_text = text[1..addr_end];
const port: u16 = p: {
if (addr_end == text.len - 1) break :p 0;
if (text[addr_end + 1] != ':') return error.InvalidAddress;
break :p std.fmt.parseInt(u16, text[addr_end + 2 ..], 10) catch return error.InvalidPort;
};
return parseIp6(addr_text, port) catch error.InvalidAddress;
}
if (std.mem.findScalar(u8, text, ':')) |i| {
const addr = Ip4Address.parse(text[0..i], 0) catch return error.InvalidAddress;
return .{ .ip4 = .{
.bytes = addr.bytes,
.port = std.fmt.parseInt(u16, text[i + 1 ..], 10) catch return error.InvalidPort,
} };
}
return parseIp4(text, 0) catch error.InvalidAddress;
}
/// Parse the given IP address string into an `IpAddress` value.
///
/// This is a pure function but it cannot handle IPv6 addresses that have
/// scope ids ("%foo" at the end). To also handle those, `resolve` must be
/// called instead.
pub fn parse(text: []const u8, port: u16) !IpAddress {
if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.NonCanonical,
=> {},
}
return parseIp6(text, port);
}
pub fn parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddress {
return .{ .ip4 = try Ip4Address.parse(text, port) };
}
/// This is a pure function but it cannot handle IPv6 addresses that have
/// scope ids ("%foo" at the end). To also handle those, `resolveIp6` must be
/// called instead.
pub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddress {
return .{ .ip6 = try Ip6Address.parse(text, port) };
}
/// This function requires an `Io` parameter because it must query the operating
/// system to convert interface name to index. For example, in
/// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
/// creating a socket and then using an `ioctl` syscall.
///
/// For a pure function that cannot handle scopes, see `parse`.
pub fn resolve(io: Io, text: []const u8, port: u16) !IpAddress {
if (parseIp4(text, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.NonCanonical,
=> {},
}
return resolveIp6(io, text, port);
}
pub fn resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddress {
return .{ .ip6 = try Ip6Address.resolve(io, text, port) };
}
/// Returns the port in native endian.
pub fn getPort(a: IpAddress) u16 {
return switch (a) {
inline .ip4, .ip6 => |x| x.port,
};
}
/// `port` is native-endian.
pub fn setPort(a: *IpAddress, port: u16) void {
switch (a.*) {
.ip4 => a.ip4.port = port,
.ip6 => a.ip6.port = port,
}
}
/// Converts from an IPv4-mapped IPv6 address, or returns the IPv6 address directly.
pub fn fromIp6(ip6: Ip6Address) IpAddress {
return if (Ip4Address.fromIp6(ip6)) |ip4| .{ .ip4 = ip4 } else .{ .ip6 = ip6 };
}
/// Includes the optional scope ("%foo" at the end) in IPv6 addresses.
///
/// See `format` for an alternative that omits scopes and does
/// not require an `Io` parameter.
pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!void {
switch (a) {
.ip4 => |x| return x.format(w),
.ip6 => |x| return x.formatResolved(io, w),
}
}
/// See `formatResolved` for an alternative that additionally prints the optional
/// scope at the end of IPv6 addresses and requires an `Io` parameter.
pub fn format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!void {
switch (a) {
inline .ip4, .ip6 => |x| return x.format(w),
}
}
pub fn eql(a: *const IpAddress, b: *const IpAddress) bool {
return switch (a.*) {
.ip4 => |a_ip4| switch (b.*) {
.ip4 => |b_ip4| a_ip4.eql(b_ip4),
else => false,
},
.ip6 => |a_ip6| switch (b.*) {
.ip6 => |b_ip6| a_ip6.eql(b_ip6),
else => false,
},
};
}
pub const ListenError = error{
/// The address is already taken. Can occur when bound port is 0 but
/// all ephemeral ports are already in use.
AddressInUse,
/// A nonexistent interface was requested or the requested address was not local.
AddressUnavailable,
/// The local network interface used to reach the destination is offline.
NetworkDown,
/// Insufficient memory or other resource internal to the operating system.
SystemResources,
/// Per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// System-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
/// The requested address family (IPv4 or IPv6) is not supported by the operating system.
AddressFamilyUnsupported,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
/// One of the `ListenOptions` is not supported by the Io
/// implementation.
OptionUnsupported,
} || Io.UnexpectedError || Io.Cancelable;
pub const ListenOptions = struct {
/// How many connections the kernel will accept on the application's behalf.
/// If more than this many connections pool in the kernel, clients will start
/// seeing "Connection refused".
kernel_backlog: u31 = default_kernel_backlog,
/// Sets SO_REUSEADDR and SO_REUSEPORT on POSIX.
/// Sets SO_REUSEADDR on Windows, which is roughly equivalent.
reuse_address: bool = false,
/// Only connection-oriented modes may be used here, which includes:
/// * `Socket.Mode.stream`
/// * `Socket.Mode.seqpacket`
mode: Socket.Mode = .stream,
/// Only connection-oriented protocols may be used here, which includes:
/// * `Protocol.tcp`
/// * `Protocol.tp`
/// * `Protocol.dccp`
/// * `Protocol.sctp`
protocol: Protocol = .tcp,
};
/// Waits for a TCP connection. When using this API, `bind` does not need
/// to be called. The returned `Server` has an open `stream`.
pub fn listen(address: *const IpAddress, io: Io, options: ListenOptions) ListenError!Server {
return .{
.socket = try io.vtable.netListenIp(io.userdata, address, options),
.options = if (Server.AcceptOptions != void) .{
.mode = options.mode,
.protocol = options.protocol,
},
};
}
pub const BindError = error{
/// The address is already taken. Can occur when bound port is 0 but
/// all ephemeral ports are already in use.
AddressInUse,
/// A nonexistent interface was requested or the requested address was not local.
AddressUnavailable,
/// The address is not valid for the address family of socket.
AddressFamilyUnsupported,
/// Insufficient memory or other resource internal to the operating system.
SystemResources,
/// The local network interface used to reach the destination is offline.
NetworkDown,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
/// Per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// System-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
SocketModeUnsupported,
/// One of the `BindOptions` is not supported by the Io
/// implementation.
OptionUnsupported,
} || Io.UnexpectedError || Io.Cancelable;
pub const BindOptions = struct {
/// The socket is restricted to sending and receiving IPv6 packets only.
/// In this case, an IPv4 and an IPv6 application can bind to a single port
/// at the same time.
///
/// The default is determined by system configuration.
ip6_only: ?bool = null,
/// Allow the socket to send datagrams to broadcast addresses.
/// When not enabled any attempt to send datagrams to a broadcast address
/// will fail with `error.AccessDenied`
allow_broadcast: bool = false,
mode: Socket.Mode,
protocol: ?Protocol = null,
};
/// Associates an address with a `Socket` which can be used to receive UDP
/// packets and other kinds of non-streaming messages. See `listen` for a
/// streaming alternative.
///
/// One bound `Socket` can be used to receive messages from multiple
/// different addresses.
pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!Socket {
return io.vtable.netBindIp(io.userdata, address, options);
}
pub const ConnectError = error{
AddressUnavailable,
AddressFamilyUnsupported,
/// Insufficient memory or other resource internal to the operating system.
SystemResources,
ConnectionPending,
ConnectionRefused,
ConnectionResetByPeer,
HostUnreachable,
NetworkUnreachable,
Timeout,
/// One of the `ConnectOptions` is not supported by the Io
/// implementation.
OptionUnsupported,
/// Per-process limit on the number of open file descriptors has been reached.
ProcessFdQuotaExceeded,
/// System-wide limit on the total number of open files has been reached.
SystemFdQuotaExceeded,
ProtocolUnsupportedBySystem,
ProtocolUnsupportedByAddressFamily,
SocketModeUnsupported,
/// The user tried to connect to a broadcast address without having the socket broadcast flag enabled or
/// the connection request failed because of a local firewall rule.
AccessDenied,
/// Non-blocking was requested and the operation cannot return immediately.
WouldBlock,
NetworkDown,
} || Io.Timeout.Error || Io.UnexpectedError || Io.Cancelable;
pub const ConnectOptions = struct {
mode: Socket.Mode,
protocol: ?Protocol = null,
timeout: Io.Timeout = .none,
};
/// Initiates a connection-oriented network stream.
pub fn connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!Stream {
return .{ .socket = try io.vtable.netConnectIp(io.userdata, address, options) };
}
}