Skip to content

Commit b591932

Browse files
authored
Merge pull request #20077 from opensourcerouting/feature/ospfv2_set_forwarder_address
ospfd: Implement forwarding-address-self command
2 parents dee3999 + 0ffa86c commit b591932

File tree

9 files changed

+202
-1
lines changed

9 files changed

+202
-1
lines changed

doc/user/ospfd.rst

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,19 @@ To start OSPF process you have to specify the OSPF router.
379379
causes ospfd to use a single socket per ospf instance for sending
380380
and receiving packets.
381381

382+
.. clicmd:: forwarding-address-self
383+
384+
By default, when an OSPF router redistributes routes into OSPF, it
385+
sets the forwarding address field of the resulting next-hop.
386+
387+
When this command is configured, the router sets the forwarding address
388+
field to its own address, causing other routers to send traffic for the
389+
redistributed routes to ASBR directly.
390+
391+
This command is useful in scenarios where the ASBR is multi-homed (ECMP)
392+
and the administrator wants to ensure that traffic for redistributed
393+
routes is sent to the correct next-hop.
394+
382395
.. _ospf-area:
383396

384397
Areas

ospfd/ospf_lsa.c

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1628,11 +1628,15 @@ static struct in_addr ospf_external_lsa_nexthop_get(struct ospf *ospf,
16281628
struct listnode *node;
16291629
struct ospf_interface *oi;
16301630

1631-
fwd.s_addr = 0;
1631+
fwd.s_addr = INADDR_ANY;
16321632

16331633
if (!nexthop.s_addr)
16341634
return fwd;
16351635

1636+
/* Force forwarding address to self for external LSAs. */
1637+
if (ospf->forwarding_address_self)
1638+
return fwd;
1639+
16361640
/* Check whether nexthop is covered by OSPF network. */
16371641
nh.family = AF_INET;
16381642
nh.u.prefix4 = nexthop;

ospfd/ospf_vty.c

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3226,6 +3226,9 @@ static int show_ip_ospf_common(struct vty *vty, struct ospf *ospf,
32263226
ospf->distance_all
32273227
? ospf->distance_all
32283228
: ZEBRA_OSPF_DISTANCE_DEFAULT);
3229+
3230+
json_object_boolean_add(json_vrf, "forwardingAddressSelf",
3231+
ospf->forwarding_address_self);
32293232
} else {
32303233
vty_out(vty, " SPF timer %s%s\n",
32313234
(ospf->t_spf_calc ? "due in " : "is "),
@@ -3249,6 +3252,9 @@ static int show_ip_ospf_common(struct vty *vty, struct ospf *ospf,
32493252
vty_out(vty, " Maximum multiple paths(ECMP) supported %d\n",
32503253
ospf->max_multipath);
32513254

3255+
if (ospf->forwarding_address_self)
3256+
vty_out(vty, " Forwarding address is set to self for external LSAs\n");
3257+
32523258
/* show administrative distance */
32533259
vty_out(vty, " Administrative distance %u\n",
32543260
ospf->distance_all ? ospf->distance_all
@@ -9606,6 +9612,22 @@ DEFUN (no_ospf_default_metric,
96069612
return CMD_SUCCESS;
96079613
}
96089614

9615+
DEFPY (ospf_forwarding_address_self,
9616+
ospf_forwarding_address_self_cmd,
9617+
"[no$no] forwarding-address-self",
9618+
NO_STR
9619+
"Set forwarding address to self for external LSAs\n")
9620+
{
9621+
VTY_DECLVAR_INSTANCE_CONTEXT(ospf, ospf);
9622+
9623+
if (no)
9624+
ospf->forwarding_address_self = false;
9625+
else
9626+
ospf->forwarding_address_self = true;
9627+
9628+
return CMD_SUCCESS;
9629+
}
9630+
96099631

96109632
DEFUN (ospf_distance,
96119633
ospf_distance_cmd,
@@ -12995,6 +13017,9 @@ static int ospf_config_write_one(struct vty *vty, struct ospf *ospf)
1299513017
if (ospf->passive_interface_default == OSPF_IF_PASSIVE)
1299613018
vty_out(vty, " passive-interface default\n");
1299713019

13020+
if (ospf->forwarding_address_self)
13021+
vty_out(vty, " forwarding-address-self\n");
13022+
1299813023
/* proactive-arp print. */
1299913024
if (ospf->proactive_arp != OSPF_PROACTIVE_ARP_DEFAULT) {
1300013025
if (ospf->proactive_arp)
@@ -13256,6 +13281,8 @@ static void ospf_vty_zebra_init(void)
1325613281
install_element(OSPF_NODE, &ospf_default_metric_cmd);
1325713282
install_element(OSPF_NODE, &no_ospf_default_metric_cmd);
1325813283

13284+
install_element(OSPF_NODE, &ospf_forwarding_address_self_cmd);
13285+
1325913286
install_element(OSPF_NODE, &ospf_distance_cmd);
1326013287
install_element(OSPF_NODE, &no_ospf_distance_cmd);
1326113288
install_element(OSPF_NODE, &no_ospf_distance_ospf_cmd);

ospfd/ospfd.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,9 @@ struct ospf {
436436
/* Per-interface write socket */
437437
bool intf_socket_enabled;
438438

439+
/* Force forwarding address to self for external LSAs. */
440+
bool forwarding_address_self;
441+
439442
QOBJ_FIELDS;
440443
};
441444
DECLARE_QOBJ_TYPE(ospf);

tests/topotests/ospf_forwarding_address_self/__init__.py

Whitespace-only changes.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ip route 172.16.10.0/24 172.16.0.10
2+
ip route 172.16.10.0/24 172.16.0.11
3+
!
4+
int r1-eth0
5+
ip address 172.16.0.1/24
6+
ip ospf area 0.0.0.0 172.16.0.1
7+
ip ospf hello-interval 1
8+
ip ospf dead-interval 5
9+
no ip ospf passive
10+
!
11+
router ospf
12+
redistribute static
13+
forwarding-address-self
14+
passive-interface default
15+
exit
16+
!
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
ip route 172.16.10.0/24 172.16.0.10
2+
ip route 172.16.10.0/24 172.16.0.11
3+
!
4+
int r2-eth0
5+
ip address 172.16.0.2/24
6+
ip ospf area 0.0.0.0 172.16.0.2
7+
ip ospf hello-interval 1
8+
ip ospf dead-interval 5
9+
no ip ospf passive
10+
!
11+
router ospf
12+
redistribute static
13+
forwarding-address-self
14+
passive-interface default
15+
exit
16+
!
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
!
2+
int r3-eth0
3+
ip address 172.16.0.3/24
4+
ip ospf area 0.0.0.0 172.16.0.3
5+
ip ospf hello-interval 1
6+
ip ospf dead-interval 5
7+
no ip ospf passive
8+
!
9+
router ospf
10+
passive-interface default
11+
exit
12+
!
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
#!/usr/bin/env python
2+
# SPDX-License-Identifier: ISC
3+
4+
# Copyright (c) 2025 by
5+
# Donatas Abraitis <[email protected]>
6+
#
7+
8+
import os
9+
import sys
10+
import json
11+
import pytest
12+
import functools
13+
14+
pytestmark = pytest.mark.ospf6d
15+
16+
CWD = os.path.dirname(os.path.realpath(__file__))
17+
sys.path.append(os.path.join(CWD, "../"))
18+
19+
# pylint: disable=C0413
20+
from lib import topotest
21+
from lib.topogen import Topogen, get_topogen
22+
23+
24+
def setup_module(mod):
25+
topodef = {"s1": ("r1", "r2", "r3")}
26+
tgen = Topogen(topodef, mod.__name__)
27+
tgen.start_topology()
28+
29+
router_list = tgen.routers()
30+
31+
for _, (rname, router) in enumerate(router_list.items(), 1):
32+
router.load_frr_config(os.path.join(CWD, "{}/frr.conf".format(rname)))
33+
34+
tgen.start_router()
35+
36+
37+
def teardown_module():
38+
tgen = get_topogen()
39+
tgen.stop_topology()
40+
41+
42+
def test_ospf_forwarding_address_self():
43+
tgen = get_topogen()
44+
45+
if tgen.routers_have_failure():
46+
pytest.skip(tgen.errors)
47+
48+
r3 = tgen.gears["r3"]
49+
50+
def _show_ospf_database_external():
51+
output = json.loads(r3.vtysh_cmd("show ip ospf database external json"))
52+
expected = {
53+
"asExternalLinkStates": [
54+
{
55+
"linkStateId": "172.16.10.0",
56+
"advertisingRouter": "172.16.0.1",
57+
"forwardAddress": "0.0.0.0",
58+
},
59+
{
60+
"linkStateId": "172.16.10.0",
61+
"advertisingRouter": "172.16.0.2",
62+
"forwardAddress": "0.0.0.0",
63+
},
64+
]
65+
}
66+
return topotest.json_cmp(output, expected)
67+
68+
test_func = functools.partial(
69+
_show_ospf_database_external,
70+
)
71+
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
72+
assert result is None, "OSPF external database is not as expected"
73+
74+
def _show_ip_route():
75+
output = json.loads(r3.vtysh_cmd("show ip route 172.16.10.0/24 json"))
76+
expected = {
77+
"172.16.10.0/24": [
78+
{
79+
"protocol": "ospf",
80+
"installed": True,
81+
"internalNextHopNum": 2,
82+
"internalNextHopActiveNum": 2,
83+
"internalNextHopFibInstalledNum": 2,
84+
"nexthops": [
85+
{
86+
"fib": True,
87+
"ip": "172.16.0.1",
88+
"interfaceName": "r3-eth0",
89+
},
90+
{
91+
"fib": True,
92+
"ip": "172.16.0.2",
93+
"interfaceName": "r3-eth0",
94+
},
95+
],
96+
}
97+
]
98+
}
99+
return topotest.json_cmp(output, expected)
100+
101+
test_func = functools.partial(
102+
_show_ip_route,
103+
)
104+
_, result = topotest.run_and_expect(test_func, None, count=60, wait=1)
105+
assert result is None, "172.16.10.0/24 route does not have ECMP entries"
106+
107+
108+
if __name__ == "__main__":
109+
args = ["-s"] + sys.argv[1:]
110+
sys.exit(pytest.main(args))

0 commit comments

Comments
 (0)