87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
const IPV4 = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/
|
|
|
|
/** Нормализация IP/endpoint из RouterOS (CIDR, zone id) — как в network-map-layout. */
|
|
export function normalizeGreEndpointAddr(addr: string): string {
|
|
return addr.trim().split("/")[0].split("%")[0].trim()
|
|
}
|
|
|
|
/** Явный IPv4 (для отличия от FQDN в remote-address GRE). */
|
|
export function isIpv4String(s: string): boolean {
|
|
return IPV4.test(s.trim())
|
|
}
|
|
|
|
/**
|
|
* Сопоставление IP WAN в каталоге с outer GRE: прямой IP или FQDN,
|
|
* зарезолвенный в API (hostname lower-case → A-запись).
|
|
*/
|
|
export function wanUplinkIpMatchesGreOuter(
|
|
wanIp: string,
|
|
greOuter: string,
|
|
resolvedIpv4ByHost?: ReadonlyMap<string, string>,
|
|
): boolean {
|
|
const a = normalizeGreEndpointAddr(wanIp)
|
|
const b = normalizeGreEndpointAddr(greOuter)
|
|
if (!a || !b) return false
|
|
if (a === b) return true
|
|
if (resolvedIpv4ByHost) {
|
|
const r = resolvedIpv4ByHost.get(b.toLowerCase())
|
|
if (r && a === normalizeGreEndpointAddr(r)) return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
/** Все варианты внешнего IP для сопоставления с каталогом (IP и зарезолвенный FQDN). */
|
|
export function greOuterComparableIps(
|
|
outer: string,
|
|
resolvedIpv4ByHost?: ReadonlyMap<string, string>,
|
|
): string[] {
|
|
const n = normalizeGreEndpointAddr(outer)
|
|
if (!n || n === "0.0.0.0") return []
|
|
const out: string[] = []
|
|
if (isIpv4String(n)) out.push(n)
|
|
const r = resolvedIpv4ByHost?.get(n.toLowerCase())
|
|
if (r) {
|
|
const ip = normalizeGreEndpointAddr(r)
|
|
if (ip && isIpv4String(ip) && !out.includes(ip)) out.push(ip)
|
|
}
|
|
return out
|
|
}
|
|
|
|
/** Собрать уникальные FQDN из local/remote-address (без чистых IPv4). */
|
|
export function collectGreEndpointHostnames(
|
|
tunnels: Array<{ localAddress: string; remoteAddress: string }>,
|
|
): string[] {
|
|
const set = new Set<string>()
|
|
for (const t of tunnels) {
|
|
for (const a of [t.localAddress, t.remoteAddress]) {
|
|
const n = normalizeGreEndpointAddr(a)
|
|
if (!n || n === "0.0.0.0") continue
|
|
if (!isIpv4String(n)) set.add(n.toLowerCase())
|
|
}
|
|
}
|
|
return [...set]
|
|
}
|
|
|
|
/** Ответ POST /api/network/resolve-hosts → Map для матчинга. */
|
|
export function greResolvedMapFromApi(
|
|
results: Record<string, string | null> | undefined,
|
|
): ReadonlyMap<string, string> {
|
|
const m = new Map<string, string>()
|
|
if (!results) return m
|
|
for (const [k, v] of Object.entries(results)) {
|
|
if (v && isIpv4String(v)) m.set(k.toLowerCase(), normalizeGreEndpointAddr(v))
|
|
}
|
|
return m
|
|
}
|
|
|
|
/** Подпись outer на карте: при FQDN дополнительно показываем разрешённый IPv4. */
|
|
export function formatGreOuterForDisplay(
|
|
addr: string,
|
|
resolvedIpv4ByHost?: ReadonlyMap<string, string>,
|
|
): string {
|
|
const n = normalizeGreEndpointAddr(addr) || "?"
|
|
if (isIpv4String(n)) return n
|
|
const r = resolvedIpv4ByHost?.get(n.toLowerCase())
|
|
return r ? `${n} (${r})` : n
|
|
}
|