Skip to content

Commit

Permalink
WIP: IPv4-mapped IPv6 addresses
Browse files Browse the repository at this point in the history
  • Loading branch information
donavanbecker committed Jun 24, 2024
1 parent 6b1c75b commit f0a4705
Show file tree
Hide file tree
Showing 8 changed files with 48 additions and 15 deletions.
11 changes: 6 additions & 5 deletions src/MDNSServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -436,7 +436,7 @@ export class MDNSServer {
}
assert(this.bound, "Cannot send packets before server is not bound!");

const ipHeaderSize = family === IPFamily.IPv4? MDNSServer.DEFAULT_IP4_HEADER: MDNSServer.DEFAULT_IP6_HEADER;
const ipHeaderSize = family === IPFamily.IPv4 ? MDNSServer.DEFAULT_IP4_HEADER : MDNSServer.DEFAULT_IP6_HEADER;

// RFC 6762 17.
assert(ipHeaderSize + MDNSServer.UDP_HEADER + message.length <= 9000,
Expand Down Expand Up @@ -495,7 +495,7 @@ export class MDNSServer {
});

socket.bind(MDNSServer.MDNS_PORT, () => {
socket.setRecvBufferSize(800*1024); // setting max recv buffer size to 800KiB (Pi will max out at 352KiB)
socket.setRecvBufferSize(800 * 1024); // setting max recv buffer size to 800KiB (Pi will max out at 352KiB)
socket.removeListener("error", errorHandler);

const multicastAddress = isIPv6 ? MDNSServer.MULTICAST_IPV6 : MDNSServer.MULTICAST_IPV4;
Expand Down Expand Up @@ -574,7 +574,8 @@ export class MDNSServer {

if (isIPv6) {
if (networkInterface.loopback !== rinfo.address.includes("%lo")) {
debug("Received packet on a %s interface (%s) which is coming from a %s interface (%s)", networkInterface.loopback ? "loopback" : "non-loopback", name, rinfo.address.includes("%lo") ? "loopback" : "non-loopback", rinfo.address);
debug("Received packet on a %s interface (%s) which is coming from a %s interface (%s)", networkInterface.loopback
? "loopback" : "non-loopback", name, rinfo.address.includes("%lo") ? "loopback" : "non-loopback", rinfo.address);
// return;
}
} else {
Expand All @@ -585,7 +586,7 @@ export class MDNSServer {
}
} else if (this.networkManager.isLoopbackNetaddressV4(ip4Netaddress)) {
debug("Received packet on interface '%s' which is not coming from the same subnet: %o", name,
{address: rinfo.address, netaddress: ip4Netaddress, interface: networkInterface.ipv4});
{ address: rinfo.address, netaddress: ip4Netaddress, interface: networkInterface.ipv4 });
return;
}
}
Expand Down Expand Up @@ -646,7 +647,7 @@ export class MDNSServer {
|| error.message.includes("ENETUNREACH") || error.message.includes("EHOSTUNREACH")
|| error.message.includes("EPERM") || error.message.includes("EINVAL");
if (silenced) {
debug ("Silenced and ignored error (This is/should not be a problem, this message is only for informational purposes): " + error.message);
debug("Silenced and ignored error (This is/should not be a problem, this message is only for informational purposes): " + error.message);
}
return silenced;
}
Expand Down
1 change: 1 addition & 0 deletions src/NetworkManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export type IPAddress = IPv4Address | IPv6Address;

export const enum IPFamily {
IPv4 = "IPv4",
IPv4MappedIPv6 = "IPv4MappedIPv6",
IPv6 = "IPv6",
}

Expand Down
5 changes: 5 additions & 0 deletions src/coder/ResourceRecord.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ describe(ResourceRecord, () => {
runRecordEncodingTest(new ARecord("sub.test.local.", "192.168.0.1"));
});

it("should encode IPv4-mapped IPv6 addresses in AAAA records", () => {
runRecordEncodingTest(new AAAARecord("test.local.", "::ffff:192.168.178.1"));
runRecordEncodingTest(new AAAARecord("sub.test.local.", "::ffff:192.168.0.1"));
});

it("should encode CNAME", () => {
runRecordEncodingTest(new CNAMERecord("test.local.", "test2.local."));
runRecordEncodingTest(new CNAMERecord("sub.test.local.", "test2.local."));
Expand Down
1 change: 1 addition & 0 deletions src/coder/ResourceRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export abstract class ResourceRecord implements DNSRecord { // RFC 1035 4.1.3.
ttl: number;

flushFlag = false;
address?: AddressInfo;

protected constructor(headerData: RecordRepresentation);
protected constructor(name: string, type: RType, ttl?: number, flushFlag?: boolean, clazz?: RClass);
Expand Down
13 changes: 10 additions & 3 deletions src/coder/records/AAAARecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,22 @@ export class AAAARecord extends ResourceRecord {
constructor(header: RecordRepresentation, ipAddress: string);
constructor(name: string | RecordRepresentation, ipAddress: string, flushFlag?: boolean, ttl?: number) {
if (typeof name === "string") {
super(name, RType.AAAA, ttl || AAAARecord.RR_DEFAULT_TTL_SHORT, flushFlag);
super(name, RType.AAAA, ttl || AAAARecord.DEFAULT_TTL, flushFlag);
} else {
assert(name.type === RType.AAAA);
super(name);
}

assert(net.isIPv6(ipAddress), "IP address is not in v6 format!");

// Enhanced validation to check for IPv6 and IPv4-mapped IPv6 addresses
assert(net.isIPv6(ipAddress) || this.isIPv4MappedIPv6(ipAddress), "IP address is not in v6 or IPv4-mapped v6 format!");
this.ipAddress = ipAddress;
}

// Utility method to check for IPv4-mapped IPv6 addresses
private isIPv4MappedIPv6(ipAddress: string): boolean {
const ipv4MappedIPv6Regex = /^::ffff:(0{1,4}:){0,1}((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])$/i;
return ipv4MappedIPv6Regex.test(ipAddress);
}

protected getRDataEncodingLength(): number {
return 16; // 16 byte ipv6 address
Expand Down
13 changes: 9 additions & 4 deletions src/coder/records/ARecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,19 @@ export class ARecord extends ResourceRecord {
constructor(header: RecordRepresentation, ipAddress: string);
constructor(name: string | RecordRepresentation, ipAddress: string, flushFlag?: boolean, ttl?: number) {
if (typeof name === "string") {
super(name, RType.A, ttl || ARecord.RR_DEFAULT_TTL_SHORT, flushFlag);
super(name, RType.A, ttl || ARecord.DEFAULT_TTL, flushFlag);
} else {
assert(name.type === RType.A);
super(name);
}

assert(net.isIPv4(ipAddress), "IP address is not in v4 format!");
this.ipAddress = ipAddress;

// Adjust validation to accept IPv4-mapped IPv6 addresses
const isIPv4 = net.isIPv4(ipAddress);
const isIPv4MappedIPv6 = /^::ffff:0{0,4}:((25[0-5]|(2[0-4]|1\d|\d)\d)\.){3}(25[0-5]|(2[0-4]|1\d|\d)\d)$/i.test(ipAddress);
assert(isIPv4 || isIPv4MappedIPv6, "IP address is not in v4 or IPv4-mapped v6 format!");

// Store the original IP address or convert IPv4-mapped IPv6 to IPv4
this.ipAddress = isIPv4MappedIPv6 ? ipAddress.split(":").pop() as string : ipAddress;
}

protected getRDataEncodingLength(): number {
Expand Down
12 changes: 10 additions & 2 deletions src/coder/test-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,16 @@ import { DNSPacket } from "./DNSPacket";
import { Question } from "./Question";
import { ResourceRecord } from "./ResourceRecord";

// Utility function to convert IPv4-mapped IPv6 addresses to IPv4
function convertIPv4MappedIPv6ToIPv4(address: string): string {
const ipv4MappedIPv6Regex = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/;
const match = address.match(ipv4MappedIPv6Regex);
return match ? match[1] : address;
}

// Adjusted decodeContext to use the utility function for the address
const decodeContext: AddressInfo = {
address: "0.0.0.0",
address: convertIPv4MappedIPv6ToIPv4("::ffff:0.0.0.0"),
family: "ipv4",
port: 5353,
};
Expand All @@ -23,12 +31,12 @@ export function runRecordEncodingTest(record: Question | ResourceRecord, legacyU
coder = new DNSLabelCoder(legacyUnicast);
coder.initBuf(buffer);

// Adjusted to use the potentially converted address in decodeContext
const decodedRecord = record instanceof Question
? Question.decode(decodeContext, coder, buffer, 0)
: ResourceRecord.decode(decodeContext, coder, buffer, 0);
expect(decodedRecord.readBytes).toBe(buffer.length);

//
const record2 = decodedRecord.data!;
expect(record2).toBeDefined();

Expand Down
7 changes: 6 additions & 1 deletion src/util/domain-formatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,12 @@ export function removeTLD(hostname: string): string {
export function enlargeIPv6(address: string): string {
assert(net.isIPv6(address), "Illegal argument. Must be ipv6 address!");

// we are not supporting ipv4-mapped ipv6 addresses here
// Check if the address is an IPv4-mapped IPv6 address
if (address.includes(".")) {
const ipv4Address = address.split(".").slice(-4).join(".");
return ipv4Address;
}

assert(!address.includes("."), "ipv4-mapped ipv6 addresses are currently unsupported!");

const split = address.split(":");
Expand Down

0 comments on commit f0a4705

Please sign in to comment.