Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions doc/routing.md
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,66 @@ an Ethernet interface can be done as follows.
admin@example:/config/routing/…/ospf/area/0.0.0.0/interface/e0/>
</code></pre>


### Point-to-Multipoint

Point-to-Multipoint (P2MP) is used when multiple OSPF routers share a
common network segment but should form individual adjacencies rather
than electing a Designated Router (DR). This is common in NBMA-like
environments, DMVPN, or hub-and-spoke topologies.

Infix supports two P2MP variants via the `interface-type` setting:

| **Interface Type** | **Behavior** |
|:----------------------|:-----------------------------------------------|
| `hybrid` | P2MP with multicast Hellos (broadcast-capable) |
| `point-to-multipoint` | P2MP with unicast Hellos (non-broadcast) |

#### Hybrid (broadcast-capable P2MP)

Use `hybrid` when all neighbors on the segment can receive multicast.
Hello packets are sent to the standard OSPF multicast address (224.0.0.5)
and neighbors are discovered automatically — no manual neighbor
configuration is needed.

<pre class="cli"><code>admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 interface-type hybrid</b>
admin@example:/config/routing/…/ospf/> <b>leave</b>
admin@example:/>
</code></pre>

#### Non-broadcast P2MP

Use `point-to-multipoint` when the network does not support multicast.
Hello packets are sent as unicast directly to each configured neighbor.
Since neighbors cannot be discovered automatically, they must be
configured explicitly using static neighbors (see below).

<pre class="cli"><code>admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 interface-type point-to-multipoint</b>
admin@example:/config/routing/…/ospf/> <b>leave</b>
admin@example:/>
</code></pre>


### Static Neighbors

When using non-broadcast interface types (such as `point-to-multipoint`),
OSPF cannot discover neighbors via multicast. Static neighbors must be
configured so the router knows where to send unicast Hello packets.

<pre class="cli"><code>admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.2</b>
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.3</b>
admin@example:/config/routing/…/ospf/> <b>leave</b>
admin@example:/>
</code></pre>

> [!NOTE]
> Static neighbors are only needed for non-broadcast interface types.
> With `hybrid` (or `broadcast`), neighbors are discovered automatically
> via multicast.

### OSPF global settings

In addition to *area* and *interface* specific settings, OSPF provides
Expand Down
36 changes: 35 additions & 1 deletion src/confd/src/routing.c
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,17 @@ int parse_rip(sr_session_ctx_t *session, struct lyd_node *rip, FILE *fp)
return num_interfaces;
}

static const char *ospf_network_type(const char *yang_type)
{
if (!strcmp(yang_type, "hybrid"))
return "point-to-multipoint";
if (!strcmp(yang_type, "point-to-multipoint"))
return "point-to-multipoint non-broadcast";

/* broadcast, non-broadcast, point-to-point pass through unchanged */
return yang_type;
}

int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp)
{
struct lyd_node *interface, *interfaces, *area;
Expand Down Expand Up @@ -203,7 +214,7 @@ int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FIL
if (passive)
fputs(" ip ospf passive\n", fp);
if (interface_type)
fprintf(fp, " ip ospf network %s\n", interface_type);
fprintf(fp, " ip ospf network %s\n", ospf_network_type(interface_type));
if (cost)
fprintf(fp, " ip ospf cost %s\n", cost);
}
Expand All @@ -226,6 +237,28 @@ int parse_ospf_redistribute(sr_session_ctx_t *session, struct lyd_node *redistri
return 0;
}

static void parse_ospf_static_neighbors(struct lyd_node *areas, FILE *fp)
{
struct lyd_node *area, *interface, *interfaces, *neighbors, *neighbor;

LY_LIST_FOR(lyd_child(areas), area) {
interfaces = lydx_get_child(area, "interfaces");

LY_LIST_FOR(lyd_child(interfaces), interface) {
neighbors = lydx_get_child(interface, "static-neighbors");
if (!neighbors)
continue;

LY_LIST_FOR(lyd_child(neighbors), neighbor) {
const char *id = lydx_get_cattr(neighbor, "identifier");

if (id)
fprintf(fp, " neighbor %s\n", id);
}
}
}
}

