from .common import insert from .host import HOST def frr_to_ietf_neighbor_state(state): """Fetch OSPF neighbor state from Frr""" state = state.split("/")[0] if state == "TwoWay": return "2-way" return state.lower() def frr_to_ietf_neighbor_role(role): """Translate FRR neighbor role to YANG enumeration values""" if role == "Backup": return "BDR" # DR and DROther are already correct return role def add_routes(ospf): """Fetch OSPF routes from Frr""" cmd = ['vtysh', '-c', "show ip ospf route json"] data = HOST.run_json(cmd, default=[]) if data == []: return # No OSPF routes available routes = [] for prefix, info in data.items(): if prefix.find("/") == -1: # Ignore router IDs continue route = {} route["prefix"] = prefix nexthops = [] routetype = info["routeType"].split(" ") if len(routetype) > 1: if routetype[1] == "E1": route["route-type"] = "external-1" elif routetype[1] == "E2": route["route-type"] = "external-2" elif routetype[1] == "IA": route["route-type"] = "inter-area" elif routetype[0] == "N": route["route-type"] = "intra-area" # Add area information if available # Note: augmented by infix-routing.yang since standard ietf-ospf doesn't include it # Must use the augmenting module's prefix if info.get("area") is not None: route["infix-routing:area-id"] = info["area"] # Add metric (cost) if available if info.get("cost") is not None: route["metric"] = info["cost"] elif info.get("metric") is not None: route["metric"] = info["metric"] # Add route-tag for external routes if info.get("tag") is not None: route["route-tag"] = info["tag"] for hop in info["nexthops"]: nexthop = {} if hop["ip"] != " ": nexthop["next-hop"] = hop["ip"] else: nexthop["outgoing-interface"] = hop["directlyAttachedTo"] nexthops.append(nexthop) route["next-hops"] = {} route["next-hops"]["next-hop"] = nexthops routes.append(route) insert(ospf, "ietf-ospf:local-rib", "ietf-ospf:route", routes) def add_areas(control_protocols): """Populate OSPF status""" cmd = ['/usr/libexec/statd/ospf-status'] data = HOST.run_json(cmd, default={}) if data == {}: return # No OSPF data available control_protocol = {} control_protocol["type"] = "infix-routing:ospfv2" control_protocol["name"] = "default" control_protocol["ietf-ospf:ospf"] = {} control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"] = {} control_protocol["ietf-ospf:ospf"]["ietf-ospf:router-id"] = data.get("routerId") control_protocol["ietf-ospf:ospf"]["ietf-ospf:address-family"] = "ipv4" areas = [] for area_id, values in data.get("areas", {}).items(): area = {} area["ietf-ospf:area-id"] = area_id area["ietf-ospf:interfaces"] = {} if values.get("area-type"): area["ietf-ospf:area-type"] = values["area-type"] interfaces = [] for iface in values.get("interfaces", {}): interface = {} interface["ietf-ospf:neighbors"] = {} interface["name"] = iface["name"] if iface.get("drId"): interface["dr-router-id"] = iface["drId"] if iface.get("drAddress"): interface["dr-ip-addr"] = iface["drAddress"] if iface.get("bdrId"): interface["bdr-router-id"] = iface["bdrId"] if iface.get("bdrAddress"): interface["bdr-ip-addr"] = iface["bdrAddress"] if iface.get("timerPassiveIface"): interface["passive"] = True else: interface["passive"] = False interface["enabled"] = iface["ospfEnabled"] if iface["networkType"] == "POINTOPOINT": interface["interface-type"] = "point-to-point" if iface["networkType"] == "BROADCAST": interface["interface-type"] = "broadcast" if iface.get("state"): # Wev've never seen "DependUpon", and has no entry in # the YANG model, but is listed before down in Frr xlate = { "DependUpon": "down", "Down": "down", "Waiting": "waiting", "Loopback": "loopback", "Point-To-Point": "point-to-point", "DROther": "dr-other", "Backup": "bdr", "DR": "dr" } val = xlate.get(iface["state"], "unknown") interface["state"] = val # Interface priority (for DR/BDR election) if iface.get("priority") is not None: interface["priority"] = iface["priority"] # Interface cost if iface.get("cost") is not None: interface["cost"] = iface["cost"] # Configuration timers (in seconds) if iface.get("timerDeadSecs") is not None: interface["dead-interval"] = iface["timerDeadSecs"] if iface.get("timerRetransmitSecs") is not None: interface["retransmit-interval"] = iface["timerRetransmitSecs"] if iface.get("transmitDelaySecs") is not None: interface["transmit-delay"] = iface["transmitDelaySecs"] # Hello interval - convert from milliseconds to seconds if iface.get("timerMsecs") is not None: hello_sec = iface["timerMsecs"] // 1000 # timer-value-seconds16 requires range 1..65535, use max(1, value) if hello_sec >= 1: interface["hello-interval"] = hello_sec # Operational state timers (config false) # Hello timer - time remaining until next Hello (convert ms to seconds) if iface.get("timerHelloInMsecs") is not None: hello_timer_sec = iface["timerHelloInMsecs"] // 1000 # timer-value-seconds16 requires range 1..65535, use max(1, value) if hello_timer_sec >= 1: interface["hello-timer"] = hello_timer_sec # Wait timer - time until interface exits Waiting state if iface.get("timerWaitSecs") is not None: wait_sec = iface["timerWaitSecs"] # timer-value-seconds16 requires range 1..65535 if wait_sec >= 1: interface["wait-timer"] = wait_sec neighbors = [] for neigh in iface["neighbors"]: neighbor = {} neighbor["neighbor-router-id"] = neigh["neighborIp"] neighbor["address"] = neigh["ifaceAddress"] # Priority - use existing YANG leaf for operational data if neigh.get("nbrPriority") is not None: neighbor["priority"] = neigh["nbrPriority"] # Uptime - convert from milliseconds to seconds # Note: augmented by infix-routing.yang # Use lastPrgrsvChangeMsec from detail output (time since last progressive state change) if neigh.get("lastPrgrsvChangeMsec") is not None: uptime_sec = neigh["lastPrgrsvChangeMsec"] // 1000 neighbor["infix-routing:uptime"] = uptime_sec # Dead timer - convert from milliseconds to seconds # timer-value-seconds16 requires range 1..65535 if neigh.get("routerDeadIntervalTimerDueMsec") is not None: dead_timer_sec = neigh["routerDeadIntervalTimerDueMsec"] // 1000 if dead_timer_sec >= 1: neighbor["dead-timer"] = dead_timer_sec neighbor["state"] = frr_to_ietf_neighbor_state(neigh["nbrState"]) # Store role (DR/BDR/DROther) for display # Note: augmented by infix-routing.yang if neigh.get("role"): neighbor["infix-routing:role"] = frr_to_ietf_neighbor_role(neigh["role"]) # Store interface name with local address (e.g., "e5:10.0.23.1") # Note: augmented by infix-routing.yang # Compose from ifaceName and localIfaceAddress if neigh.get("ifaceName") and neigh.get("localIfaceAddress"): neighbor["infix-routing:interface-name"] = f"{neigh['ifaceName']}:{neigh['localIfaceAddress']}" elif neigh.get("ifaceName"): neighbor["infix-routing:interface-name"] = neigh["ifaceName"] if neigh.get("routerDesignatedId"): neighbor["dr-router-id"] = neigh["routerDesignatedId"] if neigh.get("routerDesignatedBackupId"): neighbor["bdr-router-id"] = neigh["routerDesignatedBackupId"] neighbors.append(neighbor) interface["ietf-ospf:neighbors"] = {} interface["ietf-ospf:neighbors"]["ietf-ospf:neighbor"] = neighbors interfaces.append(interface) area["ietf-ospf:interfaces"]["ietf-ospf:interface"] = interfaces areas.append(area) add_routes(control_protocol["ietf-ospf:ospf"]) control_protocol["ietf-ospf:ospf"]["ietf-ospf:areas"]["ietf-ospf:area"] = areas insert(control_protocols, "control-plane-protocol", [control_protocol]) def operational(): out = { "ietf-routing:routing": { "control-plane-protocols": { } } } add_areas(out['ietf-routing:routing']['control-plane-protocols']) return out