-
Notifications
You must be signed in to change notification settings - Fork 6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding classless IN-ADDR.ARPA (IPv4) and IP6.ARPA (IPv6) delegations (reverse lookups) #12
Changes from all commits
c25d9b8
493a5df
e79b9fb
a94301d
5a210f8
f8838b4
dce7462
0cc970c
45f4d27
61557b8
2c30d36
348a80f
c5531e9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,160 @@ | ||
const std = @import("std"); | ||
const assert = std.debug.assert; | ||
|
||
const AddressType = union(enum) { Ipv4, Ipv6 }; | ||
|
||
const Errors = error{InvalidIP}; | ||
|
||
pub const AddressMeta = union(enum) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why isn't |
||
address: []const u8, | ||
hexAddress: []const u8, | ||
type: AddressType, | ||
|
||
const Self = @This(); | ||
|
||
pub fn Ipv4() Self { | ||
return Self{ | ||
.type = .Ipv4, | ||
}; | ||
} | ||
|
||
pub fn Ipv6() Self { | ||
return Self{ | ||
.type = .Ipv6, | ||
}; | ||
} | ||
|
||
/// Creates an IpAddress from a []const u8 address | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
pub fn fromString(self: Self, ip_address: []const u8) !Self { | ||
try self.valid(ip_address); | ||
|
||
return AddressMeta{ .address = ip_address }; | ||
} | ||
|
||
/// Validates the calling ip address is actually valid | ||
pub fn valid(self: Self, ip_address: []const u8) !void { | ||
switch (self.type) { | ||
.Ipv4 => { | ||
_ = try std.net.Ip4Address.parse(ip_address, 0); | ||
}, | ||
.Ipv6 => { | ||
_ = try std.net.Ip6Address.parse(ip_address, 0); | ||
}, | ||
} | ||
} | ||
}; | ||
|
||
// RFC reference for ARPA address formation: https://www.ietf.org/rfc/rfc2317.txt | ||
pub const IpAddress = struct { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this probably should be renamed to |
||
address: AddressMeta, | ||
leastSignificationShiftValue: u16 = 0xFF, // Least significant bitmask value | ||
// Masks for nibble shifting for ipv6 | ||
nib_shift_low: u16 = 0x0F, // Lowest significant bit | ||
nib_shift_high: u16 = 0xF0, // Most significant bit | ||
arpa_suffix: []const u8 = ".in-addr.arpa", | ||
arpa_suffix_ipv6: []const u8 = "ip6.arpa", | ||
|
||
allocator: std.mem.Allocator, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the design of |
||
|
||
const Self = @This(); | ||
|
||
pub fn init(allocator: std.mem.Allocator, address: AddressMeta) !Self { | ||
return Self{ | ||
.allocator = allocator, | ||
.address = address, | ||
}; | ||
} | ||
|
||
/// Reverse IP address for Classless IN-ADDR.ARPA delegation | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add this also applies to |
||
pub fn reverseIpv4(self: Self) ![]const u8 { | ||
const ip = try std.net.Ip4Address.parse(self.address.address, 0); | ||
// ip.sa.addr is the raw addr u32 representation of the parsed address. | ||
var shifted_ip = self.bitmask(ip.sa.addr); | ||
|
||
// Just use native zig reverse | ||
std.mem.reverse(u32, &shifted_ip); | ||
|
||
// Note - If we buf print here, buffer will fill with nullbytes when we use dns.Name.fromString(buf, &alloc_locatio); So we alloc print here to avoid future complications | ||
return try std.fmt.allocPrint(self.allocator, "{d}.{d}.{d}.{d}{s}", .{ shifted_ip[0], shifted_ip[1], shifted_ip[2], shifted_ip[3], self.arpa_suffix }); | ||
} | ||
|
||
/// Reverse ipv6 address as per RFC: https://datatracker.ietf.org/doc/html/rfc3596 | ||
/// https://datatracker.ietf.org/doc/html/rfc3596#section-2.5 (notable section referenced) | ||
pub fn reverseIpv6(self: Self) ![]const u8 { | ||
var parsed_address = try std.net.Ip6Address.parse(self.address.address, 0); | ||
|
||
std.mem.reverse(u8, &parsed_address.sa.addr); | ||
|
||
var ipv6_parsed: []const u8 = undefined; | ||
var index: usize = 0; | ||
for (parsed_address.sa.addr) |v| { | ||
// v is a byte at this point (8 bits) | ||
// Create a nibble, bitshift/swap the nibble values and convert to hex to build the arpa address | ||
const low_nibble = v & self.nib_shift_low; | ||
const high_nibble = ((v & self.nib_shift_high) >> 4); | ||
|
||
// This string formatting is a little annoying and there may be a better way | ||
if (index == 0) { | ||
ipv6_parsed = try std.fmt.allocPrint(std.heap.page_allocator, "{x}.{x}", .{ low_nibble, high_nibble }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. the given |
||
|
||
index += 1; | ||
|
||
continue; | ||
} | ||
|
||
if (index == parsed_address.sa.addr.len - 1) { | ||
ipv6_parsed = try std.fmt.allocPrint(std.heap.page_allocator, "{s}.{x}.{x}.{s}", .{ ipv6_parsed, low_nibble, high_nibble, self.arpa_suffix_ipv6 }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this function leaks memory as the previous |
||
} else { | ||
ipv6_parsed = try std.fmt.allocPrint(std.heap.page_allocator, "{s}.{x}.{x}", .{ ipv6_parsed, low_nibble, high_nibble }); | ||
} | ||
|
||
index += 1; | ||
} | ||
|
||
return ipv6_parsed; | ||
} | ||
|
||
/// Converts from the little-endian hex values. Used for addresses stored on disk (Unix hosts) from sectors like /proc/net/tcp || /proc/net/udp | ||
pub fn hexConvertAddress(self: Self) ![4]u32 { | ||
return self.bitmask(try std.fmt.parseInt(u32, self.address.hexAddress, 16)); | ||
} | ||
|
||
// Bit masking to ascertain least significant bit for parsing Ipv4 out of u32 | ||
fn bitmask(self: Self, value: u32) [4]u32 { | ||
const b1 = (value & self.leastSignificationShiftValue); | ||
const b2 = (value >> 8) & self.leastSignificationShiftValue; | ||
const b3 = (value >> 16) & self.leastSignificationShiftValue; | ||
const b4 = (value >> 24) & self.leastSignificationShiftValue; | ||
|
||
return [4]u32{ b1, b2, b3, b4 }; | ||
} | ||
}; | ||
|
||
test "test bitmask and reverseral of ip address" { | ||
const ip = IpAddress{ .address = .{ .address = "8.8.4.4" }, .allocator = std.heap.page_allocator }; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use (applies to other tests) |
||
const reversed = try ip.reverseIpv4(); | ||
|
||
assert(std.mem.eql(u8, reversed, "4.4.8.8.in-addr.arpa")); | ||
} | ||
|
||
test "text hex conversion into IP address" { | ||
const hex_address: []const u8 = "0100007F"; // Converted to "0x0100007F for 127.0.0.1" | ||
|
||
const ip = IpAddress{ .address = .{ .hexAddress = hex_address }, .allocator = std.heap.page_allocator }; | ||
const hex_converted = try ip.hexConvertAddress(); | ||
|
||
var buf: [512]u8 = undefined; | ||
const c_val = try std.fmt.bufPrint(&buf, "{d}.{d}.{d}.{d}", .{ hex_converted[0], hex_converted[1], hex_converted[2], hex_converted[3] }); | ||
|
||
assert(std.mem.eql(u8, c_val, "127.0.0.1")); | ||
} | ||
|
||
test "test reversal of ipv6 address" { | ||
const address_reversed = "b.a.9.8.7.6.5.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2.ip6.arpa"; | ||
const address = "2001:db8::567:89ab"; | ||
|
||
const ip = IpAddress{ .address = .{ .address = address }, .allocator = std.heap.page_allocator }; | ||
const ipv6_parsed = try ip.reverseIpv6(); | ||
|
||
assert(std.mem.eql(u8, address_reversed, ipv6_parsed)); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
const std = @import("std"); | ||
const address = @import("./address.zig"); | ||
const dns = @import("./lib.zig"); | ||
const assert = std.debug.assert; | ||
|
||
/// ReverseLookup is the primary interface for classless IN-ADDR.ARPA (IPv4) and IP6.ARPA (IPv6) delegations | ||
pub const ReverseLookup = struct { | ||
allocator: std.mem.Allocator, | ||
ip_address: []const u8, | ||
packet_id: u16, // Arbitrary packet ID | ||
|
||
const Self = @This(); | ||
const max_ipv6_label_size = 35; | ||
const max_ipv4_label_size = 6; | ||
|
||
pub fn init(allocator: std.mem.Allocator, ip_address: []const u8, packet_id: u16) !Self { | ||
return Self{ | ||
.allocator = allocator, | ||
.ip_address = ip_address, | ||
.packet_id = packet_id, | ||
}; | ||
} | ||
|
||
/// Reverse lookup on a given ipv4 ip address | ||
pub fn lookupIpv4(self: Self) ![][]const u8 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if |
||
var add = try address.IpAddress.init(self.allocator, try address.AddressMeta.Ipv4().fromString(self.ip_address)); | ||
const arpa_address = try add.reverseIpv4(); | ||
|
||
var labels: [max_ipv4_label_size][]const u8 = undefined; | ||
const apra_address_dns_name = try dns.Name.fromString(arpa_address, &labels); | ||
|
||
return try self.buildAndSendPacket(apra_address_dns_name); | ||
} | ||
|
||
/// Reverse lookup on a given ipv6 ip address | ||
pub fn lookupIpv6(self: Self) ![][]const u8 { | ||
var add = try address.IpAddress.init(self.allocator, try address.AddressMeta.Ipv6().fromString(self.ip_address)); | ||
const arpa_address = try add.reverseIpv6(); | ||
|
||
var labels: [max_ipv6_label_size][]const u8 = undefined; | ||
const apra_address_dns_name = try dns.Name.fromString(arpa_address, &labels); | ||
|
||
return self.buildAndSendPacket(apra_address_dns_name) catch &[_][]const u8{}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. this should return the error instead of an empty stack-allocated array. currently this makes it not clear who owns the memory given by this function, as an error in example: const std = @import("std");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
const allocator = gpa.allocator();
const coolData = &[1][]const u8{"test"};
allocator.free(coolData);
}
|
||
} | ||
|
||
/// Internal function to build and send the DNS packet for reverse lookup agnostic of IP address type (ipv4 | ipv6) | ||
fn buildAndSendPacket(self: Self, apra_address_dns_name: dns.Name) ![][]const u8 { | ||
var name_pool = dns.NamePool.init(self.allocator); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. if this helper class can not operate with an allocator-free paradigm, then it should be marked as such in the main doc comment for |
||
defer name_pool.deinitWithNames(); | ||
|
||
var question = [_]dns.Question{.{ | ||
.class = .IN, | ||
.typ = .PTR, | ||
.name = apra_address_dns_name, | ||
}}; | ||
|
||
var empty = [_]dns.Resource{}; | ||
|
||
const packet = dns.Packet{ | ||
.header = .{ | ||
.id = self.packet_id, | ||
.wanted_recursion = true, // Need recursion of at least 1 depth for reverse lookup | ||
.answer_length = 0, // 0 for reverse query | ||
.question_length = 1, | ||
.nameserver_length = 0, | ||
.additional_length = 0, | ||
.opcode = .Query, | ||
}, | ||
.questions = &question, | ||
.answers = &empty, | ||
.nameservers = &empty, | ||
.additionals = &[_]dns.Resource{}, | ||
}; | ||
|
||
const conn = try dns.helpers.connectToSystemResolver(); | ||
defer conn.close(); | ||
try conn.sendPacket(packet); | ||
|
||
const reply = try conn.receiveFullPacket( | ||
self.allocator, | ||
4096, | ||
.{ .name_pool = &name_pool }, | ||
); | ||
defer reply.deinit(.{ .names = false }); | ||
|
||
const reply_packet = reply.packet; | ||
|
||
// Parse reply packet, if there are answers return them in a serialized fashion for human consumption. Else empty | ||
if (reply_packet.answers.len > 0) { | ||
var dns_names = try self.allocator.alloc([]u8, reply_packet.answers.len); | ||
var index: usize = 0; | ||
for (reply_packet.answers) |resource| { | ||
const resource_data = try dns.ResourceData.fromOpaque( | ||
resource.typ, | ||
resource.opaque_rdata.?, | ||
.{ | ||
.name_provider = .{ .full = &name_pool }, | ||
.allocator = name_pool.allocator, | ||
}, | ||
); | ||
|
||
//fromOpaque read the data, so if we utilize any standard reader/writer we have access to the []const u8 opaque data value. So we just allocprint here | ||
const dns_name = try std.fmt.allocPrint(self.allocator, "{s}", .{resource_data}); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you should verify that the returned |
||
|
||
dns_names[index] = dns_name; | ||
index += 1; | ||
} | ||
|
||
return dns_names; | ||
} else { | ||
return &[_][]const u8{}; // empty | ||
} | ||
} | ||
}; | ||
|
||
test "reverse lookup of Ipv4 address" { | ||
const name = "dns.google."; | ||
const test_address = "8.8.4.4"; | ||
var reverse = try ReverseLookup.init(std.heap.page_allocator, test_address, 123); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
const names = try reverse.lookupIpv4(); | ||
|
||
assert(names.len > 0); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
assert(std.mem.eql(u8, names[0], name)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. use |
||
|
||
// Test when no name matches | ||
const non_address = "123.123.123.123"; | ||
reverse = try ReverseLookup.init(std.heap.page_allocator, non_address, 123); | ||
const names_non = try reverse.lookupIpv4(); | ||
|
||
// This should be empty | ||
assert(names_non.len == 0); | ||
} | ||
|
||
test "reverse lookup of ipv6" { | ||
// Test existing Ipv6 address (google dns - same as 8.8.4.4) | ||
const google_ipv6_address = "2001:4860:4860::8888"; | ||
const name = "dns.google."; | ||
var reverse = try ReverseLookup.init(std.heap.page_allocator, google_ipv6_address, 123); | ||
const names = try reverse.lookupIpv6(); | ||
|
||
assert(names.len > 0); | ||
assert(std.mem.eql(u8, names[0], name)); | ||
|
||
// // Test when no name matches (localhost ipv6) | ||
const non_existent_ipv6 = "2001:6665:1234::1234"; | ||
reverse = try ReverseLookup.init(std.heap.page_allocator, non_existent_ipv6, 124); | ||
const names_non = try reverse.lookupIpv6(); | ||
|
||
// This should be empty | ||
assert(names_non.len == 0); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name
is not being used in this example, why is that?