default-gateway
Advanced tools
Comparing version 6.0.3 to 7.0.0
320
index.js
@@ -1,35 +0,301 @@ | ||
"use strict"; | ||
import {isIP} from "node:net"; | ||
import {execa, execaSync} from "execa"; | ||
import {platform, type, release, networkInterfaces} from "node:os"; | ||
const {platform, type} = require("os"); | ||
const plat = platform(); | ||
const dests = new Set(["default", "0.0.0.0", "0.0.0.0/0", "::", "::/0"]); | ||
let promise, sync; | ||
const supportedPlatforms = new Set([ | ||
"aix", | ||
"android", | ||
"darwin", | ||
"freebsd", | ||
"linux", | ||
"openbsd", | ||
"sunos", | ||
"win32" | ||
]); | ||
if (plat === "linux") { | ||
const args = { | ||
v4: ["-4", "r"], | ||
v6: ["-6", "r"], | ||
}; | ||
const plat = platform(); | ||
const parse = (stdout, family) => { | ||
for (const line of (stdout || "").trim().split("\n")) { | ||
const results = /default( via .+?)?( dev .+?)( |$)/.exec(line) || []; | ||
const gateway = (results[1] || "").substring(5); | ||
const iface = (results[2] || "").substring(5); | ||
if (gateway && isIP(gateway)) { // default via 1.2.3.4 dev en0 | ||
return {gateway, int: (iface ?? null)}; | ||
} else if (iface && !gateway) { // default via dev en0 | ||
const interfaces = networkInterfaces(); | ||
const addresses = interfaces[iface]; | ||
for (const addr of addresses || []) { | ||
if (addr.family.substring(2) === family && isIP(addr.address)) { | ||
return {gateway: addr.address, int: (iface ?? null)}; | ||
} | ||
} | ||
} | ||
} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
if (supportedPlatforms.has(plat)) { | ||
let file = plat; | ||
if (plat === "aix") { | ||
file = type() === "OS400" ? "ibmi" : "sunos"; // AIX `netstat` output is compatible with Solaris | ||
promise = async family => { | ||
const {stdout} = await execa("ip", args[family]); | ||
return parse(stdout, family); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("ip", args[family]); | ||
return parse(stdout, family); | ||
}; | ||
} else if (plat === "darwin") { | ||
const args = { | ||
v4: ["-rn", "-f", "inet"], | ||
v6: ["-rn", "-f", "inet6"], | ||
}; | ||
// The IPv4 gateway is in column 3 in Darwin 19 (macOS 10.15 Catalina) and higher, | ||
// previously it was in column 5 | ||
const v4IfaceColumn = parseInt(release()) >= 19 ? 3 : 5; | ||
const parse = (stdout, family) => { | ||
for (const line of (stdout || "").trim().split("\n")) { | ||
const results = line.split(/ +/) || []; | ||
const target = results[0]; | ||
const gateway = results[1]; | ||
const iface = results[family === "v4" ? v4IfaceColumn : 3]; | ||
if (dests.has(target) && gateway && isIP(gateway)) { | ||
return {gateway, int: (iface ?? null)}; | ||
} | ||
} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
promise = async family => { | ||
const {stdout} = await execa("netstat", args[family]); | ||
return parse(stdout, family); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("netstat", args[family]); | ||
return parse(stdout, family); | ||
}; | ||
} else if (plat === "win32") { | ||
const gwArgs = "path Win32_NetworkAdapterConfiguration where IPEnabled=true get DefaultIPGateway,GatewayCostMetric,IPConnectionMetric,Index /format:table".split(" "); | ||
const ifArgs = index => `path Win32_NetworkAdapter where Index=${index} get NetConnectionID,MACAddress /format:table`.split(" "); | ||
const spawnOpts = { | ||
windowsHide: true, | ||
}; | ||
// Parsing tables like this. The final metric is GatewayCostMetric + IPConnectionMetric | ||
// | ||
// DefaultIPGateway GatewayCostMetric Index IPConnectionMetric | ||
// {"1.2.3.4", "2001:db8::1"} {0, 256} 12 25 | ||
// {"2.3.4.5"} {25} 12 55 | ||
function parseGwTable(gwTable, family) { // eslint-disable-line no-inner-declarations | ||
let [bestGw, bestMetric, bestId] = [null, null, null]; | ||
for (let line of (gwTable || "").trim().split(/\r?\n/).splice(1)) { | ||
line = line.trim(); | ||
const [_, gwArr, gwCostsArr, id, ipMetric] = /({.+?}) +({.+?}) +([0-9]+) +([0-9]+)/.exec(line) || []; | ||
if (!gwArr) continue; | ||
const gateways = (gwArr.match(/"(.+?)"/g) || []).map(match => match.substring(1, match.length - 1)); | ||
const gatewayCosts = (gwCostsArr.match(/[0-9]+/g) || []); | ||
for (const [index, gateway] of Object.entries(gateways)) { | ||
if (!gateway || `v${isIP(gateway)}` !== family) continue; | ||
const metric = parseInt(gatewayCosts[index]) + parseInt(ipMetric); | ||
if (!bestGw || metric < bestMetric) { | ||
[bestGw, bestMetric, bestId] = [gateway, metric, id]; | ||
} | ||
} | ||
} | ||
if (bestGw) return [bestGw, bestId]; | ||
} | ||
const m = require(`./${file}.js`); | ||
module.exports.v4 = () => m.v4(); | ||
module.exports.v6 = () => m.v6(); | ||
module.exports.v4.sync = () => m.v4.sync(); | ||
module.exports.v6.sync = () => m.v6.sync(); | ||
function parseIfTable(ifTable) { // eslint-disable-line no-inner-declarations | ||
const line = (ifTable || "").trim().split("\n")[1]; | ||
let [mac, name] = line.trim().split(/\s+/); | ||
mac = mac.toLowerCase(); | ||
// try to get the interface name by matching the mac to os.networkInterfaces to avoid wmic's encoding issues | ||
// https://github.com/silverwind/default-gateway/issues/14 | ||
for (const [osname, addrs] of Object.entries(networkInterfaces())) { | ||
for (const addr of addrs) { | ||
if (addr && addr.mac && addr.mac.toLowerCase() === mac) { | ||
return osname; | ||
} | ||
} | ||
} | ||
return name; | ||
} | ||
promise = async family => { | ||
const {stdout} = await execa("wmic", gwArgs, spawnOpts); | ||
const [gateway, id] = parseGwTable(stdout, family) || []; | ||
if (!gateway) throw new Error("Unable to determine default gateway"); | ||
let name; | ||
if (id) { | ||
const {stdout} = await execa("wmic", ifArgs(id), spawnOpts); | ||
name = parseIfTable(stdout); | ||
} | ||
return {gateway, int: name ?? null}; | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("wmic", gwArgs, spawnOpts); | ||
const [gateway, id] = parseGwTable(stdout, family) || []; | ||
if (!gateway) throw new Error("Unable to determine default gateway"); | ||
let name; | ||
if (id) { | ||
const {stdout} = execaSync("wmic", ifArgs(id), spawnOpts); | ||
name = parseIfTable(stdout); | ||
} | ||
return {gateway, int: name ?? null}; | ||
}; | ||
} else if (plat === "android") { | ||
const args = { | ||
v4: ["-4", "r"], | ||
v6: ["-6", "r"], | ||
}; | ||
const parse = stdout => { | ||
for (const line of (stdout || "").trim().split("\n")) { | ||
const [_, gateway, iface] = /default via (.+?) dev (.+?)( |$)/.exec(line) || []; | ||
if (gateway && isIP(gateway)) { | ||
return {gateway, int: (iface ?? null)}; | ||
} | ||
} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
promise = async family => { | ||
const {stdout} = await execa("ip", args[family]); | ||
return parse(stdout); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("ip", args[family]); | ||
return parse(stdout); | ||
}; | ||
} else if (plat === "freebsd") { | ||
const args = { | ||
v4: ["-rn", "-f", "inet"], | ||
v6: ["-rn", "-f", "inet6"], | ||
}; | ||
const parse = stdout => { | ||
for (const line of (stdout || "").trim().split("\n")) { | ||
const [target, gateway, _, iface] = line.split(/ +/) || []; | ||
if (dests.has(target) && gateway && isIP(gateway)) { | ||
return {gateway, int: (iface ?? null)}; | ||
} | ||
} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
promise = async family => { | ||
const {stdout} = await execa("netstat", args[family]); | ||
return parse(stdout); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("netstat", args[family]); | ||
return parse(stdout); | ||
}; | ||
} else if (plat === "aix" && type() === "OS400") { | ||
const args = { | ||
v4: "IPV4", | ||
v6: "IPV6", | ||
}; | ||
const db2util = "/QOpenSys/pkgs/bin/db2util"; | ||
const sql = "select NEXT_HOP, LOCAL_BINDING_INTERFACE from QSYS2.NETSTAT_ROUTE_INFO where ROUTE_TYPE='DFTROUTE' and NEXT_HOP!='*DIRECT' and CONNECTION_TYPE=?"; | ||
const parse = stdout => { | ||
try { | ||
const resultObj = JSON.parse(stdout); | ||
const gateway = resultObj.records[0].NEXT_HOP; | ||
const iface = resultObj.records[0].LOCAL_BINDING_INTERFACE; | ||
return {gateway, iface}; | ||
} catch {} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
promise = async family => { | ||
const {stdout} = await execa(db2util, [sql, "-p", args[family], "-o", "json"]); | ||
return parse(stdout); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync(db2util, [sql, "-p", args[family], "-o", "json"]); | ||
return parse(stdout); | ||
}; | ||
} else if (plat === "openbsd") { | ||
const args = { | ||
v4: ["-rn", "-f", "inet"], | ||
v6: ["-rn", "-f", "inet6"], | ||
}; | ||
const parse = stdout => { | ||
for (const line of (stdout || "").trim().split("\n")) { | ||
const results = line.split(/ +/) || []; | ||
const target = results[0]; | ||
const gateway = results[1]; | ||
const iface = results[7]; | ||
if (dests.has(target) && gateway && isIP(gateway)) { | ||
return {gateway, int: (iface ?? null)}; | ||
} | ||
} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
promise = async family => { | ||
const {stdout} = await execa("netstat", args[family]); | ||
return parse(stdout); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("netstat", args[family]); | ||
return parse(stdout); | ||
}; | ||
} else if (plat === "sunos" || (plat === "aix" && type() !== "OS400")) { | ||
// AIX `netstat` output is compatible with Solaris | ||
const args = { | ||
v4: ["-rn", "-f", "inet"], | ||
v6: ["-rn", "-f", "inet6"], | ||
}; | ||
const parse = stdout => { | ||
for (const line of (stdout || "").trim().split("\n")) { | ||
const results = line.split(/ +/) || []; | ||
const target = results[0]; | ||
const gateway = results[1]; | ||
const iface = results[5]; | ||
if (dests.has(target) && gateway && isIP(gateway)) { | ||
return {gateway, int: (iface ?? null)}; | ||
} | ||
} | ||
throw new Error("Unable to determine default gateway"); | ||
}; | ||
promise = async family => { | ||
const {stdout} = await execa("netstat", args[family]); | ||
return parse(stdout); | ||
}; | ||
sync = family => { | ||
const {stdout} = execaSync("netstat", args[family]); | ||
return parse(stdout); | ||
}; | ||
} else { | ||
const err = new Error(`Unsupported Platform: ${plat}`); | ||
module.exports.v4 = () => Promise.reject(err); | ||
module.exports.v6 = () => Promise.reject(err); | ||
module.exports.v4.sync = () => { throw err; }; | ||
module.exports.v6.sync = () => { throw err; }; | ||
promise = (_) => { throw new Error("Unsupported Platform"); }; | ||
sync = (_) => { throw new Error("Unsupported Platform"); }; | ||
} | ||
export const gateway4async = () => promise("v4"); | ||
export const gateway6async = () => promise("v6"); | ||
export const gateway4sync = () => sync("v4"); | ||
export const gateway6sync = () => sync("v6"); |
{ | ||
"name": "default-gateway", | ||
"version": "6.0.3", | ||
"version": "7.0.0", | ||
"description": "Get the default network gateway, cross-platform.", | ||
@@ -8,35 +8,20 @@ "author": "silverwind", | ||
"license": "BSD-2-Clause", | ||
"main": "index.js", | ||
"type": "module", | ||
"exports": "./index.js", | ||
"engines": { | ||
"node": ">= 10" | ||
"node": ">= 16" | ||
}, | ||
"files": [ | ||
"index.js", | ||
"android.js", | ||
"darwin.js", | ||
"freebsd.js", | ||
"linux.js", | ||
"openbsd.js", | ||
"sunos.js", | ||
"win32.js", | ||
"ibmi.js" | ||
"./index.js" | ||
], | ||
"dependencies": { | ||
"execa": "^5.0.0" | ||
"execa": "^7.1.1" | ||
}, | ||
"devDependencies": { | ||
"eslint": "7.17.0", | ||
"eslint-config-silverwind": "27.0.0", | ||
"jest": "26.6.3", | ||
"updates": "11.4.2", | ||
"versions": "8.4.4" | ||
}, | ||
"keywords": [ | ||
"default gateway", | ||
"network", | ||
"default", | ||
"gateway", | ||
"routing", | ||
"route" | ||
] | ||
"eslint": "8.42.0", | ||
"eslint-config-silverwind": "73.0.2", | ||
"updates": "14.1.1", | ||
"versions": "11.0.1", | ||
"vitest": "0.32.0" | ||
} | ||
} |
# default-gateway | ||
[![](https://img.shields.io/npm/v/default-gateway.svg?style=flat)](https://www.npmjs.org/package/default-gateway) [![](https://img.shields.io/npm/dm/default-gateway.svg)](https://www.npmjs.org/package/default-gateway) | ||
[![](https://img.shields.io/npm/v/default-gateway.svg?style=flat)](https://www.npmjs.org/package/default-gateway) [![](https://img.shields.io/npm/dm/default-gateway.svg)](https://www.npmjs.org/package/default-gateway) [![](https://packagephobia.com/badge?p=default-gateway)](https://packagephobia.com/result?p=default-gateway) | ||
Obtains the machine's default gateway through `exec` calls to OS routing interfaces. | ||
Obtains the machine's default gateway through `exec` calls to OS routing ints. | ||
@@ -11,42 +11,34 @@ - On Linux and Android, the `ip` command must be available (usually provided by the `iproute2` package). | ||
## Installation | ||
## Usage | ||
``` | ||
$ npm i default-gateway | ||
``` | ||
## Example | ||
```js | ||
const defaultGateway = require('default-gateway'); | ||
import {gateway4async, gateway4sync, gateway6async, gateway6sync} from "default-gateway"; | ||
const {gateway, interface} = await defaultGateway.v4(); | ||
// gateway = '1.2.3.4', interface = 'en1' | ||
const {gateway, int} = await gateway4async(); | ||
// gateway = '1.2.3.4', int = 'en1' | ||
const {gateway, interface} = await defaultGateway.v6(); | ||
// gateway = '2001:db8::1', interface = 'en2' | ||
const {gateway, int} = await gateway6async(); | ||
// gateway = '2001:db8::1', int = 'en2' | ||
const {gateway, interface} = defaultGateway.v4.sync(); | ||
// gateway = '1.2.3.4', interface = 'en1' | ||
const {gateway, int} = gateway4sync(); | ||
// gateway = '1.2.3.4', int = 'en1' | ||
const {gateway, interface} = defaultGateway.v6.sync(); | ||
// gateway = '2001:db8::1', interface = 'en2' | ||
const {gateway, int} = gateway6sync(); | ||
// gateway = '2001:db8::1', int = 'en2' | ||
``` | ||
## API | ||
### defaultGateway.v4() | ||
### defaultGateway.v6() | ||
### defaultGateway.v4.sync() | ||
### defaultGateway.v6.sync() | ||
### gateway4async() | ||
### gateway6async() | ||
### gateway4sync() | ||
### gateway6sync() | ||
Returns: `result` *Object* | ||
- `gateway`: The IP address of the default gateway. | ||
- `interface`: The name of the interface. On Windows, this is the network adapter name. | ||
- `int`: The name of the interface. On Windows, this is the network adapter name. | ||
The `.v{4,6}()` methods return a Promise while the `.v{4,6}.sync()` variants will return the result synchronously. | ||
The `gateway` property will always be defined on success, while `int` can be `null` if it cannot be determined. All methods reject/throw on unexpected conditions. | ||
The `gateway` property will always be defined on success, while `interface` can be `null` if it cannot be determined. All methods reject/throw on unexpected conditions. | ||
## License | ||
© [silverwind](https://github.com/silverwind), distributed under BSD licence |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
0
1
Yes
11956
3
259
44
+ Addedexeca@7.2.0(transitive)
+ Addedhuman-signals@4.3.1(transitive)
+ Addedis-stream@3.0.0(transitive)
+ Addedmimic-fn@4.0.0(transitive)
+ Addednpm-run-path@5.3.0(transitive)
+ Addedonetime@6.0.0(transitive)
+ Addedpath-key@4.0.0(transitive)
+ Addedstrip-final-newline@3.0.0(transitive)
- Removedexeca@5.1.1(transitive)
- Removedhuman-signals@2.1.0(transitive)
- Removedis-stream@2.0.1(transitive)
- Removedmimic-fn@2.1.0(transitive)
- Removednpm-run-path@4.0.1(transitive)
- Removedonetime@5.1.2(transitive)
- Removedstrip-final-newline@2.0.0(transitive)
Updatedexeca@^7.1.1