Files
scrypted/server/src/ip.ts
2024-11-15 10:02:13 -08:00

107 lines
3.0 KiB
TypeScript

import os from 'os';
import net from 'net';
export const loopbackList = new net.BlockList();
loopbackList.addSubnet('127.0.0.0', 8);
loopbackList.addAddress('::1', 'ipv6');
const unusableList = new net.BlockList();
// link local
unusableList.addSubnet('169.254.0.0', 16);
unusableList.addSubnet('fe80::', 64, 'ipv6');
// cg nat
unusableList.addSubnet('100.64.0.0', 10);
// documentation and testing
unusableList.addSubnet('192.0.2.0', 24);
unusableList.addSubnet('198.51.100.0', 24);
unusableList.addSubnet('203.0.113.0', 24);
// reserved
unusableList.addSubnet('0.0.0.0', 8);
unusableList.addSubnet('240.0.0.0', 4);
// benchmarking
unusableList.addSubnet('198.18.0.0', 15);
const privateList = new net.BlockList();
privateList.addSubnet('10.0.0.0', 8);
privateList.addSubnet('172.16.0.0', 12);
privateList.addSubnet('192.168.0.0', 16);
privateList.addSubnet('fc00::', 7, 'ipv6');
export function isIPv4EmbeddedIPv6(address: string) {
// this is valid ipv6 address with ipv4 embedded
// ::ffff:10.0.0.1
return net.isIPv6(address) && address.startsWith('::ffff:');
}
export function removeIPv4EmbeddedIPv6(address: string) {
if (isIPv4EmbeddedIPv6(address))
return address.slice(7);
return address;
}
function isUsableNetworkAddress(address: string) {
if (!address)
return false;
try {
address = removeIPv4EmbeddedIPv6(address);
const type = net.isIPv6(address) ? 'ipv6' : 'ipv4';
// ipv6 addresses are "usable" in the context of scrypted if they are public.
if (type === 'ipv6' && privateList.check(address, type))
return false;
return !unusableList.check(address, type) && !loopbackList.check(address, type);
}
catch (e) {
return false;
}
}
export function getUsableNetworkAddresses() {
const nis = os.networkInterfaces();
const getUsable = (family: string | number) => {
const usable = Object.values(nis)
.flat()
.filter((details) => details.family === family)
.filter(details => isUsableNetworkAddress(details.address));
return usable;
}
const ipv4 = getUsable('IPv4');
const ipv6 = getUsable('IPv6');
// ipv6 generates temporary per connection ips that we want to filter out.
// 2001:abc:def::7b0
const fixedAddresses = ipv6.filter(details => details.address.includes('::'));
const fixedRanges = fixedAddresses.map(details => {
const block = new net.BlockList();
const cidr = parseInt(details.address.split('/')[1]) || 1;
block.addSubnet(details.address, cidr, 'ipv6');
return block;
});
// 2001:abc:def:0:dc:28a7:261b:9324
const fixedFiltered = ipv6.filter(details => {
for (const block of fixedRanges) {
if (block.check(details.address, 'ipv6'))
return false;
}
return true;
});
return [
...ipv4.map(details => details.address),
...fixedAddresses.map(details => details.address),
...fixedFiltered.map(details => details.address),
];
}