pub const IpAddress = union(enum) ip4: Ip4Addressip6: Ip6Addresspub fn parseLiteral(text: []const u8) ParseLiteralError!IpAddressParse 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 parse(text: []const u8, port: u16) !IpAddressParse 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 parseIp4(text: []const u8, port: u16) Ip4Address.ParseError!IpAddresspub fn parseIp6(text: []const u8, port: u16) Ip6Address.ParseError!IpAddressThis 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 resolve(io: Io, text: []const u8, port: u16) !IpAddressThis 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 resolveIp6(io: Io, text: []const u8, port: u16) Ip6Address.ResolveError!IpAddresspub fn getPort(a: IpAddress) u16Returns the port in native endian.
pub fn setPort(a: *IpAddress, port: u16) voidport is native-endian.
pub fn fromIp6(ip6: Ip6Address) IpAddressConverts from an IPv4-mapped IPv6 address, or returns the IPv6 address directly.
pub fn formatResolved(a: IpAddress, io: Io, w: *Io.Writer) Ip6Address.FormatError!voidIncludes 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 format(a: IpAddress, w: *Io.Writer) Io.Writer.Error!voidSee formatResolved for an alternative that additionally prints the optional scope at the end of IPv6 addresses and requires an Io parameter.
pub fn eql(a: *const IpAddress, b: *const IpAddress) boolpub fn listen(address: *const IpAddress, io: Io, options: ListenOptions) ListenError!ServerWaits for a TCP connection. When using this API, bind does not need to be called. The returned Server has an open stream.
pub fn bind(address: *const IpAddress, io: Io, options: BindOptions) BindError!SocketAssociates 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 connect(address: *const IpAddress, io: Io, options: ConnectOptions) ConnectError!StreamInitiates a connection-oriented network stream.
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348pub 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 protected and the current user does not have permission to bind it.
AccessDenied,
/// 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 protected and the current user does not have permission to bind it.
AccessDenied,
/// 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) };
}
}