Merge pull request #15052 from louis-6wind/rpki-vrf-92

bgpd: add VRF support to RPKI
This commit is contained in:
Donatas Abraitis 2024-01-22 16:16:34 +02:00 committed by GitHub
commit 20ec72d7ca
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 1218 additions and 261 deletions

View file

@ -51,10 +51,16 @@
#include "bgpd/bgp_routemap_nb.h"
#include "bgpd/bgp_community_alias.h"
DEFINE_HOOK(bgp_hook_config_write_vrf, (struct vty *vty, struct vrf *vrf),
(vty, vrf));
#ifdef ENABLE_BGP_VNC
#include "bgpd/rfapi/rfapi_backend.h"
#endif
DEFINE_HOOK(bgp_hook_vrf_update, (struct vrf *vrf, bool enabled),
(vrf, enabled));
/* bgpd options, we use GNU getopt library. */
static const struct option longopts[] = {
{ "bgp_port", required_argument, NULL, 'p' },
@ -287,6 +293,7 @@ static int bgp_vrf_enable(struct vrf *vrf)
bgp_handle_socket(bgp, vrf, old_vrf_id, true);
bgp_instance_up(bgp);
hook_call(bgp_hook_vrf_update, vrf, true);
vpn_leak_zebra_vrf_label_update(bgp, AFI_IP);
vpn_leak_zebra_vrf_label_update(bgp, AFI_IP6);
vpn_leak_zebra_vrf_sid_update(bgp, AFI_IP);
@ -333,15 +340,36 @@ static int bgp_vrf_disable(struct vrf *vrf)
* "down". */
bgp_instance_down(bgp);
bgp_vrf_unlink(bgp, vrf);
hook_call(bgp_hook_vrf_update, vrf, false);
}
/* Note: This is a callback, the VRF will be deleted by the caller. */
return 0;
}
static int bgp_vrf_config_write(struct vty *vty)
{
struct vrf *vrf;
RB_FOREACH (vrf, vrf_name_head, &vrfs_by_name) {
if (vrf->vrf_id == VRF_DEFAULT) {
vty_out(vty, "!\n");
continue;
}
vty_out(vty, "vrf %s\n", vrf->name);
hook_call(bgp_hook_config_write_vrf, vty, vrf);
vty_out(vty, "exit-vrf\n!\n");
}
return 0;
}
static void bgp_vrf_init(void)
{
vrf_init(bgp_vrf_new, bgp_vrf_enable, bgp_vrf_disable, bgp_vrf_delete);
vrf_cmd_init(bgp_vrf_config_write);
}
static void bgp_vrf_terminate(void)

File diff suppressed because it is too large Load diff

View file

