41 lines
2.9 KiB
TypeScript
41 lines
2.9 KiB
TypeScript
export type OspfNeighborState = "Full" | "2-Way" | "ExStart" | "Down" | "Attempt" | "Init" | "Exchange" | "Loading" | "Unknown"
|
|
export type BfdState = "Up" | "Down" | "Init" | "AdminDown" | "Unknown"
|
|
const neighborStates: Record<string, OspfNeighborState> = { full: "Full", "2-way": "2-Way", exstart: "ExStart", down: "Down", attempt: "Attempt", init: "Init", exchange: "Exchange", loading: "Loading" }
|
|
const bfdStates: Record<string, BfdState> = { up: "Up", down: "Down", init: "Init", admindown: "AdminDown" }
|
|
export function normalizeOspfState(value: string): OspfNeighborState { return neighborStates[value.trim().toLowerCase()] ?? "Unknown" }
|
|
export function normalizeBfdState(value: string): BfdState { return bfdStates[value.trim().toLowerCase()] ?? "Unknown" }
|
|
export function ospfNodeState(states: OspfNeighborState[]): "Full" | "2-Way" | "Pending" | "Down" | "Unknown" | "none" {
|
|
if (!states.length) return "none"
|
|
if (states.includes("Full")) return "Full"
|
|
if (states.includes("Unknown")) return "Unknown"
|
|
if (states.includes("2-Way")) return "2-Way"
|
|
if (states.some(state => state !== "Down")) return "Pending"
|
|
return "Down"
|
|
}
|
|
|
|
/** Correlate only a single observed direct BFD session on an explicitly configured interface. */
|
|
export function ospfBfdConflicts<T extends { localRouter: string; localIface: string; neighborAddress?: string; state: OspfNeighborState; dataUnknown?: boolean }>(
|
|
neighbors: T[],
|
|
interfaces: Array<{ serverId: string; interfaceName: string; useBfd?: boolean | null; active?: boolean | null; dataUnknown?: boolean }>,
|
|
sessions: Array<{ serverId: string; iface: string; remoteAddr: string; state: BfdState; multihop?: boolean | null; dataUnknown?: boolean }>,
|
|
): T[] {
|
|
const ip = (value: string, iface: string): string | null => {
|
|
const [address, zone, ...extra] = value.trim().split("%")
|
|
if (!address || extra.length || (zone !== undefined && zone !== iface)) return null
|
|
if (address.includes(":")) {
|
|
if (!/^[0-9a-f:.]+$/i.test(address)) return null
|
|
try { return new URL(`http://[${address}]/`).hostname } catch { return null }
|
|
}
|
|
const parts = address.split(".")
|
|
return parts.length === 4 && parts.every(p => /^(0|[1-9]\d{0,2})$/.test(p) && Number(p) <= 255) ? address : null
|
|
}
|
|
return neighbors.filter(n => {
|
|
if (n.dataUnknown || n.state !== "Full" || !n.neighborAddress || !n.localIface) return false
|
|
if (!interfaces.some(i => !i.dataUnknown && i.active === true && i.useBfd === true && i.serverId === n.localRouter && i.interfaceName === n.localIface)) return false
|
|
const peer = ip(n.neighborAddress, n.localIface)
|
|
if (!peer) return false
|
|
const matching = sessions.filter(s => s.multihop === false && s.serverId === n.localRouter && s.iface === n.localIface && ip(s.remoteAddr, s.iface) === peer)
|
|
return matching.length === 1 && !matching[0]!.dataUnknown && (matching[0]!.state === "Down" || matching[0]!.state === "AdminDown")
|
|
})
|
|
}
|