Skip to content

Commit 9086c8e

Browse files
committed
ospf: add point-to-multipoint and hybrid interface type support
1 parent 30b12d1 commit 9086c8e

15 files changed

Lines changed: 916 additions & 39 deletions

File tree

doc/routing.md

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,66 @@ an Ethernet interface can be done as follows.
131131
admin@example:/config/routing/…/ospf/area/0.0.0.0/interface/e0/>
132132
</code></pre>
133133

134+
135+
### Point-to-Multipoint
136+
137+
Point-to-Multipoint (P2MP) is used when multiple OSPF routers share a
138+
common network segment but should form individual adjacencies rather
139+
than electing a Designated Router (DR). This is common in NBMA-like
140+
environments, DMVPN, or hub-and-spoke topologies.
141+
142+
Infix supports two P2MP variants via the `interface-type` setting:
143+
144+
| **Interface Type** | **Behavior** |
145+
|:----------------------|:-----------------------------------------------|
146+
| `hybrid` | P2MP with multicast Hellos (broadcast-capable) |
147+
| `point-to-multipoint` | P2MP with unicast Hellos (non-broadcast) |
148+
149+
#### Hybrid (broadcast-capable P2MP)
150+
151+
Use `hybrid` when all neighbors on the segment can receive multicast.
152+
Hello packets are sent to the standard OSPF multicast address (224.0.0.5)
153+
and neighbors are discovered automatically — no manual neighbor
154+
configuration is needed.
155+
156+
<pre class="cli"><code>admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
157+
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 interface-type hybrid</b>
158+
admin@example:/config/routing/…/ospf/> <b>leave</b>
159+
admin@example:/>
160+
</code></pre>
161+
162+
#### Non-broadcast P2MP
163+
164+
Use `point-to-multipoint` when the network does not support multicast.
165+
Hello packets are sent as unicast directly to each configured neighbor.
166+
Since neighbors cannot be discovered automatically, they must be
167+
configured explicitly using static neighbors (see below).
168+
169+
<pre class="cli"><code>admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
170+
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 interface-type point-to-multipoint</b>
171+
admin@example:/config/routing/…/ospf/> <b>leave</b>
172+
admin@example:/>
173+
</code></pre>
174+
175+
176+
### Static Neighbors
177+
178+
When using non-broadcast interface types (such as `point-to-multipoint`),
179+
OSPF cannot discover neighbors via multicast. Static neighbors must be
180+
configured so the router knows where to send unicast Hello packets.
181+
182+
<pre class="cli"><code>admin@example:/config/> <b>edit routing control-plane-protocol ospfv2 name default ospf</b>
183+
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.2</b>
184+
admin@example:/config/routing/…/ospf/> <b>set area 0.0.0.0 interface e0 static-neighbors neighbor 10.0.123.3</b>
185+
admin@example:/config/routing/…/ospf/> <b>leave</b>
186+
admin@example:/>
187+
</code></pre>
188+
189+
> [!NOTE]
190+
> Static neighbors are only needed for non-broadcast interface types.
191+
> With `hybrid` (or `broadcast`), neighbors are discovered automatically
192+
> via multicast.
193+
134194
### OSPF global settings
135195

136196
In addition to *area* and *interface* specific settings, OSPF provides

src/confd/src/routing.c

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,17 @@ int parse_rip(sr_session_ctx_t *session, struct lyd_node *rip, FILE *fp)
154154
return num_interfaces;
155155
}
156156