@ -8,6 +8,8 @@
#ifndef __BGP_RPKI_H__
#define __BGP_RPKI_H__
extern struct zebra_privs_t bgpd_privs;
enum rpki_states {
RPKI_NOT_BEING_USED,
RPKI_VALID,

View file

@ -34,6 +34,9 @@
#include "lib/bfd.h"
DECLARE_HOOK(bgp_hook_config_write_vrf, (struct vty *vty, struct vrf *vrf),
(vty, vrf));
#define BGP_MAX_HOSTNAME 64 /* Linux max, is larger than most other sys */
#define BGP_PEER_MAX_HASH_SIZE 16384
@ -839,6 +842,8 @@ DECLARE_HOOK(bgp_inst_config_write,
(bgp, vty));
DECLARE_HOOK(bgp_snmp_traps_config_write, (struct vty *vty), (vty));
DECLARE_HOOK(bgp_config_end, (struct bgp *bgp), (bgp));
DECLARE_HOOK(bgp_hook_vrf_update, (struct vrf *vrf, bool enabled),
(vrf, enabled));
/* Thread callback information */
struct afi_safi_info {

View file

@ -62,8 +62,9 @@ otherwise ``bgpd`` daemon won't startup.
This command enables the RPKI configuration mode. Most commands that start
with *rpki* can only be used in this mode.
When it is used in a telnet session, leaving of this mode cause rpki to be
initialized.
This command is available either in *configure node* for default *vrf* or
in *vrf node* for specific *vrf*. When it is used in a telnet session,
leaving of this mode cause rpki to be initialized.
Executing this command alone does not activate prefix validation. You need
to configure at least one reachable cache server. See section
@ -90,6 +91,9 @@ Examples of the error::
router(config)# rpki
% [BGP] Unknown command: rpki
router(config-vrf)# rpki
% [BGP] Unknown command: rpki
Note that the RPKI commands will be available in vtysh when running
``find rpki`` regardless of whether the module is loaded.
@ -98,7 +102,14 @@ Note that the RPKI commands will be available in vtysh when running
Configuring RPKI/RTR Cache Servers
----------------------------------
The following commands are independent of a specific cache server.
RPKI/RTR can be configured independently, either in configure node, or in *vrf*
sub context. If configured in configure node, the core *bgp* instance of default
*vrf* is impacted by the configuration.
Each RPKI/RTR context is mapped to a *vrf* and can be made up of a specific list
of cache-servers, and specific settings.
The following commands are available for independent of a specific cache server.
.. clicmd:: rpki polling_period (1-3600)
@ -166,7 +177,7 @@ Validating BGP Updates
.. code-block:: frr
! Allow for invalid routes in route selection process
route bgp 60001
route bgp 65001
!
! Set local preference of invalid prefixes to 10
route-map rpki permit 10
@ -200,39 +211,39 @@ Debugging
Displaying RPKI
---------------
.. clicmd:: show rpki configuration [json]
.. clicmd:: show rpki configuration [vrf NAME] [json]
Display RPKI configuration state including timers values.
.. clicmd:: show rpki prefix <A.B.C.D/M|X:X::X:X/M> [(1-4294967295)] [json]
.. clicmd:: show rpki prefix <A.B.C.D/M|X:X::X:X/M> [(1-4294967295)] [vrf NAME] [json]
Display validated prefixes received from the cache servers filtered
by the specified prefix.
.. clicmd:: show rpki as-number ASN [json]
.. clicmd:: show rpki as-number ASN [vrf NAME] [json]
Display validated prefixes received from the cache servers filtered
by ASN.
.. clicmd:: show rpki prefix-table [json]
.. clicmd:: show rpki prefix-table [vrf NAME] [json]
Display all validated prefix to origin AS mappings/records which have been
received from the cache servers and stored in the router. Based on this data,
the router validates BGP Updates.
.. clicmd:: show rpki cache-server [json]
.. clicmd:: show rpki cache-server [vrf NAME] [json]
Display all configured cache servers, whether active or not.
.. clicmd:: show rpki cache-connection [json]
.. clicmd:: show rpki cache-connection [vrf NAME] [json]
Display all cache connections, and show which is connected or not.
.. clicmd:: show bgp [afi] [safi] <A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> rpki <valid|invalid|notfound>
.. clicmd:: show bgp [vrf NAME] [afi] [safi] <A.B.C.D|A.B.C.D/M|X:X::X:X|X:X::X:X/M> rpki <valid|invalid|notfound>
Display for the specified prefix or address the bgp paths that match the given rpki state.
.. clicmd:: show bgp [afi] [safi] rpki <valid|invalid|notfound>
.. clicmd:: show bgp [vrf NAME] [afi] [safi] rpki <valid|invalid|notfound>
Display all prefixes that match the given rpki state.
@ -248,25 +259,52 @@ RPKI Configuration Example
debug bgp keepalives
debug rpki
!
vrf VRF1
rpki
rpki polling_period 1000
rpki timeout 10
! SSH Example:
rpki cache example.com source 141.22.28.223 22 rtr-ssh ./ssh_key/id_rsa ./ssh_key/id_rsa.pub preference 1
rpki cache example.com 22 rtr-ssh ./ssh_key/id_rsa preference 1
! TCP Example:
rpki cache rpki-validator.realmv6.org 8282 preference 2
exit
!
router bgp 60001
bgp router-id 141.22.28.223
network 192.168.0.0/16
neighbor 123.123.123.0 remote-as 60002
neighbor 123.123.123.0 route-map rpki in
neighbor 123.123.123.0 update-source 141.22.28.223
exit-vrf
!
rpki
rpki polling_period 1000
rpki timeout 10
! SSH Example:
rpki cache example.com source 198.51.100.223 22 rtr-ssh ./ssh_key/id_rsa preference 1
! TCP Example:
rpki cache rpki-validator.realmv6.org 8282 preference 2
exit
!
router bgp 65001
bgp router-id 198.51.100.223
neighbor 203.0.113.1 remote-as 65002
neighbor 203.0.113.1 update-source 198.51.100.223
address-family ipv4
network 192.0.2.0/24
neighbor 203.0.113.1 route-map rpki in
exit-address-family
!
address-family ipv6
neighbor 123.123.123.0 activate
neighbor 123.123.123.0 route-map rpki in
neighbor 203.0.113.1 activate
neighbor 203.0.113.1 route-map rpki in
exit-address-family
!
router bgp 65001 vrf VRF1
bgp router-id 198.51.100.223
neighbor 203.0.113.1 remote-as 65002
address-family ipv4
network 192.0.2.0/24
neighbor 203.0.113.1 route-map rpki in
exit-address-family
!
address-family ipv6
neighbor 203.0.113.1 activate
neighbor 203.0.113.1 route-map rpki in
exit-address-family
!
route-map rpki permit 10

View file

@ -178,6 +178,7 @@ enum node_type {
ISIS_SRV6_NODE, /* ISIS SRv6 node */
ISIS_SRV6_NODE_MSD_NODE, /* ISIS SRv6 Node MSDs node */
MGMTD_NODE, /* MGMTD node. */
RPKI_VRF_NODE, /* RPKI node for VRF */
NODE_TYPE_MAX, /* maximum */
};
/* clang-format on */

View file

@ -1 +1,5 @@
ip route 192.0.2.1/32 192.168.1.1
!
vrf vrf10
ip route 192.0.2.3/32 192.168.2.3
!

View file

@ -7,3 +7,6 @@ interface vrf10 vrf vrf10
interface r2-eth0
ip address 192.168.1.2/24
!
interface r2-eth1 vrf vrf10
ip address 192.168.2.2/24
!

View file

@ -0,0 +1,14 @@
router bgp 65530
no bgp ebgp-requires-policy
no bgp network import-check
neighbor 192.0.2.2 remote-as 65002
neighbor 192.0.2.2 timers 1 3
neighbor 192.0.2.2 timers connect 1
neighbor 192.0.2.2 ebgp-multihop 3
neighbor 192.0.2.2 update-source 192.0.2.3
address-family ipv4 unicast
network 198.51.100.0/24
network 203.0.113.0/24
network 10.0.0.0/24
exit-address-family
!

View file

@ -0,0 +1 @@
../r1/rtrd.py

View file

@ -0,0 +1 @@
ip route 192.0.2.2/32 192.168.2.2

View file

@ -0,0 +1 @@
../r1/vrps.csv
1 ../r1/vrps.csv

View file

@ -0,0 +1,5 @@
interface lo
ip address 192.0.2.3/32
!
interface r3-eth0
ip address 192.168.2.3/24

View file

@ -22,13 +22,17 @@ pytestmark = [pytest.mark.bgpd]
def build_topo(tgen):
for routern in range(1, 3):
for routern in range(1, 4):
tgen.add_router("r{}".format(routern))
switch = tgen.add_switch("s1")
switch.add_link(tgen.gears["r1"])
switch.add_link(tgen.gears["r2"])
switch = tgen.add_switch("s2")
switch.add_link(tgen.gears["r2"])
switch.add_link(tgen.gears["r3"])
def setup_module(mod):
tgen = Topogen(build_topo, mod.__name__)
@ -49,25 +53,34 @@ def setup_module(mod):
" -M bgpd_rpki" if rname == "r2" else "",
)
tgen.gears["r2"].run("ip link add vrf10 type vrf table 10")
tgen.gears["r2"].run("ip link set vrf10 up")
tgen.gears["r2"].run("ip link set r2-eth1 master vrf10")
tgen.start_router()
global rtrd_process
rtrd_process = {}
rname = "r1"
for rname in ["r1", "r3"]:
rtr_path = os.path.join(CWD, rname)
log_dir = os.path.join(tgen.logdir, rname)
log_file = os.path.join(log_dir, "rtrd.log")
tgen.gears[rname].cmd("chmod u+x {}/rtrd.py".format(rtr_path))
rtrd_process = tgen.gears[rname].popen("{}/rtrd.py {}".format(rtr_path, log_file))
rtrd_process[rname] = tgen.gears[rname].popen(
"{}/rtrd.py {}".format(rtr_path, log_file)
)
def teardown_module(mod):
tgen = get_topogen()
logger.info("r1: sending SIGTERM to rtrd RPKI server")
rtrd_process.kill()
for rname in ["r1", "r3"]:
logger.info("{}: sending SIGTERM to rtrd RPKI server".format(rname))
rtrd_process[rname].kill()
tgen.stop_topology()
@ -114,7 +127,7 @@ def test_show_bgp_rpki_prefixes():
for rname in ["r1", "r3"]:
logger.info("{}: checking if rtrd is running".format(rname))
if rtrd_process.poll() is not None:
if rtrd_process[rname].poll() is not None:
pytest.skip(tgen.errors)
rname = "r2"
@ -156,7 +169,7 @@ def test_show_bgp_rpki_prefixes_no_rpki_cache():
for rname in ["r1", "r3"]:
logger.info("{}: checking if rtrd is running".format(rname))
if rtrd_process.poll() is not None:
if rtrd_process[rname].poll() is not None:
pytest.skip(tgen.errors)
def _show_rpki_no_connection(rname):
@ -192,7 +205,7 @@ def test_show_bgp_rpki_prefixes_reconnect():
for rname in ["r1", "r3"]:
logger.info("{}: checking if rtrd is running".format(rname))
if rtrd_process.poll() is not None:
if rtrd_process[rname].poll() is not None:
pytest.skip(tgen.errors)
step("Restore RPKI server configuration")
@ -241,7 +254,7 @@ def test_show_bgp_rpki_route_map():
for rname in ["r1", "r3"]:
logger.info("{}: checking if rtrd is running".format(rname))
if rtrd_process.poll() is not None:
if rtrd_process[rname].poll() is not None:
pytest.skip(tgen.errors)
step("Apply RPKI valid route-map on neighbor")
@ -283,6 +296,112 @@ router bgp 65002
assert result is None, "Unexpected prefixes RPKI state on {}".format(rname)
def test_show_bgp_rpki_prefixes_vrf():
tgen = get_topogen()
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
for rname in ["r1", "r3"]:
logger.info("{}: checking if rtrd is running".format(rname))
if rtrd_process[rname].poll() is not None:
pytest.skip(tgen.errors)
step("Configure RPKI cache server on vrf10")
rname = "r2"
tgen.gears[rname].vtysh_cmd(
"""
configure
vrf vrf10
rpki
rpki cache 192.0.2.3 15432 preference 1
exit
exit
"""
)
step("Check vrf10 RPKI prefix table")
expected = open(os.path.join(CWD, "{}/rpki_prefix_table.json".format(rname))).read()
expected_json = json.loads(expected)
test_func = functools.partial(show_rpki_prefixes, rname, expected_json, vrf="vrf10")
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert result is None, "Failed to see RPKI prefixes on {}".format(rname)
for rpki_state in ["valid", "notfound", None]:
if rpki_state:
step(
"Check RPKI state of prefixes in vrf10 BGP table: {}".format(rpki_state)
)
else:
step("Check prefixes in vrf10 BGP table")
expected = open(
os.path.join(
CWD,
"{}/bgp_table_rpki_{}.json".format(
rname, rpki_state if rpki_state else "any"
),
)
).read()
expected_json = json.loads(expected)
test_func = functools.partial(
show_bgp_ipv4_table_rpki, rname, rpki_state, expected_json, vrf="vrf10"
)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert result is None, "Unexpected prefixes RPKI state on {}".format(rname)
def test_show_bgp_rpki_route_map_vrf():
tgen = get_topogen()
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
for rname in ["r1", "r3"]:
logger.info("{}: checking if rtrd is running".format(rname))
if rtrd_process[rname].poll() is not None:
pytest.skip(tgen.errors)
step("Apply RPKI valid route-map on vrf10 neighbor")
rname = "r2"
tgen.gears[rname].vtysh_cmd(
"""
configure
router bgp 65002 vrf vrf10
address-family ipv4 unicast
neighbor 192.0.2.3 route-map RPKI in
"""
)
for rpki_state in ["valid", "notfound", None]:
if rpki_state:
step(
"Check RPKI state of prefixes in vrf10 BGP table: {}".format(rpki_state)
)
else:
step("Check prefixes in vrf10 BGP table")
expected = open(
os.path.join(
CWD,
"{}/bgp_table_rmap_rpki_{}.json".format(
rname, rpki_state if rpki_state else "any"
),
)
).read()
expected_json = json.loads(expected)
test_func = functools.partial(
show_bgp_ipv4_table_rpki,
rname,
rpki_state,
expected_json,
vrf="vrf10",
)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert result is None, "Unexpected prefixes RPKI state on {}".format(rname)
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))

