Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions tests/bgp/templates/bgpmon.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"BGP_MONITORS": {
"{{ peer_addr }}": {
"admin_status": "up",
"asn": "{{ asn }}",
"holdtime": "10",
"keepalive": "3",
"local_addr": "{{ local_addr }}",
"name": "{{ peer_name }}",
"nhopself": "0",
"rrclient": "0"
}
}
}

153 changes: 153 additions & 0 deletions tests/bgp/test_bgpmon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import pytest
import logging
from netaddr import IPNetwork
import ptf.testutils as testutils
from jinja2 import Template
import ptf.packet as scapy
from ptf.mask import Mask
import json
from tests.common.fixtures.ptfhost_utils import change_mac_addresses # lgtm[py/unused-import]
from tests.common.fixtures.ptfhost_utils import remove_ip_addresses # lgtm[py/unused-import]
from tests.common.helpers.generators import generate_ips as generate_ips
from tests.common.helpers.assertions import pytest_assert

pytestmark = [
pytest.mark.topology('any'),
]

BGPMON_TEMPLATE_FILE = 'bgp/templates/bgpmon.j2'
BGPMON_CONFIG_FILE = '/tmp/bgpmon.json'
BGP_PORT = 179
BGP_CONNECT_TIMEOUT = 121
ZERO_ADDR = r'0.0.0.0/0'

logger = logging.getLogger(__name__)

def route_through_default_routes(host, ip_addr):
output = host.shell("show ip route {} json".format(ip_addr))['stdout']
routes_info = json.loads(output)
ret = True
for prefix in routes_info.keys():
if prefix != ZERO_ADDR:
ret = False
break
return ret

def generate_ip_through_default_route(host):
# Generate an IP address routed through default routes
for leading in range(11, 255):
ip_addr = generate_ips(1, "{}.0.0.1/24".format(leading), [])[0]
if route_through_default_routes(host, ip_addr):
return ip_addr
return None

def get_default_route_ports(host):
mg_facts = host.minigraph_facts(host=host.hostname)['ansible_facts']
route_info = json.loads(host.shell("show ip route {} json".format(ZERO_ADDR))['stdout'])
ports = []
for route in route_info[ZERO_ADDR]:
if route['protocol'] != 'bgp':
continue
for itfs in route['nexthops']:
ports.append(itfs['interfaceName'])
port_indices = []
for port in ports:
if 'PortChannel' in port:
for member in mg_facts['minigraph_portchannels'][port]['members']:
port_indices.append(mg_facts['minigraph_port_indices'][member])
else:
port_indices.append(mg_facts['minigraph_port_indices'][port])
return port_indices

@pytest.fixture
def common_setup_teardown(duthost, ptfhost):
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)
mg_facts = duthost.minigraph_facts(host=duthost.hostname)['ansible_facts']
local_addr = mg_facts['minigraph_lo_interfaces'][0]['addr']
# Assign peer addr to an interface on ptf
logger.info("Generated peer address {}".format(peer_addr))
bgpmon_args = {
'peer_addr': peer_addr,
'asn': mg_facts['minigraph_bgp_asn'],
'local_addr': local_addr,
'peer_name': 'bgp_monitor'
}
bgpmon_template = Template(open(BGPMON_TEMPLATE_FILE).read())
Copy link
Collaborator

@lolyu lolyu Nov 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better to use Ansible module template to do this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to add those extra vars to a AnsibleHost such as ptfhost or duthost if we want to use template in Ansible module, right?

duthost.copy(content=bgpmon_template.render(**bgpmon_args),
dest=BGPMON_CONFIG_FILE)
yield local_addr, peer_addr, peer_ports
# Cleanup bgp monitor
duthost.shell("redis-cli -n 4 -c DEL 'BGP_MONITORS|{}'".format(peer_addr))
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")

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")
exp_packet.set_do_not_care_scapy(scapy.TCP, "reserved")
exp_packet.set_do_not_care_scapy(scapy.TCP, "dataofs")
exp_packet.set_do_not_care_scapy(scapy.TCP, "window")
exp_packet.set_do_not_care_scapy(scapy.TCP, "chksum")
exp_packet.set_do_not_care_scapy(scapy.TCP, "urgptr")

exp_packet.set_ignore_extra_bytes()
return exp_packet

def test_bgpmon(duthost, common_setup_teardown, ptfadapter):
"""
Add a bgp monitor on ptf and verify that DUT is attempting to establish connection to it
"""
local_addr, peer_addr, peer_ports = common_setup_teardown
exp_packet = build_syn_pkt(local_addr, peer_addr)
# Load bgp monitor config
logger.info("Configured bgpmon and verifying packet on {}".format(peer_ports))
duthost.command("sonic-cfggen -j {} -w".format(BGPMON_CONFIG_FILE))
# Verify syn packet on ptf
testutils.verify_packet_any_port(test=ptfadapter, pkt=exp_packet, ports=peer_ports, timeout=BGP_CONNECT_TIMEOUT)

def test_bgpmon_no_resolve_via_default(duthost, common_setup_teardown, ptfadapter):
"""
Verify no syn for BGP is sent when 'ip nht resolve-via-default' is disabled.
"""
local_addr, peer_addr, peer_ports = common_setup_teardown
exp_packet = build_syn_pkt(local_addr, peer_addr)
# Load bgp monitor config
logger.info("Configured bgpmon and verifying no packet on {} when resolve-via-default is disabled".format(peer_ports))
try:
# Disable resolve-via-default
duthost.command("vtysh -c \"configure terminal\" \
-c \"no ip nht resolve-via-default\"")
duthost.command("sonic-cfggen -j {} -w".format(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")
finally:
# Re-enable resolve-via-default
duthost.command("vtysh -c \"configure terminal\" \
-c \"ip nht resolve-via-default\"")

3 changes: 2 additions & 1 deletion tests/kvmtest.sh
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ telemetry/test_telemetry.py \
test_features.py \
test_procdockerstatsd.py \
iface_namingmode/test_iface_namingmode.py \
platform_tests/test_cpu_memory_usage.py"
platform_tests/test_cpu_memory_usage.py \
bgp/test_bgpmon.py"

# FIXME: The lldp test has been temporarily disabled for https://github.com/Azure/sonic-mgmt/pull/2413
# and https://github.com/Azure/sonic-buildimage/pull/5698. The reason is that these two PRs dependent on each other.
Expand Down