@push.rocks/smartnetwork 4.7.3 → 4.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.smartconfig.json +3 -2
- package/dist_rust/rustnetwork_linux_amd64 +0 -0
- package/dist_rust/rustnetwork_linux_arm64 +0 -0
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/helpers/ip.d.ts +21 -0
- package/dist_ts/helpers/ip.js +71 -0
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +2 -1
- package/dist_ts/smartnetwork.classes.publicip.js +4 -17
- package/package.json +4 -3
- package/readme.hints.md +7 -0
- package/readme.md +10 -0
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/helpers/ip.ts +69 -0
- package/ts/index.ts +1 -0
- package/ts/smartnetwork.classes.publicip.ts +3 -17
package/.smartconfig.json
CHANGED
|
Binary file
|
|
Binary file
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@push.rocks/smartnetwork',
|
|
6
|
-
version: '4.
|
|
6
|
+
version: '4.8.0',
|
|
7
7
|
description: 'A toolkit for network diagnostics including speed tests, port availability checks, and more.'
|
|
8
8
|
};
|
|
9
9
|
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiMDBfY29tbWl0aW5mb19kYXRhLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vdHMvMDBfY29tbWl0aW5mb19kYXRhLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOztHQUVHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sVUFBVSxHQUFHO0lBQ3hCLElBQUksRUFBRSwwQkFBMEI7SUFDaEMsT0FBTyxFQUFFLE9BQU87SUFDaEIsV0FBVyxFQUFFLDhGQUE4RjtDQUM1RyxDQUFBIn0=
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure IP address validation helpers.
|
|
3
|
+
* Runtime-agnostic replacements for node:net's isIP/isIPv4/isIPv6, with one
|
|
4
|
+
* intentional divergence: zone-indexed IPv6 ('fe80::1%eth0') is rejected.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strict IPv4 validation: four decimal octets 0-255, no leading zeros.
|
|
8
|
+
*/
|
|
9
|
+
export declare function isValidIpv4(ip: string): boolean;
|
|
10
|
+
/**
|
|
11
|
+
* Strict IPv6 validation: hex groups with at most one '::' compression,
|
|
12
|
+
* optional embedded IPv4 tail (e.g. '::ffff:192.168.1.1').
|
|
13
|
+
* Zone indices ('%eth0') are intentionally rejected — node:net.isIPv6 accepts
|
|
14
|
+
* them, but a zone-scoped address is never a valid routable identity.
|
|
15
|
+
*/
|
|
16
|
+
export declare function isValidIpv6(ip: string): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Returns 4 for a valid IPv4 address, 6 for a valid IPv6 address, 0 otherwise.
|
|
19
|
+
* Behaves like node:net.isIP() except that zone-indexed IPv6 is rejected.
|
|
20
|
+
*/
|
|
21
|
+
export declare function getIpVersion(ip: string): 4 | 6 | 0;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure IP address validation helpers.
|
|
3
|
+
* Runtime-agnostic replacements for node:net's isIP/isIPv4/isIPv6, with one
|
|
4
|
+
* intentional divergence: zone-indexed IPv6 ('fe80::1%eth0') is rejected.
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* Strict IPv4 validation: four decimal octets 0-255, no leading zeros.
|
|
8
|
+
*/
|
|
9
|
+
export function isValidIpv4(ip) {
|
|
10
|
+
if (!ip)
|
|
11
|
+
return false;
|
|
12
|
+
const parts = ip.split('.');
|
|
13
|
+
if (parts.length !== 4)
|
|
14
|
+
return false;
|
|
15
|
+
return parts.every((part) => {
|
|
16
|
+
if (!/^\d{1,3}$/.test(part))
|
|
17
|
+
return false;
|
|
18
|
+
if (part.length > 1 && part.startsWith('0'))
|
|
19
|
+
return false;
|
|
20
|
+
const num = Number(part);
|
|
21
|
+
return num >= 0 && num <= 255;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Strict IPv6 validation: hex groups with at most one '::' compression,
|
|
26
|
+
* optional embedded IPv4 tail (e.g. '::ffff:192.168.1.1').
|
|
27
|
+
* Zone indices ('%eth0') are intentionally rejected — node:net.isIPv6 accepts
|
|
28
|
+
* them, but a zone-scoped address is never a valid routable identity.
|
|
29
|
+
*/
|
|
30
|
+
export function isValidIpv6(ip) {
|
|
31
|
+
if (!ip || ip.includes('%'))
|
|
32
|
+
return false;
|
|
33
|
+
const doubleColonSplit = ip.split('::');
|
|
34
|
+
if (doubleColonSplit.length > 2)
|
|
35
|
+
return false;
|
|
36
|
+
const hasCompression = doubleColonSplit.length === 2;
|
|
37
|
+
const parseGroups = (segment) => segment === '' ? [] : segment.split(':');
|
|
38
|
+
const headGroups = parseGroups(doubleColonSplit[0]);
|
|
39
|
+
const tailGroups = hasCompression ? parseGroups(doubleColonSplit[1]) : [];
|
|
40
|
+
const groups = hasCompression ? [...headGroups, ...tailGroups] : headGroups;
|
|
41
|
+
if (groups.some((group) => group === ''))
|
|
42
|
+
return false;
|
|
43
|
+
// An embedded IPv4 tail counts as two 16-bit groups and must be last.
|
|
44
|
+
let groupCount = groups.length;
|
|
45
|
+
const lastGroup = groups[groups.length - 1];
|
|
46
|
+
if (lastGroup !== undefined && lastGroup.includes('.')) {
|
|
47
|
+
if (!isValidIpv4(lastGroup))
|
|
48
|
+
return false;
|
|
49
|
+
groupCount += 1;
|
|
50
|
+
groups.pop();
|
|
51
|
+
}
|
|
52
|
+
if (!groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group)))
|
|
53
|
+
return false;
|
|
54
|
+
if (hasCompression) {
|
|
55
|
+
// '::' must stand for at least one zero group.
|
|
56
|
+
return groupCount < 8;
|
|
57
|
+
}
|
|
58
|
+
return groupCount === 8;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns 4 for a valid IPv4 address, 6 for a valid IPv6 address, 0 otherwise.
|
|
62
|
+
* Behaves like node:net.isIP() except that zone-indexed IPv6 is rejected.
|
|
63
|
+
*/
|
|
64
|
+
export function getIpVersion(ip) {
|
|
65
|
+
if (isValidIpv4(ip))
|
|
66
|
+
return 4;
|
|
67
|
+
if (isValidIpv6(ip))
|
|
68
|
+
return 6;
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi90cy9oZWxwZXJzL2lwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7O0dBSUc7QUFFSDs7R0FFRztBQUNILE1BQU0sVUFBVSxXQUFXLENBQUMsRUFBVTtJQUNwQyxJQUFJLENBQUMsRUFBRTtRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQ3RCLE1BQU0sS0FBSyxHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFDNUIsSUFBSSxLQUFLLENBQUMsTUFBTSxLQUFLLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUNyQyxPQUFPLEtBQUssQ0FBQyxLQUFLLENBQUMsQ0FBQyxJQUFJLEVBQUUsRUFBRTtRQUMxQixJQUFJLENBQUMsV0FBVyxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUM7WUFBRSxPQUFPLEtBQUssQ0FBQztRQUMxQyxJQUFJLElBQUksQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxVQUFVLENBQUMsR0FBRyxDQUFDO1lBQUUsT0FBTyxLQUFLLENBQUM7UUFDMUQsTUFBTSxHQUFHLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxDQUFDO1FBQ3pCLE9BQU8sR0FBRyxJQUFJLENBQUMsSUFBSSxHQUFHLElBQUksR0FBRyxDQUFDO0lBQ2hDLENBQUMsQ0FBQyxDQUFDO0FBQ0wsQ0FBQztBQUVEOzs7OztHQUtHO0FBQ0gsTUFBTSxVQUFVLFdBQVcsQ0FBQyxFQUFVO0lBQ3BDLElBQUksQ0FBQyxFQUFFLElBQUksRUFBRSxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUUxQyxNQUFNLGdCQUFnQixHQUFHLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxDQUFDLENBQUM7SUFDeEMsSUFBSSxnQkFBZ0IsQ0FBQyxNQUFNLEdBQUcsQ0FBQztRQUFFLE9BQU8sS0FBSyxDQUFDO0lBQzlDLE1BQU0sY0FBYyxHQUFHLGdCQUFnQixDQUFDLE1BQU0sS0FBSyxDQUFDLENBQUM7SUFFckQsTUFBTSxXQUFXLEdBQUcsQ0FBQyxPQUFlLEVBQVksRUFBRSxDQUNoRCxPQUFPLEtBQUssRUFBRSxDQUFDLENBQUMsQ0FBQyxFQUFFLENBQUMsQ0FBQyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsR0FBRyxDQUFDLENBQUM7SUFFM0MsTUFBTSxVQUFVLEdBQUcsV0FBVyxDQUFDLGdCQUFnQixDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUM7SUFDcEQsTUFBTSxVQUFVLEdBQUcsY0FBYyxDQUFDLENBQUMsQ0FBQyxXQUFXLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDO0lBQzFFLE1BQU0sTUFBTSxHQUFHLGNBQWMsQ0FBQyxDQUFDLENBQUMsQ0FBQyxHQUFHLFVBQVUsRUFBRSxHQUFHLFVBQVUsQ0FBQyxDQUFDLENBQUMsQ0FBQyxVQUFVLENBQUM7SUFDNUUsSUFBSSxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxLQUFLLEtBQUssRUFBRSxDQUFDO1FBQUUsT0FBTyxLQUFLLENBQUM7SUFFdkQsc0VBQXNFO0lBQ3RFLElBQUksVUFBVSxHQUFHLE1BQU0sQ0FBQyxNQUFNLENBQUM7SUFDL0IsTUFBTSxTQUFTLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEdBQUcsQ0FBQyxDQUFDLENBQUM7SUFDNUMsSUFBSSxTQUFTLEtBQUssU0FBUyxJQUFJLFNBQVMsQ0FBQyxRQUFRLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQztRQUN2RCxJQUFJLENBQUMsV0FBVyxDQUFDLFNBQVMsQ0FBQztZQUFFLE9BQU8sS0FBSyxDQUFDO1FBQzFDLFVBQVUsSUFBSSxDQUFDLENBQUM7UUFDaEIsTUFBTSxDQUFDLEdBQUcsRUFBRSxDQUFDO0lBQ2YsQ0FBQztJQUVELElBQUksQ0FBQyxNQUFNLENBQUMsS0FBSyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUsQ0FBQyxvQkFBb0IsQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLENBQUM7UUFBRSxPQUFPLEtBQUssQ0FBQztJQUU3RSxJQUFJLGNBQWMsRUFBRSxDQUFDO1FBQ25CLCtDQUErQztRQUMvQyxPQUFPLFVBQVUsR0FBRyxDQUFDLENBQUM7SUFDeEIsQ0FBQztJQUNELE9BQU8sVUFBVSxLQUFLLENBQUMsQ0FBQztBQUMxQixDQUFDO0FBRUQ7OztHQUdHO0FBQ0gsTUFBTSxVQUFVLFlBQVksQ0FBQyxFQUFVO0lBQ3JDLElBQUksV0FBVyxDQUFDLEVBQUUsQ0FBQztRQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzlCLElBQUksV0FBVyxDQUFDLEVBQUUsQ0FBQztRQUFFLE9BQU8sQ0FBQyxDQUFDO0lBQzlCLE9BQU8sQ0FBQyxDQUFDO0FBQ1gsQ0FBQyJ9
|
package/dist_ts/index.d.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { DomainIntelligence } from './smartnetwork.classes.domainintelligence.js
|
|
|
8
8
|
export type { IDomainIntelligenceResult, IDomainIntelligenceOptions } from './smartnetwork.classes.domainintelligence.js';
|
|
9
9
|
export { setLogger, getLogger } from './logging.js';
|
|
10
10
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
11
|
+
export { isValidIpv4, isValidIpv6, getIpVersion } from './helpers/ip.js';
|
package/dist_ts/index.js
CHANGED
|
@@ -5,4 +5,5 @@ export { IpIntelligence } from './smartnetwork.classes.ipintelligence.js';
|
|
|
5
5
|
export { DomainIntelligence } from './smartnetwork.classes.domainintelligence.js';
|
|
6
6
|
export { setLogger, getLogger } from './logging.js';
|
|
7
7
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
8
|
-
|
|
8
|
+
export { isValidIpv4, isValidIpv6, getIpVersion } from './helpers/ip.js';
|
|
9
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9pbmRleC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxjQUFjLHdDQUF3QyxDQUFDO0FBRXZELE9BQU8sRUFBRSxpQkFBaUIsRUFBRSxNQUFNLHNDQUFzQyxDQUFDO0FBQ3pFLE9BQU8sRUFBRSxRQUFRLEVBQUUsTUFBTSxvQ0FBb0MsQ0FBQztBQUM5RCxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0sMENBQTBDLENBQUM7QUFFMUUsT0FBTyxFQUFFLGtCQUFrQixFQUFFLE1BQU0sOENBQThDLENBQUM7QUFFbEYsT0FBTyxFQUFFLFNBQVMsRUFBRSxTQUFTLEVBQUUsTUFBTSxjQUFjLENBQUM7QUFDcEQsT0FBTyxFQUFFLFlBQVksRUFBRSxZQUFZLEVBQUUsTUFBTSxhQUFhLENBQUM7QUFDekQsT0FBTyxFQUFFLFdBQVcsRUFBRSxXQUFXLEVBQUUsWUFBWSxFQUFFLE1BQU0saUJBQWlCLENBQUMifQ==
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLogger } from './logging.js';
|
|
2
|
+
import { isValidIpv4, isValidIpv6 } from './helpers/ip.js';
|
|
2
3
|
/**
|
|
3
4
|
* PublicIp class for detecting public IPv4 and IPv6 addresses
|
|
4
5
|
* Uses multiple fallback services for reliability
|
|
@@ -110,27 +111,13 @@ export class PublicIp {
|
|
|
110
111
|
* Validate IPv4 address format
|
|
111
112
|
*/
|
|
112
113
|
isValidIpv4(ip) {
|
|
113
|
-
|
|
114
|
-
return false;
|
|
115
|
-
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
116
|
-
if (!ipv4Regex.test(ip))
|
|
117
|
-
return false;
|
|
118
|
-
const parts = ip.split('.');
|
|
119
|
-
return parts.every(part => {
|
|
120
|
-
const num = parseInt(part, 10);
|
|
121
|
-
return num >= 0 && num <= 255;
|
|
122
|
-
});
|
|
114
|
+
return isValidIpv4(ip);
|
|
123
115
|
}
|
|
124
116
|
/**
|
|
125
117
|
* Validate IPv6 address format
|
|
126
118
|
*/
|
|
127
119
|
isValidIpv6(ip) {
|
|
128
|
-
|
|
129
|
-
return false;
|
|
130
|
-
// Simplified IPv6 validation - checks for colon-separated hex values
|
|
131
|
-
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
132
|
-
const ipv6CompressedRegex = /^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$/;
|
|
133
|
-
return ipv6Regex.test(ip) || ipv6CompressedRegex.test(ip);
|
|
120
|
+
return isValidIpv6(ip);
|
|
134
121
|
}
|
|
135
122
|
}
|
|
136
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
123
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoic21hcnRuZXR3b3JrLmNsYXNzZXMucHVibGljaXAuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi90cy9zbWFydG5ldHdvcmsuY2xhc3Nlcy5wdWJsaWNpcC50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEVBQUUsU0FBUyxFQUFFLE1BQU0sY0FBYyxDQUFDO0FBQ3pDLE9BQU8sRUFBRSxXQUFXLEVBQUUsV0FBVyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFZM0Q7OztHQUdHO0FBQ0gsTUFBTSxPQUFPLFFBQVE7SUEyQm5CLFlBQVksT0FBOEI7UUExQnpCLGFBQVEsR0FBZ0I7WUFDdkM7Z0JBQ0UsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsS0FBSyxFQUFFLG1DQUFtQztnQkFDMUMsS0FBSyxFQUFFLG9DQUFvQzthQUM1QztZQUNEO2dCQUNFLElBQUksRUFBRSxVQUFVO2dCQUNoQixLQUFLLEVBQUUscUJBQXFCO2dCQUM1QixLQUFLLEVBQUUscUJBQXFCO2FBQzdCO1lBQ0Q7Z0JBQ0UsSUFBSSxFQUFFLE9BQU87Z0JBQ2IsS0FBSyxFQUFFLHdCQUF3QjtnQkFDL0IsS0FBSyxFQUFFLHdCQUF3QjthQUNoQztZQUNEO2dCQUNFLElBQUksRUFBRSxXQUFXO2dCQUNqQixLQUFLLEVBQUUsNEJBQTRCO2dCQUNuQyxLQUFLLEVBQUUsNEJBQTRCO2FBQ3BDO1NBQ0YsQ0FBQztRQUdlLFdBQU0sR0FBRyxTQUFTLEVBQUUsQ0FBQztRQUdwQyxJQUFJLENBQUMsT0FBTyxHQUFHLE9BQU8sRUFBRSxPQUFPLElBQUksSUFBSSxDQUFDO0lBQzFDLENBQUM7SUFFRDs7T0FFRztJQUNJLEtBQUssQ0FBQyxhQUFhO1FBQ3hCLEtBQUssTUFBTSxPQUFPLElBQUksSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO1lBQ3BDLElBQUksQ0FBQyxPQUFPLENBQUMsS0FBSztnQkFBRSxTQUFTO1lBRTdCLElBQUksQ0FBQztnQkFDSCxNQUFNLEVBQUUsR0FBRyxNQUFN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
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@push.rocks/smartnetwork",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.8.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A toolkit for network diagnostics including speed tests, port availability checks, and more.",
|
|
6
6
|
"exports": {
|
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@git.zone/tsbuild": "^4.4.2",
|
|
14
14
|
"@git.zone/tsrun": "^2.0.4",
|
|
15
|
-
"@git.zone/tsrust": "^1.
|
|
15
|
+
"@git.zone/tsrust": "^1.4.1",
|
|
16
16
|
"@git.zone/tstest": "^3.6.6",
|
|
17
|
-
"@types/node": "^
|
|
17
|
+
"@types/node": "^26.0.1"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@push.rocks/smartdns": "^7.9.2",
|
|
@@ -59,6 +59,7 @@
|
|
|
59
59
|
},
|
|
60
60
|
"scripts": {
|
|
61
61
|
"test": "(tstest test/ --verbose)",
|
|
62
|
+
"test:rust": "(cd rust && cargo test)",
|
|
62
63
|
"build": "(tsbuild tsfolders) && (tsrust)",
|
|
63
64
|
"buildDocs": "tsdoc"
|
|
64
65
|
}
|
package/readme.hints.md
CHANGED
|
@@ -97,3 +97,10 @@ Key features:
|
|
|
97
97
|
- Promise-based async/await throughout
|
|
98
98
|
- Error propagation with custom error types
|
|
99
99
|
- Graceful degradation when ICMP permissions unavailable
|
|
100
|
+
|
|
101
|
+
## Static Rust Binaries
|
|
102
|
+
|
|
103
|
+
- The Rust binaries in `dist_rust/` are statically linked (static-pie) via `"static": true` in the `@git.zone/tsrust` block of `.smartconfig.json` (tsrust >= 1.4.1). They run on both glibc (Debian/Ubuntu) and musl (Alpine) systems.
|
|
104
|
+
- tsrust injects `RUSTFLAGS="-C target-feature=+crt-static"` only into its own per-target cargo invocations and verifies the result (no `PT_INTERP` ELF header). Keep `rust/.cargo/config.toml` free of `rustflags` entries: the injected env variable would replace them, and a repo-wide `rustflags` would also break plain `cargo test`/`cargo check` (proc-macros cannot build with `+crt-static` on linux-gnu without an explicit `--target`).
|
|
105
|
+
- `pnpm run test:rust` runs the Rust unit tests; plain `cargo test` in `rust/` works as well.
|
|
106
|
+
- Verify linkage manually with `ldd dist_rust/<binary>_linux_amd64` → "statically linked".
|
package/readme.md
CHANGED
|
@@ -489,6 +489,16 @@ const monitor = async () => {
|
|
|
489
489
|
| `getIpIntelligence(ip)` | ASN + org + geo + RDAP registration | No |
|
|
490
490
|
| `getDomainIntelligence(domain)` | Registrar, nameservers, events, DNS records, DNSSEC | No |
|
|
491
491
|
|
|
492
|
+
### Standalone helpers
|
|
493
|
+
|
|
494
|
+
Pure functions exported alongside the `SmartNetwork` class — no instance or Rust bridge needed:
|
|
495
|
+
|
|
496
|
+
| Function | Description |
|
|
497
|
+
|----------|-------------|
|
|
498
|
+
| `isValidIpv4(ip)` | Strict IPv4 validation (four decimal octets 0–255, no leading zeros) |
|
|
499
|
+
| `isValidIpv6(ip)` | Strict IPv6 validation (single `::` compression, embedded IPv4 tail; zone indices like `%eth0` are rejected) |
|
|
500
|
+
| `getIpVersion(ip)` | Returns `4`, `6`, or `0` — like `node:net.isIP()`, except zone-indexed IPv6 is rejected |
|
|
501
|
+
|
|
492
502
|
## License and Legal Information
|
|
493
503
|
|
|
494
504
|
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the [license](./license) file.
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/helpers/ip.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure IP address validation helpers.
|
|
3
|
+
* Runtime-agnostic replacements for node:net's isIP/isIPv4/isIPv6, with one
|
|
4
|
+
* intentional divergence: zone-indexed IPv6 ('fe80::1%eth0') is rejected.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Strict IPv4 validation: four decimal octets 0-255, no leading zeros.
|
|
9
|
+
*/
|
|
10
|
+
export function isValidIpv4(ip: string): boolean {
|
|
11
|
+
if (!ip) return false;
|
|
12
|
+
const parts = ip.split('.');
|
|
13
|
+
if (parts.length !== 4) return false;
|
|
14
|
+
return parts.every((part) => {
|
|
15
|
+
if (!/^\d{1,3}$/.test(part)) return false;
|
|
16
|
+
if (part.length > 1 && part.startsWith('0')) return false;
|
|
17
|
+
const num = Number(part);
|
|
18
|
+
return num >= 0 && num <= 255;
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Strict IPv6 validation: hex groups with at most one '::' compression,
|
|
24
|
+
* optional embedded IPv4 tail (e.g. '::ffff:192.168.1.1').
|
|
25
|
+
* Zone indices ('%eth0') are intentionally rejected — node:net.isIPv6 accepts
|
|
26
|
+
* them, but a zone-scoped address is never a valid routable identity.
|
|
27
|
+
*/
|
|
28
|
+
export function isValidIpv6(ip: string): boolean {
|
|
29
|
+
if (!ip || ip.includes('%')) return false;
|
|
30
|
+
|
|
31
|
+
const doubleColonSplit = ip.split('::');
|
|
32
|
+
if (doubleColonSplit.length > 2) return false;
|
|
33
|
+
const hasCompression = doubleColonSplit.length === 2;
|
|
34
|
+
|
|
35
|
+
const parseGroups = (segment: string): string[] =>
|
|
36
|
+
segment === '' ? [] : segment.split(':');
|
|
37
|
+
|
|
38
|
+
const headGroups = parseGroups(doubleColonSplit[0]);
|
|
39
|
+
const tailGroups = hasCompression ? parseGroups(doubleColonSplit[1]) : [];
|
|
40
|
+
const groups = hasCompression ? [...headGroups, ...tailGroups] : headGroups;
|
|
41
|
+
if (groups.some((group) => group === '')) return false;
|
|
42
|
+
|
|
43
|
+
// An embedded IPv4 tail counts as two 16-bit groups and must be last.
|
|
44
|
+
let groupCount = groups.length;
|
|
45
|
+
const lastGroup = groups[groups.length - 1];
|
|
46
|
+
if (lastGroup !== undefined && lastGroup.includes('.')) {
|
|
47
|
+
if (!isValidIpv4(lastGroup)) return false;
|
|
48
|
+
groupCount += 1;
|
|
49
|
+
groups.pop();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!groups.every((group) => /^[0-9a-fA-F]{1,4}$/.test(group))) return false;
|
|
53
|
+
|
|
54
|
+
if (hasCompression) {
|
|
55
|
+
// '::' must stand for at least one zero group.
|
|
56
|
+
return groupCount < 8;
|
|
57
|
+
}
|
|
58
|
+
return groupCount === 8;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns 4 for a valid IPv4 address, 6 for a valid IPv6 address, 0 otherwise.
|
|
63
|
+
* Behaves like node:net.isIP() except that zone-indexed IPv6 is rejected.
|
|
64
|
+
*/
|
|
65
|
+
export function getIpVersion(ip: string): 4 | 6 | 0 {
|
|
66
|
+
if (isValidIpv4(ip)) return 4;
|
|
67
|
+
if (isValidIpv6(ip)) return 6;
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
package/ts/index.ts
CHANGED
|
@@ -8,3 +8,4 @@ export { DomainIntelligence } from './smartnetwork.classes.domainintelligence.js
|
|
|
8
8
|
export type { IDomainIntelligenceResult, IDomainIntelligenceOptions } from './smartnetwork.classes.domainintelligence.js';
|
|
9
9
|
export { setLogger, getLogger } from './logging.js';
|
|
10
10
|
export { NetworkError, TimeoutError } from './errors.js';
|
|
11
|
+
export { isValidIpv4, isValidIpv6, getIpVersion } from './helpers/ip.js';
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { getLogger } from './logging.js';
|
|
2
|
+
import { isValidIpv4, isValidIpv6 } from './helpers/ip.js';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Service configuration for IP detection
|
|
@@ -136,28 +137,13 @@ export class PublicIp {
|
|
|
136
137
|
* Validate IPv4 address format
|
|
137
138
|
*/
|
|
138
139
|
private isValidIpv4(ip: string): boolean {
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const ipv4Regex = /^(\d{1,3}\.){3}\d{1,3}$/;
|
|
142
|
-
if (!ipv4Regex.test(ip)) return false;
|
|
143
|
-
|
|
144
|
-
const parts = ip.split('.');
|
|
145
|
-
return parts.every(part => {
|
|
146
|
-
const num = parseInt(part, 10);
|
|
147
|
-
return num >= 0 && num <= 255;
|
|
148
|
-
});
|
|
140
|
+
return isValidIpv4(ip);
|
|
149
141
|
}
|
|
150
142
|
|
|
151
143
|
/**
|
|
152
144
|
* Validate IPv6 address format
|
|
153
145
|
*/
|
|
154
146
|
private isValidIpv6(ip: string): boolean {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
// Simplified IPv6 validation - checks for colon-separated hex values
|
|
158
|
-
const ipv6Regex = /^([0-9a-fA-F]{0,4}:){2,7}[0-9a-fA-F]{0,4}$/;
|
|
159
|
-
const ipv6CompressedRegex = /^::([0-9a-fA-F]{0,4}:){0,6}[0-9a-fA-F]{0,4}$|^([0-9a-fA-F]{0,4}:){1,7}:$/;
|
|
160
|
-
|
|
161
|
-
return ipv6Regex.test(ip) || ipv6CompressedRegex.test(ip);
|
|
147
|
+
return isValidIpv6(ip);
|
|
162
148
|
}
|
|
163
149
|
}
|