View file

@ -1625,6 +1625,14 @@ static struct cmd_node rpki_node = {
.parent_node = CONFIG_NODE,
.prompt = "%s(config-rpki)# ",
};
static struct cmd_node rpki_vrf_node = {
.name = "rpki",
.node = RPKI_VRF_NODE,
.parent_node = VRF_NODE,
.prompt = "%s(config-vrf-rpki)# ",
};
#endif /* HAVE_BGPD */
#if HAVE_BFDD > 0
@ -1855,7 +1863,10 @@ DEFUNSH(VTYSH_BGPD,
"rpki",
"Enable rpki and enter rpki configuration mode\n")
{
if (vty->node == CONFIG_NODE)
vty->node = RPKI_NODE;
else
vty->node = RPKI_VRF_NODE;
return CMD_SUCCESS;
}
@ -5071,6 +5082,12 @@ void vtysh_init_vty(void)
install_element(VRF_NODE, &vtysh_exit_vrf_cmd);
install_element(VRF_NODE, &vtysh_quit_vrf_cmd);
install_node(&rpki_vrf_node);
install_element(VRF_NODE, &rpki_cmd);
install_element(RPKI_VRF_NODE, &rpki_exit_cmd);
install_element(RPKI_VRF_NODE, &rpki_quit_cmd);
install_element(RPKI_VRF_NODE, &vtysh_end_all_cmd);
install_element(CONFIG_NODE, &vtysh_affinity_map_cmd);
install_element(CONFIG_NODE, &vtysh_no_affinity_map_cmd);

