Skip to content

Commit

Permalink
Cleanup and refactor getLocalNetworkInterface and address a potential…
Browse files Browse the repository at this point in the history
… edge case. (#1056)
  • Loading branch information
hjdhjd authored Jul 8, 2024
1 parent 7656ed2 commit c4fd1d4
Showing 1 changed file with 58 additions and 59 deletions.
117 changes: 58 additions & 59 deletions src/lib/util/eventedhttp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -807,86 +807,85 @@ export class HAPConnection extends EventEmitter {
}

public getLocalAddress(ipVersion: "ipv4" | "ipv6"): string {
const infos = os.networkInterfaces()[this.networkInterface];
const interfaceDetails = os.networkInterfaces()[this.networkInterface];
if (!interfaceDetails) {
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
}

// Find our first local IPv4 address.
if (ipVersion === "ipv4") {
if (infos) {
for (const info of infos) {
// @ts-expect-error Nodejs 18+ uses the number 4 the string "IPv4"
if (info.family === "IPv4" || info.family === 4) {
return info.address;
}
}
}
const ipv4Info = interfaceDetails.find(info => info.family === "IPv4");

throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
} else {
let localUniqueAddress: string | undefined = undefined;

if (infos) {
for (const info of infos) {
// @ts-expect-error Nodejs 18+ uses the number 6 instead of the string "IPv6"
if (info.family === "IPv6" || info.family === 6) {
if (!info.scopeid) {
return info.address;
} else if (!localUniqueAddress) {
localUniqueAddress = info.address;
}
}
}
if (ipv4Info) {
return ipv4Info.address;
}

if (!localUniqueAddress) {
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface + ".");
}

let localUniqueAddress;

for (const v6entry of interfaceDetails.filter(entry => entry.family === "IPv6")) {
if (!v6entry.scopeid) {
return v6entry.address;
}

localUniqueAddress ??= v6entry.address;
}

if(localUniqueAddress) {
return localUniqueAddress;
}

throw new Error("Could not find " + ipVersion + " address for interface " + this.networkInterface);
}

private static getLocalNetworkInterface(socket: Socket): string {
let localAddress = socket.localAddress as string;

if (localAddress.startsWith("::ffff:")) { // IPv4-Mapped IPv6 Address https://tools.ietf.org/html/rfc4291#section-2.5.5.2
localAddress = localAddress.substring(7);
} else {
const index = localAddress.indexOf("%");
if (index !== -1) { // link-local ipv6
localAddress = localAddress.substring(0, index);
}
}
let localAddress = socket.localAddress;

// Grab the list of network interfaces.
const interfaces = os.networkInterfaces();
for (const [name, infos] of Object.entries(interfaces)) {
if (infos) {
for (const info of infos) {
if (info.address === localAddress) {
return name;
}
}

// Default to the first non-loopback interface we see.
const defaultInterface = () => Object.entries(interfaces).find(([name, addresses]) => addresses?.some(address => !address.internal))?.[0] ?? "unknown";

// No local address return our default.
if(!localAddress) {
return defaultInterface();
}

// Handle IPv4-mapped IPv6 addresses.
localAddress = localAddress.replace(/^::ffff:/i, "");

// Handle edge cases where we have an IPv4-mapped IPv6 address without the requisite prefix.
if(/^::(?:\d{1,3}\.){3}\d{1,3}$/.test(localAddress)) {
localAddress = localAddress.replace(/^::/, "");
}

// Handle link-local IPv6 addresses.
localAddress = localAddress.split("%")[0];

// Let's find an exact match using the IP.
for (const [name, addresses] of Object.entries(interfaces)) {
if (addresses?.some(({ address }) => address === localAddress)) {
return name;
}
}

// we couldn't map the address from above, we try now to match subnets (see https://github.com/homebridge/HAP-NodeJS/issues/847)
// We couldn't find an interface to match the address from above, so we attempt to match subnets (see https://github.com/homebridge/HAP-NodeJS/issues/847).
const family = net.isIPv4(localAddress)? "IPv4": "IPv6";
for (const [name, infos] of Object.entries(interfaces)) {
if (infos) {
for (const info of infos) {
if (info.family !== family) {
continue;
}

// check if the localAddress is in the same subnet
if (getNetAddress(localAddress, info.netmask) === getNetAddress(info.address, info.netmask)) {
return name;
}
}

// Let's find a match based on the subnet.
for (const [name, addresses] of Object.entries(interfaces)) {
if (addresses?.some(entry => entry.family === family && getNetAddress(localAddress, entry.netmask) === getNetAddress(entry.address, entry.netmask))) {
return name;
}
}

console.log(`WARNING couldn't map socket coming from remote address ${socket.remoteAddress}:${socket.remotePort} \
at local address ${socket.localAddress} to a interface!`);
console.log("WARNING: unable to determine which interface to use for socket coming from " + socket.remoteAddress + ":" + socket.remotePort + " to " +
socket.localAddress + ".");

return Object.keys(interfaces)[1]; // just use the first interface after the loopback interface as fallback
return defaultInterface();
}

}

0 comments on commit c4fd1d4

Please sign in to comment.