实现路由生成和聚合
This commit is contained in:
parent
0c58ae4d3d
commit
a0275e28a8
@ -1,6 +1,7 @@
|
||||
import { ReactNode, useContext } from 'react';
|
||||
import { Handle, Position, NodeProps, useReactFlow} from '@xyflow/react';
|
||||
import { AppNode, AppEdge, SettingsContext, NodeData } from '../types/graph';
|
||||
import { AppNode, AppEdge, SettingsContext, NodeData, Settings } from '../types/graph';
|
||||
import { CIDR, IPNetwork } from '../utils/iputils';
|
||||
import './CustomNode.css';
|
||||
import toast from 'react-hot-toast';
|
||||
|
||||
@ -13,34 +14,45 @@ class ConfigResult {
|
||||
}
|
||||
|
||||
function generateConfig(
|
||||
data : NodeData,
|
||||
getEdges : () => AppEdge[],
|
||||
getNode : (id: string) => AppNode | undefined) : ConfigResult {
|
||||
settings: Settings,
|
||||
data: NodeData,
|
||||
getEdges: () => AppEdge[],
|
||||
getEdge: (id: string) => AppEdge | undefined,
|
||||
getNode: (id: string) => AppNode | undefined) : ConfigResult {
|
||||
|
||||
const getNearEdges = (node: AppNode) : AppEdge[] => {
|
||||
return getEdges().filter(edge => edge.source === node.id || edge.target === node.id);
|
||||
};
|
||||
|
||||
const getNextNode = (edge: AppEdge, node: AppNode) : AppNode | undefined => {
|
||||
const getNextNode = (edge: AppEdge, node: AppNode) : AppNode => {
|
||||
const nextNodeId = edge.source === node.id ? edge.target : edge.source;
|
||||
return getNode(nextNodeId);
|
||||
return getNode(nextNodeId)!;
|
||||
};
|
||||
|
||||
const node = getNode(data.id);
|
||||
if(!node) {
|
||||
return new ConfigResult(false, undefined, "节点未找到");
|
||||
const node = getNode(data.id)!;
|
||||
|
||||
// 1. 预处理disallowIPs
|
||||
const disallowCIDRs : CIDR[] = [];
|
||||
if(data.disallowIPs) {
|
||||
const disallowList = data.disallowIPs.split(',').map(ip => ip.trim()).filter(ip => ip.length > 0);
|
||||
for(const ip of disallowList) {
|
||||
const result = IPNetwork.parse(ip);
|
||||
if(!result.cidr) {
|
||||
return new ConfigResult(false, undefined, `无效的禁止访问IP: ${ip} (${result.error})`);
|
||||
}
|
||||
disallowCIDRs.push(result.cidr);
|
||||
}
|
||||
}
|
||||
|
||||
// 2. 按边分组
|
||||
const belongsToEdge : Record<string, string | undefined> = {[node.id]: node.id};
|
||||
|
||||
const queue : AppNode[] = [];
|
||||
const nearEdges = getNearEdges(node);
|
||||
nearEdges.forEach(edge => {
|
||||
const nextNode = getNextNode(edge, node);
|
||||
if(nextNode) {
|
||||
belongsToEdge[nextNode.id] = edge.id;
|
||||
queue.push(nextNode);
|
||||
}
|
||||
const nextNode = getNextNode(edge, node)!;
|
||||
belongsToEdge[nextNode.id] = edge.id;
|
||||
queue.push(nextNode);
|
||||
});
|
||||
|
||||
while(queue.length > 0) {
|
||||
@ -49,8 +61,8 @@ function generateConfig(
|
||||
if(!fromEdgeId) continue;
|
||||
|
||||
getNearEdges(currentNode).forEach(edge => {
|
||||
const nextNode = getNextNode(edge, currentNode);
|
||||
if(nextNode && !belongsToEdge[nextNode.id]) {
|
||||
const nextNode = getNextNode(edge, currentNode)!;
|
||||
if(!belongsToEdge[nextNode.id]) {
|
||||
belongsToEdge[nextNode.id] = fromEdgeId;
|
||||
queue.push(nextNode);
|
||||
}
|
||||
@ -58,30 +70,72 @@ function generateConfig(
|
||||
}
|
||||
|
||||
const groupedByEdge: Record<string, string[] | undefined> = {};
|
||||
const nodeIds : string[] = [];
|
||||
for (const nodeId in belongsToEdge) {
|
||||
const edgeId = belongsToEdge[nodeId];
|
||||
if(edgeId === nodeId) continue; // 跳过起始节点
|
||||
nodeIds.push(nodeId);
|
||||
if(!edgeId) continue;
|
||||
|
||||
if (!groupedByEdge[edgeId]) {
|
||||
groupedByEdge[edgeId] = [];
|
||||
}
|
||||
|
||||
groupedByEdge[edgeId].push(nodeId);
|
||||
}
|
||||
|
||||
|
||||
// 3. 生成节点到cidr的映射(ipv4)
|
||||
const nodeIdToCIDR : Record<string, CIDR> = {};
|
||||
for(const nodeId of nodeIds) {
|
||||
const node = getNode(nodeId)!;
|
||||
const ipv4 = node.data.ipv4Address;
|
||||
if(!ipv4) continue;
|
||||
const result = IPNetwork.parse(ipv4);
|
||||
const cidr = result.cidr;
|
||||
if(!cidr) {
|
||||
return new ConfigResult(false, undefined, `节点 ${node.data.label} 的IPv4地址无效: ${ipv4} (${result.error})`);
|
||||
}
|
||||
cidr.mask = 32;
|
||||
nodeIdToCIDR[nodeId] = cidr;
|
||||
}
|
||||
|
||||
// 4. 为每个分组生成allowIPs列表
|
||||
const allCIDRs = nodeIds.flatMap(nodeId => {
|
||||
const cidr = nodeIdToCIDR[nodeId];
|
||||
return cidr ? [cidr] : [];
|
||||
});
|
||||
for(const edgeId in groupedByEdge) {
|
||||
const groupNodeIds = groupedByEdge[edgeId];
|
||||
if(!groupNodeIds) continue;
|
||||
|
||||
const targetCIDRs = groupNodeIds.flatMap(nodeId => {
|
||||
const cidr = nodeIdToCIDR[nodeId];
|
||||
if(!cidr || disallowCIDRs.some(disallow => disallow.contains(cidr))) {
|
||||
return [];
|
||||
}
|
||||
return [cidr];
|
||||
});
|
||||
|
||||
const nextNode = getNextNode(getEdge(edgeId)!, node)!;
|
||||
console.log(`${node.data.label} -> ${nextNode.data.label}:`);
|
||||
console.table(allCIDRs.map(c => c.toString()));
|
||||
console.table(targetCIDRs.map(c => c.toString()));
|
||||
|
||||
const result = IPNetwork.mergeCIDRs(allCIDRs, targetCIDRs);
|
||||
|
||||
console.table(result?.map(c => c.toString()) || []);
|
||||
}
|
||||
|
||||
return new ConfigResult(false);
|
||||
}
|
||||
|
||||
export default function CustomNode({
|
||||
data,
|
||||
selected
|
||||
}: NodeProps<AppNode>): ReactNode {
|
||||
const { getNode, getEdges } = useReactFlow<AppNode, AppEdge>();
|
||||
const settings = useContext(SettingsContext);
|
||||
const { getNode, getEdge, getEdges } = useReactFlow<AppNode, AppEdge>();
|
||||
|
||||
const handleGenerate = (node : NodeData) => {
|
||||
const result = generateConfig(node, getEdges, getNode);
|
||||
const result = generateConfig(settings, node, getEdges, getEdge, getNode);
|
||||
if(result.success && result.config) {
|
||||
navigator.clipboard.writeText(result.config).then(() => {
|
||||
toast.success("配置已复制到剪贴板");
|
||||
@ -93,7 +147,6 @@ export default function CustomNode({
|
||||
}
|
||||
};
|
||||
|
||||
const settings = useContext(SettingsContext);
|
||||
|
||||
return (
|
||||
<div className={`custom-node ${selected ? 'selected' : ''}`}>
|
||||
@ -102,25 +155,15 @@ export default function CustomNode({
|
||||
</div>
|
||||
|
||||
<div className='node-info'>
|
||||
{settings.ipv4Subnet && (
|
||||
<div className="info-item">
|
||||
<span className="label">IPv4地址:</span>
|
||||
<span className="value">{data.ipv4Address || "未设置"}</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="info-item">
|
||||
<span className="label">IPv4地址:</span>
|
||||
<span className="value">{data.ipv4Address || "未设置"}</span>
|
||||
</div>
|
||||
|
||||
{settings.ipv6Subnet && (
|
||||
<div className="info-item">
|
||||
<span className="label">IPv6地址:</span>
|
||||
<span className="value">{data.ipv6Address || "未设置"}</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(!settings.ipv4Subnet && !settings.ipv6Subnet) && (
|
||||
<div className="info-item">
|
||||
<span className="label">未设置任何子网</span>
|
||||
</div>
|
||||
)}
|
||||
<div className="info-item">
|
||||
<span className="label">IPv6地址:</span>
|
||||
<span className="value">{data.ipv6Address || "未设置"}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="node-actions">
|
||||
|
||||
@ -15,7 +15,6 @@ interface NodeEditorProps {
|
||||
function Validate(updateData : NodeDataUpdate, settings : Settings) : string[] {
|
||||
const errors: string[] = [];
|
||||
const {ipv4Address, ipv6Address, mtu, listenPort} = updateData;
|
||||
const {ipv4Subnet, ipv6Subnet} = settings;
|
||||
|
||||
if(!updateData.label) {
|
||||
errors.push("Label不能是空");
|
||||
@ -25,17 +24,17 @@ function Validate(updateData : NodeDataUpdate, settings : Settings) : string[] {
|
||||
errors.push("privateKey不能是空");
|
||||
}
|
||||
|
||||
if(ipv4Subnet) {
|
||||
const cidr = IPNetwork.parse(ipv4Subnet);
|
||||
if(ipv4Address && !cidr.contains(IPNetwork.parse(`${ipv4Address}/32`))) {
|
||||
errors.push("IPv4不在子网范围中");
|
||||
if(ipv4Address) {
|
||||
const result = IPNetwork.parse(ipv4Address);
|
||||
if(!result.cidr || result.cidr.version !== 'IPv4') {
|
||||
errors.push("IPv4地址无效");
|
||||
}
|
||||
}
|
||||
|
||||
if(ipv6Subnet) {
|
||||
const cidr = IPNetwork.parse(ipv6Subnet);
|
||||
if(ipv6Address && !cidr.contains(IPNetwork.parse(`${ipv6Address}/128`))) {
|
||||
errors.push("IPv6不在子网范围中");
|
||||
if(ipv6Address) {
|
||||
const result = IPNetwork.parse(ipv6Address);
|
||||
if(!result.cidr || result.cidr.version !== 'IPv6') {
|
||||
errors.push("IPv6地址无效");
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,29 +147,25 @@ export default function NodeEditor({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{settings.ipv4Subnet && (
|
||||
<div className="form-group">
|
||||
<label>IPv4地址</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv4Address || ''}
|
||||
onChange={e => setIpv4Address(e.target.value)}
|
||||
placeholder={`当前子网:${settings.ipv4Subnet}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-group">
|
||||
<label>IPv4地址</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv4Address || ''}
|
||||
onChange={e => setIpv4Address(e.target.value)}
|
||||
placeholder={`例如:172.29.0.1/16,留空代表不使用ipv4`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{settings.ipv6Subnet && (
|
||||
<div className="form-group">
|
||||
<label>IPv6地址</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv6Address || ''}
|
||||
onChange={e => setIpv6Address(e.target.value)}
|
||||
placeholder={`当前子网:${settings.ipv6Subnet}`}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className="form-group">
|
||||
<label>IPv6地址</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv6Address || ''}
|
||||
onChange={e => setIpv6Address(e.target.value)}
|
||||
placeholder={`例如:fd23:23:23::1/64,留空代表不使用ipv6`}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Folder title='高级'>
|
||||
<div className="form-group">
|
||||
|
||||
@ -12,25 +12,7 @@ interface SettingEditorProps {
|
||||
function Validate(updateData: Settings) : string[] {
|
||||
const errors: string[] = [];
|
||||
|
||||
const {ipv4Subnet, ipv6Subnet, mtu, listenPort} = updateData;
|
||||
|
||||
if(ipv4Subnet) {
|
||||
const result = IPNetwork.parse(ipv4Subnet)
|
||||
if(!result.isValid) {
|
||||
errors.push("IPv4子网:" + (result.error || "ipv4子网不合法"))
|
||||
} else if(result.version != 'IPv4') {
|
||||
errors.push("IPv4子网:" + "非IPv4 CIDR");
|
||||
}
|
||||
}
|
||||
|
||||
if(ipv6Subnet) {
|
||||
const result = IPNetwork.parse(ipv6Subnet)
|
||||
if(!result.isValid) {
|
||||
errors.push("IPv6子网:" + (result.error || "子网不合法"));
|
||||
} else if(result.version != 'IPv6') {
|
||||
errors.push("IPv6子网:" + "非IPv6 CIDR");
|
||||
}
|
||||
}
|
||||
const {mtu, listenPort} = updateData;
|
||||
|
||||
if(isNaN(listenPort)) {
|
||||
errors.push("监听端口不是数字");
|
||||
@ -56,15 +38,11 @@ export default function SettingsEditor({
|
||||
|
||||
const [listenPort, setListenPort] = useState<number>(settings.listenPort);
|
||||
const [mtu, setmtu] = useState<number>(settings.mtu);
|
||||
const [ipv4Subnet, setIpv4Subnet] = useState(settings.ipv4Subnet)
|
||||
const [ipv6Subnet, setIpv6Subnet] = useState(settings.ipv6Subnet)
|
||||
|
||||
const handleSave = (): void => {
|
||||
const updateData = {
|
||||
listenPort: listenPort,
|
||||
mtu: mtu,
|
||||
ipv4Subnet: ipv4Subnet,
|
||||
ipv6Subnet: ipv6Subnet
|
||||
};
|
||||
|
||||
const validation = Validate(updateData);
|
||||
@ -122,26 +100,6 @@ export default function SettingsEditor({
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>ipv4子网</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv4Subnet || ''}
|
||||
onChange={e => setIpv4Subnet(e.target.value)}
|
||||
placeholder='例如:172.29.0.0/16,留空代表不使用ipv4'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="form-group">
|
||||
<label>ipv6子网</label>
|
||||
<input
|
||||
type="text"
|
||||
value={ipv6Subnet || ''}
|
||||
onChange={e => setIpv6Subnet(e.target.value)}
|
||||
placeholder='例如:fd23:23:23::/64,留空代表不使用ipv6'
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div className="editor-actions">
|
||||
<button className="btn-save" onClick={handleSave}>保存</button>
|
||||
<button className="btn-cancel" onClick={onClose}>取消</button>
|
||||
|
||||
@ -35,9 +35,6 @@ export type EdgeDataUpdate = {
|
||||
export interface Settings {
|
||||
listenPort: number;
|
||||
mtu: number;
|
||||
|
||||
ipv4Subnet?: string;
|
||||
ipv6Subnet?: string;
|
||||
}
|
||||
|
||||
export const initialSettings : Settings = {
|
||||
|
||||
@ -2,115 +2,178 @@ export type IPVersion = 'IPv4' | 'IPv6' | 'invalid';
|
||||
|
||||
export class CIDR {
|
||||
constructor(
|
||||
public isValid: boolean,
|
||||
public version: IPVersion,
|
||||
public binary: string,
|
||||
public mask: number,
|
||||
public error?: string
|
||||
public version: IPVersion,
|
||||
public binary: string,
|
||||
public mask: number,
|
||||
) {}
|
||||
|
||||
contains(cidr: CIDR) : boolean {
|
||||
if(!cidr.isValid || !this.isValid) return false;
|
||||
if(this.version !== cidr.version) return false;
|
||||
if(this.mask > cidr.mask) return false;
|
||||
|
||||
contains(cidr: CIDR): boolean {
|
||||
if (this.version !== cidr.version) return false;
|
||||
if (this.mask > cidr.mask) return false;
|
||||
return this.binary.slice(0, this.mask) === cidr.binary.slice(0, this.mask);
|
||||
}
|
||||
|
||||
toString(): string {
|
||||
if (this.version === 'invalid') return 'invalid';
|
||||
|
||||
if (this.version === 'IPv4') {
|
||||
// Split 32 bits into 4 octets
|
||||
const octets = [];
|
||||
for (let i = 0; i < 32; i += 8) {
|
||||
octets.push(parseInt(this.binary.slice(i, i + 8), 2));
|
||||
}
|
||||
return `${octets.join('.')}/${this.mask}`;
|
||||
} else {
|
||||
// Split 128 bits into 8 blocks of 16 bits
|
||||
const blocks = [];
|
||||
for (let i = 0; i < 128; i += 16) {
|
||||
blocks.push(parseInt(this.binary.slice(i, i + 16), 2).toString(16));
|
||||
}
|
||||
return `${blocks.join(':')}/${this.mask}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface CIDRParseResult {
|
||||
cidr?: CIDR;
|
||||
error: string;
|
||||
}
|
||||
|
||||
export class IPNetwork {
|
||||
/**
|
||||
* 解析 CIDR,返回结果对象(不抛出异常)
|
||||
*/
|
||||
static parse(cidr: string): CIDR {
|
||||
const parts = cidr.split('/');
|
||||
if (parts.length !== 2) {
|
||||
return this.invalid('格式错误,缺少掩码 (如 /24)');
|
||||
static mergeCIDRs(allCIDRs: CIDR[], targetCIDRs: CIDR[]): CIDR[] | undefined {
|
||||
// 返回符合条件的CIDR集合:
|
||||
// 1. 它们能够覆盖所有targetCIDRs,但是不能覆盖到任何在allCIDRs中不属于targetCIDRs的CIDR
|
||||
// 2. 如果有多个集合满足条件,返回其中CIDR数量最少的一个;如果有多个数量最少的集合,返回掩码尽可能大的
|
||||
// 3. 如果无法找到这样的集合(例如存在属于allCIDRs但不属于targetCIDRs的CIDR,它被targetCIDRs中的某个CIDR包含),则返回undefined
|
||||
|
||||
|
||||
// 1. 获取排除列表:属于 allCIDRs 但不属于 targetCIDRs 的
|
||||
const excluded = allCIDRs.filter(a =>
|
||||
!targetCIDRs.some(t => t.binary === a.binary && t.mask === a.mask)
|
||||
);
|
||||
|
||||
// 2. 基础冲突检查
|
||||
// 如果 target 包含 excluded,或者 excluded 包含 target,返回 undefined
|
||||
for (const t of targetCIDRs) {
|
||||
for (const e of excluded) {
|
||||
if (t.contains(e) || e.contains(t)) return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// 3. 分版本处理
|
||||
const v4 = this._runAggregation('IPv4', 32, targetCIDRs, excluded);
|
||||
const v6 = this._runAggregation('IPv6', 128, targetCIDRs, excluded);
|
||||
|
||||
return [...v4, ...v6];
|
||||
}
|
||||
|
||||
private static _runAggregation(version: IPVersion, maxBits: number, targets: CIDR[], excluded: CIDR[]): CIDR[] {
|
||||
const versionTargets = targets.filter(c => c.version === version);
|
||||
const versionExcluded = excluded.filter(c => c.version === version);
|
||||
if (versionTargets.length === 0) return [];
|
||||
|
||||
// 初始根节点:0.0.0.0/0 或 ::/0
|
||||
const rootBinary = '0'.repeat(maxBits);
|
||||
return this._solve(new CIDR(version, rootBinary, 0), versionTargets, versionExcluded, maxBits);
|
||||
}
|
||||
|
||||
private static _solve(node: CIDR, targets: CIDR[], excluded: CIDR[], maxBits: number): CIDR[] {
|
||||
// 如果当前节点不包含任何目标,直接返回空
|
||||
const hasTarget = targets.some(t => node.contains(t) || t.contains(node));
|
||||
if (!hasTarget) return [];
|
||||
|
||||
// 如果当前节点包含任何排除项,必须向下拆分
|
||||
const hasExcluded = excluded.some(e => node.contains(e));
|
||||
if (hasExcluded) {
|
||||
return this._splitAndSolve(node, targets, excluded, maxBits);
|
||||
}
|
||||
|
||||
// 走到这里说明 node 是“干净”的(不含任何 excluded)
|
||||
// 检查:如果这个 node 已经在 targets 里的某一个被完全包含,或者它本身就是 target
|
||||
// 我们需要判断:是直接用这个大的 node,还是用更小的子 node?
|
||||
|
||||
// 获取子节点的递归结果
|
||||
const subResults = this._splitAndSolve(node, targets, excluded, maxBits);
|
||||
|
||||
// 核心逻辑:
|
||||
// 1. 如果子节点汇总后数量 > 1,合并成当前 node 可以减少数量,选当前 node
|
||||
// 2. 如果子节点汇总后数量 <= 1 且能覆盖所有目标,保持子节点(因为子节点掩码更大)
|
||||
if (subResults.length > 1) {
|
||||
return [node];
|
||||
} else {
|
||||
// 特殊情况:如果当前 node 本身就在 targets 中,且 subResults 为空(因为 targets 可能在更深层)
|
||||
// 或者 subResults 长度就是 1,我们返回 subResults 以保持 mask 尽可能大
|
||||
return subResults.length === 0 ? (targets.some(t => t.contains(node)) ? [node] : []) : subResults;
|
||||
}
|
||||
}
|
||||
|
||||
private static _splitAndSolve(node: CIDR, targets: CIDR[], excluded: CIDR[], maxBits: number): CIDR[] {
|
||||
if (node.mask >= maxBits) return [];
|
||||
|
||||
const nextMask = node.mask + 1;
|
||||
// 左子节点:第 mask 位为 0
|
||||
const leftBinary = node.binary.slice(0, node.mask) + '0' + node.binary.slice(nextMask);
|
||||
// 右子节点:第 mask 位为 1
|
||||
const rightBinary = node.binary.slice(0, node.mask) + '1' + node.binary.slice(nextMask);
|
||||
|
||||
const left = new CIDR(node.version, leftBinary, nextMask);
|
||||
const right = new CIDR(node.version, rightBinary, nextMask);
|
||||
|
||||
return [
|
||||
...this._solve(left, targets, excluded, maxBits),
|
||||
...this._solve(right, targets, excluded, maxBits)
|
||||
];
|
||||
}
|
||||
|
||||
static parse(cidrStr: string): CIDRParseResult {
|
||||
const parts = cidrStr.split('/');
|
||||
if (parts.length !== 2) return { error: 'Invalid CIDR format' };
|
||||
|
||||
const [ip, maskStr] = parts;
|
||||
const mask = parseInt(maskStr, 10);
|
||||
const version = this.getVersion(ip);
|
||||
|
||||
// 基础校验
|
||||
if (version === 'invalid') return this.invalid('非法的 IP 格式');
|
||||
if (isNaN(mask)) return this.invalid('掩码必须是数字');
|
||||
|
||||
// 掩码范围校验
|
||||
if (version === 'IPv4' && (mask < 0 || mask > 32)) return this.invalid('IPv4 掩码范围应为 0-32');
|
||||
if (version === 'IPv6' && (mask < 0 || mask > 128)) return this.invalid('IPv6 掩码范围应为 0-128');
|
||||
|
||||
try {
|
||||
const binary = version === 'IPv4' ? this.ipv4ToBinary(ip) : this.ipv6ToBinary(ip);
|
||||
if (!binary) return this.invalid('IP 地址数值非法');
|
||||
|
||||
return new CIDR(true, version, binary, mask);
|
||||
} catch (e) {
|
||||
return this.invalid('解析过程中出错');
|
||||
}
|
||||
}
|
||||
|
||||
private static getVersion(ip: string): IPVersion {
|
||||
if (/^(\d{1,3}\.){3}\d{1,3}$/.test(ip)) return 'IPv4';
|
||||
if (ip.includes(':')) return 'IPv6'; // IPv6 逻辑较复杂,在转换函数中进一步精确校验
|
||||
return 'invalid';
|
||||
}
|
||||
|
||||
private static ipv4ToBinary(ip: string): string | null {
|
||||
const octets = ip.split('.');
|
||||
let binary = '';
|
||||
for (const o of octets) {
|
||||
const n = parseInt(o, 10);
|
||||
if (n < 0 || n > 255 || isNaN(n)) return null;
|
||||
binary += n.toString(2).padStart(8, '0');
|
||||
}
|
||||
return binary;
|
||||
}
|
||||
|
||||
private static ipv6ToBinary(ip: string): string | null {
|
||||
let fullIP = ip;
|
||||
try {
|
||||
if (ip.includes('::')) {
|
||||
const parts = ip.split('::');
|
||||
if (parts.length > 2) return null; // 只能有一个 ::
|
||||
const left = parts[0].split(':').filter(x => x.length > 0);
|
||||
const right = parts[1].split(':').filter(x => x.length > 0);
|
||||
const missing = 8 - (left.length + right.length);
|
||||
if (missing < 0) return null;
|
||||
fullIP = [...left, ...Array(missing).fill('0'), ...right].join(':');
|
||||
}
|
||||
const groups = fullIP.split(':');
|
||||
if (groups.length !== 8) return null;
|
||||
if (ip.includes('.')) {
|
||||
// IPv4 Logic
|
||||
if (isNaN(mask) || mask < 0 || mask > 32) return { error: 'Invalid IPv4 mask' };
|
||||
const octets = ip.split('.');
|
||||
if (octets.length !== 4) return { error: 'Invalid IPv4 address' };
|
||||
|
||||
let binary = '';
|
||||
for (const hex of groups) {
|
||||
const n = parseInt(hex, 16);
|
||||
if (isNaN(n) || n < 0 || n > 0xFFFF) return null;
|
||||
binary += n.toString(2).padStart(16, '0');
|
||||
for (const octet of octets) {
|
||||
const val = parseInt(octet, 10);
|
||||
if (isNaN(val) || val < 0 || val > 255) return { error: 'Invalid IPv4 octet' };
|
||||
binary += val.toString(2).padStart(8, '0');
|
||||
}
|
||||
return binary;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static invalid(msg: string): CIDR {
|
||||
return new CIDR(false, 'invalid', '', -1, msg);
|
||||
}
|
||||
|
||||
/**
|
||||
* 二进制转回 CIDR 字符串
|
||||
*/
|
||||
static fromBinary(binary: string, mask: number, version: IPVersion): string {
|
||||
if (version === 'IPv4' && binary.length === 32) {
|
||||
const octets = [];
|
||||
for (let i = 0; i < 32; i += 8) octets.push(parseInt(binary.slice(i, i + 8), 2));
|
||||
return `${octets.join('.')}/${mask}`;
|
||||
return { cidr: new CIDR('IPv4', binary, mask), error: '' };
|
||||
}
|
||||
if (version === 'IPv6' && binary.length === 128) {
|
||||
const segments = [];
|
||||
for (let i = 0; i < 128; i += 16) segments.push(parseInt(binary.slice(i, i + 16), 2).toString(16));
|
||||
return `${segments.join(':')}/${mask}`;
|
||||
|
||||
if (ip.includes(':')) {
|
||||
// IPv6 Logic
|
||||
if (isNaN(mask) || mask < 0 || mask > 128) return { error: 'Invalid IPv6 mask' };
|
||||
|
||||
// Expand "::" shorthand
|
||||
let fullIp = ip;
|
||||
if (ip.includes('::')) {
|
||||
const sides = ip.split('::');
|
||||
const left = sides[0].split(':').filter(x => x !== '');
|
||||
const right = sides[1].split(':').filter(x => x !== '');
|
||||
const missingCount = 8 - (left.length + right.length);
|
||||
const middle = new Array(missingCount).fill('0');
|
||||
fullIp = [...left, ...middle, ...right].join(':');
|
||||
}
|
||||
|
||||
const blocks = fullIp.split(':');
|
||||
if (blocks.length !== 8) return { error: 'Invalid IPv6 address' };
|
||||
|
||||
let binary = '';
|
||||
for (const block of blocks) {
|
||||
const val = parseInt(block || '0', 16);
|
||||
if (isNaN(val) || val < 0 || val > 0xFFFF) return { error: 'Invalid IPv6 block' };
|
||||
binary += val.toString(2).padStart(16, '0');
|
||||
}
|
||||
return { cidr: new CIDR('IPv6', binary, mask), error: '' };
|
||||
}
|
||||
return 'invalid-input';
|
||||
|
||||
return { error: '未知 IP version' };
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user