View file

@ -58,7 +58,7 @@ extern struct event_loop *master;
VTYSH_EIGRPD | VTYSH_BABELD | VTYSH_PBRD | VTYSH_FABRICD | \
VTYSH_VRRPD | VTYSH_MGMTD
#define VTYSH_INTERFACE VTYSH_INTERFACE_SUBSET | VTYSH_BGPD
#define VTYSH_VRF VTYSH_INTERFACE_SUBSET
#define VTYSH_VRF VTYSH_INTERFACE_SUBSET | RPKI_VRF_NODE
#define VTYSH_KEYS VTYSH_RIPD | VTYSH_EIGRPD | VTYSH_OSPF6D | VTYSH_OSPFD
/* Daemons who can process nexthop-group configs */
#define VTYSH_NH_GROUP VTYSH_PBRD|VTYSH_SHARPD

View file

@ -315,11 +315,20 @@ void vtysh_config_parse_line(void *arg, const char *line)
} else if (!strncmp(line, " ip mroute",
strlen(" ip mroute"))) {
config_add_line_uniq_end(config->line, line);
} else if ((strncmp(line, " rpki", strlen(" rpki")) ==
0) &&
config->index == VRF_NODE) {
config_add_line(config->line, line);
config->index = RPKI_VRF_NODE;
} else if (config->index == RMAP_NODE ||
config->index == INTERFACE_NODE ||
config->index == VTY_NODE)
config_add_line_uniq(config->line, line);
else if (config->index == NH_GROUP_NODE) {
else if (config->index == RPKI_VRF_NODE &&
strncmp(line, " exit", strlen(" exit")) == 0) {
config_add_line(config->line, line);
config->index = VRF_NODE;
} else if (config->index == NH_GROUP_NODE) {
if (strncmp(line, " resilient",
strlen(" resilient")) == 0)
config_add_line_head(config->line,