Merge pull request #12727 from opensourcerouting/feature/bgp_software_version_capability

bgpd: Software Version Capability
This commit is contained in:
Donald Sharp 2023-02-17 08:18:22 -05:00 committed by GitHub
commit a15b0b1024
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 369 additions and 6 deletions

View File

@ -297,6 +297,15 @@ static struct peer *peer_xfer_conn(struct peer *from_peer)
from_peer->domainname = NULL;
}
if (peer->soft_version) {
XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version);
peer->soft_version = NULL;
}
if (from_peer->soft_version) {
peer->soft_version = from_peer->soft_version;
from_peer->soft_version = NULL;
}
FOREACH_AFI_SAFI (afi, safi) {
peer->af_sflags[afi][safi] = from_peer->af_sflags[afi][safi];
peer->af_cap[afi][safi] = from_peer->af_cap[afi][safi];

View File

@ -139,3 +139,5 @@ DEFINE_MTYPE(BGPD, BGP_SRV6_FUNCTION, "BGP srv6 function");
DEFINE_MTYPE(BGPD, EVPN_REMOTE_IP, "BGP EVPN Remote IP hash entry");
DEFINE_MTYPE(BGPD, BGP_NOTIFICATION, "BGP Notification Message");
DEFINE_MTYPE(BGPD, BGP_SOFT_VERSION, "Software Version");

View File

@ -138,4 +138,6 @@ DECLARE_MTYPE(EVPN_REMOTE_IP);
DECLARE_MTYPE(BGP_NOTIFICATION);
DECLARE_MTYPE(BGP_SOFT_VERSION);
#endif /* _QUAGGA_BGP_MEMORY_H */

View File

@ -59,6 +59,7 @@ static const struct message capcode_str[] = {
{CAPABILITY_CODE_EXT_MESSAGE, "BGP Extended Message"},
{CAPABILITY_CODE_LLGR, "Long-lived BGP Graceful Restart"},
{CAPABILITY_CODE_ROLE, "Role"},
{CAPABILITY_CODE_SOFT_VERSION, "Software Version"},
{0}};
/* Minimum sizes for length field of each cap (so not inc. the header) */
@ -79,6 +80,7 @@ static const size_t cap_minsizes[] = {
[CAPABILITY_CODE_EXT_MESSAGE] = CAPABILITY_CODE_EXT_MESSAGE_LEN,
[CAPABILITY_CODE_LLGR] = CAPABILITY_CODE_LLGR_LEN,
[CAPABILITY_CODE_ROLE] = CAPABILITY_CODE_ROLE_LEN,
[CAPABILITY_CODE_SOFT_VERSION] = CAPABILITY_CODE_SOFT_VERSION_LEN,
};
/* value the capability must be a multiple of.
@ -103,6 +105,7 @@ static const size_t cap_modsizes[] = {
[CAPABILITY_CODE_EXT_MESSAGE] = 1,
[CAPABILITY_CODE_LLGR] = 1,
[CAPABILITY_CODE_ROLE] = 1,
[CAPABILITY_CODE_SOFT_VERSION] = 1,
};
/* BGP-4 Multiprotocol Extentions lead us to the complex world. We can
@ -921,6 +924,41 @@ static int bgp_capability_role(struct peer *peer, struct capability_header *hdr)
return 0;
}
static int bgp_capability_software_version(struct peer *peer,
struct capability_header *hdr)
{
struct stream *s = BGP_INPUT(peer);
char str[BGP_MAX_SOFT_VERSION + 1];
size_t end = stream_get_getp(s) + hdr->length;
uint8_t len;
SET_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_RCV);
len = stream_getc(s);
if (stream_get_getp(s) + len > end) {
flog_warn(
EC_BGP_CAPABILITY_INVALID_DATA,
"%s: Received malformed Software Version capability from peer %s",
__func__, peer->host);
return -1;
}
if (len) {
stream_get(str, s, len);
str[len] = '\0';
XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version);
peer->soft_version = XSTRDUP(MTYPE_BGP_SOFT_VERSION, str);
if (bgp_debug_neighbor_events(peer))
zlog_debug("%s sent Software Version: %s", peer->host,
peer->soft_version);
}
return 0;
}
/**
* Parse given capability.
* XXX: This is reading into a stream, but not using stream API
@ -989,6 +1027,7 @@ static int bgp_capability_parse(struct peer *peer, size_t length,
case CAPABILITY_CODE_ENHANCED_RR:
case CAPABILITY_CODE_EXT_MESSAGE:
case CAPABILITY_CODE_ROLE:
case CAPABILITY_CODE_SOFT_VERSION:
/* Check length. */
if (caphdr.length < cap_minsizes[caphdr.code]) {
zlog_info(
@ -1089,6 +1128,9 @@ static int bgp_capability_parse(struct peer *peer, size_t length,
case CAPABILITY_CODE_ROLE:
ret = bgp_capability_role(peer, &caphdr);
break;
case CAPABILITY_CODE_SOFT_VERSION:
ret = bgp_capability_software_version(peer, &caphdr);
break;
default:
if (caphdr.code > 128) {
/* We don't send Notification for unknown vendor
@ -1928,6 +1970,50 @@ uint16_t bgp_open_capability(struct stream *s, struct peer *peer,
bgp_peer_send_gr_capability(s, peer, ext_opt_params);
bgp_peer_send_llgr_capability(s, peer, ext_opt_params);
/* Software Version capability
* An implementation is REQUIRED Extended Optional Parameters
* Length for BGP OPEN Message support as defined in [RFC9072].
* The inclusion of the Software Version Capability is OPTIONAL.
* If an implementation supports the inclusion of the capability,
* the implementation MUST include a configuration switch to enable
* or disable its use, and that switch MUST be off by default.
*/
if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_SOFT_VERSION) ||
peer->sort == BGP_PEER_IBGP) {
SET_FLAG(peer->cap, PEER_CAP_SOFT_VERSION_ADV);
stream_putc(s, BGP_OPEN_OPT_CAP);
rcapp = stream_get_endp(s);
ext_opt_params ? stream_putw(s, 0)
: stream_putc(s, 0); /* Capability Length */
stream_putc(s, CAPABILITY_CODE_SOFT_VERSION);
capp = stream_get_endp(s);
stream_putc(s, 0); /* dummy placeholder len */
/* The Capability Length SHOULD be no greater than 64.
* This is the limit to allow other capabilities as much
* space as they require.
*/
len = strlen(cmd_software_version_get());
if (len > BGP_MAX_SOFT_VERSION)
len = BGP_MAX_SOFT_VERSION;
stream_putc(s, len);
stream_put(s, cmd_software_version_get(), len);
/* Software Version capability Len. */
len = stream_get_endp(s) - rcapp - 1;
ext_opt_params ? stream_putw_at(s, rcapp, len - 1)
: stream_putc_at(s, rcapp, len);
/* Total Capability Len. */
len = stream_get_endp(s) - capp - 1;
stream_putc_at(s, capp, len);
if (bgp_debug_neighbor_events(peer))
zlog_debug("%s Sending Software Version cap, value: %s",
peer->host, cmd_software_version_get());
}
/* Total Opt Parm Len. */
len = stream_get_endp(s) - cp - 1;

View File

@ -52,6 +52,7 @@ struct graceful_restart_af {
#define CAPABILITY_CODE_ENHANCED_RR 70 /* Enhanced Route Refresh capability */
#define CAPABILITY_CODE_LLGR 71 /* Long-lived Graceful Restart */
#define CAPABILITY_CODE_FQDN 73 /* Advertise hostname capability */
#define CAPABILITY_CODE_SOFT_VERSION 75 /* Software Version capability */
#define CAPABILITY_CODE_ENHE 5 /* Extended Next Hop Encoding */
#define CAPABILITY_CODE_REFRESH_OLD 128 /* Route Refresh Capability(cisco) */
#define CAPABILITY_CODE_ORF_OLD 130 /* Cooperative Route Filtering Capability(cisco) */
@ -72,6 +73,7 @@ struct graceful_restart_af {
#define CAPABILITY_CODE_ORF_LEN 5
#define CAPABILITY_CODE_EXT_MESSAGE_LEN 0 /* Extended Message Support */
#define CAPABILITY_CODE_ROLE_LEN 1
#define CAPABILITY_CODE_SOFT_VERSION_LEN 1
/* Cooperative Route Filtering Capability. */

View File

@ -1685,6 +1685,13 @@ static int bgp_open_receive(struct peer *peer, bgp_size_t size)
peer->v_keepalive = peer->bgp->default_keepalive;
}
/* If another side disabled sending Software Version capability,
* we MUST drop the previous from showing in the outputs to avoid
* stale information and due to security reasons.
*/
if (peer->soft_version)
XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version);
/* Open option part parse. */
if (optlen != 0) {
if (bgp_open_option_parse(peer, optlen, &mp_capability) < 0)

View File

@ -5575,6 +5575,30 @@ DEFUN (no_neighbor_capability_enhe,
PEER_FLAG_CAPABILITY_ENHE);
}
/* neighbor capability software-version */
DEFPY(neighbor_capability_software_version,
neighbor_capability_software_version_cmd,
"[no$no] neighbor <A.B.C.D|X:X::X:X|WORD>$neighbor capability software-version",
NO_STR
NEIGHBOR_STR
NEIGHBOR_ADDR_STR2
"Advertise capability to the peer\n"
"Advertise Software Version capability to the peer\n")
{
struct peer *peer;
peer = peer_and_group_lookup_vty(vty, neighbor);
if (peer && peer->conf_if)
return CMD_SUCCESS;
if (no)
return peer_flag_unset_vty(vty, neighbor,
PEER_FLAG_CAPABILITY_SOFT_VERSION);
else
return peer_flag_set_vty(vty, neighbor,
PEER_FLAG_CAPABILITY_SOFT_VERSION);
}
static int peer_af_flag_modify_vty(struct vty *vty, const char *peer_str,
afi_t afi, safi_t safi, uint32_t flag,
int set)
@ -10833,6 +10857,9 @@ static void bgp_show_peer_reset(struct vty * vty, struct peer *peer,
peer_down_str[(int)peer->last_reset]);
json_object_int_add(json_peer, "lastResetCode",
peer->last_reset);
json_object_string_add(json_peer, "softwareVersion",
peer->soft_version ? peer->soft_version
: "n/a");
} else {
if (peer->last_reset == PEER_DOWN_NOTIFY_SEND
|| peer->last_reset == PEER_DOWN_NOTIFY_RECEIVED) {
@ -10851,8 +10878,10 @@ static void bgp_show_peer_reset(struct vty * vty, struct peer *peer,
BGP_NOTIFY_CEASE_HARD_RESET)
: "");
} else {
vty_out(vty, " %s\n",
peer_down_str[(int)peer->last_reset]);
vty_out(vty, " %s (%s)\n",
peer_down_str[(int)peer->last_reset],
peer->soft_version ? peer->soft_version
: "n/a");
}
}
}
@ -13852,6 +13881,27 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
json_object_object_add(json_cap, "hostName",
json_hname);
/* Software Version capability */
json_object *json_soft_version = NULL;
json_soft_version = json_object_new_object();
if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_ADV))
json_object_string_add(
json_soft_version,
"advertisedSoftwareVersion",
cmd_software_version_get());
if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_RCV))
json_object_string_add(
json_soft_version,
"receivedSoftwareVersion",
p->soft_version ? p->soft_version
: "n/a");
json_object_object_add(json_cap, "softwareVersion",
json_soft_version);
/* Graceful Restart */
if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) ||
CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) {
@ -14227,6 +14277,25 @@ static void bgp_show_peer(struct vty *vty, struct peer *p, bool use_json,
vty_out(vty, "\n");
/* Software Version capability */
vty_out(vty, " Version Capability:");
if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_ADV)) {
vty_out(vty,
" advertised software version (%s)",
cmd_software_version_get());
} else
vty_out(vty, " not advertised");
if (CHECK_FLAG(p->cap, PEER_CAP_SOFT_VERSION_RCV)) {
vty_out(vty, " received software version (%s)",
p->soft_version ? p->soft_version
: "n/a");
} else
vty_out(vty, " not received");
vty_out(vty, "\n");
/* Graceful Restart */
if (CHECK_FLAG(p->cap, PEER_CAP_RESTART_RCV) ||
CHECK_FLAG(p->cap, PEER_CAP_RESTART_ADV)) {
@ -16967,7 +17036,7 @@ static void bgp_config_write_redistribute(struct vty *vty, struct bgp *bgp,
/* peer-group helpers for config-write */
static bool peergroup_flag_check(struct peer *peer, uint64_t flag)
bool peergroup_flag_check(struct peer *peer, uint64_t flag)
{
if (!peer_group_active(peer)) {
if (CHECK_FLAG(peer->flags_invert, flag))
@ -17507,6 +17576,11 @@ static void bgp_config_write_peer_global(struct vty *vty, struct bgp *bgp,
addr);
}
/* capability software-version */
if (peergroup_flag_check(peer, PEER_FLAG_CAPABILITY_SOFT_VERSION))
vty_out(vty, " neighbor %s capability software-version\n",
addr);
/* dont-capability-negotiation */
if (peergroup_flag_check(peer, PEER_FLAG_DONT_CAPABILITY))
vty_out(vty, " neighbor %s dont-capability-negotiate\n", addr);
@ -19677,6 +19751,9 @@ void bgp_vty_init(void)
install_element(BGP_NODE, &neighbor_capability_enhe_cmd);
install_element(BGP_NODE, &no_neighbor_capability_enhe_cmd);
/* "neighbor capability software-version" commands.*/
install_element(BGP_NODE, &neighbor_capability_software_version_cmd);
/* "neighbor capability orf prefix-list" commands.*/
install_element(BGP_NODE, &neighbor_capability_orf_prefix_hidden_cmd);
install_element(BGP_NODE,

View File

@ -181,5 +181,6 @@ int bgp_vty_find_and_parse_bgp(struct vty *vty, struct cmd_token **argv,
extern int bgp_show_summary_vty(struct vty *vty, const char *name, afi_t afi,
safi_t safi, const char *neighbor, int as_type,
as_t as, uint16_t show_flags);
extern bool peergroup_flag_check(struct peer *peer, uint64_t flag);
#endif /* _QUAGGA_BGP_VTY_H */

View File

@ -1178,6 +1178,8 @@ static void peer_free(struct peer *peer)
XFREE(MTYPE_PEER_CONF_IF, peer->conf_if);
XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version);
/* Remove BFD configuration. */
if (peer->bfd_config)
bgp_peer_remove_bfd_config(peer);
@ -2631,6 +2633,7 @@ int peer_delete(struct peer *peer)
XFREE(MTYPE_BGP_PEER_HOST, peer->hostname);
XFREE(MTYPE_BGP_PEER_HOST, peer->domainname);
XFREE(MTYPE_BGP_SOFT_VERSION, peer->soft_version);
peer_unlock(peer); /* initial reference */
@ -2774,6 +2777,13 @@ static void peer_group2peer_config_copy(struct peer_group *group,
if (CHECK_FLAG(conf->flags, PEER_FLAG_CAPABILITY_ENHE))
SET_FLAG(peer->flags, PEER_FLAG_CAPABILITY_ENHE);
/* capability software-version apply */
if (!CHECK_FLAG(peer->flags_override,
PEER_FLAG_CAPABILITY_SOFT_VERSION))
if (CHECK_FLAG(conf->flags, PEER_FLAG_CAPABILITY_SOFT_VERSION))
SET_FLAG(peer->flags,
PEER_FLAG_CAPABILITY_SOFT_VERSION);
/* password apply */
if (!CHECK_FLAG(peer->flags_override, PEER_FLAG_PASSWORD))
PEER_STR_ATTR_INHERIT(peer, group, password,
@ -4386,6 +4396,7 @@ static const struct peer_flag_action peer_flag_action_list[] = {
{PEER_FLAG_PORT, 0, peer_change_reset},
{PEER_FLAG_AIGP, 0, peer_change_none},
{PEER_FLAG_GRACEFUL_SHUTDOWN, 0, peer_change_none},
{PEER_FLAG_CAPABILITY_SOFT_VERSION, 0, peer_change_reset},
{0, 0, 0}};
static const struct peer_flag_action peer_af_flag_action_list[] = {

View File

@ -1250,6 +1250,8 @@ struct peer {
#define PEER_CAP_GRACEFUL_RESTART_N_BIT_RCV (1U << 24)
#define PEER_CAP_ROLE_ADV (1U << 25) /* role advertised */
#define PEER_CAP_ROLE_RCV (1U << 26) /* role received */
#define PEER_CAP_SOFT_VERSION_ADV (1U << 27)
#define PEER_CAP_SOFT_VERSION_RCV (1U << 28)
/* Capability flags (reset in bgp_stop) */
uint32_t af_cap[AFI_MAX][SAFI_MAX];
@ -1376,6 +1378,7 @@ struct peer {
#define PEER_FLAG_PORT (1ULL << 33)
#define PEER_FLAG_AIGP (1ULL << 34)
#define PEER_FLAG_GRACEFUL_SHUTDOWN (1ULL << 35)
#define PEER_FLAG_CAPABILITY_SOFT_VERSION (1ULL << 36)
/*
*GR-Disabled mode means unset PEER_FLAG_GRACEFUL_RESTART
@ -1771,6 +1774,10 @@ struct peer {
/* Path attributes treat-as-withdraw */
bool withdraw_attrs[BGP_ATTR_MAX];
/* BGP Software Version Capability */
#define BGP_MAX_SOFT_VERSION 64
char *soft_version;
QOBJ_FIELDS;
};
DECLARE_QOBJ_TYPE(peer);

View File

@ -2020,10 +2020,17 @@ Capability Negotiation
.. clicmd:: neighbor PEER override-capability
Override the result of Capability Negotiation with local configuration.
Ignore remote peer's capability value.
.. clicmd:: neighbor PEER capability software-version
Send the software version in the BGP OPEN message to the neighbor. This is
very useful in environments with a large amount of peers with different
versions of FRR or any other vendor.
Disabled by default.
.. _bgp-as-path-access-lists:
AS Path Access Lists

View File

@ -127,6 +127,11 @@ bool cmd_allow_reserved_ranges_get(void)
return host.allow_reserved_ranges;
}
const char *cmd_software_version_get(void)
{
return FRR_FULL_NAME "/" FRR_VERSION;
}
static int root_on_exit(struct vty *vty);
/* Standard command node structures. */

View File

@ -606,6 +606,7 @@ extern const char *cmd_domainname_get(void);
extern const char *cmd_system_get(void);
extern const char *cmd_release_get(void);
extern const char *cmd_version_get(void);
extern const char *cmd_software_version_get(void);
extern bool cmd_allow_reserved_ranges_get(void);
/* NOT safe for general use; call this only if DEV_BUILD! */

View File

@ -176,9 +176,9 @@ struct test_peer_attr {
enum test_peer_attr_type type;
union {
uint32_t flag;
uint64_t flag;
struct {
uint32_t flag;
uint64_t flag;
size_t direct;
} filter;
} u;
@ -281,6 +281,18 @@ static struct test_peer_attr test_peer_attrs[] = {
.o.invert_peer = true,
.o.use_iface_peer = true,
},
{
.cmd = "capability software-version",
.u.flag = PEER_FLAG_CAPABILITY_SOFT_VERSION,
.type = PEER_AT_GLOBAL_FLAG,
},
{
.cmd = "capability software-version",
.u.flag = PEER_FLAG_CAPABILITY_SOFT_VERSION,
.type = PEER_AT_GLOBAL_FLAG,
.o.invert_peer = true,
.o.use_iface_peer = true,
},
{
.cmd = "description",
.peer_cmd = "description FRR Peer",

View File

@ -0,0 +1,8 @@
!
router bgp 65001
no bgp ebgp-requires-policy
neighbor 192.168.1.2 remote-as external
neighbor 192.168.1.2 timers 1 3
neighbor 192.168.1.2 timers connect 1
neighbor 192.168.1.2 capability software-version
!

View File

@ -0,0 +1,4 @@
!
int r1-eth0
ip address 192.168.1.1/24
!

View File

@ -0,0 +1,7 @@
router bgp 65002
no bgp ebgp-requires-policy
neighbor 192.168.1.1 remote-as external
neighbor 192.168.1.1 timers 1 3
neighbor 192.168.1.1 timers connect 1
neighbor 192.168.1.1 capability software-version
!

View File

@ -0,0 +1,4 @@
!
int r2-eth0
ip address 192.168.1.2/24
!

View File

@ -0,0 +1,111 @@
#!/usr/bin/env python
# Copyright (c) 2022 by
# Donatas Abraitis <donatas@opensourcerouting.org>
#
# Permission to use, copy, modify, and/or distribute this software
# for any purpose with or without fee is hereby granted, provided
# that the above copyright notice and this permission notice appear
# in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND NETDEF DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL NETDEF BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY
# DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
# WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
# ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
# OF THIS SOFTWARE.
#
"""
Test if Software Version capability works if forced with a knob.
Reference: https://datatracker.ietf.org/doc/html/draft-abraitis-bgp-version-capability
"""
import os
import re
import sys
import json
import pytest
import functools
pytestmark = pytest.mark.bgpd
CWD = os.path.dirname(os.path.realpath(__file__))
sys.path.append(os.path.join(CWD, "../"))
# pylint: disable=C0413
from lib import topotest
from lib.topogen import Topogen, TopoRouter, get_topogen
pytestmark = [pytest.mark.bgpd]
def setup_module(mod):
topodef = {"s1": ("r1", "r2")}
tgen = Topogen(topodef, mod.__name__)
tgen.start_topology()
router_list = tgen.routers()
for i, (rname, router) in enumerate(router_list.items(), 1):
router.load_config(
TopoRouter.RD_ZEBRA, os.path.join(CWD, "{}/zebra.conf".format(rname))
)
router.load_config(
TopoRouter.RD_BGP, os.path.join(CWD, "{}/bgpd.conf".format(rname))
)
tgen.start_router()
def teardown_module(mod):
tgen = get_topogen()
tgen.stop_topology()
def test_bgp_software_version():
tgen = get_topogen()
if tgen.routers_have_failure():
pytest.skip(tgen.errors)
r1 = tgen.gears["r1"]
def _bgp_converge():
output = json.loads(r1.vtysh_cmd("show bgp summary json"))
expected = {"ipv4Unicast": {"peers": {"192.168.1.2": {"state": "Established"}}}}
return topotest.json_cmp(output, expected)
test_func = functools.partial(
_bgp_converge,
)
_, result = topotest.run_and_expect(test_func, None, count=60, wait=0.5)
assert result is None, "Can't converge"
def _bgp_check_software_version():
output = json.loads(r1.vtysh_cmd("show bgp neighbor 192.168.1.2 json"))
try:
versions = output["192.168.1.2"]["neighborCapabilities"]["softwareVersion"]
adv = versions["advertisedSoftwareVersion"]
rcv = versions["receivedSoftwareVersion"]
if not adv and not rcv:
return False
pattern = "^FRRouting/\\d.+"
if re.search(pattern, adv) and re.search(pattern, rcv):
return True
except:
return False
return False
assert _bgp_check_software_version(), "Neighbor's software version is n/a"
if __name__ == "__main__":
args = ["-s"] + sys.argv[1:]
sys.exit(pytest.main(args))