authorgravatar for kenta@lithdew.netlithdew <kenta@lithdew.net> 2021-04-30 18:17:39+09:00
committergravatar for kenta@lithdew.netlithdew <kenta@lithdew.net> 2021-05-03 14:49:10+09:00
log2ab588049e41a96337a4fa8c2d9507320bc4278b
treed4958fc71559cdd4f4c3bd5952e266bfe1dd4499
parentc47028cd025f4ce405a9395fa979ff14a3e6349a

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).

6 files changed, 967 insertions(+), 113 deletions(-)

lib/std/os/bits/darwin.zig+7
......@@ -832,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006;
832832pub const SO_ERROR = 0x1007;
833833pub const SO_TYPE = 0x1008;
834834
835pub const SO_NREAD = 0x1020;
836pub const SO_NKE = 0x1021;
837pub const SO_NOSIGPIPE = 0x1022;
838pub const SO_NOADDRERR = 0x1023;
839pub const SO_NWRITE = 0x1024;
840pub const SO_REUSESHAREUID = 0x1025;
841
835842fn wstatus(x: u32) u32 {
836843 return x & 0o177;
837844}
lib/std/x.zig+8-1
......@@ -1 +1,8 @@
1pub const os = @import("x/os/os.zig");
1pub const os = struct {
2 pub const Socket = @import("x/os/Socket.zig");
3 pub usingnamespace @import("x/os/net.zig");
4};
5
6pub const net = struct {
7 pub const TCP = @import("x/net/TCP.zig");
8};
lib/std/x/net/TCP.zig created+399
......@@ -0,0 +1,399 @@
1const std = @import("../../std.zig");
2
3const os = std.os;
4const fmt = std.fmt;
5const mem = std.mem;
6const testing = std.testing;
7
8const IPv4 = std.x.os.IPv4;
9const IPv6 = std.x.os.IPv6;
10const Socket = std.x.os.Socket;
11
12/// A generic TCP socket abstraction.
13const TCP = @This();
14
15/// A TCP client-address pair.
16pub const Connection = struct {
17 client: TCP.Client,
18 address: TCP.Address,
19
20 /// Enclose a TCP client and address into a client-address pair.
21 pub fn from(socket: Socket, address: TCP.Address) Connection {
22 return .{ .client = TCP.Client.from(socket), .address = address };
23 }
24
25 /// Closes the underlying client of the connection.
26 pub fn deinit(self: TCP.Connection) void {
27 self.client.deinit();
28 }
29};
30
31/// Possible domains that a TCP client/listener may operate over.
32pub const Domain = extern enum(u16) {
33 ip = os.AF_INET,
34 ipv6 = os.AF_INET6,
35};
36
37/// A TCP client.
38pub const Client = struct {
39 socket: Socket,
40
41 /// Opens a new client.
42 pub fn init(domain: TCP.Domain, flags: u32) !Client {
43 return Client{
44 .socket = try Socket.init(
45 @enumToInt(domain),
46 os.SOCK_STREAM | flags,
47 os.IPPROTO_TCP,
48 ),
49 };
50 }
51
52 /// Enclose a TCP client over an existing socket.
53 pub fn from(socket: Socket) Client {
54 return Client{ .socket = socket };
55 }
56
57 /// Closes the client.
58 pub fn deinit(self: Client) void {
59 self.socket.deinit();
60 }
61
62 /// Shutdown either the read side, write side, or all sides of the client's underlying socket.
63 pub fn shutdown(self: Client, how: os.ShutdownHow) !void {
64 return self.socket.shutdown(how);
65 }
66
67 /// Have the client attempt to the connect to an address.
68 pub fn connect(self: Client, address: TCP.Address) !void {
69 return self.socket.connect(TCP.Address, address);
70 }
71
72 /// Read data from the socket into the buffer provided. It returns the
73 /// number of bytes read into the buffer provided.
74 pub fn read(self: Client, buf: []u8) !usize {
75 return self.socket.read(buf);
76 }
77
78 /// Read data from the socket into the buffer provided with a set of flags
79 /// specified. It returns the number of bytes read into the buffer provided.
80 pub fn recv(self: Client, buf: []u8, flags: u32) !usize {
81 return self.socket.recv(buf, flags);
82 }
83
84 /// Write a buffer of data provided to the socket. It returns the number
85 /// of bytes that are written to the socket.
86 pub fn write(self: Client, buf: []const u8) !usize {
87 return self.socket.write(buf);
88 }
89
90 /// Writes multiple I/O vectors to the socket. It returns the number
91 /// of bytes that are written to the socket.
92 pub fn writev(self: Client, buffers: []const os.iovec_const) !usize {
93 return self.socket.writev(buffers);
94 }
95
96 /// Write a buffer of data provided to the socket with a set of flags specified.
97 /// It returns the number of bytes that are written to the socket.
98 pub fn send(self: Client, buf: []const u8, flags: u32) !usize {
99 return self.socket.send(buf, flags);
100 }
101
102 /// Writes multiple I/O vectors with a prepended message header to the socket
103 /// with a set of flags specified. It returns the number of bytes that are
104 /// written to the socket.
105 pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize {
106 return self.socket.sendmsg(msg, flags);
107 }
108
109 /// Query and return the latest cached error on the client's underlying socket.
110 pub fn getError(self: Client) !void {
111 return self.socket.getError();
112 }
113
114 /// Query the read buffer size of the client's underlying socket.
115 pub fn getReadBufferSize(self: Client) !u32 {
116 return self.socket.getReadBufferSize();
117 }
118
119 /// Query the write buffer size of the client's underlying socket.
120 pub fn getWriteBufferSize(self: Client) !u32 {
121 return self.socket.getWriteBufferSize();
122 }
123
124 /// Query the address that the client's socket is locally bounded to.
125 pub fn getLocalAddress(self: Client) !TCP.Address {
126 return self.socket.getLocalAddress(TCP.Address);
127 }
128
129 /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
130 /// the host does not support sockets disabling Nagle's algorithm.
131 pub fn setNoDelay(self: Client, enabled: bool) !void {
132 if (comptime @hasDecl(os, "TCP_NODELAY")) {
133 const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
134 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes);
135 }
136 return error.UnsupportedSocketOption;
137 }
138
139 /// Set the write buffer size of the socket.
140 pub fn setWriteBufferSize(self: Client, size: u32) !void {
141 return self.socket.setWriteBufferSize(size);
142 }
143
144 /// Set the read buffer size of the socket.
145 pub fn setReadBufferSize(self: Client, size: u32) !void {
146 return self.socket.setReadBufferSize(size);
147 }
148
149 /// Set a timeout on the socket that is to occur if no messages are successfully written
150 /// to its bound destination after a specified number of milliseconds. A subsequent write
151 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
152 pub fn setWriteTimeout(self: Client, milliseconds: usize) !void {
153 return self.socket.setWriteTimeout(milliseconds);
154 }
155
156 /// Set a timeout on the socket that is to occur if no messages are successfully read
157 /// from its bound destination after a specified number of milliseconds. A subsequent
158 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
159 /// exceeded.
160 pub fn setReadTimeout(self: Client, milliseconds: usize) !void {
161 return self.socket.setReadTimeout(milliseconds);
162 }
163};
164
165/// A TCP listener.
166pub const Listener = struct {
167 socket: Socket,
168
169 /// Opens a new listener.
170 pub fn init(domain: TCP.Domain, flags: u32) !Listener {
171 return Listener{
172 .socket = try Socket.init(
173 @enumToInt(domain),
174 os.SOCK_STREAM | flags,
175 os.IPPROTO_TCP,
176 ),
177 };
178 }
179
180 /// Closes the listener.
181 pub fn deinit(self: Listener) void {
182 self.socket.deinit();
183 }
184
185 /// Shuts down the underlying listener's socket. The next subsequent call, or
186 /// a current pending call to accept() after shutdown is called will return
187 /// an error.
188 pub fn shutdown(self: Listener) !void {
189 return self.socket.shutdown(.recv);
190 }
191
192 /// Binds the listener's socket to an address.
193 pub fn bind(self: Listener, address: TCP.Address) !void {
194 return self.socket.bind(TCP.Address, address);
195 }
196
197 /// Start listening for incoming connections.
198 pub fn listen(self: Listener, max_backlog_size: u31) !void {
199 return self.socket.listen(max_backlog_size);
200 }
201
202 /// Accept a pending incoming connection queued to the kernel backlog
203 /// of the listener's socket.
204 pub fn accept(self: Listener, flags: u32) !TCP.Connection {
205 return self.socket.accept(TCP.Connection, TCP.Address, flags);
206 }
207
208 /// Query and return the latest cached error on the listener's underlying socket.
209 pub fn getError(self: Client) !void {
210 return self.socket.getError();
211 }
212
213 /// Query the address that the listener's socket is locally bounded to.
214 pub fn getLocalAddress(self: Listener) !TCP.Address {
215 return self.socket.getLocalAddress(TCP.Address);
216 }
217
218 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
219 /// the host does not support sockets listening the same address.
220 pub fn setReuseAddress(self: Listener, enabled: bool) !void {
221 return self.socket.setReuseAddress(enabled);
222 }
223
224 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
225 /// the host does not supports sockets listening on the same port.
226 pub fn setReusePort(self: Listener, enabled: bool) !void {
227 return self.socket.setReusePort(enabled);
228 }
229
230 /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
231 /// support TCP Fast Open.
232 pub fn setFastOpen(self: Listener, enabled: bool) !void {
233 if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
234 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
235 }
236 return error.UnsupportedSocketOption;
237 }
238
239 /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
240 /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
241 pub fn setQuickACK(self: Listener, enabled: bool) !void {
242 if (comptime @hasDecl(os, "TCP_QUICKACK")) {
243 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
244 }
245 return error.UnsupportedSocketOption;
246 }
247
248 /// Set a timeout on the listener that is to occur if no new incoming connections come in
249 /// after a specified number of milliseconds. A subsequent accept call to the listener
250 /// will thereafter return `error.WouldBlock` should the timeout be exceeded.
251 pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void {
252 return self.socket.setReadTimeout(milliseconds);
253 }
254};
255
256/// A TCP socket address designated by a host IP and port. A TCP socket
257/// address comprises of 28 bytes. It may freely be used in place of
258/// `sockaddr` when working with socket syscalls.
259///
260/// It is not recommended to touch the fields of an `Address`, but to
261/// instead make use of its available accessor methods.
262pub const Address = extern struct {
263 family: u16,
264 port: u16,
265 host: extern union {
266 ipv4: extern struct {
267 address: IPv4,
268 },
269 ipv6: extern struct {
270 flow_info: u32 = 0,
271 address: IPv6,
272 },
273 },
274
275 /// Instantiate a new TCP address with a IPv4 host and port.
276 pub fn initIPv4(host: IPv4, port: u16) Address {
277 return Address{
278 .family = os.AF_INET,
279 .port = mem.nativeToBig(u16, port),
280 .host = .{
281 .ipv4 = .{
282 .address = host,
283 },
284 },
285 };
286 }
287
288 /// Instantiate a new TCP address with a IPv6 host and port.
289 pub fn initIPv6(host: IPv6, port: u16) Address {
290 return Address{
291 .family = os.AF_INET6,
292 .port = mem.nativeToBig(u16, port),
293 .host = .{
294 .ipv6 = .{
295 .address = host,
296 },
297 },
298 };
299 }
300
301 /// Extract the host of the address.
302 pub fn getHost(self: Address) union(enum) { v4: IPv4, v6: IPv6 } {
303 return switch (self.family) {
304 os.AF_INET => .{ .v4 = self.host.ipv4.address },
305 os.AF_INET6 => .{ .v6 = self.host.ipv6.address },
306 else => unreachable,
307 };
308 }
309
310 /// Extract the port of the address.
311 pub fn getPort(self: Address) u16 {
312 return mem.nativeToBig(u16, self.port);
313 }
314
315 /// Set the port of the address.
316 pub fn setPort(self: *Address, port: u16) void {
317 self.port = mem.nativeToBig(u16, port);
318 }
319
320 /// Implements the `std.fmt.format` API.
321 pub fn format(
322 self: Address,
323 comptime layout: []const u8,
324 opts: fmt.FormatOptions,
325 writer: anytype,
326 ) !void {
327 switch (self.getHost()) {
328 .v4 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
329 .v6 => |host| try fmt.format(writer, "{}:{}", .{ host, self.getPort() }),
330 }
331 }
332};
333
334test {
335 testing.refAllDecls(@This());
336}
337
338test "tcp: create non-blocking pair" {
339 const a = try TCP.Listener.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
340 defer a.deinit();
341
342 try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
343 try a.listen(128);
344
345 const binded_address = try a.getLocalAddress();
346
347 const b = try TCP.Client.init(.ip, os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
348 defer b.deinit();
349
350 testing.expectError(error.WouldBlock, b.connect(binded_address));
351 try b.getError();
352
353 const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
354 defer ab.deinit();
355}
356
357test "tcp/client: set read timeout of 1 millisecond on blocking client" {
358 const a = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
359 defer a.deinit();
360
361 try a.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
362 try a.listen(128);
363
364 const binded_address = try a.getLocalAddress();
365
366 const b = try TCP.Client.init(.ip, os.SOCK_CLOEXEC);
367 defer b.deinit();
368
369 try b.connect(binded_address);
370 try b.setReadTimeout(1);
371
372 const ab = try a.accept(os.SOCK_CLOEXEC);
373 defer ab.deinit();
374
375 var buf: [1]u8 = undefined;
376 testing.expectError(error.WouldBlock, b.read(&buf));
377}
378
379test "tcp/listener: bind to unspecified ipv4 address" {
380 const socket = try TCP.Listener.init(.ip, os.SOCK_CLOEXEC);
381 defer socket.deinit();
382
383 try socket.bind(TCP.Address.initIPv4(IPv4.unspecified, 0));
384 try socket.listen(128);
385
386 const address = try socket.getLocalAddress();
387 testing.expect(address.getHost() == .v4);
388}
389
390test "tcp/listener: bind to unspecified ipv6 address" {
391 const socket = try TCP.Listener.init(.ipv6, os.SOCK_CLOEXEC);
392 defer socket.deinit();
393
394 try socket.bind(TCP.Address.initIPv6(IPv6.unspecified, 0));
395 try socket.listen(128);
396
397 const address = try socket.getLocalAddress();
398 testing.expect(address.getHost() == .v6);
399}
lib/std/x/os/Socket.zig+21-103
......@@ -2,19 +2,11 @@ const std = @import("../../std.zig");
22
33const os = std.os;
44const mem = std.mem;
5const net = std.net;
65const time = std.time;
7const builtin = std.builtin;
8const testing = std.testing;
96
7/// A generic socket abstraction.
108const Socket = @This();
119
12/// A socket-address pair.
13pub const Connection = struct {
14 socket: Socket,
15 address: net.Address,
16};
17
1810/// The underlying handle of a socket.
1911fd: os.socket_t,
2012
......@@ -23,19 +15,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
2315 return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
2416}
2517
18/// Enclose a socket abstraction over an existing socket file descriptor.
19pub fn from(fd: os.socket_t) Socket {
20 return Socket{ .fd = fd };
21}
22
2623/// Closes the socket.
2724pub fn deinit(self: Socket) void {
2825 os.closeSocket(self.fd);
2926}
3027
31/// Shutdown either the read side, or write side, or the entirety of a socket.
28/// Shutdown either the read side, write side, or all side of the socket.
3229pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
3330 return os.shutdown(self.fd, how);
3431}
3532
3633/// Binds the socket to an address.
37pub fn bind(self: Socket, address: net.Address) !void {
38 return os.bind(self.fd, &address.any, address.getOsSockLen());
34pub fn bind(self: Socket, comptime Address: type, address: Address) !void {
35 return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));
3936}
4037
4138/// Start listening for incoming connections on the socket.
......@@ -44,22 +41,19 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {
4441}
4542
4643/// Have the socket attempt to the connect to an address.
47pub fn connect(self: Socket, address: net.Address) !void {
48 return os.connect(self.fd, &address.any, address.getOsSockLen());
44pub fn connect(self: Socket, comptime Address: type, address: Address) !void {
45 return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address), @sizeOf(Address));
4946}
5047
5148/// Accept a pending incoming connection queued to the kernel backlog
5249/// of the socket.
53pub fn accept(self: Socket, flags: u32) !Socket.Connection {
54 var address: os.sockaddr = undefined;
55 var address_len: u32 = @sizeOf(os.sockaddr);
50pub fn accept(self: Socket, comptime Connection: type, comptime Address: type, flags: u32) !Connection {
51 var address: Address = undefined;
52 var address_len: u32 = @sizeOf(Address);
5653
57 const fd = try os.accept(self.fd, &address, &address_len, flags);
54 const fd = try os.accept(self.fd, @ptrCast(*os.sockaddr, &address), &address_len, flags);
5855
59 return Connection{
60 .socket = Socket{ .fd = fd },
61 .address = net.Address.initPosix(@alignCast(4, &address)),
62 };
56 return Connection.from(.{ .fd = fd }, address);
6357}
6458
6559/// 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 {
10094}
10195
10296/// Query the address that the socket is locally bounded to.
103pub fn getLocalAddress(self: Socket) !net.Address {
104 var address: os.sockaddr = undefined;
105 var address_len: u32 = @sizeOf(os.sockaddr);
106 try os.getsockname(self.fd, &address, &address_len);
107 return net.Address.initPosix(@alignCast(4, &address));
97pub fn getLocalAddress(self: Socket, comptime Address: type) !Address {
98 var address: Address = undefined;
99 var address_len: u32 = @sizeOf(Address);
100 try os.getsockname(self.fd, @ptrCast(*os.sockaddr, &address), &address_len);
101 return address;
108102}
109103
110104/// Query and return the latest cached error on the socket.
......@@ -164,33 +158,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void {
164158 return error.UnsupportedSocketOption;
165159}
166160
167/// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not support
168/// sockets disabling Nagle's algorithm.
169pub fn setNoDelay(self: Socket, enabled: bool) !void {
170 if (comptime @hasDecl(os, "TCP_NODELAY")) {
171 return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_NODELAY, mem.asBytes(&@as(usize, @boolToInt(enabled))));
172 }
173 return error.UnsupportedSocketOption;
174}
175
176/// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
177/// support TCP Fast Open.
178pub fn setFastOpen(self: Socket, enabled: bool) !void {
179 if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
180 return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
181 }
182 return error.UnsupportedSocketOption;
183}
184
185/// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
186/// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
187pub fn setQuickACK(self: Socket, enabled: bool) !void {
188 if (comptime @hasDecl(os, "TCP_QUICKACK")) {
189 return os.setsockopt(self.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
190 }
191 return error.UnsupportedSocketOption;
192}
193
194161/// Set the write buffer size of the socket.
195162pub fn setWriteBufferSize(self: Socket, size: u32) !void {
196163 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 {
225192
226193 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
227194}
228
229test {
230 testing.refAllDecls(@This());
231}
232
233test "socket/linux: set read timeout of 1 millisecond on blocking socket" {
234 if (builtin.os.tag != .linux) return error.SkipZigTest;
235
236 const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
237 defer a.deinit();
238
239 try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0));
240 try a.listen(128);
241
242 const binded_address = try a.getLocalAddress();
243
244 const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
245 defer b.deinit();
246
247 try b.connect(binded_address);
248 try b.setReadTimeout(1);
249
250 const ab = try a.accept(os.SOCK_CLOEXEC);
251 defer ab.socket.deinit();
252
253 var buf: [1]u8 = undefined;
254 testing.expectError(error.WouldBlock, b.read(&buf));
255}
256
257test "socket/linux: create non-blocking socket pair" {
258 if (builtin.os.tag != .linux) return error.SkipZigTest;
259
260 const a = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
261 defer a.deinit();
262
263 try a.bind(net.Address.initIp4([_]u8{ 0, 0, 0, 0 }, 0));
264 try a.listen(128);
265
266 const binded_address = try a.getLocalAddress();
267
268 const b = try Socket.init(os.AF_INET, os.SOCK_STREAM | os.SOCK_NONBLOCK | os.SOCK_CLOEXEC, os.IPPROTO_TCP);
269 defer b.deinit();
270
271 testing.expectError(error.WouldBlock, b.connect(binded_address));
272 try b.getError();
273
274 const ab = try a.accept(os.SOCK_NONBLOCK | os.SOCK_CLOEXEC);
275 defer ab.socket.deinit();
276}
lib/std/x/os/net.zig created+532
......@@ -0,0 +1,532 @@
1const std = @import("../../std.zig");
2
3const os = std.os;
4const fmt = std.fmt;
5const mem = std.mem;
6const math = std.math;
7const builtin = std.builtin;
8const testing = std.testing;
9
10/// Resolves a network interface name into a scope/zone ID. It returns
11/// an error if either resolution fails, or if the interface name is
12/// too long.
13pub fn resolveScopeID(name: []const u8) !u32 {
14 if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong;
15
16 const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0);
17 defer os.closeSocket(fd);
18
19 var f: os.ifreq = undefined;
20 mem.copy(u8, &f.ifrn.name, name);
21 f.ifrn.name[name.len] = 0;
22
23 try os.ioctl_SIOCGIFINDEX(fd, &f);
24
25 return @bitCast(u32, f.ifru.ivalue);
26}
27
28/// An IPv4 address comprised of 4 bytes.
29pub const IPv4 = extern struct {
30 /// Octets of a IPv4 address designating the local host.
31 pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
32
33 /// The IPv4 address of the local host.
34 pub const localhost: IPv4 = .{ .octets = localhost_octets };
35
36 /// Octets of an unspecified IPv4 address.
37 pub const unspecified_octets = [_]u8{0} ** 4;
38
39 /// An unspecified IPv4 address.
40 pub const unspecified: IPv4 = .{ .octets = unspecified_octets };
41
42 /// Octets of a broadcast IPv4 address.
43 pub const broadcast_octets = [_]u8{255} ** 4;
44
45 /// An IPv4 broadcast address.
46 pub const broadcast: IPv4 = .{ .octets = broadcast_octets };
47
48 /// The prefix octet pattern of a link-local IPv4 address.
49 pub const link_local_prefix = [_]u8{ 169, 254 };
50
51 /// The prefix octet patterns of IPv4 addresses intended for
52 /// documentation.
53 pub const documentation_prefixes = [_][]const u8{
54 &[_]u8{ 192, 0, 2 },
55 &[_]u8{ 198, 51, 100 },
56 &[_]u8{ 203, 0, 113 },
57 };
58
59 octets: [4]u8,
60
61 /// Returns whether or not the two addresses are equal to, less than, or
62 /// greater than each other.
63 pub fn cmp(self: IPv4, other: IPv4) math.Order {
64 return mem.order(u8, &self.octets, &other.octets);
65 }
66
67 /// Returns true if both addresses are semantically equivalent.
68 pub fn eql(self: IPv4, other: IPv4) bool {
69 return mem.eql(u8, &self.octets, &other.octets);
70 }
71
72 /// Returns true if the address is a loopback address.
73 pub fn isLoopback(self: IPv4) bool {
74 return self.octets[0] == 127;
75 }
76
77 /// Returns true if the address is an unspecified IPv4 address.
78 pub fn isUnspecified(self: IPv4) bool {
79 return mem.eql(u8, &self.octets, &unspecified_octets);
80 }
81
82 /// Returns true if the address is a private IPv4 address.
83 pub fn isPrivate(self: IPv4) bool {
84 return self.octets[0] == 10 or
85 (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or
86 (self.octets[0] == 192 and self.octets[1] == 168);
87 }
88
89 /// Returns true if the address is a link-local IPv4 address.
90 pub fn isLinkLocal(self: IPv4) bool {
91 return mem.startsWith(u8, &self.octets, &link_local_prefix);
92 }
93
94 /// Returns true if the address is a multicast IPv4 address.
95 pub fn isMulticast(self: IPv4) bool {
96 return self.octets[0] >= 224 and self.octets[0] <= 239;
97 }
98
99 /// Returns true if the address is a IPv4 broadcast address.
100 pub fn isBroadcast(self: IPv4) bool {
101 return mem.eql(u8, &self.octets, &broadcast_octets);
102 }
103
104 /// Returns true if the address is in a range designated for documentation. Refer
105 /// to IETF RFC 5737 for more details.
106 pub fn isDocumentation(self: IPv4) bool {
107 inline for (documentation_prefixes) |prefix| {
108 if (mem.startsWith(u8, &self.octets, prefix)) {
109 return true;
110 }
111 }
112 return false;
113 }
114
115 /// Implements the `std.fmt.format` API.
116 pub fn format(
117 self: IPv4,
118 comptime layout: []const u8,
119 opts: fmt.FormatOptions,
120 writer: anytype,
121 ) !void {
122 if (comptime layout.len != 0 and layout[0] != 's') {
123 @compileError("Unsupported format specifier for IPv4 type '" ++ layout ++ "'.");
124 }
125
126 try fmt.format(writer, "{}.{}.{}.{}", .{
127 self.octets[0],
128 self.octets[1],
129 self.octets[2],
130 self.octets[3],
131 });
132 }
133
134 /// Set of possible errors that may encountered when parsing an IPv4
135 /// address.
136 pub const ParseError = error{
137 UnexpectedEndOfOctet,
138 TooManyOctets,
139 OctetOverflow,
140 UnexpectedToken,
141 IncompleteAddress,
142 };
143
144 /// Parses an arbitrary IPv4 address.
145 pub fn parse(buf: []const u8) ParseError!IPv4 {
146 var octets: [4]u8 = undefined;
147 var octet: u8 = 0;
148
149 var index: u8 = 0;
150 var saw_any_digits: bool = false;
151
152 for (buf) |c| {
153 switch (c) {
154 '.' => {
155 if (!saw_any_digits) return error.UnexpectedEndOfOctet;
156 if (index == 3) return error.TooManyOctets;
157 octets[index] = octet;
158 index += 1;
159 octet = 0;
160 saw_any_digits = false;
161 },
162 '0'...'9' => {
163 saw_any_digits = true;
164 octet = math.mul(u8, octet, 10) catch return error.OctetOverflow;
165 octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow;
166 },
167 else => return error.UnexpectedToken,
168 }
169 }
170
171 if (index == 3 and saw_any_digits) {
172 octets[index] = octet;
173 return IPv4{ .octets = octets };
174 }
175
176 return error.IncompleteAddress;
177 }
178
179 /// Maps the address to its IPv6 equivalent. In most cases, you would
180 /// want to map the address to its IPv6 equivalent rather than directly
181 /// re-interpreting the address.
182 pub fn mapToIPv6(self: IPv4) IPv6 {
183 var octets: [16]u8 = undefined;
184 mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix);
185 mem.copy(u8, octets[12..], &self.octets);
186 return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
187 }
188
189 /// Directly re-interprets the address to its IPv6 equivalent. In most
190 /// cases, you would want to map the address to its IPv6 equivalent rather
191 /// than directly re-interpreting the address.
192 pub fn toIPv6(self: IPv4) IPv6 {
193 var octets: [16]u8 = undefined;
194 mem.set(u8, octets[0..12], 0);
195 mem.copy(u8, octets[12..], &self.octets);
196 return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
197 }
198};
199
200/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
201/// for a scope ID; cumulatively summing to 20 bytes in total.
202pub const IPv6 = extern struct {
203 /// Octets of a IPv6 address designating the local host.
204 pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};
205
206 /// The IPv6 address of the local host.
207 pub const localhost: IPv6 = .{
208 .octets = localhost_octets,
209 .scope_id = no_scope_id,
210 };
211
212 /// Octets of an unspecified IPv6 address.
213 pub const unspecified_octets = [_]u8{0} ** 16;
214
215 /// An unspecified IPv6 address.
216 pub const unspecified: IPv6 = .{
217 .octets = unspecified_octets,
218 .scope_id = no_scope_id,
219 };
220
221 /// The prefix of a IPv6 address that is mapped to a IPv4 address.
222 pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2;
223
224 /// A marker value used to designate an IPv6 address with no
225 /// associated scope ID.
226 pub const no_scope_id = math.maxInt(u32);
227
228 octets: [16]u8,
229 scope_id: u32,
230
231 /// Returns whether or not the two addresses are equal to, less than, or
232 /// greater than each other.
233 pub fn cmp(self: IPv6, other: IPv6) math.Order {
234 return switch (mem.order(u8, self.octets, other.octets)) {
235 .eq => math.order(self.scope_id, other.scope_id),
236 else => |order| order,
237 };
238 }
239
240 /// Returns true if both addresses are semantically equivalent.
241 pub fn eql(self: IPv6, other: IPv6) bool {
242 return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets);
243 }
244
245 /// Returns true if the address is an unspecified IPv6 address.
246 pub fn isUnspecified(self: IPv6) bool {
247 return mem.eql(u8, &self.octets, &unspecified_octets);
248 }
249
250 /// Returns true if the address is a loopback address.
251 pub fn isLoopback(self: IPv6) bool {
252 return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and
253 mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 });
254 }
255
256 /// Returns true if the address maps to an IPv4 address.
257 pub fn mapsToIPv4(self: IPv6) bool {
258 return mem.startsWith(u8, &self.octets, &v4_mapped_prefix);
259 }
260
261 /// Returns an IPv4 address representative of the address should
262 /// it the address be mapped to an IPv4 address. It returns null
263 /// otherwise.
264 pub fn toIPv4(self: IPv6) ?IPv4 {
265 if (!self.mapsToIPv4()) return null;
266 return IPv4{ .octets = self.octets[12..][0..4].* };
267 }
268
269 /// Returns true if the address is a multicast IPv6 address.
270 pub fn isMulticast(self: IPv6) bool {
271 return self.octets[0] == 0xFF;
272 }
273
274 /// Returns true if the address is a unicast link local IPv6 address.
275 pub fn isLinkLocal(self: IPv6) bool {
276 return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80;
277 }
278
279 /// Returns true if the address is a deprecated unicast site local
280 /// IPv6 address. Refer to IETF RFC 3879 for more details as to
281 /// why they are deprecated.
282 pub fn isSiteLocal(self: IPv6) bool {
283 return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0;
284 }
285
286 /// IPv6 multicast address scopes.
287 pub const Scope = enum(u8) {
288 interface = 1,
289 link = 2,
290 realm = 3,
291 admin = 4,
292 site = 5,
293 organization = 8,
294 global = 14,
295 unknown = 0xFF,
296 };
297
298 /// Returns the multicast scope of the address.
299 pub fn scope(self: IPv6) Scope {
300 if (!self.isMulticast()) return .unknown;
301
302 return switch (self.octets[0] & 0x0F) {
303 1 => .interface,
304 2 => .link,
305 3 => .realm,
306 4 => .admin,
307 5 => .site,
308 8 => .organization,
309 14 => .global,
310 else => .unknown,
311 };
312 }
313
314 /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the
315 /// address lower-cased octets, while specifying 'X' or 'S' formats the
316 /// address using upper-cased ASCII octets.
317 ///
318 /// The default specifier is 'x'.
319 pub fn format(
320 self: IPv6,
321 comptime layout: []const u8,
322 opts: fmt.FormatOptions,
323 writer: anytype,
324 ) !void {
325 comptime const specifier = &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) {
326 'x', 'X' => |specifier| specifier,
327 's' => 'x',
328 'S' => 'X',
329 else => @compileError("Unsupported format specifier for IPv6 type '" ++ layout ++ "'."),
330 }};
331
332 if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) {
333 return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{
334 0xFF,
335 0xFF,
336 self.octets[12],
337 self.octets[13],
338 self.octets[14],
339 self.octets[15],
340 });
341 }
342
343 const zero_span = span: {
344 var i: usize = 0;
345 while (i < self.octets.len) : (i += 2) {
346 if (self.octets[i] == 0 and self.octets[i + 1] == 0) break;
347 } else break :span .{ .from = 0, .to = 0 };
348
349 const from = i;
350
351 while (i < self.octets.len) : (i += 2) {
352 if (self.octets[i] != 0 or self.octets[i + 1] != 0) break;
353 }
354
355 break :span .{ .from = from, .to = i };
356 };
357
358 var i: usize = 0;
359 while (i != 16) : (i += 2) {
360 if (zero_span.from != zero_span.to and i == zero_span.from) {
361 try writer.writeAll("::");
362 } else if (i >= zero_span.from and i < zero_span.to) {} else {
363 if (i != 0 and i != zero_span.to) try writer.writeAll(":");
364
365 const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1];
366 try fmt.formatIntValue(val, specifier, .{}, writer);
367 }
368 }
369
370 if (self.scope_id != no_scope_id and self.scope_id != 0) {
371 try fmt.format(writer, "%{d}", .{self.scope_id});
372 }
373 }
374
375 /// Set of possible errors that may encountered when parsing an IPv6
376 /// address.
377 pub const ParseError = error{
378 MalformedV4Mapping,
379 BadScopeID,
380 } || IPv4.ParseError;
381
382 /// Parses an arbitrary IPv6 address, including link-local addresses.
383 pub fn parse(buf: []const u8) ParseError!IPv6 {
384 if (mem.lastIndexOfScalar(u8, buf, '%')) |index| {
385 const ip_slice = buf[0..index];
386 const scope_id_slice = buf[index + 1 ..];
387
388 if (scope_id_slice.len == 0) return error.BadScopeID;
389
390 const scope_id: u32 = switch (scope_id_slice[0]) {
391 '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10),
392 else => resolveScopeID(scope_id_slice),
393 } catch return error.BadScopeID;
394
395 return parseWithScopeID(ip_slice, scope_id);
396 }
397
398 return parseWithScopeID(buf, no_scope_id);
399 }
400
401 /// Parses an IPv6 address with a pre-specified scope ID. Presumes
402 /// that the address is not a link-local address.
403 pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 {
404 var octets: [16]u8 = undefined;
405 var octet: u16 = 0;
406 var tail: [16]u8 = undefined;
407
408 var out: []u8 = &octets;
409 var index: u8 = 0;
410
411 var saw_any_digits: bool = false;
412 var abbrv: bool = false;
413
414 for (buf) |c, i| {
415 switch (c) {
416 ':' => {
417 if (!saw_any_digits) {
418 if (abbrv) return error.UnexpectedToken;
419 if (i != 0) abbrv = true;
420 mem.set(u8, out[index..], 0);
421 out = &tail;
422 index = 0;
423 continue;
424 }
425 if (index == 14) return error.TooManyOctets;
426
427 out[index] = @truncate(u8, octet >> 8);
428 index += 1;
429 out[index] = @truncate(u8, octet);
430 index += 1;
431
432 octet = 0;
433 saw_any_digits = false;
434 },
435 '.' => {
436 if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) {
437 return error.MalformedV4Mapping;
438 }
439 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
440 const v4 = try IPv4.parse(buf[start_index..]);
441 octets[10] = 0xFF;
442 octets[11] = 0xFF;
443 mem.copy(u8, octets[12..], &v4.octets);
444
445 return IPv6{ .octets = octets, .scope_id = scope_id };
446 },
447 else => {
448 saw_any_digits = true;
449 const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken;
450 octet = math.mul(u16, octet, 16) catch return error.OctetOverflow;
451 octet = math.add(u16, octet, digit) catch return error.OctetOverflow;
452 },
453 }
454 }
455
456 if (!saw_any_digits and !abbrv) {
457 return error.IncompleteAddress;
458 }
459
460 if (index == 14) {
461 out[14] = @truncate(u8, octet >> 8);
462 out[15] = @truncate(u8, octet);
463 } else {
464 out[index] = @truncate(u8, octet >> 8);
465 index += 1;
466 out[index] = @truncate(u8, octet);
467 index += 1;
468 mem.copy(u8, octets[16 - index ..], out[0..index]);
469 }
470
471 return IPv6{ .octets = octets, .scope_id = scope_id };
472 }
473};
474
475test {
476 testing.refAllDecls(@This());
477}
478
479test "ip: convert to and from ipv6" {
480 try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()});
481 testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4());
482
483 try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()});
484 testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4());
485
486 testing.expect(IPv4.localhost.toIPv6().toIPv4() == null);
487 try testing.expectFmt("127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6().toIPv4()});
488}
489
490test "ipv4: parse & format" {
491 const cases = [_][]const u8{
492 "0.0.0.0",
493 "255.255.255.255",
494 "1.2.3.4",
495 "123.255.0.91",
496 "127.0.0.1",
497 };
498
499 for (cases) |case| {
500 try testing.expectFmt(case, "{}", .{try IPv4.parse(case)});
501 }
502}
503
504test "ipv6: parse & format" {
505 const inputs = [_][]const u8{
506 "FF01:0:0:0:0:0:0:FB",
507 "FF01::Fb",
508 "::1",
509 "::",
510 "2001:db8::",
511 "::1234:5678",
512 "2001:db8::1234:5678",
513 "::ffff:123.5.123.5",
514 "FF01::FB%lo",
515 };
516
517 const outputs = [_][]const u8{
518 "ff01::fb",
519 "ff01::fb",
520 "::1",
521 "::",
522 "2001:db8::",
523 "::1234:5678",
524 "2001:db8::1234:5678",
525 "::ffff:123.5.123.5",
526 "ff01::fb%1",
527 };
528
529 for (inputs) |input, i| {
530 try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)});
531 }
532}
lib/std/x/os/os.zig deleted-9
......@@ -1,9 +0,0 @@
1const std = @import("../../std.zig");
2
3const testing = std.testing;
4
5pub const Socket = @import("Socket.zig");
6
7test {
8 testing.refAllDecls(@This());
9}