mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-22 01:13:00 +02:00
ospf: add point-to-multipoint and hybrid interface type support
This commit is contained in:
@@ -25,6 +25,8 @@ All notable changes to the project are documented in this file.
|
||||
- `set ssh known-hosts <host> <keytype> <pubkey>` — pre-enroll a host key
|
||||
received out-of-band, e.g. after a factory reset changes the device host key
|
||||
- `no ssh known-hosts <host>` — remove a stale or changed host key entry
|
||||
- Add OSPF point-to-multipoint (P2MP) and hybrid interface type support
|
||||
|
||||
|
||||
### Fixes
|
||||
|
||||
|
||||
@@ -152,6 +152,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
|
||||
|
||||
+35
-1
@@ -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;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -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. */
|
||||
|
||||
@@ -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"
|
||||
@@ -31,7 +31,7 @@ MODULES=(
|
||||
"ieee802-dot1q-types@2022-10-29.yang"
|
||||
"infix-ip@2025-11-02.yang"
|
||||
"infix-if-type@2026-01-07.yang"
|
||||
"infix-routing@2025-12-02.yang"
|
||||
"infix-routing@2026-03-04.yang "
|
||||
"ieee802-dot1ab-lldp@2022-03-15.yang"
|
||||
"infix-lldp@2025-05-05.yang"
|
||||
"infix-dhcp-common@2025-12-21.yang"
|
||||
|
||||
@@ -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.";
|
||||
@@ -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" {
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -4923,7 +4923,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)
|
||||
@@ -4999,9 +4999,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:
|
||||
@@ -5041,30 +5043,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'
|
||||
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))
|
||||
|
||||
# Shorten router IDs for display
|
||||
dr_display = dr_id if dr_id != '-' else '-'
|
||||
bdr_display = bdr_id if bdr_id != '-' else '-'
|
||||
|
||||
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):
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
@@ -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[]
|
||||
|
||||
<<<
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
Binary file not shown.
@@ -0,0 +1,49 @@
|
||||
=== OSPF Point-to-Multipoint
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_point_to_multipoint]
|
||||
|
||||
==== Description
|
||||
|
||||
Verify OSPF point-to-multipoint (non-broadcast) interface type by
|
||||
configuring three routers on a shared multi-access network with the
|
||||
ietf-ospf 'point-to-multipoint' interface type and static neighbors.
|
||||
This maps to FRR's 'point-to-multipoint non-broadcast' network type,
|
||||
which requires manual neighbor configuration since there is no
|
||||
multicast neighbor discovery.
|
||||
|
||||
R2 acts as the hub, bridging two physical links (link1, link2) into a
|
||||
single broadcast domain (br0). R1 and R3 each connect to one of R2's
|
||||
ports. The test verifies that all routers form OSPF adjacencies via
|
||||
unicast, exchange routes, and that the interface type is correctly
|
||||
reported as point-to-multipoint.
|
||||
|
||||
....
|
||||
+------------------+ +------------------+
|
||||
| R1 | | R3 |
|
||||
| 10.0.1.1/32 | | 10.0.3.1/32 |
|
||||
| (lo) | | (lo) |
|
||||
+--------+---------+ +--------+---------+
|
||||
| .1 | .3
|
||||
| +------------------+ |
|
||||
+----link1------+ R2 +------link2--------+
|
||||
| 10.0.2.1/32 |
|
||||
| (lo) |
|
||||
| br0: 10.0.123.2 |
|
||||
+------------------+
|
||||
10.0.123.0/24
|
||||
(P2MP non-broadcast / shared segment)
|
||||
....
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[OSPF Point-to-Multipoint topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure targets
|
||||
. Wait for OSPF routes
|
||||
. Verify interface type is point-to-multipoint
|
||||
. Verify connectivity between all DUTs
|
||||
|
||||
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OSPF Point-to-Multipoint
|
||||
|
||||
Verify OSPF point-to-multipoint (non-broadcast) interface type by
|
||||
configuring three routers on a shared multi-access network with the
|
||||
ietf-ospf 'point-to-multipoint' interface type and static neighbors.
|
||||
This maps to FRR's 'point-to-multipoint non-broadcast' network type,
|
||||
which requires manual neighbor configuration since there is no
|
||||
multicast neighbor discovery.
|
||||
|
||||
R2 acts as the hub, bridging two physical links (link1, link2) into a
|
||||
single broadcast domain (br0). R1 and R3 each connect to one of R2's
|
||||
ports. The test verifies that all routers form OSPF adjacencies via
|
||||
unicast, exchange routes, and that the interface type is correctly
|
||||
reported as point-to-multipoint.
|
||||
|
||||
....
|
||||
+------------------+ +------------------+
|
||||
| R1 | | R3 |
|
||||
| 10.0.1.1/32 | | 10.0.3.1/32 |
|
||||
| (lo) | | (lo) |
|
||||
+--------+---------+ +--------+---------+
|
||||
| .1 | .3
|
||||
| +------------------+ |
|
||||
+----link1------+ R2 +------link2--------+
|
||||
| 10.0.2.1/32 |
|
||||
| (lo) |
|
||||
| br0: 10.0.123.2 |
|
||||
+------------------+
|
||||
10.0.123.0/24
|
||||
(P2MP non-broadcast / shared segment)
|
||||
....
|
||||
"""
|
||||
|
||||
import infamy
|
||||
import infamy.route as route
|
||||
from infamy.util import until, parallel
|
||||
|
||||
|
||||
def config_target1(target, link, data):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.123.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": data,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.10.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lo",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "10.0.1.1",
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ospf": {
|
||||
"redistribute": {
|
||||
"redistribute": [{
|
||||
"protocol": "connected"
|
||||
}]
|
||||
},
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"hello-interval": 1,
|
||||
"dead-interval": 3,
|
||||
"interface-type": "point-to-multipoint",
|
||||
"static-neighbors": {
|
||||
"neighbor": [
|
||||
{"identifier": "10.0.123.2"},
|
||||
{"identifier": "10.0.123.3"}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "lo",
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def config_target2(target, link1, link2):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "br0",
|
||||
"type": "infix-if-type:bridge",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.123.2",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link1,
|
||||
"enabled": True,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link2,
|
||||
"enabled": True,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lo",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "10.0.2.1",
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ospf": {
|
||||
"redistribute": {
|
||||
"redistribute": [{
|
||||
"protocol": "connected"
|
||||
}]
|
||||
},
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": "br0",
|
||||
"enabled": True,
|
||||
"hello-interval": 1,
|
||||
"dead-interval": 3,
|
||||
"interface-type": "point-to-multipoint",
|
||||
"static-neighbors": {
|
||||
"neighbor": [
|
||||
{"identifier": "10.0.123.1"},
|
||||
{"identifier": "10.0.123.3"}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "lo",
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def config_target3(target, link, data):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.123.3",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": data,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.30.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lo",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "10.0.3.1",
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ospf": {
|
||||
"redistribute": {
|
||||
"redistribute": [{
|
||||
"protocol": "connected"
|
||||
}]
|
||||
},
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"hello-interval": 1,
|
||||
"dead-interval": 3,
|
||||
"interface-type": "point-to-multipoint",
|
||||
"static-neighbors": {
|
||||
"neighbor": [
|
||||
{"identifier": "10.0.123.1"},
|
||||
{"identifier": "10.0.123.2"}
|
||||
]
|
||||
}
|
||||
}, {
|
||||
"name": "lo",
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUTs"):
|
||||
env = infamy.Env()
|
||||
R1 = env.attach("R1", "mgmt")
|
||||
R2 = env.attach("R2", "mgmt")
|
||||
R3 = env.attach("R3", "mgmt")
|
||||
|
||||
|
||||
with test.step("Configure targets"):
|
||||
_, R1link = env.ltop.xlate("R1", "link")
|
||||
_, R1data = env.ltop.xlate("R1", "data")
|
||||
_, R2link1 = env.ltop.xlate("R2", "link1")
|
||||
_, R2link2 = env.ltop.xlate("R2", "link2")
|
||||
_, R3link = env.ltop.xlate("R3", "link")
|
||||
_, R3data = env.ltop.xlate("R3", "data")
|
||||
|
||||
parallel(config_target1(R1, R1link, R1data),
|
||||
config_target2(R2, R2link1, R2link2),
|
||||
config_target3(R3, R3link, R3data))
|
||||
|
||||
with test.step("Wait for OSPF routes"):
|
||||
print("Waiting for OSPF routes from all routers")
|
||||
until(lambda: route.ipv4_route_exist(R1, "10.0.2.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R1, "10.0.3.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R2, "10.0.1.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R2, "10.0.3.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R3, "10.0.1.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R3, "10.0.2.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
|
||||
with test.step("Verify interface type is point-to-multipoint"):
|
||||
print("Checking OSPF interface type on all routers")
|
||||
assert route.ospf_get_interface_type(R1, "0.0.0.0", R1link) == "point-to-multipoint"
|
||||
assert route.ospf_get_interface_type(R2, "0.0.0.0", "br0") == "point-to-multipoint"
|
||||
assert route.ospf_get_interface_type(R3, "0.0.0.0", R3link) == "point-to-multipoint"
|
||||
|
||||
with test.step("Verify connectivity between all DUTs"):
|
||||
_, hport1 = env.ltop.xlate("PC", "data1")
|
||||
_, hport2 = env.ltop.xlate("PC", "data2")
|
||||
with infamy.IsolatedMacVlan(hport1) as ns1, \
|
||||
infamy.IsolatedMacVlan(hport2) as ns2:
|
||||
ns1.addip("10.0.10.2")
|
||||
ns2.addip("10.0.30.2")
|
||||
ns1.addroute("10.0.3.1/32", "10.0.10.1")
|
||||
ns2.addroute("10.0.1.1/32", "10.0.30.1")
|
||||
parallel(
|
||||
lambda: ns1.must_reach("10.0.3.1"),
|
||||
lambda: ns2.must_reach("10.0.1.1"),
|
||||
)
|
||||
test.succeed()
|
||||
@@ -0,0 +1,39 @@
|
||||
graph "3x1" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+20";
|
||||
size=10
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
PC [
|
||||
label="PC | { <mgmt1> mgmt1 | <data1> data1 | \n\n\n\n | <mgmt2> mgmt2 | <mgmt3> mgmt3 | <data2> data2 }",
|
||||
pos="20,30!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
R1 [
|
||||
label="{ <mgmt> mgmt | <data> data | <link> link } | R1 \n 10.0.1.1/32 \n(lo)",
|
||||
pos="160,60!",
|
||||
requires="infix",
|
||||
];
|
||||
R2 [
|
||||
label="{ <link1> link1 | <mgmt> mgmt | <link2> link2 } | R2 \n 10.0.2.1/32 \n(lo) \n(br0: 10.0.123.2/24)",
|
||||
pos="160,30!",
|
||||
requires="infix",
|
||||
];
|
||||
R3 [
|
||||
label="{ <link> link | <mgmt> mgmt | <data> data } | R3 \n 10.0.3.1/32 \n(lo)",
|
||||
pos="160,0!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
|
||||
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
|
||||
PC:mgmt3 -- R3:mgmt [requires="mgmt", color="lightgray"]
|
||||
PC:data1 -- R1:data [taillabel="10.0.10.2/24", headlabel="10.0.10.1/24"]
|
||||
R3:data -- PC:data2 [taillabel="10.0.30.1/24", headlabel="10.0.30.2/24"]
|
||||
R1:link -- R2:link1 [taillabel="10.0.123.1/24"]
|
||||
R3:link -- R2:link2 [taillabel="10.0.123.3/24"]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 3x1 Pages: 1 -->
|
||||
<svg width="720pt" height="312pt"
|
||||
viewBox="0.00 0.00 720.00 312.15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(0.98 0.98) rotate(0) translate(4 314.02)">
|
||||
<title>3x1</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-314.02 729.56,-314.02 729.56,4 -4,4"/>
|
||||
<!-- PC -->
|
||||
<g id="node1" class="node">
|
||||
<title>PC</title>
|
||||
<polygon fill="none" stroke="black" points="0,-61.51 0,-248.51 91,-248.51 91,-61.51 0,-61.51"/>
|
||||
<text text-anchor="middle" x="16.5" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
|
||||
<polyline fill="none" stroke="black" points="33,-61.51 33,-248.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-233.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
|
||||
<polyline fill="none" stroke="black" points="33,-225.51 91,-225.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-210.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="33,-202.51 91,-202.51 "/>
|
||||
<polyline fill="none" stroke="black" points="33,-130.51 91,-130.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-115.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
|
||||
<polyline fill="none" stroke="black" points="33,-107.51 91,-107.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-92.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
|
||||
<polyline fill="none" stroke="black" points="33,-84.51 91,-84.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-69.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
</g>
|
||||
<!-- R1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>R1</title>
|
||||
<polygon fill="none" stroke="black" points="518.56,-240.52 518.56,-309.52 692.56,-309.52 692.56,-240.52 518.56,-240.52"/>
|
||||
<text text-anchor="middle" x="543.56" y="-294.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-286.52 568.56,-286.52 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-271.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-263.52 568.56,-263.52 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-248.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="568.56,-240.52 568.56,-309.52 "/>
|
||||
<text text-anchor="middle" x="630.56" y="-286.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-271.32" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 10.0.1.1/32 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-256.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
|
||||
</g>
|
||||
<!-- PC--R1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>PC:mgmt1--R1:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-237.01C91.5,-237.01 518.56,-298.02 518.56,-298.02"/>
|
||||
</g>
|
||||
<!-- PC--R1 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>PC:data1--R1:data</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-214.01C91.5,-214.01 518.56,-275.02 518.56,-275.02"/>
|
||||
<text text-anchor="middle" x="473.06" y="-278.82" font-family="DejaVu Serif, Book" font-size="14.00">10.0.10.1/24</text>
|
||||
<text text-anchor="middle" x="137" y="-217.81" font-family="DejaVu Serif, Book" font-size="14.00">10.0.10.2/24</text>
|
||||
</g>
|
||||
<!-- R2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>R2</title>
|
||||
<polygon fill="none" stroke="black" points="485.56,-120.51 485.56,-189.51 725.56,-189.51 725.56,-120.51 485.56,-120.51"/>
|
||||
<text text-anchor="middle" x="514.56" y="-174.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">link1</text>
|
||||
<polyline fill="none" stroke="black" points="485.56,-166.51 543.56,-166.51 "/>
|
||||
<text text-anchor="middle" x="514.56" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="485.56,-143.51 543.56,-143.51 "/>
|
||||
<text text-anchor="middle" x="514.56" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">link2</text>
|
||||
<polyline fill="none" stroke="black" points="543.56,-120.51 543.56,-189.51 "/>
|
||||
<text text-anchor="middle" x="634.56" y="-173.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 </text>
|
||||
<text text-anchor="middle" x="634.56" y="-158.81" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 10.0.2.1/32 </text>
|
||||
<text text-anchor="middle" x="634.56" y="-143.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo) </text>
|
||||
<text text-anchor="middle" x="634.56" y="-128.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">(br0: 10.0.123.2/24)</text>
|
||||
</g>
|
||||
<!-- PC--R2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>PC:mgmt2--R2:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-119.01C91.5,-119.01 485.56,-155.01 485.56,-155.01"/>
|
||||
</g>
|
||||
<!-- R3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>R3</title>
|
||||
<polygon fill="none" stroke="black" points="518.56,-0.5 518.56,-69.5 692.56,-69.5 692.56,-0.5 518.56,-0.5"/>
|
||||
<text text-anchor="middle" x="543.56" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-46.5 568.56,-46.5 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-23.5 568.56,-23.5 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="568.56,-0.5 568.56,-69.5 "/>
|
||||
<text text-anchor="middle" x="630.56" y="-46.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 10.0.3.1/32 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-16.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
|
||||
</g>
|
||||
<!-- PC--R3 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>PC:mgmt3--R3:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-96.01C91.5,-96.01 518.56,-35 518.56,-35"/>
|
||||
</g>
|
||||
<!-- R1--R2 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>R1:link--R2:link1</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M543.56,-240.02C543.56,-240.02 514.56,-190.01 514.56,-190.01"/>
|
||||
<text text-anchor="middle" x="493.56" y="-228.82" font-family="DejaVu Serif, Book" font-size="14.00">10.0.123.1/24</text>
|
||||
</g>
|
||||
<!-- R3--PC -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>R3:data--PC:data2</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M518.56,-12C518.56,-12 91.5,-73.01 91.5,-73.01"/>
|
||||
<text text-anchor="middle" x="137" y="-76.81" font-family="DejaVu Serif, Book" font-size="14.00">10.0.30.2/24</text>
|
||||
<text text-anchor="middle" x="473.06" y="-15.8" font-family="DejaVu Serif, Book" font-size="14.00">10.0.30.1/24</text>
|
||||
</g>
|
||||
<!-- R3--R2 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>R3:link--R2:link2</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M543.56,-70C543.56,-70 514.56,-120.01 514.56,-120.01"/>
|
||||
<text text-anchor="middle" x="493.56" y="-73.8" font-family="DejaVu Serif, Book" font-size="14.00">10.0.123.3/24</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
@@ -0,0 +1 @@
|
||||
test.adoc
|
||||
Binary file not shown.
@@ -0,0 +1,46 @@
|
||||
=== OSPF Point-to-Multipoint Hybrid
|
||||
|
||||
ifdef::topdoc[:imagesdir: {topdoc}../../test/case/routing/ospf_point_to_multipoint_hybrid]
|
||||
|
||||
==== Description
|
||||
|
||||
Verify OSPF point-to-multipoint hybrid (broadcast) interface type by
|
||||
configuring three routers on a shared multi-access network with the
|
||||
ietf-ospf 'hybrid' interface type. This maps to FRR's 'point-to-multipoint'
|
||||
network type, which uses multicast for neighbor discovery.
|
||||
|
||||
R2 acts as the hub, bridging two physical links (link1, link2) into a
|
||||
single broadcast domain (br0). R1 and R3 each connect to one of R2's
|
||||
ports. The test verifies that all routers form OSPF adjacencies, exchange
|
||||
routes, and that the interface type is correctly reported as hybrid.
|
||||
|
||||
....
|
||||
+------------------+ +------------------+
|
||||
| R1 | | R3 |
|
||||
| 10.0.1.1/32 | | 10.0.3.1/32 |
|
||||
| (lo) | | (lo) |
|
||||
+--------+---------+ +--------+---------+
|
||||
| .1 | .3
|
||||
| +------------------+ |
|
||||
+----link1------+ R2 +------link2--------+
|
||||
| 10.0.2.1/32 |
|
||||
| (lo) |
|
||||
| br0: 10.0.123.2 |
|
||||
+------------------+
|
||||
10.0.123.0/24
|
||||
(P2MP hybrid / shared segment)
|
||||
....
|
||||
|
||||
==== Topology
|
||||
|
||||
image::topology.svg[OSPF Point-to-Multipoint Hybrid topology, align=center, scaledwidth=75%]
|
||||
|
||||
==== Sequence
|
||||
|
||||
. Set up topology and attach to target DUTs
|
||||
. Configure targets
|
||||
. Wait for OSPF routes
|
||||
. Verify interface type is hybrid
|
||||
. Verify connectivity between all DUTs
|
||||
|
||||
|
||||
+334
@@ -0,0 +1,334 @@
|
||||
#!/usr/bin/env python3
|
||||
"""OSPF Point-to-Multipoint Hybrid
|
||||
|
||||
Verify OSPF point-to-multipoint hybrid (broadcast) interface type by
|
||||
configuring three routers on a shared multi-access network with the
|
||||
ietf-ospf 'hybrid' interface type. This maps to FRR's 'point-to-multipoint'
|
||||
network type, which uses multicast for neighbor discovery.
|
||||
|
||||
R2 acts as the hub, bridging two physical links (link1, link2) into a
|
||||
single broadcast domain (br0). R1 and R3 each connect to one of R2's
|
||||
ports. The test verifies that all routers form OSPF adjacencies, exchange
|
||||
routes, and that the interface type is correctly reported as hybrid.
|
||||
|
||||
....
|
||||
+------------------+ +------------------+
|
||||
| R1 | | R3 |
|
||||
| 10.0.1.1/32 | | 10.0.3.1/32 |
|
||||
| (lo) | | (lo) |
|
||||
+--------+---------+ +--------+---------+
|
||||
| .1 | .3
|
||||
| +------------------+ |
|
||||
+----link1------+ R2 +------link2--------+
|
||||
| 10.0.2.1/32 |
|
||||
| (lo) |
|
||||
| br0: 10.0.123.2 |
|
||||
+------------------+
|
||||
10.0.123.0/24
|
||||
(P2MP hybrid / shared segment)
|
||||
....
|
||||
"""
|
||||
|
||||
import infamy
|
||||
import infamy.route as route
|
||||
from infamy.util import until, parallel
|
||||
|
||||
|
||||
def config_target1(target, link, data):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.123.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": data,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.10.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lo",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "10.0.1.1",
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-system": {
|
||||
"system": {
|
||||
"hostname": "R1"
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ospf": {
|
||||
"redistribute": {
|
||||
"redistribute": [{
|
||||
"protocol": "connected"
|
||||
}]
|
||||
},
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"interface-type": "hybrid",
|
||||
"hello-interval": 1,
|
||||
"dead-interval": 3
|
||||
}, {
|
||||
"name": "lo",
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def config_target2(target, link1, link2):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": "br0",
|
||||
"type": "infix-if-type:bridge",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.123.2",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link1,
|
||||
"enabled": True,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": link2,
|
||||
"enabled": True,
|
||||
"infix-interfaces:bridge-port": {
|
||||
"bridge": "br0"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lo",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "10.0.2.1",
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-system": {
|
||||
"system": {
|
||||
"hostname": "R2"
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ospf": {
|
||||
"redistribute": {
|
||||
"redistribute": [{
|
||||
"protocol": "connected"
|
||||
}]
|
||||
},
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": "br0",
|
||||
"enabled": True,
|
||||
"interface-type": "hybrid",
|
||||
"hello-interval": 1,
|
||||
"dead-interval": 3
|
||||
}, {
|
||||
"name": "lo",
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
def config_target3(target, link, data):
|
||||
target.put_config_dicts({
|
||||
"ietf-interfaces": {
|
||||
"interfaces": {
|
||||
"interface": [
|
||||
{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.123.3",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": data,
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"forwarding": True,
|
||||
"address": [{
|
||||
"ip": "10.0.30.1",
|
||||
"prefix-length": 24
|
||||
}]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "lo",
|
||||
"enabled": True,
|
||||
"ipv4": {
|
||||
"address": [{
|
||||
"ip": "10.0.3.1",
|
||||
"prefix-length": 32
|
||||
}]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"ietf-system": {
|
||||
"system": {
|
||||
"hostname": "R3"
|
||||
}
|
||||
},
|
||||
"ietf-routing": {
|
||||
"routing": {
|
||||
"control-plane-protocols": {
|
||||
"control-plane-protocol": [{
|
||||
"type": "infix-routing:ospfv2",
|
||||
"name": "default",
|
||||
"ospf": {
|
||||
"redistribute": {
|
||||
"redistribute": [{
|
||||
"protocol": "connected"
|
||||
}]
|
||||
},
|
||||
"areas": {
|
||||
"area": [{
|
||||
"area-id": "0.0.0.0",
|
||||
"interfaces": {
|
||||
"interface": [{
|
||||
"name": link,
|
||||
"enabled": True,
|
||||
"interface-type": "hybrid",
|
||||
"hello-interval": 1,
|
||||
"dead-interval": 3
|
||||
}, {
|
||||
"name": "lo",
|
||||
"enabled": True
|
||||
}]
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
with infamy.Test() as test:
|
||||
with test.step("Set up topology and attach to target DUTs"):
|
||||
env = infamy.Env()
|
||||
R1 = env.attach("R1", "mgmt")
|
||||
R2 = env.attach("R2", "mgmt")
|
||||
R3 = env.attach("R3", "mgmt")
|
||||
|
||||
with test.step("Configure targets"):
|
||||
_, R1link = env.ltop.xlate("R1", "link")
|
||||
_, R1data = env.ltop.xlate("R1", "data")
|
||||
_, R2link1 = env.ltop.xlate("R2", "link1")
|
||||
_, R2link2 = env.ltop.xlate("R2", "link2")
|
||||
_, R3link = env.ltop.xlate("R3", "link")
|
||||
_, R3data = env.ltop.xlate("R3", "data")
|
||||
|
||||
parallel(config_target1(R1, R1link, R1data),
|
||||
config_target2(R2, R2link1, R2link2),
|
||||
config_target3(R3, R3link, R3data))
|
||||
|
||||
with test.step("Wait for OSPF routes"):
|
||||
print("Waiting for OSPF routes to converge")
|
||||
until(lambda: route.ipv4_route_exist(R1, "10.0.2.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R1, "10.0.3.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R2, "10.0.1.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R2, "10.0.3.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R3, "10.0.1.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
until(lambda: route.ipv4_route_exist(R3, "10.0.2.1/32", proto="ietf-ospf:ospfv2"), attempts=200)
|
||||
|
||||
with test.step("Verify interface type is hybrid"):
|
||||
print("Checking OSPF interface type on all routers")
|
||||
assert route.ospf_get_interface_type(R1, "0.0.0.0", R1link) == "hybrid"
|
||||
assert route.ospf_get_interface_type(R2, "0.0.0.0", "br0") == "hybrid"
|
||||
assert route.ospf_get_interface_type(R3, "0.0.0.0", R3link) == "hybrid"
|
||||
|
||||
with test.step("Verify connectivity between all DUTs"):
|
||||
_, hport1 = env.ltop.xlate("PC", "data1")
|
||||
_, hport2 = env.ltop.xlate("PC", "data2")
|
||||
with infamy.IsolatedMacVlan(hport1) as ns1, \
|
||||
infamy.IsolatedMacVlan(hport2) as ns2:
|
||||
ns1.addip("10.0.10.2")
|
||||
ns2.addip("10.0.30.2")
|
||||
ns1.addroute("10.0.3.1/32", "10.0.10.1")
|
||||
ns2.addroute("10.0.1.1/32", "10.0.30.1")
|
||||
parallel(
|
||||
lambda: ns1.must_reach("10.0.3.1"),
|
||||
lambda: ns2.must_reach("10.0.1.1"),
|
||||
)
|
||||
test.succeed()
|
||||
@@ -0,0 +1,39 @@
|
||||
graph "3r-p2mp" {
|
||||
layout="neato";
|
||||
overlap="false";
|
||||
esep="+20";
|
||||
size=10
|
||||
|
||||
node [shape=record, fontname="DejaVu Sans Mono, Book"];
|
||||
edge [color="cornflowerblue", penwidth="2", fontname="DejaVu Serif, Book"];
|
||||
|
||||
PC [
|
||||
label="PC | { <mgmt1> mgmt1 | <data1> data1 | \n\n\n\n | <mgmt2> mgmt2 | <mgmt3> mgmt3 | <data2> data2 }",
|
||||
pos="20,30!",
|
||||
requires="controller",
|
||||
];
|
||||
|
||||
R1 [
|
||||
label="{ <mgmt> mgmt | <data> data | <link> link } | R1 \n 10.0.1.1/32 \n(lo)",
|
||||
pos="160,60!",
|
||||
requires="infix",
|
||||
];
|
||||
R2 [
|
||||
label="{ <link1> link1 | <mgmt> mgmt | <link2> link2 } | R2 \n 10.0.2.1/32 \n(lo) \n(br0: 10.0.123.2/24)",
|
||||
pos="160,30!",
|
||||
requires="infix",
|
||||
];
|
||||
R3 [
|
||||
label="{ <link> link | <mgmt> mgmt | <data> data } | R3 \n 10.0.3.1/32 \n(lo)",
|
||||
pos="160,0!",
|
||||
requires="infix",
|
||||
];
|
||||
|
||||
PC:mgmt1 -- R1:mgmt [requires="mgmt", color="lightgray"]
|
||||
PC:mgmt2 -- R2:mgmt [requires="mgmt", color="lightgray"]
|
||||
PC:mgmt3 -- R3:mgmt [requires="mgmt", color="lightgray"]
|
||||
PC:data1 -- R1:data [taillabel="10.0.10.2/24", headlabel="10.0.10.1/24"]
|
||||
R3:data -- PC:data2 [taillabel="10.0.30.1/24", headlabel="10.0.30.2/24"]
|
||||
R1:link -- R2:link1 [taillabel="10.0.123.1/24"]
|
||||
R3:link -- R2:link2 [taillabel="10.0.123.3/24"]
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
|
||||
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
|
||||
<!-- Title: 3r-p2mp Pages: 1 -->
|
||||
<svg width="720pt" height="312pt"
|
||||
viewBox="0.00 0.00 720.00 312.15" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<g id="graph0" class="graph" transform="scale(0.98 0.98) rotate(0) translate(4 314.02)">
|
||||
<title>3r-p2mp</title>
|
||||
<polygon fill="white" stroke="transparent" points="-4,4 -4,-314.02 729.56,-314.02 729.56,4 -4,4"/>
|
||||
<!-- PC -->
|
||||
<g id="node1" class="node">
|
||||
<title>PC</title>
|
||||
<polygon fill="none" stroke="black" points="0,-61.51 0,-248.51 91,-248.51 91,-61.51 0,-61.51"/>
|
||||
<text text-anchor="middle" x="16.5" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">PC</text>
|
||||
<polyline fill="none" stroke="black" points="33,-61.51 33,-248.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-233.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt1</text>
|
||||
<polyline fill="none" stroke="black" points="33,-225.51 91,-225.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-210.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data1</text>
|
||||
<polyline fill="none" stroke="black" points="33,-202.51 91,-202.51 "/>
|
||||
<polyline fill="none" stroke="black" points="33,-130.51 91,-130.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-115.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt2</text>
|
||||
<polyline fill="none" stroke="black" points="33,-107.51 91,-107.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-92.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt3</text>
|
||||
<polyline fill="none" stroke="black" points="33,-84.51 91,-84.51 "/>
|
||||
<text text-anchor="middle" x="62" y="-69.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">data2</text>
|
||||
</g>
|
||||
<!-- R1 -->
|
||||
<g id="node2" class="node">
|
||||
<title>R1</title>
|
||||
<polygon fill="none" stroke="black" points="518.56,-240.52 518.56,-309.52 692.56,-309.52 692.56,-240.52 518.56,-240.52"/>
|
||||
<text text-anchor="middle" x="543.56" y="-294.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-286.52 568.56,-286.52 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-271.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-263.52 568.56,-263.52 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-248.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="568.56,-240.52 568.56,-309.52 "/>
|
||||
<text text-anchor="middle" x="630.56" y="-286.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">R1 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-271.32" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 10.0.1.1/32 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-256.32" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
|
||||
</g>
|
||||
<!-- PC--R1 -->
|
||||
<g id="edge1" class="edge">
|
||||
<title>PC:mgmt1--R1:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-237.01C91.5,-237.01 518.56,-298.02 518.56,-298.02"/>
|
||||
</g>
|
||||
<!-- PC--R1 -->
|
||||
<g id="edge4" class="edge">
|
||||
<title>PC:data1--R1:data</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M91.5,-214.01C91.5,-214.01 518.56,-275.02 518.56,-275.02"/>
|
||||
<text text-anchor="middle" x="473.06" y="-278.82" font-family="DejaVu Serif, Book" font-size="14.00">10.0.10.1/24</text>
|
||||
<text text-anchor="middle" x="137" y="-217.81" font-family="DejaVu Serif, Book" font-size="14.00">10.0.10.2/24</text>
|
||||
</g>
|
||||
<!-- R2 -->
|
||||
<g id="node3" class="node">
|
||||
<title>R2</title>
|
||||
<polygon fill="none" stroke="black" points="485.56,-120.51 485.56,-189.51 725.56,-189.51 725.56,-120.51 485.56,-120.51"/>
|
||||
<text text-anchor="middle" x="514.56" y="-174.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">link1</text>
|
||||
<polyline fill="none" stroke="black" points="485.56,-166.51 543.56,-166.51 "/>
|
||||
<text text-anchor="middle" x="514.56" y="-151.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="485.56,-143.51 543.56,-143.51 "/>
|
||||
<text text-anchor="middle" x="514.56" y="-128.31" font-family="DejaVu Sans Mono, Book" font-size="14.00">link2</text>
|
||||
<polyline fill="none" stroke="black" points="543.56,-120.51 543.56,-189.51 "/>
|
||||
<text text-anchor="middle" x="634.56" y="-173.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">R2 </text>
|
||||
<text text-anchor="middle" x="634.56" y="-158.81" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 10.0.2.1/32 </text>
|
||||
<text text-anchor="middle" x="634.56" y="-143.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo) </text>
|
||||
<text text-anchor="middle" x="634.56" y="-128.81" font-family="DejaVu Sans Mono, Book" font-size="14.00">(br0: 10.0.123.2/24)</text>
|
||||
</g>
|
||||
<!-- PC--R2 -->
|
||||
<g id="edge2" class="edge">
|
||||
<title>PC:mgmt2--R2:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-119.01C91.5,-119.01 485.56,-155.01 485.56,-155.01"/>
|
||||
</g>
|
||||
<!-- R3 -->
|
||||
<g id="node4" class="node">
|
||||
<title>R3</title>
|
||||
<polygon fill="none" stroke="black" points="518.56,-0.5 518.56,-69.5 692.56,-69.5 692.56,-0.5 518.56,-0.5"/>
|
||||
<text text-anchor="middle" x="543.56" y="-54.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">link</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-46.5 568.56,-46.5 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">mgmt</text>
|
||||
<polyline fill="none" stroke="black" points="518.56,-23.5 568.56,-23.5 "/>
|
||||
<text text-anchor="middle" x="543.56" y="-8.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">data</text>
|
||||
<polyline fill="none" stroke="black" points="568.56,-0.5 568.56,-69.5 "/>
|
||||
<text text-anchor="middle" x="630.56" y="-46.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">R3 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-31.3" font-family="DejaVu Sans Mono, Book" font-size="14.00"> 10.0.3.1/32 </text>
|
||||
<text text-anchor="middle" x="630.56" y="-16.3" font-family="DejaVu Sans Mono, Book" font-size="14.00">(lo)</text>
|
||||
</g>
|
||||
<!-- PC--R3 -->
|
||||
<g id="edge3" class="edge">
|
||||
<title>PC:mgmt3--R3:mgmt</title>
|
||||
<path fill="none" stroke="lightgray" stroke-width="2" d="M91.5,-96.01C91.5,-96.01 518.56,-35 518.56,-35"/>
|
||||
</g>
|
||||
<!-- R1--R2 -->
|
||||
<g id="edge6" class="edge">
|
||||
<title>R1:link--R2:link1</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M543.56,-240.02C543.56,-240.02 514.56,-190.01 514.56,-190.01"/>
|
||||
<text text-anchor="middle" x="493.56" y="-228.82" font-family="DejaVu Serif, Book" font-size="14.00">10.0.123.1/24</text>
|
||||
</g>
|
||||
<!-- R3--PC -->
|
||||
<g id="edge5" class="edge">
|
||||
<title>R3:data--PC:data2</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M518.56,-12C518.56,-12 91.5,-73.01 91.5,-73.01"/>
|
||||
<text text-anchor="middle" x="137" y="-76.81" font-family="DejaVu Serif, Book" font-size="14.00">10.0.30.2/24</text>
|
||||
<text text-anchor="middle" x="473.06" y="-15.8" font-family="DejaVu Serif, Book" font-size="14.00">10.0.30.1/24</text>
|
||||
</g>
|
||||
<!-- R3--R2 -->
|
||||
<g id="edge7" class="edge">
|
||||
<title>R3:link--R2:link2</title>
|
||||
<path fill="none" stroke="cornflowerblue" stroke-width="2" d="M543.56,-70C543.56,-70 514.56,-120.01 514.56,-120.01"/>
|
||||
<text text-anchor="middle" x="493.56" y="-73.8" font-family="DejaVu Serif, Book" font-size="14.00">10.0.123.3/24</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 7.4 KiB |
Reference in New Issue
Block a user