Merge branch 'lun-4-resolve-ip6'

closes #4864
This commit is contained in:
Andrew Kelley 2020-06-02 15:31:14 -04:00
commit 14d235dd6e
6 changed files with 276 additions and 15 deletions

View File

@ -132,6 +132,7 @@ pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
pub extern "c" fn flock(fd: fd_t, operation: c_int) c_int;
pub extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int;
pub extern "c" fn uname(buf: *utsname) c_int;
pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;

View File

@ -21,6 +21,9 @@ pub const Address = extern union {
// TODO this crashed the compiler. https://github.com/ziglang/zig/issues/3512
//pub const localhost = initIp4(parseIp4("127.0.0.1") catch unreachable, 0);
/// Parse the given IP address string into an Address value.
/// It is recommended to use `resolveIp` instead, to handle
/// IPv6 link-local unix addresses.
pub fn parseIp(name: []const u8, port: u16) !Address {
if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
@ -42,6 +45,28 @@ pub const Address = extern union {
return error.InvalidIPAddressFormat;
}
pub fn resolveIp(name: []const u8, port: u16) !Address {
if (parseIp4(name, port)) |ip4| return ip4 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
=> {},
}
if (resolveIp6(name, port)) |ip6| return ip6 else |err| switch (err) {
error.Overflow,
error.InvalidEnd,
error.InvalidCharacter,
error.Incomplete,
error.InvalidIpv4Mapping,
=> {},
else => return err,
}
return error.InvalidIPAddressFormat;
}
pub fn parseExpectingFamily(name: []const u8, family: os.sa_family_t, port: u16) !Address {
switch (family) {
os.AF_INET => return parseIp4(name, port),
@ -51,6 +76,9 @@ pub const Address = extern union {
}
}
/// Parse a given IPv6 address string into an Address.
/// Assumes the Scope ID of the address is fully numeric.
/// For non-numeric addresses, see `resolveIp6`.
pub fn parseIp6(buf: []const u8, port: u16) !Address {
var result = Address{
.in6 = os.sockaddr_in6{
@ -157,6 +185,136 @@ pub const Address = extern union {
}
}
pub fn resolveIp6(buf: []const u8, port: u16) !Address {
// TODO: Unify the implementations of resolveIp6 and parseIp6.
var result = Address{
.in6 = os.sockaddr_in6{
.scope_id = 0,
.port = mem.nativeToBig(u16, port),
.flowinfo = 0,
.addr = undefined,
},
};
var ip_slice = result.in6.addr[0..];
var tail: [16]u8 = undefined;
var x: u16 = 0;
var saw_any_digits = false;
var index: u8 = 0;
var abbrv = false;
var scope_id = false;
var scope_id_value: [os.IFNAMESIZE - 1]u8 = undefined;
var scope_id_index: usize = 0;
for (buf) |c, i| {
if (scope_id) {
// Handling of percent-encoding should be for an URI library.
if ((c >= '0' and c <= '9') or
(c >= 'A' and c <= 'Z') or
(c >= 'a' and c <= 'z') or
(c == '-') or (c == '.') or (c == '_') or (c == '~'))
{
if (scope_id_index >= scope_id_value.len) {
return error.Overflow;
}
scope_id_value[scope_id_index] = c;
scope_id_index += 1;
} else {
return error.InvalidCharacter;
}
} else if (c == ':') {
if (!saw_any_digits) {
if (abbrv) return error.InvalidCharacter; // ':::'
if (i != 0) abbrv = true;
mem.set(u8, ip_slice[index..], 0);
ip_slice = tail[0..];
index = 0;
continue;
}
if (index == 14) {
return error.InvalidEnd;
}
ip_slice[index] = @truncate(u8, x >> 8);
index += 1;
ip_slice[index] = @truncate(u8, x);
index += 1;
x = 0;
saw_any_digits = false;
} else if (c == '%') {
if (!saw_any_digits) {
return error.InvalidCharacter;
}
scope_id = true;
saw_any_digits = false;
} else if (c == '.') {
if (!abbrv or ip_slice[0] != 0xff or ip_slice[1] != 0xff) {
// must start with '::ffff:'
return error.InvalidIpv4Mapping;
}
const start_index = mem.lastIndexOfScalar(u8, buf[0..i], ':').? + 1;
const addr = (parseIp4(buf[start_index..], 0) catch {
return error.InvalidIpv4Mapping;
}).in.addr;
ip_slice = result.in6.addr[0..];
ip_slice[10] = 0xff;
ip_slice[11] = 0xff;
const ptr = mem.sliceAsBytes(@as(*const [1]u32, &addr)[0..]);
ip_slice[12] = ptr[0];
ip_slice[13] = ptr[1];
ip_slice[14] = ptr[2];
ip_slice[15] = ptr[3];
return result;
} else {
const digit = try std.fmt.charToDigit(c, 16);
if (@mulWithOverflow(u16, x, 16, &x)) {
return error.Overflow;
}
if (@addWithOverflow(u16, x, digit, &x)) {
return error.Overflow;
}
saw_any_digits = true;
}
}
if (!saw_any_digits and !abbrv) {
return error.Incomplete;
}
if (scope_id and scope_id_index == 0) {
return error.Incomplete;
}
var resolved_scope_id: u32 = 0;
if (scope_id_index > 0) {
const scope_id_str = scope_id_value[0..scope_id_index];
resolved_scope_id = std.fmt.parseInt(u32, scope_id_str, 10) catch |err| blk: {
if (err != error.InvalidCharacter) return err;
break :blk try if_nametoindex(scope_id_str);
};
}
result.in6.scope_id = resolved_scope_id;
if (index == 14) {
ip_slice[14] = @truncate(u8, x >> 8);
ip_slice[15] = @truncate(u8, x);
return result;
} else {
ip_slice[index] = @truncate(u8, x >> 8);
index += 1;
ip_slice[index] = @truncate(u8, x);
index += 1;
mem.copy(u8, result.in6.addr[16 - index ..], ip_slice[0..index]);
return result;
}
}
pub fn parseIp4(buf: []const u8, port: u16) !Address {
var result = Address{
.in = os.sockaddr_in{
@ -380,6 +538,20 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
};
}
fn if_nametoindex(name: []const u8) !u32 {
var ifr: os.ifreq = undefined;
var sockfd = try os.socket(os.AF_UNIX, os.SOCK_DGRAM | os.SOCK_CLOEXEC, 0);
defer os.close(sockfd);
std.mem.copy(u8, &ifr.ifrn.name, name);
ifr.ifrn.name[name.len] = 0;
// TODO investigate if this needs to be integrated with evented I/O.
try os.ioctl_SIOCGIFINDEX(sockfd, &ifr);
return @bitCast(u32, ifr.ifru.ivalue);
}
pub const AddressList = struct {
arena: std.heap.ArenaAllocator,
addrs: []Address,
@ -1071,7 +1243,7 @@ fn linuxLookupNameFromNumericUnspec(
name: []const u8,
port: u16,
) !void {
const addr = try Address.parseIp(name, port);
const addr = try Address.resolveIp(name, port);
(try addrs.addOne()).* = LookupAddr{ .addr = addr };
}

View File

@ -34,6 +34,12 @@ test "parse and render IPv6 addresses" {
var addr = net.Address.parseIp6(ip, 0) catch unreachable;
var newIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr}) catch unreachable;
std.testing.expect(std.mem.eql(u8, printed[i], newIp[1 .. newIp.len - 3]));
if (std.builtin.os.tag == .linux) {
var addr_via_resolve = net.Address.resolveIp6(ip, 0) catch unreachable;
var newResolvedIp = std.fmt.bufPrint(buffer[0..], "{}", .{addr_via_resolve}) catch unreachable;
std.testing.expect(std.mem.eql(u8, printed[i], newResolvedIp[1 .. newResolvedIp.len - 3]));
}
}
testing.expectError(error.InvalidCharacter, net.Address.parseIp6(":::", 0));
@ -42,6 +48,22 @@ test "parse and render IPv6 addresses" {
testing.expectError(error.InvalidEnd, net.Address.parseIp6("FF01:0:0:0:0:0:0:FB:", 0));
testing.expectError(error.Incomplete, net.Address.parseIp6("FF01:", 0));
testing.expectError(error.InvalidIpv4Mapping, net.Address.parseIp6("::123.123.123.123", 0));
// TODO Make this test pass on other operating systems.
if (std.builtin.os.tag == .linux) {
testing.expectError(error.Incomplete, net.Address.resolveIp6("ff01::fb%", 0));
testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%wlp3s0s0s0s0s0s0s0s0", 0));
testing.expectError(error.Overflow, net.Address.resolveIp6("ff01::fb%12345678901234", 0));
}
}
test "invalid but parseable IPv6 scope ids" {
if (std.builtin.os.tag != .linux) {
// Currently, resolveIp6 with alphanumerical scope IDs only works on Linux.
// TODO Make this test pass on other operating systems.
return error.SkipZigTest;
}
testing.expectError(error.InterfaceNotFound, net.Address.resolveIp6("ff01::fb%123s45678901234", 0));
}
test "parse and render IPv4 addresses" {

View File

@ -405,7 +405,6 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
else => |err| return unexpectedErrno(err),
}
}
const iov_count = math.cast(u31, iov.len) catch math.maxInt(u31);
while (true) {
// 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 {
return true;
}
if (builtin.os.tag == .linux) {
var wsz: linux.winsize = undefined;
return linux.syscall3(.ioctl, @bitCast(usize, @as(isize, handle)), linux.TIOCGWINSZ, @ptrToInt(&wsz)) == 0;
while (true) {
var wsz: linux.winsize = undefined;
const fd = @bitCast(usize, @as(isize, handle));
switch (linux.syscall3(.ioctl, fd, linux.TIOCGWINSZ, @ptrToInt(&wsz))) {
0 => return true,
EINTR => continue,
else => return false,
}
}
}
unreachable;
}
@ -2451,9 +2457,8 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
if (builtin.os.tag == .windows) {
// NOTE: windows translates the SOCK_NONBLOCK/SOCK_CLOEXEC flags into windows-analagous operations
const filtered_sock_type = socket_type & ~@as(u32, SOCK_NONBLOCK | SOCK_CLOEXEC);
const flags : u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type),
@intCast(c_int, protocol), null, 0, flags);
const flags: u32 = if ((socket_type & SOCK_CLOEXEC) != 0) windows.ws2_32.WSA_FLAG_NO_HANDLE_INHERIT else 0;
const rc = windows.ws2_32.WSASocketW(@intCast(c_int, domain), @intCast(c_int, filtered_sock_type), @intCast(c_int, protocol), null, 0, flags);
if (rc == windows.ws2_32.INVALID_SOCKET) switch (windows.ws2_32.WSAGetLastError()) {
.WSAEMFILE => return error.ProcessFdQuotaExceeded,
.WSAENOBUFS => return error.SystemResources,
@ -2463,7 +2468,7 @@ pub fn socket(domain: u32, socket_type: u32, protocol: u32) SocketError!socket_t
};
errdefer windows.closesocket(rc) catch unreachable;
if ((socket_type & SOCK_NONBLOCK) != 0) {
var mode : c_ulong = 1; // nonblocking
var mode: c_ulong = 1; // nonblocking
if (windows.ws2_32.SOCKET_ERROR == windows.ws2_32.ioctlsocket(rc, windows.ws2_32.FIONBIO, &mode)) {
switch (windows.ws2_32.WSAGetLastError()) {
// 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
.WSAECONNREFUSED => return error.ConnectionRefused,
.WSAETIMEDOUT => return error.ConnectionTimedOut,
.WSAEHOSTUNREACH // TODO: should we return NetworkUnreachable in this case as well?
,.WSAENETUNREACH => return error.NetworkUnreachable,
, .WSAENETUNREACH => return error.NetworkUnreachable,
.WSAEFAULT => unreachable,
.WSAEINVAL => unreachable,
.WSAEISCONN => unreachable,
@ -4882,12 +4887,15 @@ pub fn getrusage(who: i32) rusage {
pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
var term: termios = undefined;
switch (errno(system.tcgetattr(handle, &term))) {
0 => return term,
EBADF => unreachable,
ENOTTY => return error.NotATerminal,
else => |err| return unexpectedErrno(err),
while (true) {
var term: termios = undefined;
switch (errno(system.tcgetattr(handle, &term))) {
0 => return term,
EINTR => continue,
EBADF => unreachable,
ENOTTY => return error.NotATerminal,
else => |err| return unexpectedErrno(err),
}
}
}
@ -4906,3 +4914,25 @@ pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) Termio
}
}
}
const IoCtl_SIOCGIFINDEX_Error = error{
FileSystem,
InterfaceNotFound,
} || UnexpectedError;
pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void {
while (true) {
switch (errno(system.ioctl(fd, SIOCGIFINDEX, @ptrToInt(ifr)))) {
0 => return,
EINVAL => unreachable, // Bad parameters.
ENOTTY => unreachable,
ENXIO => unreachable,
EBADF => unreachable, // Always a race condition.
EFAULT => unreachable, // Bad pointer parameter.
EINTR => continue,
EIO => return error.FileSystem,
ENODEV => return error.InterfaceNotFound,
else => |err| return unexpectedErrno(err),
}
}
}

View File

@ -1705,3 +1705,35 @@ pub const termios = extern struct {
ispeed: speed_t,
ospeed: speed_t,
};
pub const SIOCGIFINDEX = 0x8933;
pub const IFNAMESIZE = 16;
pub const ifmap = extern struct {
mem_start: u32,
mem_end: u32,
base_addr: u16,
irq: u8,
dma: u8,
port: u8,
};
pub const ifreq = extern struct {
ifrn: extern union {
name: [IFNAMESIZE]u8,
},
ifru: extern union {
addr: sockaddr,
dstaddr: sockaddr,
broadaddr: sockaddr,
netmask: sockaddr,
hwaddr: sockaddr,
flags: i16,
ivalue: i32,
mtu: i32,
map: ifmap,
slave: [IFNAMESIZE - 1:0]u8,
newname: [IFNAMESIZE - 1:0]u8,
data: ?[*]u8,
},
};

View File

@ -1193,6 +1193,10 @@ pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usi
return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));
}
pub fn ioctl(fd: fd_t, request: u32, arg: usize) usize {
return syscall3(.ioctl, @bitCast(usize, @as(isize, fd)), request, arg);
}
test "" {
if (builtin.os.tag == .linux) {
_ = @import("linux/test.zig");