std.net: port the RFC 3484/6724 destination...

...address selection from musl libc
This commit is contained in:
Andrew Kelley 2019-10-29 16:10:14 -04:00
parent 9ade31faaf
commit 8d3b7689ad
No known key found for this signature in database
GPG Key ID: 7C5F548F728501A9
2 changed files with 214 additions and 13 deletions

View File

@ -357,7 +357,7 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
const hints = os.addrinfo{
.flags = c.AI_NUMERICSERV,
.family = os.AF_INET, // TODO os.AF_UNSPEC,
.family = os.AF_UNSPEC,
.socktype = os.SOCK_STREAM,
.protocol = os.IPPROTO_TCP,
.canonname = null,
@ -415,14 +415,11 @@ pub fn getAddressList(allocator: *mem.Allocator, name: []const u8, port: u16) !*
}
if (builtin.os == .linux) {
const flags = std.c.AI_NUMERICSERV;
const family = os.AF_INET; //TODO os.AF_UNSPEC;
// The limit of 48 results is a non-sharp bound on the number of addresses
// that can fit in one 512-byte DNS packet full of v4 results and a second
// packet full of v6 results. Due to headers, the actual limit is lower.
const family = os.AF_UNSPEC;
var addrs = std.ArrayList(LookupAddr).init(allocator);
defer addrs.deinit();
var canon = std.Buffer.initNull(allocator);
var canon = std.Buffer.initNull(arena);
defer canon.deinit();
try linuxLookupName(&addrs, &canon, name, family, flags);
@ -467,6 +464,14 @@ const LookupAddr = struct {
sortkey: i32 = 0,
};
const DAS_USABLE = 0x40000000;
const DAS_MATCHINGSCOPE = 0x20000000;
const DAS_MATCHINGLABEL = 0x10000000;
const DAS_PREC_SHIFT = 20;
const DAS_SCOPE_SHIFT = 16;
const DAS_PREFIX_SHIFT = 8;
const DAS_ORDER_SHIFT = 0;
fn linuxLookupName(
addrs: *std.ArrayList(LookupAddr),
canon: *std.Buffer,
@ -493,8 +498,201 @@ fn linuxLookupName(
// No further processing is needed if there are fewer than 2
// results or if there are only IPv4 results.
if (addrs.len == 1 or family == os.AF_INET) return;
const all_ip4 = for (addrs.toSliceConst()) |addr| {
if (addr.family != os.AF_INET) break false;
} else true;
if (all_ip4) return;
@panic("port the RFC 3484/6724 destination address selection from musl libc");
// The following implements a subset of RFC 3484/6724 destination
// address selection by generating a single 31-bit sort key for
// each address. Rules 3, 4, and 7 are omitted for having
// excessive runtime and code size cost and dubious benefit.
// So far the label/precedence table cannot be customized.
// This implementation is ported from musl libc.
// A more idiomatic "ziggy" implementation would be welcome.
for (addrs.toSlice()) |*addr, i| {
var key: i32 = 0;
var sa6: os.sockaddr_in6 = undefined;
@memset(@ptrCast([*]u8, &sa6), 0, @sizeOf(os.sockaddr_in6));
var da6 = os.sockaddr_in6{
.family = os.AF_INET6,
.scope_id = addr.scope_id,
.port = 65535,
.flowinfo = 0,
.addr = [1]u8{0} ** 16,
};
var sa4: os.sockaddr_in = undefined;
@memset(@ptrCast([*]u8, &sa4), 0, @sizeOf(os.sockaddr_in));
var da4 = os.sockaddr_in{
.family = os.AF_INET,
.port = 65535,
.addr = 0,
.zero = [1]u8{0} ** 8,
};
var sa: *os.sockaddr = undefined;
var da: *os.sockaddr = undefined;
var salen: os.socklen_t = undefined;
var dalen: os.socklen_t = undefined;
if (addr.family == os.AF_INET6) {
mem.copy(u8, &da6.addr, &addr.addr);
da = @ptrCast(*os.sockaddr, &da6);
dalen = @sizeOf(os.sockaddr_in6);
sa = @ptrCast(*os.sockaddr, &sa6);
salen = @sizeOf(os.sockaddr_in6);
} else {
mem.copy(u8, &sa6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
mem.copy(u8, &da6.addr, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff");
mem.copy(u8, da6.addr[12..], addr.addr[0..4]);
da4.addr = mem.readIntNative(u32, @ptrCast(*const [4]u8, &addr.addr));
da = @ptrCast(*os.sockaddr, &da4);
dalen = @sizeOf(os.sockaddr_in);
sa = @ptrCast(*os.sockaddr, &sa4);
salen = @sizeOf(os.sockaddr_in);
}
const dpolicy = policyOf(da6.addr);
const dscope: i32 = scopeOf(da6.addr);
const dlabel = dpolicy.label;
const dprec: i32 = dpolicy.prec;
const MAXADDRS = 3;
var prefixlen: i32 = 0;
if (os.socket(addr.family, os.SOCK_DGRAM | os.SOCK_CLOEXEC, os.IPPROTO_UDP)) |fd| syscalls: {
defer os.close(fd);
os.connect(fd, da, dalen) catch break :syscalls;
key |= DAS_USABLE;
os.getsockname(fd, sa, &salen) catch break :syscalls;
if (addr.family == os.AF_INET) {
// TODO sa6.addr[12..16] should return *[4]u8, making this cast unnecessary.
mem.writeIntNative(u32, @ptrCast(*[4]u8, &sa6.addr[12]), sa4.addr);
}
if (dscope == i32(scopeOf(sa6.addr))) key |= DAS_MATCHINGSCOPE;
if (dlabel == labelOf(sa6.addr)) key |= DAS_MATCHINGLABEL;
prefixlen = prefixMatch(sa6.addr, da6.addr);
} else |_| {}
key |= dprec << DAS_PREC_SHIFT;
key |= (15 - dscope) << DAS_SCOPE_SHIFT;
key |= prefixlen << DAS_PREFIX_SHIFT;
key |= (MAXADDRS - @intCast(i32, i)) << DAS_ORDER_SHIFT;
addr.sortkey = key;
}
std.sort.sort(LookupAddr, addrs.toSlice(), addrCmpLessThan);
}
const Policy = struct {
addr: [16]u8,
len: u8,
mask: u8,
prec: u8,
label: u8,
};
const defined_policies = [_]Policy{
Policy{
.addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01",
.len = 15,
.mask = 0xff,
.prec = 50,
.label = 0,
},
Policy{
.addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00",
.len = 11,
.mask = 0xff,
.prec = 35,
.label = 4,
},
Policy{
.addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
.len = 1,
.mask = 0xff,
.prec = 30,
.label = 2,
},
Policy{
.addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
.len = 3,
.mask = 0xff,
.prec = 5,
.label = 5,
},
Policy{
.addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
.len = 0,
.mask = 0xfe,
.prec = 3,
.label = 13,
},
// These are deprecated and/or returned to the address
// pool, so despite the RFC, treating them as special
// is probably wrong.
// { "", 11, 0xff, 1, 3 },
// { "\xfe\xc0", 1, 0xc0, 1, 11 },
// { "\x3f\xfe", 1, 0xff, 1, 12 },
// Last rule must match all addresses to stop loop.
Policy{
.addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00",
.len = 0,
.mask = 0,
.prec = 40,
.label = 1,
},
};
fn policyOf(a: [16]u8) *const Policy {
for (defined_policies) |*policy| {
if (!mem.eql(u8, a[0..policy.len], policy.addr[0..policy.len])) continue;
if ((a[policy.len] & policy.mask) != policy.addr[policy.len]) continue;
return policy;
}
unreachable;
}
fn scopeOf(a: [16]u8) u8 {
if (IN6_IS_ADDR_MULTICAST(a)) return a[1] & 15;
if (IN6_IS_ADDR_LINKLOCAL(a)) return 2;
if (IN6_IS_ADDR_LOOPBACK(a)) return 2;
if (IN6_IS_ADDR_SITELOCAL(a)) return 5;
return 14;
}
fn prefixMatch(s: [16]u8, d: [16]u8) u8 {
// TODO: This FIXME inherited from porting from musl libc.
// I don't want this to go into zig std lib 1.0.0.
// FIXME: The common prefix length should be limited to no greater
// than the nominal length of the prefix portion of the source
// address. However the definition of the source prefix length is
// not clear and thus this limiting is not yet implemented.
var i: u8 = 0;
while (i < 128 and ((s[i / 8] ^ d[i / 8]) & (u8(128) >> @intCast(u3, i % 8))) == 0) : (i += 1) {}
return i;
}
fn labelOf(a: [16]u8) u8 {
return policyOf(a).label;
}
fn IN6_IS_ADDR_MULTICAST(a: [16]u8) bool {
return a[0] == 0xff;
}
fn IN6_IS_ADDR_LINKLOCAL(a: [16]u8) bool {
return a[0] == 0xfe and (a[1] & 0xc0) == 0x80;
}
fn IN6_IS_ADDR_LOOPBACK(a: [16]u8) bool {
return a[0] == 0 and a[1] == 0 and
a[2] == 0 and
a[12] == 0 and a[13] == 0 and
a[14] == 0 and a[15] == 1;
}
fn IN6_IS_ADDR_SITELOCAL(a: [16]u8) bool {
return a[0] == 0xfe and (a[1] & 0xc0) == 0xc0;
}
// Parameters `b` and `a` swapped to make this descending.
fn addrCmpLessThan(b: LookupAddr, a: LookupAddr) bool {
return a.sortkey < b.sortkey;
}
fn linuxLookupNameFromNumericUnspec(addrs: *std.ArrayList(LookupAddr), name: []const u8) !void {
@ -770,7 +968,6 @@ fn getResolvConf(allocator: *mem.Allocator, rc: *ResolvConf) !void {
};
defer file.close();
var cnt: usize = 0;
const stream = &std.io.BufferedInStream(fs.File.ReadError).init(&file.inStream().stream).stream;
var line_buf: [512]u8 = undefined;
while (stream.readUntilDelimiterOrEof(&line_buf, '\n') catch |err| switch (err) {
@ -990,6 +1187,8 @@ fn dnsParse(
ctx: var,
comptime callback: var,
) !void {
// This implementation is ported from musl libc.
// A more idiomatic "ziggy" implementation would be welcome.
if (r.len < 12) return error.InvalidDnsPacket;
if ((r[3] & 15) != 0) return;
var p = r.ptr + 12;

View File

@ -1808,11 +1808,9 @@ pub const GetSockNameError = error{
SystemResources,
} || UnexpectedError;
pub fn getsockname(sockfd: i32) GetSockNameError!sockaddr {
var addr: sockaddr = undefined;
var addrlen: socklen_t = @sizeOf(sockaddr);
switch (errno(system.getsockname(sockfd, &addr, &addrlen))) {
0 => return addr,
pub fn getsockname(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t) GetSockNameError!void {
switch (errno(system.getsockname(sockfd, addr, addrlen))) {
0 => return,
else => |err| return unexpectedErrno(err),
EBADF => unreachable, // always a race condition
@ -2844,6 +2842,8 @@ pub fn res_mkquery(
newrr: ?[*]const u8,
buf: []u8,
) usize {
// This implementation is ported from musl libc.
// A more idiomatic "ziggy" implementation would be welcome.
var name = dname;
if (mem.endsWith(u8, name, ".")) name.len -= 1;
assert(name.len <= 253);
@ -3084,6 +3084,8 @@ pub fn dn_expand(
comp_dn: []const u8,
exp_dn: []u8,
) DnExpandError!usize {
// This implementation is ported from musl libc.
// A more idiomatic "ziggy" implementation would be welcome.
var p = comp_dn.ptr;
var len: usize = std.math.maxInt(usize);
const end = msg.ptr + msg.len;