mirror of
https://github.com/clash-verge-rev/clash-verge-rev.git
synced 2026-11-09 11:52:08 +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:
@@ -49,6 +49,7 @@ import { showNotice } from "@/services/notice-service";
|
||||
import { useThemeMode } from "@/services/states";
|
||||
import type { TranslationKey } from "@/types/generated/i18n-keys";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { isValidIpCidr } from "@/utils/network";
|
||||
|
||||
interface Props {
|
||||
groupsUid: string;
|
||||
@@ -65,16 +66,6 @@ const portValidator = (value: string): boolean => {
|
||||
"^(?:[1-9]\\d{0,3}|[1-5]\\d{4}|6[0-4]\\d{3}|65[0-4]\\d{2}|655[0-2]\\d|6553[0-5])$",
|
||||
).test(value);
|
||||
};
|
||||
const ipv4CIDRValidator = (value: string): boolean => {
|
||||
return new RegExp(
|
||||
"^(?:(?:[1-9]?[0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5]))\\.){3}(?:[1-9]?[0-9]|1[0-9][0-9]|2(?:[0-4][0-9]|5[0-5]))(?:\\/(?:[12]?[0-9]|3[0-2]))$",
|
||||
).test(value);
|
||||
};
|
||||
const ipv6CIDRValidator = (value: string): boolean => {
|
||||
return new RegExp(
|
||||
"^([0-9a-fA-F]{1,4}(?::[0-9a-fA-F]{1,4}){7}|::|:(?::[0-9a-fA-F]{1,4}){1,6}|[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,5}|(?:[0-9a-fA-F]{1,4}:){2}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){3}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){4}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){5}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,6}:)\\/(?:12[0-8]|1[01][0-9]|[1-9]?[0-9])$",
|
||||
).test(value);
|
||||
};
|
||||
|
||||
const rules: {
|
||||
name: string;
|
||||
@@ -127,29 +118,29 @@ const rules: {
|
||||
name: "IP-CIDR",
|
||||
example: "127.0.0.0/8",
|
||||
noResolve: true,
|
||||
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
|
||||
validator: isValidIpCidr,
|
||||
},
|
||||
{
|
||||
name: "IP-CIDR6",
|
||||
example: "2620:0:2d0:200::7/32",
|
||||
noResolve: true,
|
||||
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
|
||||
validator: isValidIpCidr,
|
||||
},
|
||||
{
|
||||
name: "SRC-IP-CIDR",
|
||||
example: "192.168.1.201/32",
|
||||
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
|
||||
validator: isValidIpCidr,
|
||||
},
|
||||
{
|
||||
name: "IP-SUFFIX",
|
||||
example: "8.8.8.8/24",
|
||||
noResolve: true,
|
||||
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
|
||||
validator: isValidIpCidr,
|
||||
},
|
||||
{
|
||||
name: "SRC-IP-SUFFIX",
|
||||
example: "192.168.1.201/8",
|
||||
validator: (value) => ipv4CIDRValidator(value) || ipv6CIDRValidator(value),
|
||||
validator: isValidIpCidr,
|
||||
},
|
||||
{
|
||||
name: "SRC-PORT",
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
getWebdavStatus,
|
||||
setWebdavStatus,
|
||||
} from "@/services/webdav-status";
|
||||
import { isValidUrl } from "@/utils/helper";
|
||||
import { isValidUrl } from "@/utils/network";
|
||||
|
||||
interface BackupConfigViewerProps {
|
||||
onBackupSuccess: () => Promise<void>;
|
||||
|
||||
@@ -23,6 +23,7 @@ import { useClash } from "@/hooks/use-clash";
|
||||
import { enhanceProfiles } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import getSystem from "@/utils/get-system";
|
||||
import { areValidIpCidrs } from "@/utils/network";
|
||||
|
||||
import { StackModeSwitch } from "./stack-mode-switch";
|
||||
|
||||
@@ -52,6 +53,17 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
mtu: 1500,
|
||||
});
|
||||
|
||||
const routeExcludeAddressItems = splitRouteExcludeAddress(
|
||||
values.routeExcludeAddress,
|
||||
);
|
||||
const routeExcludeAddressError =
|
||||
values.autoRoute &&
|
||||
routeExcludeAddressItems.length > 0 &&
|
||||
!areValidIpCidrs(routeExcludeAddressItems);
|
||||
const routeExcludeAddressHelperText = routeExcludeAddressError
|
||||
? t("settings.modals.tun.messages.invalidRouteExcludeAddress")
|
||||
: t("settings.modals.tun.messages.routeExcludeAddressHint");
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
open: () => {
|
||||
setOpen(true);
|
||||
@@ -78,9 +90,15 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
|
||||
const onSave = useLockFn(async () => {
|
||||
try {
|
||||
const routeExcludeAddress = splitRouteExcludeAddress(
|
||||
values.routeExcludeAddress,
|
||||
);
|
||||
const routeExcludeAddress = routeExcludeAddressItems;
|
||||
|
||||
if (routeExcludeAddressError) {
|
||||
showNotice.error(
|
||||
"settings.modals.tun.messages.invalidRouteExcludeAddress",
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
const tun: IConfigData["tun"] = {
|
||||
stack: values.stack,
|
||||
device:
|
||||
@@ -312,6 +330,8 @@ export function TunViewer({ ref }: { ref?: Ref<DialogRef> }) {
|
||||
placeholder="192.168.0.0/16"
|
||||
ariaLabel={t("settings.modals.tun.fields.routeExcludeAddress")}
|
||||
disabled={!values.autoRoute}
|
||||
error={routeExcludeAddressError}
|
||||
helperText={routeExcludeAddressHelperText}
|
||||
onChange={(nextValue) =>
|
||||
setValues((v) => ({ ...v, routeExcludeAddress: nextValue }))
|
||||
}
|
||||
|
||||
@@ -19,7 +19,12 @@ import { useClash } from "@/hooks/use-clash";
|
||||
import { useAppData } from "@/providers/app-data-context";
|
||||
import { isPortInUse } from "@/services/cmds";
|
||||
import { showNotice } from "@/services/notice-service";
|
||||
import { parseHost, parsedLocalhost, isValidPort } from "@/utils/helper";
|
||||
import {
|
||||
formatHostPort,
|
||||
isValidPort,
|
||||
normalizeHost,
|
||||
normalizeListenHost,
|
||||
} from "@/utils/network";
|
||||
|
||||
interface TunnelsViewerRef {
|
||||
open: () => void;
|
||||
@@ -127,8 +132,8 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
||||
}
|
||||
|
||||
// 本地地址校验(host)
|
||||
const parsedLocal = parsedLocalhost(localAddr);
|
||||
if (!parsedLocal) {
|
||||
const localHost = normalizeListenHost(localAddr);
|
||||
if (!localHost) {
|
||||
showNotice.error(
|
||||
"settings.sections.clash.form.fields.tunnels.messages.invalidLocalAddr",
|
||||
);
|
||||
@@ -151,8 +156,8 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
||||
}
|
||||
|
||||
// 目标地址校验 (host)
|
||||
const parsedTarget = parseHost(targetAddr);
|
||||
if (!parsedTarget) {
|
||||
const targetHost = normalizeHost(targetAddr);
|
||||
if (!targetHost) {
|
||||
showNotice.error(
|
||||
"settings.sections.clash.form.fields.tunnels.messages.invalidTargetAddr",
|
||||
);
|
||||
@@ -170,14 +175,8 @@ export const TunnelsViewer = forwardRef<TunnelsViewerRef>((_, ref) => {
|
||||
// 构造新 entry
|
||||
const entry: TunnelEntry = {
|
||||
network: network === "tcp+udp" ? ["tcp", "udp"] : [network],
|
||||
address:
|
||||
parsedLocal.kind === "ipv6"
|
||||
? `[${parsedLocal.host}]:${localPort}`
|
||||
: `${parsedLocal.host}:${localPort}`,
|
||||
target:
|
||||
parsedTarget.kind === "ipv6"
|
||||
? `[${parsedTarget.host}]:${targetPort}`
|
||||
: `${parsedTarget.host}:${targetPort}`,
|
||||
address: formatHostPort(localHost, localPort),
|
||||
target: formatHostPort(targetHost, targetPort),
|
||||
...(proxy ? { proxy } : {}),
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user