157+
static const char *ospf_network_type(const char *yang_type)
158+
{
159+
if (!strcmp(yang_type, "hybrid"))
160+
return "point-to-multipoint";
161+
if (!strcmp(yang_type, "point-to-multipoint"))
162+
return "point-to-multipoint non-broadcast";
163+
164+
/* broadcast, non-broadcast, point-to-point pass through unchanged */
165+
return yang_type;
166+
}
167+
157168
int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp)
158169
{
159170
struct lyd_node *interface, *interfaces, *area;
@@ -203,7 +214,7 @@ int parse_ospf_interfaces(sr_session_ctx_t *session, struct lyd_node *areas, FIL
203214
if (passive)
204215
fputs(" ip ospf passive\n", fp);
205216
if (interface_type)
206-
fprintf(fp, " ip ospf network %s\n", interface_type);
217+
fprintf(fp, " ip ospf network %s\n", ospf_network_type(interface_type));
207218
if (cost)
208219
fprintf(fp, " ip ospf cost %s\n", cost);
209220
}
@@ -226,6 +237,28 @@ int parse_ospf_redistribute(sr_session_ctx_t *session, struct lyd_node *redistri
226237
return 0;
227238
}
228239

240+
static void parse_ospf_static_neighbors(struct lyd_node *areas, FILE *fp)
241+
{
242+
struct lyd_node *area, *interface, *interfaces, *neighbors, *neighbor;
243+
244+
LY_LIST_FOR(lyd_child(areas), area) {
245+
interfaces = lydx_get_child(area, "interfaces");
246+
247+
LY_LIST_FOR(lyd_child(interfaces), interface) {
248+
neighbors = lydx_get_child(interface, "static-neighbors");
249+
if (!neighbors)
250+
continue;
251+
252+
LY_LIST_FOR(lyd_child(neighbors), neighbor) {
253+
const char *id = lydx_get_cattr(neighbor, "identifier");
254+
255+
if (id)
256+
fprintf(fp, " neighbor %s\n", id);
257+
}
258+
}
259+
}
260+
}
261+
229262
int parse_ospf_areas(sr_session_ctx_t *session, struct lyd_node *areas, FILE *fp)
230263
{
231264
int areas_configured = 0;
@@ -315,6 +348,7 @@ int parse_ospf(sr_session_ctx_t *session, struct lyd_node *ospf)
315348
fputs("router ospf\n", fp);
316349
num_areas = parse_ospf_areas(session, areas, fp);
317350
parse_ospf_redistribute(session, lydx_get_child(ospf, "redistribute"), fp);
351+
parse_ospf_static_neighbors(areas, fp);
318352
default_route = lydx_get_child(ospf, "default-route-advertise");
319353
if (default_route) {
320354
/* enable is obsolete in favor for enabled. */

src/confd/yang/confd.inc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ MODULES=(
1414
"ietf-routing@2018-03-13.yang"
1515
"ietf-ipv6-unicast-routing@2018-03-13.yang"
1616
"ietf-ipv4-unicast-routing@2018-03-13.yang"
17-
"ietf-ospf@2022-10-19.yang -e bfd -e explicit-router-id"
17+
"ietf-ospf@2022-10-19.yang -e bfd -e explicit-router-id -e hybrid-interface"
1818
"ietf-rip@2020-02-20.yang"
1919
"iana-bfd-types@2021-10-21.yang"
2020
"ietf-bfd-types@2022-09-22.yang"

src/confd/yang/confd/infix-routing.yang

Lines changed: 6 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ module infix-routing {
2626
contact "kernelkit@googlegroups.com";
2727
description "Deviations and augments for ietf-routing, ietf-ospf, and ietf-rip.";
2828

29+
revision 2026-03-04 {
30+
description "Remove interface-type deviation to expose standard ietf-ospf
31+
interface types including point-to-multipoint and hybrid.
32+
Un-deviate static-neighbors for non-broadcast P2MP support.";
33+
}
34+
2935
revision 2025-12-02 {
3036
description "Add configurable OSPF debug logging container.
3137
Add RIP (Routing Information Protocol) support.";
@@ -247,18 +253,6 @@ module infix-routing {
247253
}
248254

249255
/* OSPF */
250-
typedef infix-ospf-interface-type {
251-
type enumeration {
252-
enum broadcast {
253-
description
254-
"Specifies an OSPF broadcast multi-access network.";
255-
}
256-
enum point-to-point {
257-
description
258-
"Specifies an OSPF point-to-point network.";
259-
}
260-
}
261-
}
262256

263257

264258
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol" {
@@ -367,11 +361,6 @@ module infix-routing {
367361
}
368362
}
369363

370-
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:interface-type" {
371-
deviate replace {
372-
type infix-ospf-interface-type;
373-
}
374-
}
375364

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

465454
/* OSPF Area Interface */
466-
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:static-neighbors"
467-
{
468-
deviate not-supported;
469-
}
470455
deviation "/rt:routing/rt:control-plane-protocols/rt:control-plane-protocol/ospf:ospf/ospf:areas/ospf:area/ospf:interfaces/ospf:interface/ospf:multi-areas" {
471456
deviate not-supported;
472457
}
File renamed without changes.

src/statd/python/cli_pretty/cli_pretty.py

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4838,7 +4838,7 @@ def show_ospf_interfaces(json_data):
48384838
state = target_iface.get('state', 'down')
48394839
cost = target_iface.get('cost', 0)
48404840
priority = target_iface.get('priority', 1)
4841-
iface_type = target_iface.get('interface-type', 'unknown')
4841+
iface_type = target_iface.get('interface-type', '')
48424842
hello_interval = target_iface.get('hello-interval', 10)
48434843
dead_interval = target_iface.get('dead-interval', 40)
48444844
retransmit_interval = target_iface.get('retransmit-interval', 5)
@@ -4914,9 +4914,11 @@ def show_ospf_interfaces(json_data):
49144914
network_type_map = {
49154915
'point-to-point': 'POINTOPOINT',
49164916
'broadcast': 'BROADCAST',
4917-
'non-broadcast': 'NBMA'
4917+
'non-broadcast': 'NBMA',
4918+
'point-to-multipoint': 'POINTOMULTIPOINT',
4919+
'hybrid': 'POINTOMULTIPOINT'
49184920
}
4919-
network_type = network_type_map.get(iface_type, iface_type.upper())
4921+
network_type = network_type_map.get(iface_type, iface_type.upper() if iface_type else 'LOOPBACK')
49204922

49214923
print(f"{name} is up")
49224924
if ip_address:
@@ -4956,30 +4958,50 @@ def show_ospf_interfaces(json_data):
49564958
return
49574959

49584960
# Display table view (no specific interface)
4959-
hdr = f"{'INTERFACE':<12} {'AREA':<12} {'STATE':<10} {'COST':<6} {'PRI':<4} {'DR':<15} {'BDR':<15} {'NBRS':<5}"
4960-
print(Decore.invert(hdr))
4961+
type_display_map = {
4962+
'point-to-point': 'P2P',
4963+
'broadcast': 'Broadcast',
4964+
'non-broadcast': 'NBMA',
4965+
'point-to-multipoint': 'P2MP',
4966+
'hybrid': 'Hybrid'
4967+
}
4968+
4969+
def fmt_state(state):
4970+
if state in ('dr', 'bdr'):
4971+
return state.upper()
4972+
if state == 'dr-other':
4973+
return 'DROther'
4974+
return state.capitalize()
4975+
4976+
table = SimpleTable([
4977+
Column('INTERFACE'),
4978+
Column('AREA'),
4979+
Column('TYPE'),
4980+
Column('STATE'),
4981+
Column('COST', 'right'),
4982+
Column('PRI', 'right'),
4983+
Column('DR'),
4984+
Column('BDR'),
4985+
Column('NBRS', 'right')
4986+
])
49614987

49624988
for iface in all_interfaces:
49634989
name = iface.get('name', 'unknown')
49644990
area_id = iface.get('_area_id', '0.0.0.0')
49654991
state = iface.get('state', 'down')
4992+
iface_type = iface.get('interface-type', '')
49664993
cost = iface.get('cost', 0)
49674994
priority = iface.get('priority', 1)
49684995
dr_id = iface.get('dr-router-id', '-')
49694996
bdr_id = iface.get('bdr-router-id', '-')
49704997
neighbors = iface.get('neighbors', {}).get('neighbor', [])
4971-
nbr_count = len(neighbors)
49724998

4973-
# Capitalize state nicely
4974-
state_display = state.upper() if state in ['dr', 'bdr'] else state.capitalize()
4975-
if state == 'dr-other':
4976-
state_display = 'DROther'
4977-
4978-
# Shorten router IDs for display
4979-
dr_display = dr_id if dr_id != '-' else '-'
4980-
bdr_display = bdr_id if bdr_id != '-' else '-'
4999+
table.row(name, area_id,
5000+
type_display_map.get(iface_type, iface_type.capitalize() if iface_type else '-'),
5001+
fmt_state(state),
5002+
cost, priority, dr_id, bdr_id, len(neighbors))
49815003

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

49845006

49855007
def show_ospf_neighbor(json_data):

src/statd/python/yanger/ietf_ospf.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,8 +124,15 @@ def add_areas(control_protocols):
124124
interface["enabled"] = iface["ospfEnabled"]
125125
if iface["networkType"] == "POINTOPOINT":
126126
interface["interface-type"] = "point-to-point"
127-
if iface["networkType"] == "BROADCAST":
127+
elif iface["networkType"] == "BROADCAST":
128128
interface["interface-type"] = "broadcast"
129+
elif iface["networkType"] == "POINTOMULTIPOINT":
130+
if iface.get("p2mpNonBroadcast", False):
131+
interface["interface-type"] = "point-to-multipoint"
132+
else:
133+
interface["interface-type"] = "hybrid"
134+
elif iface["networkType"] == "NBMA":
135+
interface["interface-type"] = "non-broadcast"
129136

130137
if iface.get("state"):
131138
# Wev've never seen "DependUpon", and has no entry in

test/case/routing/Readme.adoc

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ Tests verifying standard routing protocols and configuration:
1010
- OSPF with BFD (Bidirectional Forwarding Detection)
1111
- OSPF default route advertisement and propagation
1212
- OSPF debug logging configuration and verification
13+
- OSPF point-to-multipoint hybrid (broadcast) interface type
14+
- OSPF point-to-multipoint (non-broadcast) interface type with static neighbors
1315
- RIP basic neighbor discovery and route exchange
1416
- RIP passive interface configuration
1517
- RIP route redistribution (connected, static, and OSPF)
@@ -46,6 +48,14 @@ include::ospf_debug/Readme.adoc[]
4648

4749
<<<
4850

51+
include::ospf_point_to_multipoint_hybrid/Readme.adoc[]
52+
53+
<<<
54+
55+
include::ospf_point_to_multipoint/Readme.adoc[]
56+
57+
<<<
58+
4959
include::rip_basic/Readme.adoc[]
5060

5161
<<<

test/case/routing/all.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,12 @@
2929
- name: OSPF Debug Logging
3030
case: ospf_debug/test.py
3131

32+
- name: OSPF Point-to-Multipoint Hybrid
33+
case: ospf_point_to_multipoint_hybrid/test.py
34+
35+
- name: OSPF Point-to-Multipoint
36+
case: ospf_point_to_multipoint/test.py
37+
3238
- name: RIP Basic
3339
case: rip_basic/test.py
3440

Binary file not shown.

0 commit comments

Comments
 (0)