Skip to content

Commit

Permalink
Port from ip to ipaddr.js NPM module
Browse files Browse the repository at this point in the history
`ip` is a node.js module, and was never meant for running in a browser.
It magically happened to work until version 1.1.8 despite it doing
things like `require('buffer')` (i.e. not an ESM).

In version 2.0.1 this broke completely as it tries to `require('os')`.
Replace this with https://www.npmjs.com/package/ipaddr.js which has a
comparable API and size (the actually bundled library is 12 KB for both
modules).

Closes #1451
  • Loading branch information
martinpitt authored and jelly committed Feb 22, 2024
1 parent 4c00319 commit f27fc31
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 105 deletions.
2 changes: 1 addition & 1 deletion node_modules
Submodule node_modules updated 104 files
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"eslint-plugin-react-hooks": "4.6.0",
"gettext-parser": "8.0.0",
"htmlparser": "1.7.7",
"ip": "1.1.8",
"ipaddr.js": "2.1.0",
"jed": "1.1.1",
"sass": "1.71.0",
"sizzle": "2.3.10",
Expand Down
166 changes: 63 additions & 103 deletions src/components/networks/utils.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* This file is part of Cockpit.
*
* Copyright (C) 2019 Red Hat, Inc.
* Copyright (C) 2019 - 2024 Red Hat, Inc.
*
* Cockpit is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
Expand All @@ -17,32 +17,27 @@
* along with Cockpit; If not, see <http://www.gnu.org/licenses/>.
*/

import * as ip from "ip";
import * as ipaddr from "ipaddr.js";

/**
* Validates correctness of ipv4 address
*
* @param {string} address
* @returns {boolean}
*/
export function validateIpv4(address) {
return ip.isV4Format(address);
}
export const validateIpv4 = address => ipaddr.IPv4.isValid(address);

