Skip to content
239 changes: 171 additions & 68 deletions tests/bgp/test_bgpmon.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,13 @@
import ptf.packet as scapy
from ptf.mask import Mask
import json
from tests.common.fixtures.ptfhost_utils import change_mac_addresses # noqa:F401
from tests.common.fixtures.ptfhost_utils import remove_ip_addresses # noqa:F401
from tests.common.helpers.generators import generate_ip_through_default_route
from tests.common.fixtures.ptfhost_utils import change_mac_addresses # noqa F401
from tests.common.fixtures.ptfhost_utils import remove_ip_addresses # noqa F401
from tests.common.helpers.generators import generate_ip_through_default_route, generate_ip_through_default_v6_route
from tests.common.helpers.assertions import pytest_assert
from tests.common.utilities import wait_until
from tests.common.utilities import wait_tcp_connection
from tests.common.utilities import is_ipv6_only_topology
from bgp_helpers import BGPMON_TEMPLATE_FILE, BGPMON_CONFIG_FILE, BGP_MONITOR_NAME, BGP_MONITOR_PORT

pytestmark = [
Expand All @@ -22,12 +23,14 @@
BGP_CONNECT_TIMEOUT = 121
MAX_TIME_FOR_BGPMON = 180
ZERO_ADDR = r'0.0.0.0/0'
ZERO_ADDR_V6 = r'::/0'
logger = logging.getLogger(__name__)


def get_default_route_ports(host, tbinfo, default_addr=ZERO_ADDR):
def get_default_route_ports(host, tbinfo, default_addr=ZERO_ADDR, is_ipv6=False):
mg_facts = host.get_extended_minigraph_facts(tbinfo)
route_info = json.loads(host.shell("show ip route {} json".format(default_addr))['stdout'])
ip_cmd = "ipv6" if is_ipv6 else "ip"
route_info = json.loads(host.shell("show {} route {} json".format(ip_cmd, default_addr))['stdout'])
ports = []
for route in route_info[default_addr]:
if route['protocol'] != 'bgp':
Expand All @@ -47,14 +50,41 @@ def get_default_route_ports(host, tbinfo, default_addr=ZERO_ADDR):


@pytest.fixture
def common_setup_teardown(dut_with_default_route, tbinfo):
def common_setup_teardown(dut_with_default_route, tbinfo):
duthost = dut_with_default_route
peer_addr = generate_ip_through_default_route(duthost)
pytest_assert(peer_addr, "Failed to generate ip address for test")
peer_addr = str(IPNetwork(peer_addr).ip)
peer_ports = get_default_route_ports(duthost, tbinfo)
is_ipv6_only = is_ipv6_only_topology(tbinfo)

# Generate a unique IPV4 address to be used as the BGP router identifier for the monitor connection
router_id = generate_ip_through_default_route(duthost)
pytest_assert(router_id, "Failed to generate router id")
router_id = str(IPNetwork(router_id).ip)

if is_ipv6_only:
peer_addr = generate_ip_through_default_v6_route(duthost)
pytest_assert(peer_addr, "Failed to generate ipv6 address for test")
peer_addr = str(IPNetwork(peer_addr).ip)
else:
peer_addr = router_id

peer_ports = get_default_route_ports(
duthost,
tbinfo,
default_addr=ZERO_ADDR_V6 if is_ipv6_only else ZERO_ADDR,
is_ipv6=is_ipv6_only
)
mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts']
local_addr = mg_facts['minigraph_lo_interfaces'][0]['addr']

local_addr = None
for lo_intf in mg_facts['minigraph_lo_interfaces']:
if is_ipv6_only and ':' in lo_intf['addr']:
local_addr = lo_intf['addr']
break
elif not is_ipv6_only and ':' not in lo_intf['addr']:
local_addr = lo_intf['addr']
break

pytest_assert(local_addr, "Failed to get appropriate loopback address")

# Assign peer addr to an interface on ptf
logger.info("Generated peer address {}".format(peer_addr))
bgpmon_args = {
Expand All @@ -67,35 +97,55 @@ def common_setup_teardown(dut_with_default_route, tbinfo):
bgpmon_template = Template(open(BGPMON_TEMPLATE_FILE).read())
duthost.copy(content=bgpmon_template.render(**bgpmon_args),
dest=BGPMON_CONFIG_FILE)
yield local_addr, peer_addr, peer_ports, mg_facts['minigraph_bgp_asn']
yield local_addr, peer_addr, peer_ports, mg_facts['minigraph_bgp_asn'], is_ipv6_only, router_id
# Cleanup bgp monitor
duthost.run_sonic_db_cli_cmd("CONFIG_DB del 'BGP_MONITORS|{}'".format(peer_addr), asic_index='all')
duthost.file(path=BGPMON_CONFIG_FILE, state='absent')


def build_syn_pkt(local_addr, peer_addr):
pkt = testutils.simple_tcp_packet(
pktlen=54,
ip_src=local_addr,
ip_dst=peer_addr,
tcp_dport=BGP_PORT,
tcp_flags="S"
)
exp_packet = Mask(pkt)
exp_packet.set_do_not_care_scapy(scapy.Ether, "dst")
exp_packet.set_do_not_care_scapy(scapy.Ether, "src")

exp_packet.set_do_not_care_scapy(scapy.IP, "version")
exp_packet.set_do_not_care_scapy(scapy.IP, "ihl")
exp_packet.set_do_not_care_scapy(scapy.IP, "tos")
exp_packet.set_do_not_care_scapy(scapy.IP, "len")
exp_packet.set_do_not_care_scapy(scapy.IP, "flags")
exp_packet.set_do_not_care_scapy(scapy.IP, "id")
exp_packet.set_do_not_care_scapy(scapy.IP, "frag")
exp_packet.set_do_not_care_scapy(scapy.IP, "ttl")
exp_packet.set_do_not_care_scapy(scapy.IP, "chksum")
exp_packet.set_do_not_care_scapy(scapy.IP, "options")
def build_syn_pkt(local_addr, peer_addr, is_ipv6=False):
if is_ipv6:
pkt = testutils.simple_tcpv6_packet(
pktlen=74,
ipv6_src=local_addr,
ipv6_dst=peer_addr,
tcp_dport=BGP_PORT,
tcp_flags="S"
)
exp_packet = Mask(pkt)
exp_packet.set_do_not_care_scapy(scapy.Ether, "dst")
exp_packet.set_do_not_care_scapy(scapy.Ether, "src")

exp_packet.set_do_not_care_scapy(scapy.IPv6, "version")
exp_packet.set_do_not_care_scapy(scapy.IPv6, "tc")
exp_packet.set_do_not_care_scapy(scapy.IPv6, "fl")
exp_packet.set_do_not_care_scapy(scapy.IPv6, "plen")
exp_packet.set_do_not_care_scapy(scapy.IPv6, "nh")
exp_packet.set_do_not_care_scapy(scapy.IPv6, "hlim")
else:
pkt = testutils.simple_tcp_packet(
pktlen=54,
ip_src=local_addr,
ip_dst=peer_addr,
tcp_dport=BGP_PORT,
tcp_flags="S"
)
exp_packet = Mask(pkt)
exp_packet.set_do_not_care_scapy(scapy.Ether, "dst")
exp_packet.set_do_not_care_scapy(scapy.Ether, "src")

exp_packet.set_do_not_care_scapy(scapy.IP, "version")
exp_packet.set_do_not_care_scapy(scapy.IP, "ihl")
exp_packet.set_do_not_care_scapy(scapy.IP, "tos")
exp_packet.set_do_not_care_scapy(scapy.IP, "len")
exp_packet.set_do_not_care_scapy(scapy.IP, "id")
exp_packet.set_do_not_care_scapy(scapy.IP, "flags")
exp_packet.set_do_not_care_scapy(scapy.IP, "frag")
exp_packet.set_do_not_care_scapy(scapy.IP, "ttl")
exp_packet.set_do_not_care_scapy(scapy.IP, "chksum")
exp_packet.set_do_not_care_scapy(scapy.IP, "options")

# TCP fields (common for both IPv4 and IPv6)
exp_packet.set_do_not_care_scapy(scapy.TCP, "sport")
exp_packet.set_do_not_care_scapy(scapy.TCP, "seq")
exp_packet.set_do_not_care_scapy(scapy.TCP, "ack")
Expand All @@ -120,6 +170,13 @@ def test_resolve_via_default_exist(duthost):
"ipv6 nht resolve-via-default not present in global FRR config")


def configure_ipv6_bgpmon_update_source(duthost, asn, local_addr):
duthost.run_vtysh(
"-c 'configure terminal' -c 'router bgp {}' -c 'neighbor BGPMON update-source {}'".format(asn, local_addr),
asic_index='all'
)


def test_bgpmon(dut_with_default_route, localhost, enum_rand_one_frontend_asic_index,
common_setup_teardown, set_timeout_for_bgpmon, ptfadapter, ptfhost):
"""
Expand All @@ -128,77 +185,123 @@ def test_bgpmon(dut_with_default_route, localhost, enum_rand_one_frontend_asic_i
duthost = dut_with_default_route
asichost = duthost.asic_instance(enum_rand_one_frontend_asic_index)

def bgpmon_peer_connected(duthost, bgpmon_peer):
def bgpmon_peer_connected(duthost, bgpmon_peer, is_ipv6):
try:
bgp_summary = json.loads(asichost.run_vtysh("-c 'show bgp summary json'")['stdout'])
return bgp_summary['ipv4Unicast']['peers'][bgpmon_peer]["state"] == "Established"
af_key = 'ipv6Unicast' if is_ipv6 else 'ipv4Unicast'
return bgp_summary[af_key]['peers'][bgpmon_peer]["state"] == "Established"
except Exception:
logger.info('Unable to get bgp status')
return False

local_addr, peer_addr, peer_ports, asn = common_setup_teardown
exp_packet = build_syn_pkt(local_addr, peer_addr)
local_addr, peer_addr, peer_ports, asn, is_ipv6_only, router_id = common_setup_teardown
exp_packet = build_syn_pkt(local_addr, peer_addr, is_ipv6=is_ipv6_only)
# Flush dataplane
ptfadapter.dataplane.flush()
# Load bgp monitor config
logger.info("Configured bgpmon and verifying packet on {}".format(peer_ports))
asichost.write_to_config_db(BGPMON_CONFIG_FILE)
if is_ipv6_only:
configure_ipv6_bgpmon_update_source(duthost, asn, local_addr)
# Verify syn packet on ptf
(rcvd_port_index, rcvd_pkt) = testutils.verify_packet_any_port(test=ptfadapter, pkt=exp_packet,
ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT)
(rcvd_port_index, rcvd_pkt) = testutils.verify_packet_any_port(
test=ptfadapter, pkt=exp_packet, ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT
)
# ip as BGMPMON IP , mac as the neighbor mac(mac for default nexthop that was used for sending syn packet) ,
# add the neighbor entry and the default route for dut loopback
ptf_interface = "eth" + str(peer_ports[rcvd_port_index])
res = ptfhost.shell('cat /sys/class/net/{}/address'.format(ptf_interface))
original_mac = res['stdout']
ptfhost.shell("ifconfig %s hw ether %s" % (ptf_interface, scapy.Ether(rcvd_pkt).dst))
ptfhost.shell("ip add add %s dev %s" % (peer_addr + "/24", ptf_interface))
ptfhost.exabgp(name=BGP_MONITOR_NAME,
state="started",
local_ip=peer_addr,
router_id=peer_addr,
peer_ip=local_addr,
local_asn=asn,
peer_asn=asn,
port=BGP_MONITOR_PORT, passive=True)
ptfhost.shell("ip neigh add %s lladdr %s dev %s" % (local_addr, duthost.facts["router_mac"], ptf_interface))
ptfhost.shell("ip route replace %s dev %s" % (local_addr + "/32", ptf_interface))

ip_cmd = "-6" if is_ipv6_only else ""
prefix_len = "/64" if is_ipv6_only else "/24"
route_prefix_len = "/128" if is_ipv6_only else "/32"

ptfhost.shell("ip %s addr add %s dev %s" % (ip_cmd, peer_addr + prefix_len, ptf_interface))
ptfhost.exabgp(
name=BGP_MONITOR_NAME,
state="started",
local_ip=peer_addr,
router_id=router_id,
peer_ip=local_addr,
local_asn=asn,
peer_asn=asn,
port=BGP_MONITOR_PORT,
passive=True
)
ptfhost.shell(
"ip %s neigh add %s lladdr %s dev %s"
% (ip_cmd, local_addr, duthost.facts["router_mac"], ptf_interface)
)
ptfhost.shell(
"ip %s route replace %s dev %s"
% (ip_cmd, local_addr + route_prefix_len, ptf_interface)
)
try:
pytest_assert(wait_tcp_connection(localhost, ptfhost.mgmt_ip, BGP_MONITOR_PORT, timeout_s=60),
"Failed to start bgp monitor session on PTF")
pytest_assert(wait_until(MAX_TIME_FOR_BGPMON, 5, 0, bgpmon_peer_connected, duthost, peer_addr),
"BGPMon Peer connection not established")
pytest_assert(
wait_tcp_connection(localhost, ptfhost.mgmt_ip, BGP_MONITOR_PORT, timeout_s=60),
"Failed to start bgp monitor session on PTF"
)
pytest_assert(
wait_until(
MAX_TIME_FOR_BGPMON, 5, 0, bgpmon_peer_connected, duthost, peer_addr, is_ipv6_only
),
"BGPMon Peer connection not established"
)
finally:
ptfhost.exabgp(name=BGP_MONITOR_NAME, state="absent")
ptfhost.shell("ip route del %s dev %s" % (local_addr + "/32", ptf_interface))
ptfhost.shell("ip neigh del %s lladdr %s dev %s" % (local_addr, duthost.facts["router_mac"], ptf_interface))
ptfhost.shell("ip add del %s dev %s" % (peer_addr + "/24", ptf_interface))
ptfhost.shell(
"ip %s route del %s dev %s"
% (ip_cmd, local_addr + route_prefix_len, ptf_interface)
)
ptfhost.shell(
"ip %s neigh del %s lladdr %s dev %s"
% (ip_cmd, local_addr, duthost.facts["router_mac"], ptf_interface)
)
ptfhost.shell(
"ip %s addr del %s dev %s"
% (ip_cmd, peer_addr + prefix_len, ptf_interface)
)
ptfhost.shell("ifconfig %s hw ether %s" % (ptf_interface, original_mac))


def test_bgpmon_no_resolve_via_default(dut_with_default_route, enum_rand_one_frontend_asic_index,
common_setup_teardown, ptfadapter):
"""
Verify no syn for BGP is sent when 'ip nht resolve-via-default' is disabled.
Verify no syn for BGP is sent when 'ip nht resolve-via-default' or 'ipv6 nht resolve-via-default' is disabled.
"""
duthost = dut_with_default_route
asichost = duthost.asic_instance(enum_rand_one_frontend_asic_index)
local_addr, peer_addr, peer_ports, asn = common_setup_teardown
exp_packet = build_syn_pkt(local_addr, peer_addr)
local_addr, peer_addr, peer_ports, asn, is_ipv6_only, router_id = common_setup_teardown
exp_packet = build_syn_pkt(local_addr, peer_addr, is_ipv6=is_ipv6_only)

ip_cmd = "ipv6" if is_ipv6_only else "ip"

# Load bgp monitor config
logger.info("Configured bgpmon and verifying no packet on {} when resolve-via-default is disabled"
.format(peer_ports))
logger.info(
"Configured bgpmon and verifying no packet on {} when resolve-via-default is disabled".format(peer_ports)
)
try:
# Disable resolve-via-default
duthost.run_vtysh(" -c \"configure terminal\" -c \"no ip nht resolve-via-default\"", asic_index='all')
duthost.run_vtysh(
" -c \"configure terminal\" -c \"no {} nht resolve-via-default\"".format(ip_cmd),
asic_index='all'
)
# Flush dataplane
ptfadapter.dataplane.flush()
asichost.write_to_config_db(BGPMON_CONFIG_FILE)

# Verify no syn packet is received
pytest_assert(0 == testutils.count_matched_packets_all_ports(test=ptfadapter, exp_packet=exp_packet,
ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT),
"Syn packets is captured when resolve-via-default is disabled")
pytest_assert(
0 == testutils.count_matched_packets_all_ports(
test=ptfadapter, exp_packet=exp_packet, ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT
),
"Syn packets is captured when resolve-via-default is disabled"
)
finally:
# Re-enable resolve-via-default
duthost.run_vtysh("-c \"configure terminal\" -c \"ip nht resolve-via-default\"", asic_index='all')
duthost.run_vtysh(
"-c \"configure terminal\" -c \"{} nht resolve-via-default\"".format(ip_cmd),
asic_index='all'
)
Original file line number Diff line number Diff line change
Expand Up @@ -490,11 +490,9 @@ bgp/test_bgp_update_timer.py::test_bgp_update_timer_single_route:

bgp/test_bgpmon.py:
skip:
reason: "Not supported on T2 topology or topology backend
or Skip for IPv6-only topologies, since there are v6 verison of the test"
reason: "Not supported on T2 topology or topology backend"
conditions:
- "'backend' in topo_name or topo_type in ['t2']"
- "'-v6-' in topo_name"

bgp/test_bgpmon_v6.py::test_bgpmon_no_ipv6_resolve_via_default:
skip:
Expand Down
Loading