authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2021-05-03 17:38:10-04:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2021-05-03 17:38:10-04:00
log0e3930bb5a92a60de6faad4c348f062a7a420eca
tree7a750af5bb8907f281945e794c7bdce3bd8f561e
parent5622f9382c4f54d6d2cc267590dda5352718ef60
parent96fe49ef620cfd8423e878262420f58d5983d58c
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #8649 from lithdew/master

x/os, x/net: layout tcp, ipv4/ipv6, and socket abstractions

13 files changed, 1163 insertions(+), 115 deletions(-)

lib/std/builtin.zig+1-1
...@@ -165,7 +165,7 @@ pub const CallingConvention = enum {...@@ -165,7 +165,7 @@ pub const CallingConvention = enum {
165 APCS,165 APCS,
166 AAPCS,166 AAPCS,
167 AAPCSVFP,167 AAPCSVFP,
168 SysV168 SysV,
169};169};
170170
171/// This data structure is used by the Zig language code generation and171/// This data structure is used by the Zig language code generation and
lib/std/compress/deflate.zig+2-4
...@@ -662,14 +662,12 @@ test "lengths overflow" {...@@ -662,14 +662,12 @@ test "lengths overflow" {
662 // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316)662 // malformed final dynamic block, tries to write 321 code lengths (MAXCODES is 316)
663 // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6663 // f dy hlit hdist hclen 16 17 18 0 (18) x138 (18) x138 (18) x39 (16) x6
664 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11664 // 1 10 11101 11101 0000 010 010 010 010 (11) 1111111 (11) 1111111 (11) 0011100 (01) 11
665 const stream = [_]u8{665 const stream = [_]u8{ 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110 };
666 0b11101101, 0b00011101, 0b00100100, 0b11101001, 0b11111111, 0b11111111, 0b00111001, 0b00001110
667 };
668666
669 const reader = std.io.fixedBufferStream(&stream).reader();667 const reader = std.io.fixedBufferStream(&stream).reader();
670 var window: [0x8000]u8 = undefined;668 var window: [0x8000]u8 = undefined;
671 var inflate = inflateStream(reader, &window);669 var inflate = inflateStream(reader, &window);
672670
673 var buf: [1]u8 = undefined;671 var buf: [1]u8 = undefined;
674 std.testing.expectError(error.InvalidLength, inflate.read(&buf)); 672 std.testing.expectError(error.InvalidLength, inflate.read(&buf));
675}673}
lib/std/os/bits/darwin.zig+7
...@@ -832,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006;...@@ -832,6 +832,13 @@ pub const SO_RCVTIMEO = 0x1006;
832pub const SO_ERROR = 0x1007;832pub const SO_ERROR = 0x1007;
833pub const SO_TYPE = 0x1008;833pub 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
835fn wstatus(x: u32) u32 {842fn wstatus(x: u32) u32 {
836 return x & 0o177;843 return x & 0o177;
837}844}
lib/std/os/bits/linux.zig+35-4
...@@ -32,6 +32,7 @@ pub usingnamespace @import("linux/prctl.zig");...@@ -32,6 +32,7 @@ pub usingnamespace @import("linux/prctl.zig");
32pub usingnamespace @import("linux/securebits.zig");32pub usingnamespace @import("linux/securebits.zig");
3333
34const is_mips = builtin.arch.isMIPS();34const is_mips = builtin.arch.isMIPS();
35const is_ppc = builtin.arch.isPPC();
35const is_ppc64 = builtin.arch.isPPC64();36const is_ppc64 = builtin.arch.isPPC64();
36const is_sparc = builtin.arch.isSPARC();37const is_sparc = builtin.arch.isSPARC();
3738
...@@ -458,7 +459,39 @@ pub const AF_QIPCRTR = PF_QIPCRTR;...@@ -458,7 +459,39 @@ pub const AF_QIPCRTR = PF_QIPCRTR;
458pub const AF_SMC = PF_SMC;459pub const AF_SMC = PF_SMC;
459pub const AF_MAX = PF_MAX;460pub const AF_MAX = PF_MAX;
460461
461pub usingnamespace if (!is_mips)462pub usingnamespace if (is_mips)
463 struct {}
464else if (is_ppc or is_ppc64)
465 struct {
466 pub const SO_DEBUG = 1;
467 pub const SO_REUSEADDR = 2;
468 pub const SO_TYPE = 3;
469 pub const SO_ERROR = 4;
470 pub const SO_DONTROUTE = 5;
471 pub const SO_BROADCAST = 6;
472 pub const SO_SNDBUF = 7;
473 pub const SO_RCVBUF = 8;
474 pub const SO_KEEPALIVE = 9;
475 pub const SO_OOBINLINE = 10;
476 pub const SO_NO_CHECK = 11;
477 pub const SO_PRIORITY = 12;
478 pub const SO_LINGER = 13;
479 pub const SO_BSDCOMPAT = 14;
480 pub const SO_REUSEPORT = 15;
481 pub const SO_RCVLOWAT = 16;
482 pub const SO_SNDLOWAT = 17;
483 pub const SO_RCVTIMEO = 18;
484 pub const SO_SNDTIMEO = 19;
485 pub const SO_PASSCRED = 20;
486 pub const SO_PEERCRED = 21;
487 pub const SO_ACCEPTCONN = 30;
488 pub const SO_PEERSEC = 31;
489 pub const SO_SNDBUFFORCE = 32;
490 pub const SO_RCVBUFFORCE = 33;
491 pub const SO_PROTOCOL = 38;
492 pub const SO_DOMAIN = 39;
493 }
494else
462 struct {495 struct {
463 pub const SO_DEBUG = 1;496 pub const SO_DEBUG = 1;
464 pub const SO_REUSEADDR = 2;497 pub const SO_REUSEADDR = 2;
...@@ -487,9 +520,7 @@ pub usingnamespace if (!is_mips)...@@ -487,9 +520,7 @@ pub usingnamespace if (!is_mips)
487 pub const SO_RCVBUFFORCE = 33;520 pub const SO_RCVBUFFORCE = 33;
488 pub const SO_PROTOCOL = 38;521 pub const SO_PROTOCOL = 38;
489 pub const SO_DOMAIN = 39;522 pub const SO_DOMAIN = 39;
490 }523 };
491else
492 struct {};
493524
494pub const SO_SECURITY_AUTHENTICATION = 22;525pub const SO_SECURITY_AUTHENTICATION = 22;
495pub const SO_SECURITY_ENCRYPTION_TRANSPORT = 23;526pub const SO_SECURITY_ENCRYPTION_TRANSPORT = 23;
lib/std/os/bits/linux/arm64.zig+1
...@@ -9,6 +9,7 @@...@@ -9,6 +9,7 @@
9const std = @import("../../../std.zig");9const std = @import("../../../std.zig");
10const linux = std.os.linux;10const linux = std.os.linux;
11const socklen_t = linux.socklen_t;11const socklen_t = linux.socklen_t;
12const sockaddr = linux.sockaddr;
12const iovec = linux.iovec;13const iovec = linux.iovec;
13const iovec_const = linux.iovec_const;14const iovec_const = linux.iovec_const;
14const uid_t = linux.uid_t;15const uid_t = linux.uid_t;
lib/std/os/bits/linux/riscv64.zig+5
...@@ -376,6 +376,11 @@ pub const timespec = extern struct {...@@ -376,6 +376,11 @@ pub const timespec = extern struct {
376 tv_nsec: isize,376 tv_nsec: isize,
377};377};
378378
379pub const timeval = extern struct {
380 tv_sec: time_t,
381 tv_usec: i64,
382};
383
379pub const Flock = extern struct {384pub const Flock = extern struct {
380 l_type: i16,385 l_type: i16,
381 l_whence: i16,386 l_whence: i16,
lib/std/os/bits/windows.zig+5
...@@ -22,6 +22,11 @@ pub const timespec = extern struct {...@@ -22,6 +22,11 @@ pub const timespec = extern struct {
22 tv_nsec: c_long,22 tv_nsec: c_long,
23};23};
2424
25pub const timeval = extern struct {
26 tv_sec: c_long,
27 tv_usec: c_long,
28};
29
25pub const sig_atomic_t = c_int;30pub const sig_atomic_t = c_int;
2631
27/// maximum signal number + 132/// maximum signal number + 1
lib/std/x.zig+23-1
...@@ -1 +1,23 @@...@@ -1 +1,23 @@
1pub const os = @import("x/os/os.zig");1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("std.zig");
8
9pub const os = struct {
10 pub const Socket = @import("x/os/Socket.zig");
11 pub usingnamespace @import("x/os/net.zig");
12};
13
14pub const net = struct {
15 pub const ip = @import("x/net/ip.zig");
16 pub const tcp = @import("x/net/tcp.zig");
17};
18
19test {
20 inline for (.{ os, net }) |module| {
21 std.testing.refAllDecls(module);
22 }
23}
lib/std/x/net/ip.zig created+59
...@@ -0,0 +1,59 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("../../std.zig");
8
9const IPv4 = std.x.os.IPv4;
10const IPv6 = std.x.os.IPv6;
11const Socket = std.x.os.Socket;
12
13/// A generic IP abstraction.
14const ip = @This();
15
16/// A union of all eligible types of IP addresses.
17pub const Address = union(enum) {
18 ipv4: IPv4.Address,
19 ipv6: IPv6.Address,
20
21 /// Instantiate a new address with a IPv4 host and port.
22 pub fn initIPv4(host: IPv4, port: u16) Address {
23 return .{ .ipv4 = .{ .host = host, .port = port } };
24 }
25
26 /// Instantiate a new address with a IPv6 host and port.
27 pub fn initIPv6(host: IPv6, port: u16) Address {
28 return .{ .ipv6 = .{ .host = host, .port = port } };
29 }
30
31 /// Re-interpret a generic socket address into an IP address.
32 pub fn from(address: Socket.Address) ip.Address {
33 return switch (address) {
34 .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
35 .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
36 };
37 }
38
39 /// Re-interpret an IP address into a generic socket address.
40 pub fn into(self: ip.Address) Socket.Address {
41 return switch (self) {
42 .ipv4 => |ipv4_address| .{ .ipv4 = ipv4_address },
43 .ipv6 => |ipv6_address| .{ .ipv6 = ipv6_address },
44 };
45 }
46
47 /// Implements the `std.fmt.format` API.
48 pub fn format(
49 self: ip.Address,
50 comptime layout: []const u8,
51 opts: fmt.FormatOptions,
52 writer: anytype,
53 ) !void {
54 switch (self) {
55 .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
56 .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
57 }
58 }
59};
lib/std/x/net/tcp.zig created+344
...@@ -0,0 +1,344 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("../../std.zig");
8
9const os = std.os;
10const ip = std.x.net.ip;
11
12const fmt = std.fmt;
13const mem = std.mem;
14const builtin = std.builtin;
15const testing = std.testing;
16
17const IPv4 = std.x.os.IPv4;
18const IPv6 = std.x.os.IPv6;
19const Socket = std.x.os.Socket;
20
21/// A generic TCP socket abstraction.
22const tcp = @This();
23
24/// A TCP client-address pair.
25pub const Connection = struct {
26 client: tcp.Client,
27 address: ip.Address,
28
29 /// Enclose a TCP client and address into a client-address pair.
30 pub fn from(conn: Socket.Connection) tcp.Connection {
31 return .{
32 .client = tcp.Client.from(conn.socket),
33 .address = ip.Address.from(conn.address),
34 };
35 }
36
37 /// Unravel a TCP client-address pair into a socket-address pair.
38 pub fn into(self: tcp.Connection) Socket.Connection {
39 return .{
40 .socket = self.client.socket,
41 .address = self.address.into(),
42 };
43 }
44
45 /// Closes the underlying client of the connection.
46 pub fn deinit(self: tcp.Connection) void {
47 self.client.deinit();
48 }
49};
50
51/// Possible domains that a TCP client/listener may operate over.
52pub const Domain = extern enum(u16) {
53 ip = os.AF_INET,
54 ipv6 = os.AF_INET6,
55};
56
57/// A TCP client.
58pub const Client = struct {
59 socket: Socket,
60
61 /// Opens a new client.
62 pub fn init(domain: tcp.Domain, flags: u32) !Client {
63 return Client{
64 .socket = try Socket.init(
65 @enumToInt(domain),
66 os.SOCK_STREAM | flags,
67 os.IPPROTO_TCP,
68 ),
69 };
70 }
71
72 /// Enclose a TCP client over an existing socket.
73 pub fn from(socket: Socket) Client {
74 return Client{ .socket = socket };
75 }
76
77 /// Closes the client.
78 pub fn deinit(self: Client) void {
79 self.socket.deinit();
80 }
81
82 /// Shutdown either the read side, write side, or all sides of the client's underlying socket.
83 pub fn shutdown(self: Client, how: os.ShutdownHow) !void {
84 return self.socket.shutdown(how);
85 }
86
87 /// Have the client attempt to the connect to an address.
88 pub fn connect(self: Client, address: ip.Address) !void {
89 return self.socket.connect(address.into());
90 }
91
92 /// Read data from the socket into the buffer provided. It returns the
93 /// number of bytes read into the buffer provided.
94 pub fn read(self: Client, buf: []u8) !usize {
95 return self.socket.read(buf);
96 }
97
98 /// Read data from the socket into the buffer provided with a set of flags
99 /// specified. It returns the number of bytes read into the buffer provided.
100 pub fn recv(self: Client, buf: []u8, flags: u32) !usize {
101 return self.socket.recv(buf, flags);
102 }
103
104 /// Write a buffer of data provided to the socket. It returns the number
105 /// of bytes that are written to the socket.
106 pub fn write(self: Client, buf: []const u8) !usize {
107 return self.socket.write(buf);
108 }
109
110 /// Writes multiple I/O vectors to the socket. It returns the number
111 /// of bytes that are written to the socket.
112 pub fn writev(self: Client, buffers: []const os.iovec_const) !usize {
113 return self.socket.writev(buffers);
114 }
115
116 /// Write a buffer of data provided to the socket with a set of flags specified.
117 /// It returns the number of bytes that are written to the socket.
118 pub fn send(self: Client, buf: []const u8, flags: u32) !usize {
119 return self.socket.send(buf, flags);
120 }
121
122 /// Writes multiple I/O vectors with a prepended message header to the socket
123 /// with a set of flags specified. It returns the number of bytes that are
124 /// written to the socket.
125 pub fn sendmsg(self: Client, msg: os.msghdr_const, flags: u32) !usize {
126 return self.socket.sendmsg(msg, flags);
127 }
128
129 /// Query and return the latest cached error on the client's underlying socket.
130 pub fn getError(self: Client) !void {
131 return self.socket.getError();
132 }
133
134 /// Query the read buffer size of the client's underlying socket.
135 pub fn getReadBufferSize(self: Client) !u32 {
136 return self.socket.getReadBufferSize();
137 }
138
139 /// Query the write buffer size of the client's underlying socket.
140 pub fn getWriteBufferSize(self: Client) !u32 {
141 return self.socket.getWriteBufferSize();
142 }
143
144 /// Query the address that the client's socket is locally bounded to.
145 pub fn getLocalAddress(self: Client) !ip.Address {
146 return ip.Address.from(try self.socket.getLocalAddress());
147 }
148
149 /// Disable Nagle's algorithm on a TCP socket. It returns `error.UnsupportedSocketOption` if
150 /// the host does not support sockets disabling Nagle's algorithm.
151 pub fn setNoDelay(self: Client, enabled: bool) !void {
152 if (comptime @hasDecl(os, "TCP_NODELAY")) {
153 const bytes = mem.asBytes(&@as(usize, @boolToInt(enabled)));
154 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_NODELAY, bytes);
155 }
156 return error.UnsupportedSocketOption;
157 }
158
159 /// Set the write buffer size of the socket.
160 pub fn setWriteBufferSize(self: Client, size: u32) !void {
161 return self.socket.setWriteBufferSize(size);
162 }
163
164 /// Set the read buffer size of the socket.
165 pub fn setReadBufferSize(self: Client, size: u32) !void {
166 return self.socket.setReadBufferSize(size);
167 }
168
169 /// Set a timeout on the socket that is to occur if no messages are successfully written
170 /// to its bound destination after a specified number of milliseconds. A subsequent write
171 /// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
172 pub fn setWriteTimeout(self: Client, milliseconds: usize) !void {
173 return self.socket.setWriteTimeout(milliseconds);
174 }
175
176 /// Set a timeout on the socket that is to occur if no messages are successfully read
177 /// from its bound destination after a specified number of milliseconds. A subsequent
178 /// read from the socket will thereafter return `error.WouldBlock` should the timeout be
179 /// exceeded.
180 pub fn setReadTimeout(self: Client, milliseconds: usize) !void {
181 return self.socket.setReadTimeout(milliseconds);
182 }
183};
184
185/// A TCP listener.
186pub const Listener = struct {
187 socket: Socket,
188
189 /// Opens a new listener.
190 pub fn init(domain: tcp.Domain, flags: u32) !Listener {
191 return Listener{
192 .socket = try Socket.init(
193 @enumToInt(domain),
194 os.SOCK_STREAM | flags,
195 os.IPPROTO_TCP,
196 ),
197 };
198 }
199
200 /// Closes the listener.
201 pub fn deinit(self: Listener) void {
202 self.socket.deinit();
203 }
204
205 /// Shuts down the underlying listener's socket. The next subsequent call, or
206 /// a current pending call to accept() after shutdown is called will return
207 /// an error.
208 pub fn shutdown(self: Listener) !void {
209 return self.socket.shutdown(.recv);
210 }
211
212 /// Binds the listener's socket to an address.
213 pub fn bind(self: Listener, address: ip.Address) !void {
214 return self.socket.bind(address.into());
215 }
216
217 /// Start listening for incoming connections.
218 pub fn listen(self: Listener, max_backlog_size: u31) !void {
219 return self.socket.listen(max_backlog_size);
220 }
221
222 /// Accept a pending incoming connection queued to the kernel backlog
223 /// of the listener's socket.
224 pub fn accept(self: Listener, flags: u32) !tcp.Connection {
225 return tcp.Connection.from(try self.socket.accept(flags));
226 }
227
228 /// Query and return the latest cached error on the listener's underlying socket.
229 pub fn getError(self: Client) !void {
230 return self.socket.getError();
231 }
232
233 /// Query the address that the listener's socket is locally bounded to.
234 pub fn getLocalAddress(self: Listener) !ip.Address {
235 return ip.Address.from(try self.socket.getLocalAddress());
236 }
237
238 /// Allow multiple sockets on the same host to listen on the same address. It returns `error.UnsupportedSocketOption` if
239 /// the host does not support sockets listening the same address.
240 pub fn setReuseAddress(self: Listener, enabled: bool) !void {
241 return self.socket.setReuseAddress(enabled);
242 }
243
244 /// Allow multiple sockets on the same host to listen on the same port. It returns `error.UnsupportedSocketOption` if
245 /// the host does not supports sockets listening on the same port.
246 pub fn setReusePort(self: Listener, enabled: bool) !void {
247 return self.socket.setReusePort(enabled);
248 }
249
250 /// Enables TCP Fast Open (RFC 7413) on a TCP socket. It returns `error.UnsupportedSocketOption` if the host does not
251 /// support TCP Fast Open.
252 pub fn setFastOpen(self: Listener, enabled: bool) !void {
253 if (comptime @hasDecl(os, "TCP_FASTOPEN")) {
254 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_FASTOPEN, mem.asBytes(&@as(usize, @boolToInt(enabled))));
255 }
256 return error.UnsupportedSocketOption;
257 }
258
259 /// Enables TCP Quick ACK on a TCP socket to immediately send rather than delay ACKs when necessary. It returns
260 /// `error.UnsupportedSocketOption` if the host does not support TCP Quick ACK.
261 pub fn setQuickACK(self: Listener, enabled: bool) !void {
262 if (comptime @hasDecl(os, "TCP_QUICKACK")) {
263 return os.setsockopt(self.socket.fd, os.IPPROTO_TCP, os.TCP_QUICKACK, mem.asBytes(&@as(usize, @boolToInt(enabled))));
264 }
265 return error.UnsupportedSocketOption;
266 }
267
268 /// Set a timeout on the listener that is to occur if no new incoming connections come in
269 /// after a specified number of milliseconds. A subsequent accept call to the listener
270 /// will thereafter return `error.WouldBlock` should the timeout be exceeded.
271 pub fn setAcceptTimeout(self: Listener, milliseconds: usize) !void {
272 return self.socket.setReadTimeout(milliseconds);
273 }
274};
275
276test "tcp: create client/listener pair" {
277 if (builtin.os.tag == .wasi) return error.SkipZigTest;
278
279 const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
280 defer listener.deinit();
281
282 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
283 try listener.listen(128);
284
285 const binded_address = try listener.getLocalAddress();
286
287 const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
288 defer client.deinit();
289
290 try client.connect(binded_address);
291
292 const conn = try listener.accept(os.SOCK_CLOEXEC);
293 defer conn.deinit();
294}
295
296test "tcp/client: set read timeout of 1 millisecond on blocking client" {
297 if (builtin.os.tag == .wasi) return error.SkipZigTest;
298
299 const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
300 defer listener.deinit();
301
302 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
303 try listener.listen(128);
304
305 const binded_address = try listener.getLocalAddress();
306
307 const client = try tcp.Client.init(.ip, os.SOCK_CLOEXEC);
308 defer client.deinit();
309
310 try client.connect(binded_address);
311 try client.setReadTimeout(1);
312
313 const conn = try listener.accept(os.SOCK_CLOEXEC);
314 defer conn.deinit();
315
316 var buf: [1]u8 = undefined;
317 testing.expectError(error.WouldBlock, client.read(&buf));
318}
319
320test "tcp/listener: bind to unspecified ipv4 address" {
321 if (builtin.os.tag == .wasi) return error.SkipZigTest;
322
323 const listener = try tcp.Listener.init(.ip, os.SOCK_CLOEXEC);
324 defer listener.deinit();
325
326 try listener.bind(ip.Address.initIPv4(IPv4.unspecified, 0));
327 try listener.listen(128);
328
329 const address = try listener.getLocalAddress();
330 testing.expect(address == .ipv4);
331}
332
333test "tcp/listener: bind to unspecified ipv6 address" {
334 if (builtin.os.tag == .wasi) return error.SkipZigTest;
335
336 const listener = try tcp.Listener.init(.ipv6, os.SOCK_CLOEXEC);
337 defer listener.deinit();
338
339 try listener.bind(ip.Address.initIPv6(IPv6.unspecified, 0));
340 try listener.listen(128);
341
342 const address = try listener.getLocalAddress();
343 testing.expect(address == .ipv6);
344}
lib/std/x/os/Socket.zig+114-96
...@@ -1,18 +1,109 @@...@@ -1,18 +1,109 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
1const std = @import("../../std.zig");7const std = @import("../../std.zig");
8const net = @import("net.zig");
29
3const os = std.os;10const os = std.os;
4const mem = std.mem;11const mem = std.mem;
5const net = std.net;
6const time = std.time;12const time = std.time;
7const builtin = std.builtin;
8const testing = std.testing;
913
14/// A generic socket abstraction.
10const Socket = @This();15const Socket = @This();
1116
12/// A socket-address pair.17/// A socket-address pair.
13pub const Connection = struct {18pub const Connection = struct {
14 socket: Socket,19 socket: Socket,
15 address: net.Address,20 address: Socket.Address,
21
22 /// Enclose a socket and address into a socket-address pair.
23 pub fn from(socket: Socket, address: Socket.Address) Socket.Connection {
24 return .{ .socket = socket, .address = address };
25 }
26};
27
28/// A generic socket address abstraction. It is safe to directly access and modify
29/// the fields of a `Socket.Address`.
30pub const Address = union(enum) {
31 ipv4: net.IPv4.Address,
32 ipv6: net.IPv6.Address,
33
34 /// Instantiate a new address with a IPv4 host and port.
35 pub fn initIPv4(host: net.IPv4, port: u16) Socket.Address {
36 return .{ .ipv4 = .{ .host = host, .port = port } };
37 }
38
39 /// Instantiate a new address with a IPv6 host and port.
40 pub fn initIPv6(host: net.IPv6, port: u16) Socket.Address {
41 return .{ .ipv6 = .{ .host = host, .port = port } };
42 }
43
44 /// Parses a `sockaddr` into a generic socket address.
45 pub fn fromNative(address: *align(4) const os.sockaddr) Socket.Address {
46 switch (address.family) {
47 os.AF_INET => {
48 const info = @ptrCast(*const os.sockaddr_in, address);
49 const host = net.IPv4{ .octets = @bitCast([4]u8, info.addr) };
50 const port = mem.bigToNative(u16, info.port);
51 return Socket.Address.initIPv4(host, port);
52 },
53 os.AF_INET6 => {
54 const info = @ptrCast(*const os.sockaddr_in6, address);
55 const host = net.IPv6{ .octets = info.addr, .scope_id = info.scope_id };
56 const port = mem.bigToNative(u16, info.port);
57 return Socket.Address.initIPv6(host, port);
58 },
59 else => unreachable,
60 }
61 }
62
63 /// Encodes a generic socket address into an extern union that may be reliably
64 /// casted into a `sockaddr` which may be passed into socket syscalls.
65 pub fn toNative(self: Socket.Address) extern union {
66 ipv4: os.sockaddr_in,
67 ipv6: os.sockaddr_in6,
68 } {
69 return switch (self) {
70 .ipv4 => |address| .{
71 .ipv4 = .{
72 .addr = @bitCast(u32, address.host.octets),
73 .port = mem.nativeToBig(u16, address.port),
74 },
75 },
76 .ipv6 => |address| .{
77 .ipv6 = .{
78 .addr = address.host.octets,
79 .port = mem.nativeToBig(u16, address.port),
80 .scope_id = address.host.scope_id,
81 .flowinfo = 0,
82 },
83 },
84 };
85 }
86
87 /// Returns the number of bytes that make up the `sockaddr` equivalent to the address.
88 pub fn getNativeSize(self: Socket.Address) u32 {
89 return switch (self) {
90 .ipv4 => @sizeOf(os.sockaddr_in),
91 .ipv6 => @sizeOf(os.sockaddr_in6),
92 };
93 }
94
95 /// Implements the `std.fmt.format` API.
96 pub fn format(
97 self: Socket.Address,
98 comptime layout: []const u8,
99 opts: fmt.FormatOptions,
100 writer: anytype,
101 ) !void {
102 switch (self) {
103 .ipv4 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
104 .ipv6 => |address| try fmt.format(writer, "{}:{}", .{ address.host, address.port }),
105 }
106 }
16};107};
17108
18/// The underlying handle of a socket.109/// The underlying handle of a socket.
...@@ -23,19 +114,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {...@@ -23,19 +114,24 @@ pub fn init(domain: u32, socket_type: u32, protocol: u32) !Socket {
23 return Socket{ .fd = try os.socket(domain, socket_type, protocol) };114 return Socket{ .fd = try os.socket(domain, socket_type, protocol) };
24}115}
25116
117/// Enclose a socket abstraction over an existing socket file descriptor.
118pub fn from(fd: os.socket_t) Socket {
119 return Socket{ .fd = fd };
120}
121
26/// Closes the socket.122/// Closes the socket.
27pub fn deinit(self: Socket) void {123pub fn deinit(self: Socket) void {
28 os.closeSocket(self.fd);124 os.closeSocket(self.fd);
29}125}
30126
31/// Shutdown either the read side, or write side, or the entirety of a socket.127/// Shutdown either the read side, write side, or all side of the socket.
32pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {128pub fn shutdown(self: Socket, how: os.ShutdownHow) !void {
33 return os.shutdown(self.fd, how);129 return os.shutdown(self.fd, how);
34}130}
35131
36/// Binds the socket to an address.132/// Binds the socket to an address.
37pub fn bind(self: Socket, address: net.Address) !void {133pub fn bind(self: Socket, address: Socket.Address) !void {
38 return os.bind(self.fd, &address.any, address.getOsSockLen());134 return os.bind(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
39}135}
40136
41/// Start listening for incoming connections on the socket.137/// Start listening for incoming connections on the socket.
...@@ -44,8 +140,8 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {...@@ -44,8 +140,8 @@ pub fn listen(self: Socket, max_backlog_size: u31) !void {
44}140}
45141
46/// Have the socket attempt to the connect to an address.142/// Have the socket attempt to the connect to an address.
47pub fn connect(self: Socket, address: net.Address) !void {143pub fn connect(self: Socket, address: Socket.Address) !void {
48 return os.connect(self.fd, &address.any, address.getOsSockLen());144 return os.connect(self.fd, @ptrCast(*const os.sockaddr, &address.toNative()), address.getNativeSize());
49}145}
50146
51/// Accept a pending incoming connection queued to the kernel backlog147/// Accept a pending incoming connection queued to the kernel backlog
...@@ -54,12 +150,10 @@ pub fn accept(self: Socket, flags: u32) !Socket.Connection {...@@ -54,12 +150,10 @@ pub fn accept(self: Socket, flags: u32) !Socket.Connection {
54 var address: os.sockaddr = undefined;150 var address: os.sockaddr = undefined;
55 var address_len: u32 = @sizeOf(os.sockaddr);151 var address_len: u32 = @sizeOf(os.sockaddr);
56152
57 const fd = try os.accept(self.fd, &address, &address_len, flags);153 const socket = Socket{ .fd = try os.accept(self.fd, &address, &address_len, flags) };
154 const socket_address = Socket.Address.fromNative(@alignCast(4, &address));
58155
59 return Connection{156 return Socket.Connection.from(socket, socket_address);
60 .socket = Socket{ .fd = fd },
61 .address = net.Address.initPosix(@alignCast(4, &address)),
62 };
63}157}
64158
65/// Read data from the socket into the buffer provided. It returns the159/// Read data from the socket into the buffer provided. It returns the
...@@ -100,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {...@@ -100,11 +194,11 @@ pub fn sendmsg(self: Socket, msg: os.msghdr_const, flags: u32) !usize {
100}194}
101195
102/// Query the address that the socket is locally bounded to.196/// Query the address that the socket is locally bounded to.
103pub fn getLocalAddress(self: Socket) !net.Address {197pub fn getLocalAddress(self: Socket) !Socket.Address {
104 var address: os.sockaddr = undefined;198 var address: os.sockaddr = undefined;
105 var address_len: u32 = @sizeOf(os.sockaddr);199 var address_len: u32 = @sizeOf(os.sockaddr);
106 try os.getsockname(self.fd, &address, &address_len);200 try os.getsockname(self.fd, &address, &address_len);
107 return net.Address.initPosix(@alignCast(4, &address));201 return Socket.Address.fromNative(@alignCast(4, &address));
108}202}
109203
110/// Query and return the latest cached error on the socket.204/// Query and return the latest cached error on the socket.
...@@ -164,33 +258,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void {...@@ -164,33 +258,6 @@ pub fn setReusePort(self: Socket, enabled: bool) !void {
164 return error.UnsupportedSocketOption;258 return error.UnsupportedSocketOption;
165}259}
166260
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
194/// Set the write buffer size of the socket.261/// Set the write buffer size of the socket.
195pub fn setWriteBufferSize(self: Socket, size: u32) !void {262pub fn setWriteBufferSize(self: Socket, size: u32) !void {
196 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size));263 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDBUF, mem.asBytes(&size));
...@@ -206,8 +273,8 @@ pub fn setReadBufferSize(self: Socket, size: u32) !void {...@@ -206,8 +273,8 @@ pub fn setReadBufferSize(self: Socket, size: u32) !void {
206/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.273/// to the socket will thereafter return `error.WouldBlock` should the timeout be exceeded.
207pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {274pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
208 const timeout = os.timeval{275 const timeout = os.timeval{
209 .tv_sec = @intCast(isize, milliseconds / time.ms_per_s),276 .tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
210 .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms),277 .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
211 };278 };
212279
213 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));280 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_SNDTIMEO, mem.asBytes(&timeout));
...@@ -219,58 +286,9 @@ pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {...@@ -219,58 +286,9 @@ pub fn setWriteTimeout(self: Socket, milliseconds: usize) !void {
219/// exceeded.286/// exceeded.
220pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {287pub fn setReadTimeout(self: Socket, milliseconds: usize) !void {
221 const timeout = os.timeval{288 const timeout = os.timeval{
222 .tv_sec = @intCast(isize, milliseconds / time.ms_per_s),289 .tv_sec = @intCast(i32, milliseconds / time.ms_per_s),
223 .tv_usec = @intCast(isize, (milliseconds % time.ms_per_s) * time.us_per_ms),290 .tv_usec = @intCast(i32, (milliseconds % time.ms_per_s) * time.us_per_ms),
224 };291 };
225292
226 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));293 return os.setsockopt(self.fd, os.SOL_SOCKET, os.SO_RCVTIMEO, mem.asBytes(&timeout));
227}294}
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+567
...@@ -0,0 +1,567 @@
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2015-2021 Zig Contributors
3// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
4// The MIT license requires this copyright notice to be included in all copies
5// and substantial portions of the software.
6
7const std = @import("../../std.zig");
8
9const os = std.os;
10const fmt = std.fmt;
11const mem = std.mem;
12const math = std.math;
13const builtin = std.builtin;
14const testing = std.testing;
15
16/// Resolves a network interface name into a scope/zone ID. It returns
17/// an error if either resolution fails, or if the interface name is
18/// too long.
19pub fn resolveScopeID(name: []const u8) !u32 {
20 if (comptime @hasDecl(os, "IFNAMESIZE")) {
21 if (name.len >= os.IFNAMESIZE - 1) return error.NameTooLong;
22
23 const fd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM, 0);
24 defer os.closeSocket(fd);
25
26 var f: os.ifreq = undefined;
27 mem.copy(u8, &f.ifrn.name, name);
28 f.ifrn.name[name.len] = 0;
29
30 try os.ioctl_SIOCGIFINDEX(fd, &f);
31
32 return @bitCast(u32, f.ifru.ivalue);
33 }
34 return error.Unsupported;
35}
36
37/// An IPv4 address comprised of 4 bytes.
38pub const IPv4 = extern struct {
39 /// A IPv4 host-port pair.
40 pub const Address = extern struct {
41 host: IPv4,
42 port: u16,
43 };
44
45 /// Octets of a IPv4 address designating the local host.
46 pub const localhost_octets = [_]u8{ 127, 0, 0, 1 };
47
48 /// The IPv4 address of the local host.
49 pub const localhost: IPv4 = .{ .octets = localhost_octets };
50
51 /// Octets of an unspecified IPv4 address.
52 pub const unspecified_octets = [_]u8{0} ** 4;
53
54 /// An unspecified IPv4 address.
55 pub const unspecified: IPv4 = .{ .octets = unspecified_octets };
56
57 /// Octets of a broadcast IPv4 address.
58 pub const broadcast_octets = [_]u8{255} ** 4;
59
60 /// An IPv4 broadcast address.
61 pub const broadcast: IPv4 = .{ .octets = broadcast_octets };
62
63 /// The prefix octet pattern of a link-local IPv4 address.
64 pub const link_local_prefix = [_]u8{ 169, 254 };
65
66 /// The prefix octet patterns of IPv4 addresses intended for
67 /// documentation.
68 pub const documentation_prefixes = [_][]const u8{
69 &[_]u8{ 192, 0, 2 },
70 &[_]u8{ 198, 51, 100 },
71 &[_]u8{ 203, 0, 113 },
72 };
73
74 octets: [4]u8,
75
76 /// Returns whether or not the two addresses are equal to, less than, or
77 /// greater than each other.
78 pub fn cmp(self: IPv4, other: IPv4) math.Order {
79 return mem.order(u8, &self.octets, &other.octets);
80 }
81
82 /// Returns true if both addresses are semantically equivalent.
83 pub fn eql(self: IPv4, other: IPv4) bool {
84 return mem.eql(u8, &self.octets, &other.octets);
85 }
86
87 /// Returns true if the address is a loopback address.
88 pub fn isLoopback(self: IPv4) bool {
89 return self.octets[0] == 127;
90 }
91
92 /// Returns true if the address is an unspecified IPv4 address.
93 pub fn isUnspecified(self: IPv4) bool {
94 return mem.eql(u8, &self.octets, &unspecified_octets);
95 }
96
97 /// Returns true if the address is a private IPv4 address.
98 pub fn isPrivate(self: IPv4) bool {
99 return self.octets[0] == 10 or
100 (self.octets[0] == 172 and self.octets[1] >= 16 and self.octets[1] <= 31) or
101 (self.octets[0] == 192 and self.octets[1] == 168);
102 }
103
104 /// Returns true if the address is a link-local IPv4 address.
105 pub fn isLinkLocal(self: IPv4) bool {
106 return mem.startsWith(u8, &self.octets, &link_local_prefix);
107 }
108
109 /// Returns true if the address is a multicast IPv4 address.
110 pub fn isMulticast(self: IPv4) bool {
111 return self.octets[0] >= 224 and self.octets[0] <= 239;
112 }
113
114 /// Returns true if the address is a IPv4 broadcast address.
115 pub fn isBroadcast(self: IPv4) bool {
116 return mem.eql(u8, &self.octets, &broadcast_octets);
117 }
118
119 /// Returns true if the address is in a range designated for documentation. Refer
120 /// to IETF RFC 5737 for more details.
121 pub fn isDocumentation(self: IPv4) bool {
122 inline for (documentation_prefixes) |prefix| {
123 if (mem.startsWith(u8, &self.octets, prefix)) {
124 return true;
125 }
126 }
127 return false;
128 }
129
130 /// Implements the `std.fmt.format` API.
131 pub fn format(
132 self: IPv4,
133 comptime layout: []const u8,
134 opts: fmt.FormatOptions,
135 writer: anytype,
136 ) !void {
137 if (comptime layout.len != 0 and layout[0] != 's') {
138 @compileError("Unsupported format specifier for IPv4 type '" ++ layout ++ "'.");
139 }
140
141 try fmt.format(writer, "{}.{}.{}.{}", .{
142 self.octets[0],
143 self.octets[1],
144 self.octets[2],
145 self.octets[3],
146 });
147 }
148
149 /// Set of possible errors that may encountered when parsing an IPv4
150 /// address.
151 pub const ParseError = error{
152 UnexpectedEndOfOctet,
153 TooManyOctets,
154 OctetOverflow,
155 UnexpectedToken,
156 IncompleteAddress,
157 };
158
159 /// Parses an arbitrary IPv4 address.
160 pub fn parse(buf: []const u8) ParseError!IPv4 {
161 var octets: [4]u8 = undefined;
162 var octet: u8 = 0;
163
164 var index: u8 = 0;
165 var saw_any_digits: bool = false;
166
167 for (buf) |c| {
168 switch (c) {
169 '.' => {
170 if (!saw_any_digits) return error.UnexpectedEndOfOctet;
171 if (index == 3) return error.TooManyOctets;
172 octets[index] = octet;
173 index += 1;
174 octet = 0;
175 saw_any_digits = false;
176 },
177 '0'...'9' => {
178 saw_any_digits = true;
179 octet = math.mul(u8, octet, 10) catch return error.OctetOverflow;
180 octet = math.add(u8, octet, c - '0') catch return error.OctetOverflow;
181 },
182 else => return error.UnexpectedToken,
183 }
184 }
185
186 if (index == 3 and saw_any_digits) {
187 octets[index] = octet;
188 return IPv4{ .octets = octets };
189 }
190
191 return error.IncompleteAddress;
192 }
193
194 /// Maps the address to its IPv6 equivalent. In most cases, you would
195 /// want to map the address to its IPv6 equivalent rather than directly
196 /// re-interpreting the address.
197 pub fn mapToIPv6(self: IPv4) IPv6 {
198 var octets: [16]u8 = undefined;
199 mem.copy(u8, octets[0..12], &IPv6.v4_mapped_prefix);
200 mem.copy(u8, octets[12..], &self.octets);
201 return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
202 }
203
204 /// Directly re-interprets the address to its IPv6 equivalent. In most
205 /// cases, you would want to map the address to its IPv6 equivalent rather
206 /// than directly re-interpreting the address.
207 pub fn toIPv6(self: IPv4) IPv6 {
208 var octets: [16]u8 = undefined;
209 mem.set(u8, octets[0..12], 0);
210 mem.copy(u8, octets[12..], &self.octets);
211 return IPv6{ .octets = octets, .scope_id = IPv6.no_scope_id };
212 }
213};
214
215/// An IPv6 address comprised of 16 bytes for an address, and 4 bytes
216/// for a scope ID; cumulatively summing to 20 bytes in total.
217pub const IPv6 = extern struct {
218 /// A IPv6 host-port pair.
219 pub const Address = extern struct {
220 host: IPv6,
221 port: u16,
222 };
223
224 /// Octets of a IPv6 address designating the local host.
225 pub const localhost_octets = [_]u8{0} ** 15 ++ [_]u8{0x01};
226
227 /// The IPv6 address of the local host.
228 pub const localhost: IPv6 = .{
229 .octets = localhost_octets,
230 .scope_id = no_scope_id,
231 };
232
233 /// Octets of an unspecified IPv6 address.
234 pub const unspecified_octets = [_]u8{0} ** 16;
235
236 /// An unspecified IPv6 address.
237 pub const unspecified: IPv6 = .{
238 .octets = unspecified_octets,
239 .scope_id = no_scope_id,
240 };
241
242 /// The prefix of a IPv6 address that is mapped to a IPv4 address.
243 pub const v4_mapped_prefix = [_]u8{0} ** 10 ++ [_]u8{0xFF} ** 2;
244
245 /// A marker value used to designate an IPv6 address with no
246 /// associated scope ID.
247 pub const no_scope_id = math.maxInt(u32);
248
249 octets: [16]u8,
250 scope_id: u32,
251
252 /// Returns whether or not the two addresses are equal to, less than, or
253 /// greater than each other.
254 pub fn cmp(self: IPv6, other: IPv6) math.Order {
255 return switch (mem.order(u8, self.octets, other.octets)) {
256 .eq => math.order(self.scope_id, other.scope_id),
257 else => |order| order,
258 };
259 }
260
261 /// Returns true if both addresses are semantically equivalent.
262 pub fn eql(self: IPv6, other: IPv6) bool {
263 return self.scope_id == other.scope_id and mem.eql(u8, &self.octets, &other.octets);
264 }
265
266 /// Returns true if the address is an unspecified IPv6 address.
267 pub fn isUnspecified(self: IPv6) bool {
268 return mem.eql(u8, &self.octets, &unspecified_octets);
269 }
270
271 /// Returns true if the address is a loopback address.
272 pub fn isLoopback(self: IPv6) bool {
273 return mem.eql(u8, self.octets[0..3], &[_]u8{ 0, 0, 0 }) and
274 mem.eql(u8, self.octets[12..], &[_]u8{ 0, 0, 0, 1 });
275 }
276
277 /// Returns true if the address maps to an IPv4 address.
278 pub fn mapsToIPv4(self: IPv6) bool {
279 return mem.startsWith(u8, &self.octets, &v4_mapped_prefix);
280 }
281
282 /// Returns an IPv4 address representative of the address should
283 /// it the address be mapped to an IPv4 address. It returns null
284 /// otherwise.
285 pub fn toIPv4(self: IPv6) ?IPv4 {
286 if (!self.mapsToIPv4()) return null;
287 return IPv4{ .octets = self.octets[12..][0..4].* };
288 }
289
290 /// Returns true if the address is a multicast IPv6 address.
291 pub fn isMulticast(self: IPv6) bool {
292 return self.octets[0] == 0xFF;
293 }
294
295 /// Returns true if the address is a unicast link local IPv6 address.
296 pub fn isLinkLocal(self: IPv6) bool {
297 return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0x80;
298 }
299
300 /// Returns true if the address is a deprecated unicast site local
301 /// IPv6 address. Refer to IETF RFC 3879 for more details as to
302 /// why they are deprecated.
303 pub fn isSiteLocal(self: IPv6) bool {
304 return self.octets[0] == 0xFE and self.octets[1] & 0xC0 == 0xC0;
305 }
306
307 /// IPv6 multicast address scopes.
308 pub const Scope = enum(u8) {
309 interface = 1,
310 link = 2,
311 realm = 3,
312 admin = 4,
313 site = 5,
314 organization = 8,
315 global = 14,
316 unknown = 0xFF,
317 };
318
319 /// Returns the multicast scope of the address.
320 pub fn scope(self: IPv6) Scope {
321 if (!self.isMulticast()) return .unknown;
322
323 return switch (self.octets[0] & 0x0F) {
324 1 => .interface,
325 2 => .link,
326 3 => .realm,
327 4 => .admin,
328 5 => .site,
329 8 => .organization,
330 14 => .global,
331 else => .unknown,
332 };
333 }
334
335 /// Implements the `std.fmt.format` API. Specifying 'x' or 's' formats the
336 /// address lower-cased octets, while specifying 'X' or 'S' formats the
337 /// address using upper-cased ASCII octets.
338 ///
339 /// The default specifier is 'x'.
340 pub fn format(
341 self: IPv6,
342 comptime layout: []const u8,
343 opts: fmt.FormatOptions,
344 writer: anytype,
345 ) !void {
346 comptime const specifier = &[_]u8{if (layout.len == 0) 'x' else switch (layout[0]) {
347 'x', 'X' => |specifier| specifier,
348 's' => 'x',
349 'S' => 'X',
350 else => @compileError("Unsupported format specifier for IPv6 type '" ++ layout ++ "'."),
351 }};
352
353 if (mem.startsWith(u8, &self.octets, &v4_mapped_prefix)) {
354 return fmt.format(writer, "::{" ++ specifier ++ "}{" ++ specifier ++ "}:{}.{}.{}.{}", .{
355 0xFF,
356 0xFF,
357 self.octets[12],
358 self.octets[13],
359 self.octets[14],
360 self.octets[15],
361 });
362 }
363
364 const zero_span = span: {
365 var i: usize = 0;
366 while (i < self.octets.len) : (i += 2) {
367 if (self.octets[i] == 0 and self.octets[i + 1] == 0) break;
368 } else break :span .{ .from = 0, .to = 0 };
369
370 const from = i;
371
372 while (i < self.octets.len) : (i += 2) {
373 if (self.octets[i] != 0 or self.octets[i + 1] != 0) break;
374 }
375
376 break :span .{ .from = from, .to = i };
377 };
378
379 var i: usize = 0;
380 while (i != 16) : (i += 2) {
381 if (zero_span.from != zero_span.to and i == zero_span.from) {
382 try writer.writeAll("::");
383 } else if (i >= zero_span.from and i < zero_span.to) {} else {
384 if (i != 0 and i != zero_span.to) try writer.writeAll(":");
385
386 const val = @as(u16, self.octets[i]) << 8 | self.octets[i + 1];
387 try fmt.formatIntValue(val, specifier, .{}, writer);
388 }
389 }
390
391 if (self.scope_id != no_scope_id and self.scope_id != 0) {
392 try fmt.format(writer, "%{d}", .{self.scope_id});
393 }
394 }
395
396 /// Set of possible errors that may encountered when parsing an IPv6
397 /// address.
398 pub const ParseError = error{
399 MalformedV4Mapping,
400 BadScopeID,
401 } || IPv4.ParseError;
402
403 /// Parses an arbitrary IPv6 address, including link-local addresses.
404 pub fn parse(buf: []const u8) ParseError!IPv6 {
405 if (mem.lastIndexOfScalar(u8, buf, '%')) |index| {
406 const ip_slice = buf[0..index];
407 const scope_id_slice = buf[index + 1 ..];
408
409 if (scope_id_slice.len == 0) return error.BadScopeID;
410
411 const scope_id: u32 = switch (scope_id_slice[0]) {
412 '0'...'9' => fmt.parseInt(u32, scope_id_slice, 10),
413 else => resolveScopeID(scope_id_slice),
414 } catch return error.BadScopeID;
415
416 return parseWithScopeID(ip_slice, scope_id);
417 }
418
419 return parseWithScopeID(buf, no_scope_id);
420 }
421
422 /// Parses an IPv6 address with a pre-specified scope ID. Presumes
423 /// that the address is not a link-local address.
424 pub fn parseWithScopeID(buf: []const u8, scope_id: u32) ParseError!IPv6 {
425 var octets: [16]u8 = undefined;
426 var octet: u16 = 0;
427 var tail: [16]u8 = undefined;
428
429 var out: []u8 = &octets;
430 var index: u8 = 0;
431
432 var saw_any_digits: bool = false;
433 var abbrv: bool = false;
434
435 for (buf) |c, i| {
436 switch (c) {
437 ':' => {
438 if (!saw_any_digits) {
439 if (abbrv) return error.UnexpectedToken;
440 if (i != 0) abbrv = true;
441 mem.set(u8, out[index..], 0);
442 out = &tail;
443 index = 0;
444 continue;
445 }
446 if (index == 14) return error.TooManyOctets;
447
448 out[index] = @truncate(u8, octet >> 8);
449 index += 1;
450 out[index] = @truncate(u8, octet);
451 index += 1;
452
453 octet = 0;
454 saw_any_digits = false;
455 },
456 '.' => {
457 if (!abbrv or out[0] != 0xFF and out[1] != 0xFF) {
458 return error.MalformedV4Mapping;
459 }
460 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
461 const v4 = try IPv4.parse(buf[start_index..]);
462 octets[10] = 0xFF;
463 octets[11] = 0xFF;
464 mem.copy(u8, octets[12..], &v4.octets);
465
466 return IPv6{ .octets = octets, .scope_id = scope_id };
467 },
468 else => {
469 saw_any_digits = true;
470 const digit = fmt.charToDigit(c, 16) catch return error.UnexpectedToken;
471 octet = math.mul(u16, octet, 16) catch return error.OctetOverflow;
472 octet = math.add(u16, octet, digit) catch return error.OctetOverflow;
473 },
474 }
475 }
476
477 if (!saw_any_digits and !abbrv) {
478 return error.IncompleteAddress;
479 }
480
481 if (index == 14) {
482 out[14] = @truncate(u8, octet >> 8);
483 out[15] = @truncate(u8, octet);
484 } else {
485 out[index] = @truncate(u8, octet >> 8);
486 index += 1;
487 out[index] = @truncate(u8, octet);
488 index += 1;
489 mem.copy(u8, octets[16 - index ..], out[0..index]);
490 }
491
492 return IPv6{ .octets = octets, .scope_id = scope_id };
493 }
494};
495
496test {
497 testing.refAllDecls(@This());
498}
499
500test "ip: convert to and from ipv6" {
501 try testing.expectFmt("::7f00:1", "{}", .{IPv4.localhost.toIPv6()});
502 testing.expect(!IPv4.localhost.toIPv6().mapsToIPv4());
503
504 try testing.expectFmt("::ffff:127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6()});
505 testing.expect(IPv4.localhost.mapToIPv6().mapsToIPv4());
506
507 testing.expect(IPv4.localhost.toIPv6().toIPv4() == null);
508 try testing.expectFmt("127.0.0.1", "{}", .{IPv4.localhost.mapToIPv6().toIPv4()});
509}
510
511test "ipv4: parse & format" {
512 const cases = [_][]const u8{
513 "0.0.0.0",
514 "255.255.255.255",
515 "1.2.3.4",
516 "123.255.0.91",
517 "127.0.0.1",
518 };
519
520 for (cases) |case| {
521 try testing.expectFmt(case, "{}", .{try IPv4.parse(case)});
522 }
523}
524
525test "ipv6: parse & format" {
526 const inputs = [_][]const u8{
527 "FF01:0:0:0:0:0:0:FB",
528 "FF01::Fb",
529 "::1",
530 "::",
531 "2001:db8::",
532 "::1234:5678",
533 "2001:db8::1234:5678",
534 "::ffff:123.5.123.5",
535 };
536
537 const outputs = [_][]const u8{
538 "ff01::fb",
539 "ff01::fb",
540 "::1",
541 "::",
542 "2001:db8::",
543 "::1234:5678",
544 "2001:db8::1234:5678",
545 "::ffff:123.5.123.5",
546 };
547
548 for (inputs) |input, i| {
549 try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)});
550 }
551}
552
553test "ipv6: parse & format addresses with scope ids" {
554 if (!@hasDecl(os, "IFNAMESIZE")) return error.SkipZigTest;
555
556 const inputs = [_][]const u8{
557 "FF01::FB%lo",
558 };
559
560 const outputs = [_][]const u8{
561 "ff01::fb%1",
562 };
563
564 for (inputs) |input, i| {
565 try testing.expectFmt(outputs[i], "{}", .{try IPv6.parse(input)});
566 }
567}
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}