mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-08 19:22:44 +08:00
feat(tun): validate route-exclude-address as CIDR (#6440)
* feat(tun): validate route-exclude-address as CIDR * refactor(network): replace ipaddr.js helpers with cidr-block and validator * docs: Changelog
This commit is contained in:
@@ -1,112 +0,0 @@
|
||||
import ipaddr from "ipaddr.js";
|
||||
|
||||
import { debugLog } from "@/utils/debug";
|
||||
|
||||
export type HostKind = "localhost" | "domain" | "ipv4" | "ipv6";
|
||||
|
||||
export type ParsedHost = {
|
||||
kind: HostKind;
|
||||
host: string;
|
||||
};
|
||||
|
||||
export const isValidUrl = (url: string) => {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
debugLog(e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const isValidPort = (port: string) => {
|
||||
const portNumber = Number(port);
|
||||
return Number.isInteger(portNumber) && portNumber > 0 && portNumber < 65536;
|
||||
};
|
||||
|
||||
export const isValidDomain = (domain: string) => {
|
||||
try {
|
||||
const url = new URL(`http://${domain}`);
|
||||
return url.hostname.toLowerCase() === domain.toLowerCase();
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const isLocalhostString = (host: string) => {
|
||||
const lowerHost = host.toLowerCase();
|
||||
return lowerHost === "localhost";
|
||||
};
|
||||
|
||||
const stripBrackets = (host: string): string =>
|
||||
host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
|
||||
|
||||
export const parseHost = (host: string): ParsedHost | null => {
|
||||
// 去除前后空白
|
||||
host = host.trim();
|
||||
// 优先检测 localhost
|
||||
if (isLocalhostString(host)) {
|
||||
return { kind: "localhost", host: "localhost" };
|
||||
}
|
||||
// 除去方括号后检测 IP
|
||||
const strippedHost = stripBrackets(host);
|
||||
// IPv4
|
||||
if (ipaddr.IPv4.isValidFourPartDecimal(strippedHost)) {
|
||||
return { kind: "ipv4", host: strippedHost };
|
||||
}
|
||||
// IPv6
|
||||
if (ipaddr.IPv6.isValid(strippedHost)) {
|
||||
return { kind: "ipv6", host: strippedHost };
|
||||
}
|
||||
// 再检测 domain, 防止像 [::1] 这样的合法 IPv6 地址被误判为域名
|
||||
if (isValidDomain(host)) {
|
||||
return { kind: "domain", host };
|
||||
}
|
||||
// 都不是
|
||||
return null;
|
||||
};
|
||||
|
||||
const LISTEN_CIDRS_V4 = [
|
||||
"0.0.0.0/32", // any
|
||||
"127.0.0.0/8", // loopback
|
||||
"10.0.0.0/8", // RFC1918
|
||||
"172.16.0.0/12", // RFC1918
|
||||
"192.168.0.0/16", // RFC1918
|
||||
];
|
||||
|
||||
const LISTEN_CIDRS_V6 = [
|
||||
"::/128", // any
|
||||
"::1/128", // loopback
|
||||
"fc00::/7", // ULA(LAN)
|
||||
"fe80::/10", // link-local
|
||||
];
|
||||
|
||||
export const parsedLocalhost = (host: string): ParsedHost | null => {
|
||||
// 先 parse 一下
|
||||
const parsed = parseHost(host);
|
||||
if (!parsed) return null;
|
||||
// localhost 直接通过
|
||||
if (parsed.kind === "localhost") {
|
||||
return parsed;
|
||||
}
|
||||
// IP 则检查是否在允许的 CIDR 列表内
|
||||
if (parsed.kind === "ipv4") {
|
||||
// IPv4
|
||||
const addr = ipaddr.IPv4.parse(parsed.host);
|
||||
for (const cidr of LISTEN_CIDRS_V4) {
|
||||
if (addr.match(ipaddr.IPv4.parseCIDR(cidr))) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
} else if (parsed.kind === "ipv6") {
|
||||
// IPv6
|
||||
const addr = ipaddr.IPv6.parse(parsed.host);
|
||||
for (const cidr of LISTEN_CIDRS_V6) {
|
||||
if (addr.match(ipaddr.IPv6.parseCIDR(cidr))) {
|
||||
return parsed;
|
||||
}
|
||||
}
|
||||
}
|
||||
// domain和都不符合则返回 null
|
||||
return null;
|
||||
};
|
||||
@@ -0,0 +1,87 @@
|
||||
import { ipv4, ipv6 } from "cidr-block";
|
||||
import validator from "validator";
|
||||
|
||||
const stripBrackets = (value: string) =>
|
||||
value.startsWith("[") && value.endsWith("]") ? value.slice(1, -1) : value;
|
||||
|
||||
const isIpv4 = (value: string) => validator.isIP(value, 4);
|
||||
const isIpv6 = (value: string) => validator.isIP(value, 6);
|
||||
const isHostname = (value: string) =>
|
||||
validator.isFQDN(value, { require_tld: false });
|
||||
|
||||
export const isValidUrl = (value: string) =>
|
||||
validator.isURL(value.trim(), {
|
||||
protocols: ["http", "https"],
|
||||
require_protocol: true,
|
||||
require_valid_protocol: true,
|
||||
require_host: true,
|
||||
require_tld: false,
|
||||
});
|
||||
|
||||
export const isValidPort = (value: string) => validator.isPort(value.trim());
|
||||
|
||||
export const normalizeHost = (value: string): string | null => {
|
||||
const host = value.trim();
|
||||
if (!host) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (host.toLowerCase() === "localhost") {
|
||||
return "localhost";
|
||||
}
|
||||
|
||||
const normalizedHost = stripBrackets(host);
|
||||
if (isIpv4(normalizedHost) || isIpv6(normalizedHost)) {
|
||||
return normalizedHost;
|
||||
}
|
||||
|
||||
return isHostname(host) ? host : null;
|
||||
};
|
||||
|
||||
export const normalizeListenHost = (value: string): string | null => {
|
||||
const host = normalizeHost(value);
|
||||
if (!host || host === "localhost") {
|
||||
return host;
|
||||
}
|
||||
|
||||
if (isIpv4(host)) {
|
||||
const address = ipv4.address(host);
|
||||
return address.equals("0.0.0.0") ||
|
||||
address.isLoopbackAddress() ||
|
||||
address.isPrivateAddress()
|
||||
? host
|
||||
: null;
|
||||
}
|
||||
|
||||
if (!isIpv6(host)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const address = ipv6.address(host);
|
||||
return address.isUnspecifiedAddress() ||
|
||||
address.isLoopbackAddress() ||
|
||||
address.isUniqueLocalAddress() ||
|
||||
address.isLinkLocalAddress()
|
||||
? host
|
||||
: null;
|
||||
};
|
||||
|
||||
export const formatHostPort = (host: string, port: string | number) =>
|
||||
isIpv6(host) ? `[${host}]:${port}` : `${host}:${port}`;
|
||||
|
||||
export const isValidIpCidr = (value: string): boolean => {
|
||||
const cidr = value.trim();
|
||||
const slashIndex = cidr.lastIndexOf("/");
|
||||
if (slashIndex <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const address = cidr.slice(0, slashIndex);
|
||||
return (
|
||||
(isIpv4(address) && ipv4.isValidCIDR(cidr)) ||
|
||||
(isIpv6(address) && ipv6.isValidCIDR(cidr))
|
||||
);
|
||||
};
|
||||
|
||||
export const areValidIpCidrs = (values: string[]) =>
|
||||
values.every(isValidIpCidr);
|
||||
Reference in New Issue
Block a user