fix(ospf): показывать IPv6 маршруты и фактический шлюз
This commit is contained in:
1 parent
12ead0b6c2
commit
f6b9c6941d
19 files changed
+1424
-32
No files matched your search
@@ -17,9 +17,16 @@ for (const rows of [[{ ...row, serverId: 2 }], [row, row], [{ ...row, id: " " }]
|
||||
assert.equal(result.routes.length, 0)
|
||||
assert.equal(result.failures.length, 1)
|
||||
}
|
||||
const client = { getOspfNeighbors: async () => [], getOspfAreas: async () => [], getOspfInterfaceTemplates: async () => [], getOspfInstances: async () => [], getBfdSessions: async () => [], getIpRoutes: async () => [] }
|
||||
const client = { getOspfNeighbors: async () => [], getOspfAreas: async () => [], getOspfInterfaceTemplates: async () => [], getOspfInstances: async () => [], getBfdSessions: async () => [], getIpRoutes: async () => [], getIpv6Routes: async () => [] }
|
||||
assert.equal((await readOspfCatalogRaw(client)).bfdSessions.length, 0)
|
||||
for (const section of ["getBfdSessions", "getIpRoutes"] as const) await assert.rejects(() => readOspfCatalogRaw({ ...client, [section]: async () => { throw new Error("Отказ " + section) } }), /Отказ/)
|
||||
for (const section of ["getBfdSessions", "getIpRoutes", "getIpv6Routes"] as const) await assert.rejects(() => readOspfCatalogRaw({ ...client, [section]: async () => { throw new Error("Отказ " + section) } }), /Отказ/)
|
||||
const dual = await readOspfCatalogRaw({ ...client, getIpRoutes: async () => [{ ".id": "*same", "dst-address": "192.0.2.0/24", ospf: "true" }], getIpv6Routes: async () => [{ ".id": "*same", "dst-address": "fd00:154::/64", ospf: "true", "ospf-metric": "10", "immediate-gw": "fe80::2%gre-own" }] })
|
||||
assert.deepEqual(dual.ipRoutes.map(r => r[".id"]), ["*same", "ipv6:*same"])
|
||||
assert.equal(dual.ipRoutes[1]?.["dst-address"], "fd00:154::/64")
|
||||
const ipv6Catalog = parseCatalogRoutes({ id: 1 } as typeof servers.$inferSelect, dual.ipRoutes)
|
||||
assert.equal(ipv6Catalog[1]?.via, "gre-own")
|
||||
assert.equal(ipv6Catalog[1]?.nextHop, "fe80::2")
|
||||
await assert.rejects(() => readOspfCatalogRaw({ ...client, getIpv6Routes: async () => ({}) as never }), /Некорректный ответ/)
|
||||
const app = Fastify()
|
||||
app.setValidatorCompiler(validatorCompiler)
|
||||
app.setSerializerCompiler(serializerCompiler)
|
||||
@@ -80,7 +87,7 @@ for (const type of ["home-router", "exit-node"]) {
|
||||
console.log("OSPF selected: disabled home-router/exit-node do not poll; unknown/invalid IDs fail before RouterOS")
|
||||
{
|
||||
const {MikrotikClient}=await import("../services/mikrotik.js"),original=MikrotikClient.fromServer
|
||||
MikrotikClient.fromServer=()=>({getOspfNeighbors:async()=>[],getOspfAreas:async()=>[],getOspfInterfaceTemplates:async()=>[],getOspfInstances:async()=>[],getBfdSessions:async()=>{throw Error("BFD unavailable")},getIpRoutes:async()=>[]} as unknown as MikrotikClient)
|
||||
MikrotikClient.fromServer=()=>({getOspfNeighbors:async()=>[],getOspfAreas:async()=>[],getOspfInterfaceTemplates:async()=>[],getOspfInstances:async()=>[],getBfdSessions:async()=>{throw Error("BFD unavailable")},getIpRoutes:async()=>[],getIpv6Routes:async()=>[]} as unknown as MikrotikClient)
|
||||
const testApp=Fastify();testApp.setValidatorCompiler(validatorCompiler);testApp.setSerializerCompiler(serializerCompiler)
|
||||
try{await testApp.register(createOspfRoutes({listEnabledServers:async()=>[],readCatalog:async()=>emptyOspfCatalog(),findServer:async()=>({id:1,name:"test",enabled:true} as typeof servers.$inferSelect)}),{prefix:"/api"});const r=await testApp.inject({method:"GET",url:"/api/servers/1/ospf"});assert.equal(r.statusCode,503);assert.match(r.json().error,/BFD unavailable/)}finally{await testApp.close();MikrotikClient.fromServer=original}
|
||||
}
|
||||
|
||||
@@ -80,16 +80,7 @@ function parseAddrIface(addr: string): { ip: string; iface: string } {
|
||||
|
||||
/** Fetch all OSPF + BFD data for one server */
|
||||
async function fetchServerOspf(server: ServerRow) {
|
||||
const client = MikrotikClient.fromServer(server)
|
||||
const [neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes] = await Promise.all([
|
||||
client.getOspfNeighbors(),
|
||||
client.getOspfAreas(),
|
||||
client.getOspfInterfaceTemplates(),
|
||||
client.getOspfInstances(),
|
||||
client.getBfdSessions().catch(() => [] as RosBfdSession[]), // BFD is optional
|
||||
client.getIpRoutes().catch(() => [] as RosIpRoute[]),
|
||||
])
|
||||
return { neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes }
|
||||
return readOspfCatalogRaw(MikrotikClient.fromServer(server))
|
||||
}
|
||||
|
||||
// ── BFD parser ────────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -683,6 +683,10 @@ export class MikrotikClient {
|
||||
return this.get<RosIpRoute[]>("/ip/route")
|
||||
}
|
||||
|
||||
async getIpv6Routes(): Promise<RosIpRoute[]> {
|
||||
return this.get<RosIpRoute[]>("/ipv6/route")
|
||||
}
|
||||
|
||||
async getFirewallFilters(): Promise<RosFirewallFilter[]> {
|
||||
return this.getFirewallRules("ip", "filter")
|
||||
}
|
||||
|
||||
@@ -11,11 +11,12 @@ export interface OspfCatalogCollections {
|
||||
bfdSessions: CatalogBfdSession[]; routes: CatalogRoute[];
|
||||
}
|
||||
export const emptyOspfCatalog = (): OspfCatalogCollections => ({ neighbors: [], interfaces: [], instances: [], bfdSessions: [], routes: [] })
|
||||
export async function readOspfCatalogRaw(client: Pick<MikrotikClient, "getOspfNeighbors" | "getOspfAreas" | "getOspfInterfaceTemplates" | "getOspfInstances" | "getBfdSessions" | "getIpRoutes">) {
|
||||
const [neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes] = await Promise.all([
|
||||
client.getOspfNeighbors(), client.getOspfAreas(), client.getOspfInterfaceTemplates(), client.getOspfInstances(), client.getBfdSessions(), client.getIpRoutes(),
|
||||
export async function readOspfCatalogRaw(client: Pick<MikrotikClient, "getOspfNeighbors" | "getOspfAreas" | "getOspfInterfaceTemplates" | "getOspfInstances" | "getBfdSessions" | "getIpRoutes" | "getIpv6Routes">) {
|
||||
const [neighbors, areas, ifaceTemplates, instances, bfdSessions, v4Routes, v6Routes] = await Promise.all([
|
||||
client.getOspfNeighbors(), client.getOspfAreas(), client.getOspfInterfaceTemplates(), client.getOspfInstances(), client.getBfdSessions(), client.getIpRoutes(), client.getIpv6Routes(),
|
||||
])
|
||||
if ([neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes].some(rows => !Array.isArray(rows))) throw new Error("Некорректный ответ RouterOS OSPF")
|
||||
if ([neighbors, areas, ifaceTemplates, instances, bfdSessions, v4Routes, v6Routes].some(rows => !Array.isArray(rows))) throw new Error("Некорректный ответ RouterOS OSPF")
|
||||
const ipRoutes = [...v4Routes, ...v6Routes.map((route, index) => ({ ...route, ".id": `ipv6:${route[".id"] ?? index}` }))]
|
||||
return { neighbors, areas, ifaceTemplates, instances, bfdSessions, ipRoutes }
|
||||
}
|
||||
export async function pollOspfCatalog<S extends { id: number; name: string }>(devices: readonly S[], read: (device: S) => Promise<OspfCatalogCollections>) {
|
||||
|
||||
@@ -20,6 +20,9 @@ assert.deepEqual(parseOspfGateway(route({ gateway: "10.200.0.1%gre-msk-spb" })),
|
||||
})
|
||||
|
||||
console.log("ospf-route-parse.test.ts: ok")
|
||||
assert.deepEqual(parseOspfGateway(route({ gateway: "configured-gateway", "immediate-gw": "fe80::2%gre-own" })), { nextHop: "fe80::2", via: "gre-own" })
|
||||
assert.deepEqual(parseOspfGateway(route({ gateway: "", "immediate-gw": "198.18.154.2%gre-own" })), { nextHop: "198.18.154.2", via: "gre-own" })
|
||||
assert.deepEqual(parseOspfGateway(route({ gateway: "198.18.154.2%gre-own", "immediate-gw": "" })), { nextHop: "198.18.154.2", via: "gre-own" })
|
||||
|
||||
assert.equal(parseOspfCatalogRouteType(route({ ospf: "true" })), "Unknown")
|
||||
assert.equal(parseOspfCatalogRouteType(route({ ospf: "true", "ospf-type": "future-type" })), "Unknown")
|
||||
|
||||
@@ -19,7 +19,7 @@ export function parseOspfRouteType(r: RosIpRoute): OspfRouteKind | null {
|
||||
}
|
||||
|
||||
export function parseOspfGateway(r: RosIpRoute): { nextHop: string; via: string } {
|
||||
const gw = (r.gateway ?? r["immediate-gw"] ?? "").trim()
|
||||
const gw = r["immediate-gw"]?.trim() || r.gateway?.trim() || ""
|
||||
const [ip, iface = ""] = gw.split("%")
|
||||
return {
|
||||
nextHop: ip || gw || "—",
|
||||
|
||||
Reference in new issue
Block a user