/**
* Returns if the provided address is the network's broadcast address
*
* @param {string} address
* @returns {boolean}
*/
export function ipv4IsBroadcast(address, netMask) {
if (!validateNetmask(netMask))
export function ipv4IsBroadcast(address, netmask) {
const prefix = validateNetmask(netmask);
if (prefix === null)
return false;
const mask = netmaskConvert(netMask);
const subnet = ip.subnet(address, mask);

// user provided netmask
return address === subnet.broadcastAddress;
return address === ipaddr.IPv4.broadcastAddressFromCIDR(`${address}/${prefix}`).toString();
}

/**
Expand All @@ -51,47 +46,37 @@ export function ipv4IsBroadcast(address, netMask) {
* @param {string} address
* @returns {boolean}
*/
export function ipv4IsNetworkIdentifier(address, netMask) {
if (!validateNetmask(netMask))
export function ipv4IsNetworkIdentifier(address, netmask) {
const prefix = validateNetmask(netmask);
if (prefix === null)
return false;
const mask = netmaskConvert(netMask);
const subnet = ip.subnet(address, mask);

// user provided network identifier
return address === subnet.networkAddress;
return address === ipaddr.IPv4.networkAddressFromCIDR(`${address}/${prefix}`).toString();
}

/**
* validates correctness of ipv4 prefix length or mask
*
* @param {string} prefixOrNetmask
* @returns {boolean}
* @returns int prefix length, or null if invalid
*/
export function validateNetmask(prefixOrNetmask) {
const netmaskParts = ["255", "254", "252", "248", "240", "224", "192", "128", "0"];
const parts = prefixOrNetmask.split('.');

// prefix length
if (parts.length === 1) {
if (!/^[0-9]+$/.test(parts[0].trim()))
return false;
const prefixLength = parseInt(parts[0], 10);
if (isNaN(prefixLength) || prefixLength < 1 || prefixLength > 31)
return false;

return true;
}

// netmask
if (!validateIpv4(prefixOrNetmask))
return false;

for (let i = 0; i < 4; i++) {
if (!(netmaskParts.includes(parts[i])))
return false;
if (/^[0-9]+$/.test(prefixOrNetmask)) {
// prefix
try {
const prefix = parseInt(prefixOrNetmask);
ipaddr.IPv4.subnetMaskFromPrefixLength(prefix);
return prefix;
} catch (_ex) {
return null;
}
} else {
// mask
try {
return ipaddr.IPv4.parse(prefixOrNetmask).prefixLengthFromSubnetMask();
} catch (_ex) {
return null;
}
}

return true;
}

/**
Expand All @@ -101,28 +86,36 @@ export function validateNetmask(prefixOrNetmask) {
* @returns {string}
*/
export function netmaskConvert(prefixOrNetmask) {
const parts = prefixOrNetmask.split('.');
// single number → netmask
if (/^[0-9]+$/.test(prefixOrNetmask)) {
try {
return ipaddr.IPv4.subnetMaskFromPrefixLength(parseInt(prefixOrNetmask)).toString();
} catch (_ex) {
// leave unchanged; UI will validate
}
}

if (parts.length === 4)
return prefixOrNetmask;
else if (parts.length === 1)
return ip.fromPrefixLen(prefixOrNetmask);
return prefixOrNetmask;
}

/**
* Checks whetever address @ip is in subnet defined by @network and @netmask
* Checks whetever address @address is in subnet defined by @network and @netmask
*
* @param {string} network
* @param {string} netmask
* @param {string} ip
* @param {string} address
* @returns {boolean}
*/
export function isIpv4InNetwork(network, netmask, ipaddr) {
if (!ip.isV4Format(network) || !validateNetmask(netmask) || !ip.isV4Format(ipaddr))
export function isIpv4InNetwork(network, netmask, address) {
if (!validateIpv4(network) || !validateIpv4(address))
return false;
const prefix = validateNetmask(netmask);
if (prefix === null)
return false;
const mask = netmaskConvert(netmask);

return ip.subnet(network, mask).contains(ipaddr);
const b_network = ipaddr.IPv4.broadcastAddressFromCIDR(`${network}/${prefix}`).toString();
const b_ipaddr = ipaddr.IPv4.broadcastAddressFromCIDR(`${address}/${prefix}`).toString();
return b_network === b_ipaddr;
}

/**
Expand All @@ -131,9 +124,7 @@ export function isIpv4InNetwork(network, netmask, ipaddr) {
* @param {string} address
* @returns {boolean}
*/
export function validateIpv6(address) {
return ip.isV6Format(address);
}
export const validateIpv6 = address => ipaddr.IPv6.isValid(address);

/**
* validates correctness of ipv6 prefix length
Expand All @@ -142,59 +133,28 @@ export function validateIpv6(address) {
* @returns {boolean}
*/
export function validateIpv6Prefix(prefix) {
if (!/^[0-9]+$/.test(prefix.trim()))
return false;
const prefixLength = parseInt(prefix, 10);
if (isNaN(prefixLength) || prefixLength < 0 || prefixLength > 128)
return false;

return true;
}

/**
* Converts ipv6 address to string containing it's binary representation
*
* @param {string} ip
* @returns {string}
*/
function ipv6ToBinStr(ip) {
const validGroupCount = 8;
/* Split address by `:`
* Then check if the array contains an empty string (happens at ::), and if so
* replace it with the appropriate number of 0 entries.
*/
const arrAddr = ip.split(":");
const arrAddrExpanded = arrAddr.reduce((accum, hexNum) => {
if (hexNum)
accum.push(hexNum);
else
for (let i = 0; i < (validGroupCount - arrAddr.length + 1); i++)
accum.push("0");
return accum;
}, []);

/* Convert the array of 8 hex entries into a 128 bits binary string */
return arrAddrExpanded.map(num => {
let bin = parseInt(num, 16).toString(2);
while (bin.length < 16)
bin = "0" + bin;
return bin;
}).join("");
if (/^[0-9]+$/.test(prefix.trim())) {
try {
ipaddr.IPv6.subnetMaskFromPrefixLength(prefix);
return true;
} catch (_ex) {}
}
return false;
}

/**
* Checks whetever IPv6 address @ip is in subnet defined by @network and @prefix
* Checks whetever IPv6 @address is in subnet defined by @network and @prefix
*
* @param {string} network
* @param {string} prefix
* @param {string} ip
* @param {string} address
* @returns {boolean}
*/
export function isIpv6InNetwork(network, prefix, ip) {
network = ipv6ToBinStr(network);
network = network.substring(0, prefix);
ip = ipv6ToBinStr(ip);
ip = ip.substring(0, prefix);
export function isIpv6InNetwork(network, prefix, address) {
if (!validateIpv6(network) || !validateIpv6Prefix(prefix) || !validateIpv6(address))
return false;

return network == ip;
const b_network = ipaddr.IPv6.broadcastAddressFromCIDR(`${network}/${prefix}`).toString();
const b_ipaddr = ipaddr.IPv6.broadcastAddressFromCIDR(`${address}/${prefix}`).toString();
return b_network === b_ipaddr;
}

0 comments on commit f27fc31

Please sign in to comment.