diff --git a/bgpd/bgp_fsm.c b/bgpd/bgp_fsm.c index b62a42a4f6..0f2926d060 100644 --- a/bgpd/bgp_fsm.c +++ b/bgpd/bgp_fsm.c @@ -2196,7 +2196,8 @@ void bgp_fsm_nht_update(struct peer *peer, bool has_valid_nexthops) case OpenConfirm: case Established: if (!has_valid_nexthops - && (peer->gtsm_hops == BGP_GTSM_HOPS_CONNECTED)) + && (peer->gtsm_hops == BGP_GTSM_HOPS_CONNECTED + || peer->bgp->fast_convergence)) BGP_EVENT_ADD(peer, TCP_fatal_error); case Clearing: case Deleted: diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c index dcf0fe8469..9a1453b937 100644 --- a/bgpd/bgp_vty.c +++ b/bgpd/bgp_vty.c @@ -4190,6 +4190,28 @@ DEFUN (neighbor_remote_as, return peer_remote_as_vty(vty, argv[idx_peer]->arg, argv[idx_remote_as]->arg); } +/* Enable fast convergence of bgp sessions. If this is enabled, bgp + * sessions do not wait for hold timer expiry to bring down the sessions + * when nexthop becomes unreachable + */ +DEFUN(bgp_fast_convergence, bgp_fast_convergence_cmd, "bgp fast-convergence", + BGP_STR "Fast convergence for bgp sessions\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->fast_convergence = true; + + return CMD_SUCCESS; +} + +DEFUN(no_bgp_fast_convergence, no_bgp_fast_convergence_cmd, + "no bgp fast-convergence", + NO_STR BGP_STR "Fast convergence for bgp sessions\n") +{ + VTY_DECLVAR_CONTEXT(bgp, bgp); + bgp->fast_convergence = false; + + return CMD_SUCCESS; +} static int peer_conf_interface_get(struct vty *vty, const char *conf_if, int v6only, @@ -17198,6 +17220,9 @@ int bgp_config_write(struct vty *vty) if (CHECK_FLAG(bgp->flags, BGP_FLAG_SHUTDOWN)) vty_out(vty, " bgp shutdown\n"); + if (bgp->fast_convergence) + vty_out(vty, " bgp fast-convergence\n"); + if (bgp->srv6_enabled) { vty_frame(vty, " !\n segment-routing srv6\n"); if (strlen(bgp->srv6_locator_name)) @@ -17463,6 +17488,10 @@ void bgp_vty_init(void) install_element(CONFIG_NODE, &bgp_set_route_map_delay_timer_cmd); install_element(CONFIG_NODE, &no_bgp_set_route_map_delay_timer_cmd); + /* bgp fast-convergence command */ + install_element(BGP_NODE, &bgp_fast_convergence_cmd); + install_element(BGP_NODE, &no_bgp_fast_convergence_cmd); + /* global bgp update-delay command */ install_element(CONFIG_NODE, &bgp_global_update_delay_cmd); install_element(CONFIG_NODE, &no_bgp_global_update_delay_cmd); diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c index 3d10771bcb..cea7fb4c85 100644 --- a/bgpd/bgpd.c +++ b/bgpd/bgpd.c @@ -3166,7 +3166,7 @@ static struct bgp *bgp_create(as_t *as, const char *name, bgp->reject_as_sets = false; bgp->condition_check_period = DEFAULT_CONDITIONAL_ROUTES_POLL_TIME; bgp_addpath_init_bgp_data(&bgp->tx_addpath); - + bgp->fast_convergence = false; bgp->as = *as; #ifdef ENABLE_BGP_VNC diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h index 62782f6040..dc8677d3a1 100644 --- a/bgpd/bgpd.h +++ b/bgpd/bgpd.h @@ -749,6 +749,8 @@ struct bgp { /* Process Queue for handling routes */ struct work_queue *process_queue; + bool fast_convergence; + /* BGP Conditional advertisement */ uint32_t condition_check_period; uint32_t condition_filter_count; diff --git a/doc/user/bgp.rst b/doc/user/bgp.rst index ad9e32853d..2383906150 100644 --- a/doc/user/bgp.rst +++ b/doc/user/bgp.rst @@ -4354,3 +4354,35 @@ Show command json output: .. [bgp-route-osci-cond] McPherson, D. and Gill, V. and Walton, D., "Border Gateway Protocol (BGP) Persistent Route Oscillation Condition", IETF RFC3345 .. [stable-flexible-ibgp] Flavel, A. and M. Roughan, "Stable and flexible iBGP", ACM SIGCOMM 2009 .. [ibgp-correctness] Griffin, T. and G. Wilfong, "On the correctness of IBGP configuration", ACM SIGCOMM 2002 + +.. _bgp-fast-convergence: + +BGP fast-convergence support +============================ +Whenever BGP peer address becomes unreachable we must bring down the BGP +session immediately. Currently only single-hop EBGP sessions are brought +down immediately.IBGP and multi-hop EBGP sessions wait for hold-timer +expiry to bring down the sessions. + +This new configuration option helps user to teardown BGP sessions immediately +whenever peer becomes unreachable. + +.. clicmd:: bgp fast-convergence + +This configuration is available at the bgp level. When enabled, configuration +is applied to all the neighbors configured in that bgp instance. + +.. code-block:: frr + + router bgp 64496 + neighbor 10.0.0.2 remote-as 64496 + neighbor fd00::2 remote-as 64496 + bgp fast-convergence + ! + address-family ipv4 unicast + redistribute static + exit-address-family + ! + address-family ipv6 unicast + neighbor fd00::2 activate + exit-address-family diff --git a/tests/topotests/bgp_ecmp_topo3/ibgp_ecmp_topo3.json b/tests/topotests/bgp_ecmp_topo3/ibgp_ecmp_topo3.json new file mode 100644 index 0000000000..b01f9023b0 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo3/ibgp_ecmp_topo3.json @@ -0,0 +1,232 @@ +{ + "address_types": [ + "ipv4", + "ipv6" + ], + "ipv4base": "10.0.0.0", + "ipv4mask": 24, + "ipv6base": "fd00::", + "ipv6mask": 64, + "link_ip_start": { + "ipv4": "10.0.0.0", + "v4mask": 24, + "ipv6": "fd00::", + "v6mask": 64 + }, + "lo_prefix": { + "ipv4": "1.0.", + "v4mask": 32, + "ipv6": "2001:DB8:F::", + "v6mask": 128 + }, + "routers": { + "r1": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r2": { + "dest_link": { + "r1-link1": {} + } + } + } + } + } + } + } + }, + "r2": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r1-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r3-link2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + }, + "r2-link2": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + }, + "ipv6": { + "unicast": { + "neighbor": { + "r1": { + "dest_link": { + "r2-link1": {} + } + }, + "r3": { + "dest_link": { + "r2-link1": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + }, + "r2-link2": { + "keepalivetimer": 60, + "holddowntimer": 180, + "next_hop_self": true + } + } + } + }, + "redistribute": [ + { + "redist_type": "static" + } + ] + } + } + } + } + }, + "r3": { + "links": { + "lo": { + "ipv4": "auto", + "ipv6": "auto", + "type": "loopback" + }, + "r2-link1": { + "ipv4": "auto", + "ipv6": "auto" + }, + "r2-link2": { + "ipv4": "auto", + "ipv6": "auto" + } + }, + "route_maps": { + "rmap_global": [{ + "action": "permit", + "set": { + "ipv6": { + "nexthop": "prefer-global" + } + } + }] + }, + "bgp": { + "local_as": "100", + "address_family": { + "ipv4": { + "unicast": { + "maximum_paths": { + "ibgp": 2 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 60, + "holddowntimer": 180 + }, + "r3-link2": { + "keepalivetimer": 60, + "holddowntimer": 180 + } + } + } + } + } + }, + "ipv6": { + "unicast": { + "maximum_paths": { + "ibgp": 2 + }, + "neighbor": { + "r2": { + "dest_link": { + "r3-link1": { + "keepalivetimer": 60, + "holddowntimer": 180, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + }, + "r3-link2": { + "keepalivetimer": 60, + "holddowntimer": 180, + "route_maps": [{ + "name": "rmap_global", + "direction": "in" + }] + } + } + } + } + } + } + } + } + } + } +} diff --git a/tests/topotests/bgp_ecmp_topo3/test_ibgp_ecmp_topo3.py b/tests/topotests/bgp_ecmp_topo3/test_ibgp_ecmp_topo3.py new file mode 100644 index 0000000000..5f3ac4e716 --- /dev/null +++ b/tests/topotests/bgp_ecmp_topo3/test_ibgp_ecmp_topo3.py @@ -0,0 +1,310 @@ +#!/usr/bin/env python + +# +# Copyright (c) 2019 by VMware, Inc. ("VMware") +# Used Copyright (c) 2018 by Network Device Education Foundation, Inc. +# ("NetDEF") in this file. +# +# 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 VMWARE DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL VMWARE 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. +# + + +""" +Following tests are covered to test ecmp functionality on iBGP. +1. Verify bgp fast-convergence functionality +""" +import os +import sys +import time +import json +import pytest +from time import sleep + +# Save the Current Working Directory to find configuration files. +CWD = os.path.dirname(os.path.realpath(__file__)) +sys.path.append(os.path.join(CWD, "../")) +sys.path.append(os.path.join(CWD, "../../")) + +# pylint: disable=C0413 +# Import topogen and topotest helpers +from lib.topogen import Topogen, get_topogen +from mininet.topo import Topo + +from lib.common_config import ( + start_topology, + write_test_header, + write_test_footer, + verify_rib, + create_static_routes, + check_address_types, + interface_status, + reset_config_on_routers, + required_linux_kernel_version, + shutdown_bringup_interface, + apply_raw_config, +) +from lib.topolog import logger +from lib.bgp import verify_bgp_convergence, create_router_bgp, clear_bgp +from lib.topojson import build_topo_from_json, build_config_from_json + + +pytestmark = [pytest.mark.bgpd, pytest.mark.staticd] + + +# Reading the data from JSON File for topology and configuration creation +jsonFile = "{}/ibgp_ecmp_topo3.json".format(CWD) + +try: + with open(jsonFile, "r") as topoJson: + topo = json.load(topoJson) +except IOError: + assert False, "Could not read file {}".format(jsonFile) + +# Global variables +NEXT_HOPS = {"ipv4": [], "ipv6": []} +NETWORK = {"ipv4": "192.168.1.10/32", "ipv6": "fd00:0:0:1::10/128"} +NEXT_HOP_IP = {"ipv4": "10.0.0.1", "ipv6": "fd00::1"} +BGP_CONVERGENCE = False + + +class CreateTopo(Topo): + """ + Test topology builder. + + * `Topo`: Topology object + """ + + def build(self, *_args, **_opts): + """Build function.""" + tgen = get_topogen(self) + + # Building topology from json file + build_topo_from_json(tgen, topo) + + +def setup_module(mod): + """ + Sets up the pytest environment. + + * `mod`: module name + """ + global NEXT_HOPS, INTF_LIST_R3, INTF_LIST_R2, TEST_STATIC + global ADDR_TYPES + + testsuite_run_time = time.asctime(time.localtime(time.time())) + logger.info("Testsuite start time: {}".format(testsuite_run_time)) + logger.info("=" * 40) + + logger.info("Running setup_module to create topology") + + # This function initiates the topology build with Topogen... + tgen = Topogen(CreateTopo, mod.__name__) + + # Starting topology, create tmp files which are loaded to routers + # to start deamons and then start routers + start_topology(tgen) + + # Creating configuration from JSON + build_config_from_json(tgen, topo) + + # Don't run this test if we have any failure. + if tgen.routers_have_failure(): + pytest.skip(tgen.errors) + + # Api call verify whether BGP is converged + ADDR_TYPES = check_address_types() + + BGP_CONVERGENCE = verify_bgp_convergence(tgen, topo) + assert BGP_CONVERGENCE is True, "setup_module :Failed \n Error:" " {}".format( + BGP_CONVERGENCE + ) + + # STATIC_ROUTE = True + logger.info("Running setup_module() done") + + +def teardown_module(): + """ + Teardown the pytest environment. + + * `mod`: module name + """ + + logger.info("Running teardown_module to delete topology") + + tgen = get_topogen() + + # Stop toplogy and Remove tmp files + tgen.stop_topology() + + +def static_or_nw(tgen, topo, tc_name, test_type, dut): + + if test_type == "redist_static": + input_dict_static = { + dut: { + "static_routes": [ + {"network": NETWORK["ipv4"], "next_hop": NEXT_HOP_IP["ipv4"]}, + {"network": NETWORK["ipv6"], "next_hop": NEXT_HOP_IP["ipv6"]}, + ] + } + } + logger.info("Configuring static route on router %s", dut) + result = create_static_routes(tgen, input_dict_static) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + input_dict_2 = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + "ipv6": { + "unicast": {"redistribute": [{"redist_type": "static"}]} + }, + } + } + } + } + + logger.info("Configuring redistribute static route on router %s", dut) + result = create_router_bgp(tgen, topo, input_dict_2) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + elif test_type == "advertise_nw": + input_dict_nw = { + dut: { + "bgp": { + "address_family": { + "ipv4": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv4"]}] + } + }, + "ipv6": { + "unicast": { + "advertise_networks": [{"network": NETWORK["ipv6"]}] + } + }, + } + } + } + } + + logger.info( + "Advertising networks %s %s from router %s", + NETWORK["ipv4"], + NETWORK["ipv6"], + dut, + ) + result = create_router_bgp(tgen, topo, input_dict_nw) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + +@pytest.mark.parametrize("test_type", ["redist_static"]) +def test_ecmp_fast_convergence(request, test_type): + """This test is to verify bgp fast-convergence cli functionality""" + + tc_name = request.node.name + write_test_header(tc_name) + tgen = get_topogen() + + # Verifying RIB routes + dut = "r3" + protocol = "bgp" + + reset_config_on_routers(tgen) + static_or_nw(tgen, topo, tc_name, test_type, "r2") + + for addr_type in ADDR_TYPES: + input_dict = {"r3": {"static_routes": [{"network": NETWORK[addr_type]}]}} + + logger.info("Verifying %s routes on r3", addr_type) + result = verify_rib( + tgen, + addr_type, + dut, + input_dict, + protocol=protocol, + ) + assert result is True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + intf1 = topo["routers"]["r2"]["links"]["r3-link1"]["interface"] + intf2 = topo["routers"]["r2"]["links"]["r3-link2"]["interface"] + + logger.info("Shutdown one of the link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf1, False) + + logger.info("Verify bgp neighbors are still up") + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info("Shutdown another link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf2, False) + + logger.info("Wait for 10 sec and make sure bgp neighbors are still up") + sleep(10) + result = verify_bgp_convergence(tgen, topo) + assert result is True, "Testcase {} : Failed \n Error: {}".format(tc_name, result) + + logger.info("No shut links b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf1, True) + shutdown_bringup_interface(tgen, "r2", intf2, True) + + logger.info("Enable bgp fast-convergence cli") + raw_config = { + "r2": { + "raw_config": [ + "router bgp {}".format(topo["routers"]["r2"]["bgp"]["local_as"]), + "bgp fast-convergence", + ] + } + } + result = apply_raw_config(tgen, raw_config) + assert result is True, "Testcase {} : Failed Error: {}".format(tc_name, result) + + logger.info("Shutdown one link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf1, False) + + logger.info("Verify bgp neighbors goes down immediately") + result = verify_bgp_convergence(tgen, topo, dut="r2", expected=False) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + logger.info("Shutdown second link b/w r2 and r3") + shutdown_bringup_interface(tgen, "r2", intf2, False) + + logger.info("Verify bgp neighbors goes down immediately") + result = verify_bgp_convergence(tgen, topo, dut="r2", expected=False) + assert result is not True, "Testcase {} : Failed \n Error: {}".format( + tc_name, result + ) + + write_test_footer(tc_name) + + +if __name__ == "__main__": + args = ["-s"] + sys.argv[1:] + sys.exit(pytest.main(args))