authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-06-02 15:31:14-04:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-06-02 15:31:14-04:00
log14d235dd6e79dba8d34e05f1912a1bcf9a3ef68c
tree9122d4a2eab8b9deef81e089cecee0e7699a08fa
parentbae0c9b554105092297330c8beec977fe3468da6
parent7fd937fef4547a98d7c33ea67eca76e6336f9152

Merge branch 'lun-4-resolve-ip6'

closes #4864

6 files changed, 276 insertions(+), 15 deletions(-)

lib/std/c.zig+1
......@@ -132,6 +132,7 @@ pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
132132pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
133133pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
134134pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
135pub extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
135136pub extern "c" fn uname(buf: *utsname) c_int;
136137
137138pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
lib/std/net.zig+173-1
......@@ -21,6 +21,9 @@ pub const Address = extern union {
2121 // TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512
2222 //pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);
2323
24 /// Parse the given IP address string into an Address value.
25 /// It is recommended to use `resolveIp` instead, to handle
26 /// IPv6 link-local unix addresses.
2427 pub fn parseIp(name: []const u8, port: u16) !Address {
2528 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
2629 error.Overflow,
......@@ -42,6 +45,28 @@ pub const Address = extern union {
4245 return error.InvalidIPAddressFormat;
4346 }
4447
48 pub fn resolveIp(name: []const u8, port: u16) !Address {
49 if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
50 error.Overflow,
51 error.InvalidEnd,
52 error.InvalidCharacter,
53 error.Incomplete,
54 => {},
55 }
56
57 if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
58 error.Overflow,
59 error.InvalidEnd,
60 error.InvalidCharacter,
61 error.Incomplete,
62 error.InvalidIpv4Mapping,
63 => {},
64 else => return err,
65 }
66
67 return error.InvalidIPAddressFormat;
68 }
69
4570 pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !Address {
4671 switch (family) {
4772 os.AF_INET => return parseIp4(name, port),
......@@ -51,6 +76,9 @@ pub const Address = extern union {
5176 }
5277 }
5378
79 /// Parse a given IPv6 address string into an Address.
80 /// Assumes the Scope ID of the address is fully numeric.
81 /// For non-numeric addresses, see `resolveIp6`.
5482 pub fn parseIp6(buf: []const u8, port: u16) !Address {
5583 var result = Address{
5684 .in6 = os.sockaddr_in6{
......@@ -157,6 +185,136 @@ pub const Address = extern union {
157185 }
158186 }
159187
188 pub fn resolveIp6(buf: []const u8, port: u16) !Address {
189 // TODO: Unify the implementations of resolveIp6 and parseIp6.
190 var result = Address{
191 .in6 = os.sockaddr_in6{
192 .scope_id = 0,
193 .port = mem.nativeToBig(u16, port),
194 .flowinfo = 0,
195 .addr = undefined,
196 },
197 };
198 var ip_slice = result.in6.addr[0..];
199
200 var tail: [16]u8 = undefined;
201
202 var x: u16 = 0;
203 var saw_any_digits = false;
204 var index: u8 = 0;
205 var abbrv = false;
206
207 var scope_id = false;
208 var scope_id_value: [os.IFNAMESIZE - 1]u8 = undefined;
209 var scope_id_index: usize = 0;
210
211 for (buf) |c, i| {
212 if (scope_id) {
213 // Handling of percent-encoding should be for an URI library.
214 if ((c >= '0' and c <= '9') or
215 (c >= 'A' and c <= 'Z') or
216 (c >= 'a' and c <= 'z') or
217 (c == '-') or (c == '.') or (c == '_') or (c == '~'))
218 {
219 if (scope_id_index >= scope_id_value.len) {
220 return error.Overflow;
221 }
222
223 scope_id_value[scope_id_index] = c;
224 scope_id_index += 1;
225 } else {
226 return error.InvalidCharacter;
227 }
228 } else if (c == ':') {
229 if (!saw_any_digits) {
230 if (abbrv) return error.InvalidCharacter; // ':::'
231 if (i != 0) abbrv = true;
232 mem.set(u8, ip_slice[index..], 0);
233 ip_slice = tail[0..];
234 index = 0;
235 continue;
236 }
237 if (index == 14) {
238 return error.InvalidEnd;
239 }
240 ip_slice[index] = @truncate(u8, x >> 8);
241 index += 1;
242 ip_slice[index] = @truncate(u8, x);
243 index += 1;
244
245 x = 0;
246 saw_any_digits = false;
247 } else if (c == '%') {
248 if (!saw_any_digits) {
249 return error.InvalidCharacter;
250 }
251 scope_id = true;
252 saw_any_digits = false;
253 } else if (c == '.') {
254 if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
255 // must start with '::ffff:'
256 return error.InvalidIpv4Mapping;
257 }
258 const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
259 const addr = (parseIp4(buf[start_index..], 0) catch {
260 return error.InvalidIpv4Mapping;
261 }).in.addr;
262 ip_slice = result.in6.addr[0..];
263 ip_slice[10] = 0xff;
264 ip_slice[11] = 0xff;
265
266 const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
267
268 ip_slice[12] = ptr[0];
269 ip_slice[13] = ptr[1];
270 ip_slice[14] = ptr[2];
271 ip_slice[15] = ptr[3];
272 return result;
273 } else {
274 const digit = try std.fmt.charToDigit(c, 16);
275 if (@mulWithOverflow(u16, x, 16, &x)) {
276 return error.Overflow;
277 }
278 if (@addWithOverflow(u16, x, digit, &x)) {
279 return error.Overflow;
280 }
281 saw_any_digits = true;
282 }
283 }
284
285 if (!saw_any_digits and !abbrv) {
286 return error.Incomplete;
287 }
288
289 if (scope_id and scope_id_index == 0) {
290 return error.Incomplete;
291 }
292
293 var resolved_scope_id: u32 = 0;
294 if (scope_id_index > 0) {
295 const scope_id_str = scope_id_value[0..scope_id_index];
296 resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
297 if (err != error.InvalidCharacter) return err;
298 break :blk try if_nametoindex(scope_id_str);
299 };
300 }
301
302 result.in6.scope_id = resolved_scope_id;
303
304 if (index == 14) {
305 ip_slice[14] = @truncate(u8, x >> 8);
306 ip_slice[15] = @truncate(u8, x);
307 return result;
308 } else {
309 ip_slice[index] = @truncate(u8, x >> 8);
310 index += 1;
311 ip_slice[index] = @truncate(u8, x);
312 index += 1;
313 mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
314 return result;
315 }
316 }
317
160318 pub fn parseIp4(buf: []const u8, port: u16) !Address {
161319 var result = Address{
162320 .in = os.sockaddr_in{
......@@ -380,6 +538,20 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
380538 };
381539}
382540
541fn if_nametoindex(name: []const u8) !u32 {
542 var ifr: os.ifreq = undefined;
543 var sockfd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM | os.SOCK_CLOEXEC, 0);
544 defer os.close(sockfd);
545
546 std.mem.copy(u8, &ifr.ifrn.name, name);
547 ifr.ifrn.name[name.len] = 0;
548
549 // TODO investigate if this needs to be integrated with evented I/O.
550 try os.ioctl_SIOCGIFINDEX(sockfd, &ifr);
551
552 return @bitCast(u32, ifr.ifru.ivalue);
553}
554
383555pub const AddressList = struct {
384556 arena: std.heap.ArenaAllocator,
385557 addrs: []Address,
......@@ -1071,7 +1243,7 @@ fn linuxLookupNameFromNumericUnspec(
10711243 name: []const u8,
10721244 port: u16,
10731245) !void {
1074 const addr = try Address.parseIp(name, port);
1246 const addr = try Address.resolveIp(name, port);
10751247 (try addrs.addOne()).* = LookupAddr{ .addr = addr };
10761248}
10771249
lib/std/net/test.zig+22
......@@ -34,6 +34,12 @@ test "parse and render IPv6 addresses" {
3434 var addr = net.Address.parseIp6(ip, 0) catch unreachable;
3535 var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;
3636 std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
37
38 if (std.builtin.os.tag == .linux) {
39 var addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable;
40 var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr_via_resolve}) catch unreachable;
41 std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
42 }
3743 }
3844
3945 testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));
......@@ -42,6 +48,22 @@ test "parse and render IPv6 addresses" {
4248 testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
4349 testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));
4450 testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));
51 // TODO Make this test pass on other operating systems.
52 if (std.builtin.os.tag == .linux) {
53 testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0));
54 testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3s0s0s0s0s0s0s0s0", 0));
55 testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0));
56 }
57}
58
59test "invalid but parseable IPv6 scope ids" {
60 if (std.builtin.os.tag != .linux) {
61 // Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
62 // TODO Make this test pass on other operating systems.
63 return error.SkipZigTest;
64 }
65
66 testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0));
4567}
4668
4769test "parse and render IPv4 addresses" {
lib/std/os.zig+44-14
......@@ -405,7 +405,6 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
405405 else => |err| return unexpectedErrno(err),
406406 }
407407 }
408
409408 const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
410409 while (true) {
411410 // TODO handle the case when iov_len is too large and get rid of this @intCast
......@@ -2391,8 +2390,15 @@ pub fn isatty(handle: fd_t) bool {
23912390 return true;
23922391 }
23932392 if (builtin.os.tag == .linux) {
2394 var wsz: linux.winsize = undefined;
2395 return linux.syscall3(.ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
2393 while (true) {
2394 var wsz: linux.winsize = undefined;
2395 const fd = @bitCast(usize, @as(isize, handle));
2396 switch (linux.syscall3(.ioctl, fd, linux.TIOCGWINSZ, @ptrToInt(&wsz))) {
2397 0 => return true,
2398 EINTR => continue,
2399 else => return false,
2400 }
2401 }
23962402 }
23972403 unreachable;
23982404}
......@@ -2451,9 +2457,8 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
24512457 if (builtin.os.tag == .windows) {
24522458 // NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations
24532459 const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);
2454 const flags : u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
2455 const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type),
2456 @intCast(c_int, protocol), null, 0, flags);
2460 const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
2461 const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type), @intCast(c_int, protocol), null, 0, flags);
24572462 if (rc == windows.ws2_32.INVALID_SOCKET) switch (windows.ws2_32.WSAGetLastError()) {
24582463 .WSAEMFILE => return error.ProcessFdQuotaExceeded,
24592464 .WSAENOBUFS => return error.SystemResources,
......@@ -2463,7 +2468,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
24632468 };
24642469 errdefer windows.closesocket(rc) catch unreachable;
24652470 if ((socket_type & SOCK_NONBLOCK) != 0) {
2466 var mode : c_ulong = 1; // nonblocking
2471 var mode: c_ulong = 1; // nonblocking
24672472 if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {
24682473 switch (windows.ws2_32.WSAGetLastError()) {
24692474 // have not identified any error codes that should be handled yet
......@@ -2858,7 +2863,7 @@ pub fn connect(sockfd: socket_t, sock_addr: *const sockaddr, len: socklen_t) Con
28582863 .WSAECONNREFUSED => return error.ConnectionRefused,
28592864 .WSAETIMEDOUT => return error.ConnectionTimedOut,
28602865 .WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well?
2861 ,.WSAENETUNREACH => return error.NetworkUnreachable,
2866 , .WSAENETUNREACH => return error.NetworkUnreachable,
28622867 .WSAEFAULT => unreachable,
28632868 .WSAEINVAL => unreachable,
28642869 .WSAEISCONN => unreachable,
......@@ -4882,12 +4887,15 @@ pub fn getrusage(who: i32) rusage {
48824887pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
48834888
48844889pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
4885 var term: termios = undefined;
4886 switch (errno(system.tcgetattr(handle, &term))) {
4887 0 => return term,
4888 EBADF => unreachable,
4889 ENOTTY => return error.NotATerminal,
4890 else => |err| return unexpectedErrno(err),
4890 while (true) {
4891 var term: termios = undefined;
4892 switch (errno(system.tcgetattr(handle, &term))) {
4893 0 => return term,
4894 EINTR => continue,
4895 EBADF => unreachable,
4896 ENOTTY => return error.NotATerminal,
4897 else => |err| return unexpectedErrno(err),
4898 }
48914899 }
48924900}
48934901
......@@ -4906,3 +4914,25 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio
49064914 }
49074915 }
49084916}
4917
4918const IoCtl_SIOCGIFINDEX_Error = error{
4919 FileSystem,
4920 InterfaceNotFound,
4921} || UnexpectedError;
4922
4923pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
4924 while (true) {
4925 switch (errno(system.ioctl(fd, SIOCGIFINDEX, @ptrToInt(ifr)))) {
4926 0 => return,
4927 EINVAL => unreachable, // Bad parameters.
4928 ENOTTY => unreachable,
4929 ENXIO => unreachable,
4930 EBADF => unreachable, // Always a race condition.
4931 EFAULT => unreachable, // Bad pointer parameter.
4932 EINTR => continue,
4933 EIO => return error.FileSystem,
4934 ENODEV => return error.InterfaceNotFound,
4935 else => |err| return unexpectedErrno(err),
4936 }
4937 }
4938}
lib/std/os/bits/linux.zig+32
......@@ -1705,3 +1705,35 @@ pub const termios = extern struct {
17051705 ispeed: speed_t,
17061706 ospeed: speed_t,
17071707};
1708
1709pub const SIOCGIFINDEX = 0x8933;
1710pub const IFNAMESIZE = 16;
1711
1712pub const ifmap = extern struct {
1713 mem_start: u32,
1714 mem_end: u32,
1715 base_addr: u16,
1716 irq: u8,
1717 dma: u8,
1718 port: u8,
1719};
1720
1721pub const ifreq = extern struct {
1722 ifrn: extern union {
1723 name: [IFNAMESIZE]u8,
1724 },
1725 ifru: extern union {
1726 addr: sockaddr,
1727 dstaddr: sockaddr,
1728 broadaddr: sockaddr,
1729 netmask: sockaddr,
1730 hwaddr: sockaddr,
1731 flags: i16,
1732 ivalue: i32,
1733 mtu: i32,
1734 map: ifmap,
1735 slave: [IFNAMESIZE - 1:0]u8,
1736 newname: [IFNAMESIZE - 1:0]u8,
1737 data: ?[*]u8,
1738 },
1739};
lib/std/os/linux.zig+4
......@@ -1193,6 +1193,10 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi
11931193 return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));
11941194}
11951195
1196pub fn ioctl(fd: fd_t, request: u32, arg: usize) usize {
1197 return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), request, arg);
1198}
1199
11961200test "" {
11971201 if (builtin.os.tag == .linux) {
11981202 _ = @import("linux/test.zig");