authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-07 00:20:30-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
log4e887625d45593f6053624858f85bcb848c13a62
tree4f067e734fe66c7bb8b27377550d655f128eacb3
parent676f1b492ed8d311ff97335b4311302dfce9c0c8

Io.net: implement sortLookupResults


1 files changed, 152 insertions(+), 3 deletions(-)

lib/std/Io/net.zig+152-3
...@@ -132,9 +132,58 @@ pub const HostName = struct {...@@ -132,9 +132,58 @@ pub const HostName = struct {
132 }132 }
133133
134 fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult {134 fn sortLookupResults(options: LookupOptions, result: LookupResult) !LookupResult {
135 _ = options;135 const addresses = options.addresses_buffer[0..result.addresses_len];
136 _ = result;136 // No further processing is needed if there are fewer than 2 results or
137 @panic("TODO");137 // if there are only IPv4 results.
138 if (addresses.len < 2) return result;
139 const all_ip4 = for (addresses) |a| switch (a) {
140 .ip4 => continue,
141 .ip6 => break false,
142 } else true;
143 if (all_ip4) return result;
144
145 // RFC 3484/6724 describes how destination address selection is
146 // supposed to work. However, to implement it requires making a bunch
147 // of networking syscalls, which is unnecessarily high latency,
148 // especially if implemented serially. Furthermore, rules 3, 4, and 7
149 // have excessive runtime and code size cost and dubious benefit.
150 //
151 // Therefore, this logic sorts only using values available without
152 // doing any syscalls, relying on the calling code to have a
153 // meta-strategy such as attempting connection to multiple results at
154 // once and keeping the fastest response while canceling the others.
155
156 const S = struct {
157 pub fn lessThan(s: @This(), lhs: IpAddress, rhs: IpAddress) bool {
158 return sortKey(s, lhs) < sortKey(s, rhs);
159 }
160
161 fn sortKey(s: @This(), a: IpAddress) i32 {
162 _ = s;
163 var da6: Ip6Address = .{
164 .port = 65535,
165 .bytes = undefined,
166 };
167 switch (a) {
168 .ip6 => |ip6| {
169 da6.bytes = ip6.bytes;
170 da6.scope_id = ip6.scope_id;
171 },
172 .ip4 => |ip4| {
173 da6.bytes[0..12].* = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff".*;
174 da6.bytes[12..].* = ip4.bytes;
175 },
176 }
177 const da6_scope: i32 = da6.scope();
178 const da6_prec: i32 = da6.policy().prec;
179 var key: i32 = 0;
180 key |= da6_prec << 20;
181 key |= (15 - da6_scope) << 16;
182 return key;
183 }
184 };
185 std.mem.sort(IpAddress, addresses, @as(S, .{}), S.lessThan);
186 return result;
138 }187 }
139188
140 fn lookupDns(io: Io, options: LookupOptions) !LookupResult {189 fn lookupDns(io: Io, options: LookupOptions) !LookupResult {
...@@ -406,6 +455,14 @@ pub const Ip6Address = struct {...@@ -406,6 +455,14 @@ pub const Ip6Address = struct {
406 Incomplete,455 Incomplete,
407 };456 };
408457
458 pub const Policy = struct {
459 addr: [16]u8,
460 len: u8,
461 mask: u8,
462 prec: u8,
463 label: u8,
464 };
465
409 pub fn localhost(port: u16) Ip6Address {466 pub fn localhost(port: u16) Ip6Address {
410 return .{467 return .{
411 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },468 .bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
...@@ -597,6 +654,98 @@ pub const Ip6Address = struct {...@@ -597,6 +654,98 @@ pub const Ip6Address = struct {
597 pub fn eql(a: Ip6Address, b: Ip6Address) bool {654 pub fn eql(a: Ip6Address, b: Ip6Address) bool {
598 return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes);655 return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes);
599 }656 }
657
658 pub fn isMultiCast(a: Ip6Address) bool {
659 return a.bytes[0] == 0xff;
660 }
661
662 pub fn isLinkLocal(a: Ip6Address) bool {
663 const b = &a.bytes;
664 return b[0] == 0xfe and (b[1] & 0xc0) == 0x80;
665 }
666
667 pub fn isLoopBack(a: Ip6Address) bool {
668 const b = &a.bytes;
669 return b[0] == 0 and b[1] == 0 and
670 b[2] == 0 and
671 b[12] == 0 and b[13] == 0 and
672 b[14] == 0 and b[15] == 1;
673 }
674
675 pub fn isSiteLocal(a: Ip6Address) bool {
676 const b = &a.bytes;
677 return b[0] == 0xfe and (b[1] & 0xc0) == 0xc0;
678 }
679
680 pub fn policy(a: Ip6Address) *const Policy {
681 const b = &a.bytes;
682 for (&defined_policies) |*p| {
683 if (!std.mem.eql(u8, b[0..p.len], p.addr[0..p.len])) continue;
684 if ((b[p.len] & p.mask) != p.addr[p.len]) continue;
685 return p;
686 }
687 unreachable;
688 }
689
690 pub fn scope(a: Ip6Address) u8 {
691 if (isMultiCast(a)) return a.bytes[1] & 15;
692 if (isLinkLocal(a)) return 2;
693 if (isLoopBack(a)) return 2;
694 if (isSiteLocal(a)) return 5;
695 return 14;
696 }
697
698 const defined_policies = [_]Policy{
699 .{
700 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x01".*,
701 .len = 15,
702 .mask = 0xff,
703 .prec = 50,
704 .label = 0,
705 },
706 .{
707 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\x00\x00\x00".*,
708 .len = 11,
709 .mask = 0xff,
710 .prec = 35,
711 .label = 4,
712 },
713 .{
714 .addr = "\x20\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
715 .len = 1,
716 .mask = 0xff,
717 .prec = 30,
718 .label = 2,
719 },
720 .{
721 .addr = "\x20\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
722 .len = 3,
723 .mask = 0xff,
724 .prec = 5,
725 .label = 5,
726 },
727 .{
728 .addr = "\xfc\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
729 .len = 0,
730 .mask = 0xfe,
731 .prec = 3,
732 .label = 13,
733 },
734 // These are deprecated and/or returned to the address
735 // pool, so despite the RFC, treating them as special
736 // is probably wrong.
737 // { "", 11, 0xff, 1, 3 },
738 // { "\xfe\xc0", 1, 0xc0, 1, 11 },
739 // { "\x3f\xfe", 1, 0xff, 1, 12 },
740 // Last rule must match all addresses to stop loop.
741 .{
742 .addr = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00".*,
743 .len = 0,
744 .mask = 0,
745 .prec = 40,
746 .label = 1,
747 },
748 };
600};749};
601750
602pub const Stream = struct {751pub const Stream = struct {