An IPv6 address in binary memory layout.
pub const Ip6Address = struct
pub const Ip6Address = struct {
/// Native endian
port: u16,
/// Big endian
bytes: [16]u8,
flow: u32 = 0,
interface: Interface = .none,
pub const Policy = struct {
addr: [16]u8,
len: u8,
mask: u8,
prec: u8,
label: u8,
};
pub fn loopback(port: u16) Ip6Address {
return .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 },
.port = port,
};
}
pub fn unspecified(port: u16) Ip6Address {
return .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.port = port,
};
}
/// Constructs an IPv4-mapped IPv6 address.
pub fn fromIp4(ip4: Ip4Address) Ip6Address {
return .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff } ++ ip4.bytes,
.port = ip4.port,
};
}
/// Given an `IpAddress`, converts it to an `Ip6Address` directly, or via
/// constructing an IPv4-mapped IPv6 address.
pub fn fromAny(addr: IpAddress) Ip6Address {
return switch (addr) {
.ip4 => |ip4| fromIp4(ip4),
.ip6 => |ip6| ip6,
};
}
/// An IPv6 address but with `Interface` as a name rather than index.
pub const Unresolved = struct {
/// Big endian
bytes: [16]u8,
/// Has not been checked to be a valid native interface name.
/// Externally managed memory.
interface_name: ?[]const u8,
pub const Parsed = union(enum) {
success: Unresolved,
invalid_byte: usize,
incomplete,
junk_after_end: usize,
interface_name_oversized: usize,
invalid_ip4_mapping: usize,
overflow: usize,
};
pub fn parse(text: []const u8) Parsed {
if (text.len < 2) return .incomplete;
const ip4_prefix = "::ffff:";
if (std.ascii.startsWithIgnoreCase(text, ip4_prefix)) {
const parsed = Ip4Address.parse(text[ip4_prefix.len..], 0) catch
return .{ .invalid_ip4_mapping = ip4_prefix.len };
const b = parsed.bytes;
return .{ .success = .{
.bytes = .{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff, b[0], b[1], b[2], b[3] },
.interface_name = null,
} };
}
// Has to be u16 elements to handle 3-digit hex numbers from compression.
var parts: [8]u16 = @splat(0);
var parts_i: u8 = 0;
var text_i: u8 = 0;
var digit_i: u8 = 0;
var compress_start: ?u8 = null;
var interface_name_text: ?[]const u8 = null;
const State = union(enum) { digit, end };
state: switch (State.digit) {
.digit => c: switch (text[text_i]) {
'a'...'f' => |c| {
const digit = c - 'a' + 10;
parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
.overflow = text_i,
}) + digit;
if (digit_i == 4) return .{ .invalid_byte = text_i };
digit_i += 1;
text_i += 1;
if (text.len - text_i == 0) {
parts_i += 1;
continue :state .end;
}
continue :c text[text_i];
},
'A'...'F' => |c| continue :c c - 'A' + 'a',
'0'...'9' => |c| {
const digit = c - '0';
parts[parts_i] = (std.math.mul(u16, parts[parts_i], 16) catch return .{
.overflow = text_i,
}) + digit;
if (digit_i == 4) return .{ .invalid_byte = text_i };
digit_i += 1;
text_i += 1;
if (text.len - text_i == 0) {
parts_i += 1;
continue :state .end;
}
continue :c text[text_i];
},
':' => {
if (digit_i == 0) {
if (compress_start != null) return .{ .invalid_byte = text_i };
if (text_i == 0) {
text_i += 1;
if (text[text_i] != ':') return .{ .invalid_byte = text_i };
assert(parts_i == 0);
}
compress_start = parts_i;
text_i += 1;
if (text.len - text_i == 0) continue :state .end;
continue :c text[text_i];
} else {
parts_i += 1;
if (parts.len - parts_i == 0) continue :state .end;
digit_i = 0;
text_i += 1;
if (text.len - text_i == 0) return .incomplete;
continue :c text[text_i];
}
},
'%' => {
if (digit_i == 0) return .{ .invalid_byte = text_i };
parts_i += 1;
text_i += 1;
const name = text[text_i..];
if (name.len == 0) return .incomplete;
interface_name_text = name;
text_i = std.math.cast(u8, text.len) orelse return .{ .overflow = text.len };
continue :state .end;
},
else => return .{ .invalid_byte = text_i },
},
.end => {
if (text.len - text_i != 0) return .{ .junk_after_end = text_i };
const remaining = parts.len - parts_i;
if (compress_start) |s| {
const src = parts[s..parts_i];
@memmove(parts[parts.len - src.len ..], src);
@memset(parts[s..][0..remaining], 0);
} else {
if (remaining != 0) return .incomplete;
}
for (&parts) |*part| part.* = @byteSwap(part.*);
return .{ .success = .{
.bytes = @bitCast(parts),
.interface_name = interface_name_text,
} };
},
}
}
pub fn format(u: *const Unresolved, w: *Io.Writer) Io.Writer.Error!void {
const bytes = &u.bytes;
if (std.mem.eql(u8, bytes[0..12], &[_]u8{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xff, 0xff })) {
try w.print("::ffff:{d}.{d}.{d}.{d}", .{ bytes[12], bytes[13], bytes[14], bytes[15] });
} else {
const parts: [8]u16 = .{
std.mem.readInt(u16, bytes[0..2], .big),
std.mem.readInt(u16, bytes[2..4], .big),
std.mem.readInt(u16, bytes[4..6], .big),
std.mem.readInt(u16, bytes[6..8], .big),
std.mem.readInt(u16, bytes[8..10], .big),
std.mem.readInt(u16, bytes[10..12], .big),
std.mem.readInt(u16, bytes[12..14], .big),
std.mem.readInt(u16, bytes[14..16], .big),
};
// Find the longest zero run
var longest_start: usize = 8;
var longest_len: usize = 0;
var current_start: usize = 0;
var current_len: usize = 0;
for (parts, 0..) |part, i| {
if (part == 0) {
if (current_len == 0) {
current_start = i;
}
current_len += 1;
if (current_len > longest_len) {
longest_start = current_start;
longest_len = current_len;
}
} else {
current_len = 0;
}
}
// Only compress if the longest zero run is 2 or more
if (longest_len < 2) {
longest_start = 8;
longest_len = 0;
}
var i: usize = 0;
while (parts.len - i != 0) : (i += 1) {
if (i == longest_start) {
// Emit "::" for the longest zero run
try w.writeAll(if (i == 0) "::" else ":");
i += longest_len - 1; // Skip the compressed range
continue;
}
try w.print("{x}", .{parts[i]});
if (i != parts.len - 1) {
try w.writeAll(":");
}
}
}
if (u.interface_name) |n| try w.print("%{s}", .{n});
}
};
pub const ParseError = error{
/// If this is returned, more detailed diagnostics can be obtained by
/// calling `Ip6Address.Parsed.init`.
ParseFailed,
/// If this is returned, the IPv6 address had a scope id on it ("%foo"
/// at the end) which requires calling `resolve`.
UnresolvedScope,
};
/// This is a pure function but it cannot handle IPv6 addresses that have
/// scope ids ("%foo" at the end). To also handle those, `resolve` must be
/// called instead, or the lower level `Unresolved` API may be used.
pub fn parse(buffer: []const u8, port: u16) ParseError!Ip6Address {
switch (Unresolved.parse(buffer)) {
.success => |p| return .{
.bytes = p.bytes,
.port = port,
.interface = if (p.interface_name != null) return error.UnresolvedScope else .none,
},
else => return error.ParseFailed,
}
return .{ .ip6 = try Ip6Address.parse(buffer, port) };
}
pub const ResolveError = error{
/// If this is returned, more detailed diagnostics can be obtained by
/// calling the `Parsed.init` function.
ParseFailed,
/// The interface name is longer than the host operating system supports.
NameTooLong,
} || Interface.Name.ResolveError;
/// This function requires an `Io` parameter because it must query the operating
/// system to convert interface name to index. For example, in
/// "fe80::e0e:76ff:fed4:cf22%eno1", "eno1" must be resolved to an index by
/// creating a socket and then using an `ioctl` syscall.
pub fn resolve(io: Io, buffer: []const u8, port: u16) ResolveError!Ip6Address {
return switch (Unresolved.parse(buffer)) {
.success => |p| return .{
.bytes = p.bytes,
.port = port,
.interface = i: {
const text = p.interface_name orelse break :i .none;
const name: Interface.Name = try .fromSlice(text);
break :i try name.resolve(io);
},
},
else => return error.ParseFailed,
};
}
pub const FormatError = Io.Writer.Error || Interface.NameError;
/// Includes the optional scope ("%foo" at the end).
///
/// See `format` for an alternative that omits scopes and does
/// not require an `Io` parameter.
pub fn formatResolved(a: *const Ip6Address, io: Io, w: *Io.Writer) FormatError!void {
const interface_name = if (a.interface.isNone()) null else try a.interface.name(io);
const u: Unresolved = .{
.bytes = a.bytes,
.interface_name = if (interface_name) |name| name.toSlice() else null,
};
try w.print("[{f}]:{d}", .{ u, a.port });
}
/// See `formatResolved` for an alternative that additionally prints the optional
/// scope at the end of addresses and requires an `Io` parameter.
pub fn format(a: *const Ip6Address, w: *Io.Writer) Io.Writer.Error!void {
const u: Unresolved = .{ .bytes = a.bytes, .interface_name = null };
try w.print("[{f}]:{d}", .{ u, a.port });
}
pub fn eql(a: Ip6Address, b: Ip6Address) bool {
return a.port == b.port and std.mem.eql(u8, &a.bytes, &b.bytes);
}
pub fn isMultiCast(a: Ip6Address) bool {
return a.bytes[0] == 0xff;
}
pub fn isLinkLocal(a: Ip6Address) bool {
const b = &a.bytes;
return b[0] == 0xfe and (b[1] & 0xc0) == 0x80;
}
pub fn isLoopBack(a: Ip6Address) bool {
const b = &a.bytes;
return b[0] == 0 and b[1] == 0 and
b[2] == 0 and
b[12] == 0 and b[13] == 0 and
b[14] == 0 and b[15] == 1;
}
pub fn isSiteLocal(a: Ip6Address) bool {
const b = &a.bytes;
return b[0] == 0xfe and (b[1] & 0xc0) == 0xc0;
}
pub fn policy(a: Ip6Address) *const Policy {
const b = &a.bytes;
for (&defined_policies) |*p| {
if (!std.mem.eql(u8, b[0..p.len], p.addr[0..p.len])) continue;
if ((b[p.len] & p.mask) != p.addr[p.len]) continue;
return p;
}
unreachable;
}
pub fn scope(a: Ip6Address) u8 {
if (isMultiCast(a)) return a.bytes[1] & 15;
if (isLinkLocal(a)) return 2;
if (isLoopBack(a)) return 2;
if (isSiteLocal(a)) return 5;
return 14;
}
const defined_policies = [_]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,
},
.{
.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,
},
.{
.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,
},
.{
.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,
},
.{
.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.
.{
.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,
},
};
}