Files
MikrotikManager/lib/gre-endpoint-resolve.ts
T
DenozordecandCursor 5f31bb47fb chore: synchronize pending app/backend updates and repository hygiene
Includes current frontend and backend work in progress and removes generated artifacts from tracking to keep the repository clean for дальнейшая разработка.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-07 12:29:04 +07:00

90 lines
3.3 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 rb = resolvedIpv4ByHost.get(b.toLowerCase())
if (rb && a === normalizeGreEndpointAddr(rb)) return true
const ra = resolvedIpv4ByHost.get(a.toLowerCase())
if (ra && b === normalizeGreEndpointAddr(ra)) return true
if (ra && rb && normalizeGreEndpointAddr(ra) === normalizeGreEndpointAddr(rb)) 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
}