int parse_ospf_areas(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp)
{
int areas_configured = 0;
Expand Down Expand Up @@ -315,6 +348,7 @@ int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf)
fputs("router ospf\n", fp);
num_areas = parse_ospf_areas(session, areas, fp);
parse_ospf_redistribute(session, lydx_get_child(ospf, "redistribute"), fp);
parse_ospf_static_neighbors(areas, fp);
default_route = lydx_get_child(ospf, "default-route-advertise");
if (default_route) {
/* enable is obsolete in favor for enabled. */
Expand Down
2 changes: 1 addition & 1 deletion src/confd/yang/confd.inc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ MODULES=(
"ietf-routing@2018-03-13.yang"
"ietf-ipv6-unicast-routing@2018-03-13.yang"
"ietf-ipv4-unicast-routing@2018-03-13.yang"
"ietf-ospf@2022-10-19.yang -e bfd -e explicit-router-id"
"ietf-ospf@2022-10-19.yang -e bfd -e explicit-router-id -e hybrid-interface"
"ietf-rip@2020-02-20.yang"
"iana-bfd-types@2021-10-21.yang"
"ietf-bfd-types@2022-09-22.yang"
Expand Down
27 changes: 6 additions & 21 deletions src/confd/yang/confd/infix-routing.yang
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ module infix-routing {
contact "kernelkit@googlegroups.com";
description "Deviations and augments for ietf-routing, ietf-ospf, and ietf-rip.";

revision 2026-03-04 {
description "Remove interface-type deviation to expose standard ietf-ospf
interface types including point-to-multipoint and hybrid.
Un-deviate static-neighbors for non-broadcast P2MP support.";
}

revision 2025-12-02 {
description "Add configurable OSPF debug logging container.
Add RIP (Routing Information Protocol) support.";
Expand Down Expand Up @@ -247,18 +253,6 @@ module infix-routing {
}

/* OSPF */
typedef infix-ospf-interface-type {
type enumeration {
enum broadcast {
description
"Specifies an OSPF broadcast multi-access network.";
}
enum point-to-point {
description
"Specifies an OSPF point-to-point network.";
}
}
}


deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol" {
Expand Down Expand Up @@ -367,11 +361,6 @@ module infix-routing {
}
}

deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-type" {
deviate replace {
type infix-ospf-interface-type;
}
}

deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:auto-cost" {
deviate not-supported;
Expand Down Expand Up @@ -463,10 +452,6 @@ module infix-routing {
}

/* OSPF Area Interface */
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors"
{
deviate not-supported;
}
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:multi-areas" {
deviate not-supported;
}
Expand Down
52 changes: 37 additions & 15 deletions src/statd/python/cli_pretty/cli_pretty.py
Original file line number Diff line number Diff line change
Expand Up @@ -4838,7 +4838,7 @@ def show_ospf_interfaces(json_data):
state = target_iface.get('state', 'down')
cost = target_iface.get('cost', 0)
priority = target_iface.get('priority', 1)
iface_type = target_iface.get('interface-type', 'unknown')
iface_type = target_iface.get('interface-type', '')
hello_interval = target_iface.get('hello-interval', 10)
dead_interval = target_iface.get('dead-interval', 40)
retransmit_interval = target_iface.get('retransmit-interval', 5)
Expand Down Expand Up @@ -4914,9 +4914,11 @@ def show_ospf_interfaces(json_data):
network_type_map = {
'point-to-point': 'POINTOPOINT',
'broadcast': 'BROADCAST',
'non-broadcast': 'NBMA'
'non-broadcast': 'NBMA',
'point-to-multipoint': 'POINTOMULTIPOINT',
'hybrid': 'POINTOMULTIPOINT'
}
network_type = network_type_map.get(iface_type, iface_type.upper())
network_type = network_type_map.get(iface_type, iface_type.upper() if iface_type else 'LOOPBACK')

print(f"{name} is up")
if ip_address:
Expand Down Expand Up @@ -4956,30 +4958,50 @@ def show_ospf_interfaces(json_data):
return

# Display table view (no specific interface)
hdr = f"{'INTERFACE':<12} {'AREA':<12} {'STATE':<10} {'COST':<6} {'PRI':<4} {'DR':<15} {'BDR':<15} {'NBRS':<5}"
print(Decore.invert(hdr))
type_display_map = {
'point-to-point': 'P2P',
'broadcast': 'Broadcast',
'non-broadcast': 'NBMA',
'point-to-multipoint': 'P2MP',
'hybrid': 'Hybrid'
}

def fmt_state(state):
if state in ('dr', 'bdr'):
return state.upper()
if state == 'dr-other':
return 'DROther'
return state.capitalize()

table = SimpleTable([
Column('INTERFACE'),
Column('AREA'),
Column('TYPE'),
Column('STATE'),
Column('COST', 'right'),
Column('PRI', 'right'),
Column('DR'),
Column('BDR'),
Column('NBRS', 'right')
])

for iface in all_interfaces:
name = iface.get('name', 'unknown')
area_id = iface.get('_area_id', '0.0.0.0')
state = iface.get('state', 'down')
iface_type = iface.get('interface-type', '')
cost = iface.get('cost', 0)
priority = iface.get('priority', 1)
dr_id = iface.get('dr-router-id', '-')
bdr_id = iface.get('bdr-router-id', '-')
neighbors = iface.get('neighbors', {}).get('neighbor', [])
nbr_count = len(neighbors)

# Capitalize state nicely
state_display = state.upper() if state in ['dr', 'bdr'] else state.capitalize()
if state == 'dr-other':
state_display = 'DROther'

# Shorten router IDs for display
dr_display = dr_id if dr_id != '-' else '-'
bdr_display = bdr_id if bdr_id != '-' else '-'
table.row(name, area_id,
type_display_map.get(iface_type, iface_type.capitalize() if iface_type else '-'),
fmt_state(state),
cost, priority, dr_id, bdr_id, len(neighbors))

print(f"{name:<12} {area_id:<12} {state_display:<10} {cost:<6} {priority:<4} {dr_display:<15} {bdr_display:<15} {nbr_count:<5}")
table.print()


def show_ospf_neighbor(json_data):
Expand Down
9 changes: 8 additions & 1 deletion src/statd/python/yanger/ietf_ospf.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,15 @@ def add_areas(control_protocols):
interface["enabled"] = iface["ospfEnabled"]
if iface["networkType"] == "POINTOPOINT":
interface["interface-type"] = "point-to-point"
if iface["networkType"] == "BROADCAST":
elif iface["networkType"] == "BROADCAST":
interface["interface-type"] = "broadcast"
elif iface["networkType"] == "POINTOMULTIPOINT":
if iface.get("p2mpNonBroadcast", False):
interface["interface-type"] = "point-to-multipoint"
else:
interface["interface-type"] = "hybrid"
elif iface["networkType"] == "NBMA":
interface["interface-type"] = "non-broadcast"

if iface.get("state"):
# Wev've never seen "DependUpon", and has no entry in
Expand Down
10 changes: 10 additions & 0 deletions test/case/routing/Readme.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Tests verifying standard routing protocols and configuration:
- OSPF with BFD (Bidirectional Forwarding Detection)
- OSPF default route advertisement and propagation
- OSPF debug logging configuration and verification
- OSPF point-to-multipoint hybrid (broadcast) interface type
- OSPF point-to-multipoint (non-broadcast) interface type with static neighbors
- RIP basic neighbor discovery and route exchange
- RIP passive interface configuration
- RIP route redistribution (connected, static, and OSPF)
Expand Down Expand Up @@ -46,6 +48,14 @@ include::ospf_debug/Readme.adoc[]

<<<

include::ospf_point_to_multipoint_hybrid/Readme.adoc[]

<<<

include::ospf_point_to_multipoint/Readme.adoc[]

<<<

include::rip_basic/Readme.adoc[]

<<<
Expand Down
6 changes: 6 additions & 0 deletions test/case/routing/all.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,12 @@
- name: OSPF Debug Logging
case: ospf_debug/test.py

- name: OSPF Point-to-Multipoint Hybrid
case: ospf_point_to_multipoint_hybrid/test.py

- name: OSPF Point-to-Multipoint
case: ospf_point_to_multipoint/test.py

- name: RIP Basic
case: rip_basic/test.py

Expand Down
Binary file not shown.
Loading
Loading