Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bfd test multihop #6032

Merged
merged 5 commits into from
Sep 8, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
184 changes: 184 additions & 0 deletions ansible/roles/test/files/helpers/bfd_responder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
import binascii
import socket
import struct
import select
import json
import argparse
import os.path
from fcntl import ioctl
import logging
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
import scapy.all as scapy2
scapy2.conf.use_pcap=True
from scapy.contrib.bfd import BFD
def hexdump(data):
print (" ".join("%02x" % ord(d) for d in data))
Copy link
Collaborator

Choose a reason for hiding this comment

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

This print is not python3 compatible.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed as this code wasn't running.


def get_if(iff, cmd):
s = socket.socket()
ifreq = ioctl(s, cmd, struct.pack("16s16x",iff))
s.close()

return ifreq

def get_mac(iff):
SIOCGIFHWADDR = 0x8927 # Get hardware address
return get_if(iff, SIOCGIFHWADDR)[18:24]


class Interface(object):

def __init__(self, iface):
self.iface = iface
self.socket = None
self.mac_address = get_mac(iface)

def __del__(self):
if self.socket:
self.socket.close()

def bind(self):
self.socket = scapy2.conf.L2listen(iface=self.iface, filter="udp port 4784")

def handler(self):
return self.socket

def recv(self):
sniffed = self.socket.recv()
pkt = sniffed[0]
str_pkt = str(pkt).encode("HEX")
binpkt = binascii.unhexlify(str_pkt)
return binpkt

def send(self, data):
scapy2.sendp(data, iface=self.iface)

def mac(self):
return self.mac_address

def name(self):
return self.iface


class Poller(object):
def __init__(self, interfaces, responder):
self.responder = responder
self.mapping = {}
for interface in interfaces:
self.mapping[interface.handler()] = interface

def poll(self):
handlers = self.mapping.keys()
while True:
(rdlist, _, _) = select.select(handlers, [], [])
for handler in rdlist:
self.responder.action(self.mapping[handler])


class BFDResponder(object):
def __init__(self, sessions):
self.sessions = sessions
return

def action(self, interface):
data = interface.recv()
mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state = self.extract_bfd_info(data)
if ip_dst not in self.sessions:
return
session = self.sessions[ip_dst]

if bfd_state== 3L:
interface.send(session["pkt"])
return

if bfd_state == 2L:
return
session = self.sessions[ip_dst]
session["other_disc"] = bfd_remote_disc
bfd_pkt_init = self.craft_bfd_packet( session, data, mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, 2L )
print "sending INIT", bfd_pkt_init
Copy link
Collaborator

Choose a reason for hiding this comment

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

This print is incompatible with python3.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

interface.send(bfd_pkt_init)
bfd_pkt_init.payload.payload.payload.load.sta =3L
session["pkt"] = bfd_pkt_init
return

def extract_bfd_info(self, data):
# remote_mac, remote_ip, request_ip, op_type
ether = Ether(data)
mac_src= ether.src
mac_dst = ether.dst
ip_src = ether.payload.src
ip_dst = ether.payload.dst
bfdpkt = BFD(ether.payload.payload.payload.load)
bfd_remote_disc =bfdpkt.my_discriminator
bfd_state = bfdpkt.sta
return mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state

def craft_bfd_packet(self, session, data, mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state):
ethpart = Ether(data)
bfdpart = BFD(ethpart.payload.payload.payload.load)
bfdpart.my_discriminator = session["my_disc"]
bfdpart.your_discriminator = bfd_remote_disc
bfdpart.sta = bfd_state

ethpart.payload.payload.payload.load = bfdpart
ethpart.src = mac_dst
ethpart.dst = mac_src
ethpart.payload.src= ip_dst
ethpart.payload.dst= ip_src
return ethpart

def parse_args():
parser = argparse.ArgumentParser(description='ARP autoresponder')
parser.add_argument('--conf', '-c', type=str, dest='conf', default='/tmp/from_t1.json', help='path to json file with configuration')
args = parser.parse_args()

return args

def main():
args = parse_args()

if not os.path.exists(args.conf):
print "Can't find file %s" % args.conf
Copy link
Collaborator

Choose a reason for hiding this comment

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

Incompatible with python3.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

return

with open(args.conf) as fp:
data = json.load(fp)

# generate ip_sets. every ip address will have it's own uniq mac address
sessions = {}
local_disc_base = 0xcdba0000
local_src_port = 14000
ifaces = {}
for bfd in data:
curr_session = {}
curr_session["local"] = bfd["local_addr"]
curr_session["remote"] = bfd["neighbor_addr"]
curr_session["intf"] = bfd["ptf_intf"]
curr_session["multihop"] = bfd["multihop"]
curr_session["my_disc"] = local_disc_base
curr_session["other_disc"] = 0x00
curr_session["mac"] = get_mac( str( bfd["ptf_intf"]))
curr_session["src_port"] = local_src_port
curr_session["pkt"] = ""
if bfd["ptf_intf"] not in ifaces:
ifaces[curr_session["intf"]] = curr_session["mac"]

local_disc_base +=1
local_src_port +=1
sessions[curr_session["local"]] = curr_session
ifaceobjs =[]
for iface_name in ifaces.keys():
iface = Interface(str(iface_name))
iface.bind()
ifaceobjs.append(iface)

resp = BFDResponder(sessions)

p = Poller(ifaceobjs, resp)
p.poll()

return

if __name__ == '__main__':
main()
129 changes: 123 additions & 6 deletions tests/bfd/test_bfd.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,13 @@
pytest.mark.topology('t1', 't1-lag', 't1-64-lag')
Copy link
Collaborator

Choose a reason for hiding this comment

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

Just mark topology "t1" is enough. Here the mark is about topology type, not topology name.
Both "t1", "t1-lag" and "t1-64-lag" belong to topology type "t1".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed

]

BFD_RESPONDER_SCRIPT_SRC_PATH = '../ansible/roles/test/files/helpers/bfd_responder.py'
BFD_RESPONDER_SCRIPT_DEST_PATH = '/opt/bfd_responder.py'

def is_dualtor(tbinfo):
"""Check if the testbed is dualtor."""
return "dualtor" in tbinfo["topo"]["name"]


def get_t0_intfs(mg_facts):
t0_intfs = []
Expand Down Expand Up @@ -112,6 +119,47 @@ def get_neighbors_scale(duthost, tbinfo, ipv6=False, scale_count=1):
return local_addrs, prefix, neighbor_addrs, neighbor_devs, ptf_devs


def get_loopback_intf(mg_facts, ipv6):
ipv6idx = 0 if mg_facts['minigraph_lo_interfaces'][0]['prefixlen'] == 128 else 1
if ipv6:
return mg_facts['minigraph_lo_interfaces'][ipv6idx]['addr']
else:
return mg_facts['minigraph_lo_interfaces'][(ipv6idx+1) %2]['addr']

def get_neighbors_multihop(duthost, tbinfo, ipv6=False, count=1):
mg_facts = duthost.get_extended_minigraph_facts(tbinfo)
t0_ipv4_pattern = '4.{}.{}.1'
t0_ipv6_pattern = '3000:3000:{:x}::3000'
t0_intfs = get_t0_intfs(mg_facts)
ptf_ports = [mg_facts['minigraph_ptf_indices'][port] for port in t0_intfs]
loopback_addr = get_loopback_intf( mg_facts, ipv6 )

index = random.sample(list(range(len(t0_intfs))), k=1)[0]
port_intf = t0_intfs[index]
ptf_intf = ptf_ports[index]
nexthop_ip = ""
neighbour_dev_name = mg_facts['minigraph_neighbors'][port_intf]['name']
for bgpinfo in mg_facts['minigraph_bgp']:
if bgpinfo['name'] == neighbour_dev_name:
nexthop_ip = bgpinfo['addr']
if ipv6 and ":" not in nexthop_ip :
nexthop_ip = ""
continue
break
if nexthop_ip =="":
assert False
neighbor_addrs = []
idx2 =0
for idx in range(1, count):
if idx %250 ==0:
idx2 +=1
if ipv6:
neighbor_addrs.append(t0_ipv6_pattern.format(idx))
else:
neighbor_addrs.append(t0_ipv4_pattern.format((idx%250),idx2))

return loopback_addr, ptf_intf, nexthop_ip, neighbor_addrs

def init_ptf_bfd(ptfhost):
ptfhost.shell("bfdd-beacon")

Expand Down Expand Up @@ -146,7 +194,7 @@ def del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6=Fa
cmd_buffer += "ip addr del {}/{} dev eth{} ;".format(neighbor_addrs[idx], prefix_len,
neighbor_interfaces[idx])
if idx % 50 == 0:
ptfhost.shell(cmd_buffer)
ptfhost.shell(cmd_buffer, module_ignore_errors=True)
cmd_buffer = ""
if cmd_buffer != "":
ptfhost.shell(cmd_buffer, module_ignore_errors=True)
Expand Down Expand Up @@ -202,15 +250,57 @@ def create_bfd_sessions(ptfhost, duthost, local_addrs, neighbor_addrs, dut_init_
if dut_init_first:
ptfhost.shell(ptf_buffer)

def create_bfd_sessions_multihop(ptfhost, duthost, loopback_addr, ptf_intf, neighbor_addrs):
# Create a tempfile for BFD sessions
bfd_file_dir = duthost.shell('mktemp')['stdout']
ptf_file_dir = ptfhost.shell('mktemp')['stdout']
bfd_config = []
ptf_config = []
for neighbor_addr in neighbor_addrs:
bfd_config.append({
"BFD_SESSION_TABLE:default:default:{}".format(neighbor_addr): {
"local_addr": loopback_addr,
"multihop" : "true"
},
"OP": "SET"
})
ptf_config.append(
{
"neighbor_addr": loopback_addr,
"local_addr" : neighbor_addr,
"multihop" : "true",
"ptf_intf" : "eth{}".format(ptf_intf)
}
)

# Copy json file to DUT
duthost.copy(content=json.dumps(bfd_config, indent=4), dest=bfd_file_dir, verbose=False)

# Apply BFD sessions with swssconfig
result = duthost.shell('docker exec -i swss swssconfig /dev/stdin < {}'.format(bfd_file_dir),
module_ignore_errors=True)
if result['rc'] != 0:
pytest.fail('Failed to apply BFD session configuration file: {}'.format(result['stderr']))
# Copy json file to PTF
ptfhost.copy(content=json.dumps(ptf_config, indent=4), dest=ptf_file_dir, verbose=False)

ptfhost.copy(src=BFD_RESPONDER_SCRIPT_SRC_PATH, dest=BFD_RESPONDER_SCRIPT_DEST_PATH)

extra_vars = {"bfd_responder_args" : "-c {}".format(ptf_file_dir)}
ptfhost.host.options["variable_manager"].extra_vars.update(extra_vars)

def remove_bfd_sessions(duthost, local_addrs, neighbor_addrs):
ptfhost.template(src='templates/bfd_responder.conf.j2', dest='/etc/supervisor/conf.d/bfd_responder.conf')
ptfhost.command('supervisorctl reread')
ptfhost.command('supervisorctl update')
ptfhost.command('supervisorctl start bfd_responder')

def remove_bfd_sessions(duthost, neighbor_addrs):
# Create a tempfile for BFD sessions
bfd_file_dir = duthost.shell('mktemp')['stdout']
bfd_config = []
for idx, neighbor_addr in enumerate(neighbor_addrs):
bfd_config.append({
"BFD_SESSION_TABLE:default:default:{}".format(neighbor_addr): {
"local_addr": local_addrs[idx]
},
"OP": "DEL"
})
Expand Down Expand Up @@ -284,7 +374,7 @@ def test_bfd_basic(request, rand_selected_dut, ptfhost, tbinfo, ipv6, dut_init_f
finally:
stop_ptf_bfd(ptfhost)
del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6)
remove_bfd_sessions(duthost, local_addrs, neighbor_addrs)
remove_bfd_sessions(duthost, neighbor_addrs)
remove_dut_ip(duthost, neighbor_devs, local_addrs, prefix_len)


Expand All @@ -302,7 +392,6 @@ def test_bfd_scale(request, rand_selected_dut, ptfhost, tbinfo, ipv6):
create_bfd_sessions(ptfhost, duthost, local_addrs, neighbor_addrs, False, True)

time.sleep(10)

bfd_state = ptfhost.shell("bfdd-control status")
dut_state = duthost.shell("show bfd summary")
for itr in local_addrs:
Expand All @@ -313,5 +402,33 @@ def test_bfd_scale(request, rand_selected_dut, ptfhost, tbinfo, ipv6):
time.sleep(10)
stop_ptf_bfd(ptfhost)
del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6)
remove_bfd_sessions(duthost, local_addrs, neighbor_addrs)
remove_bfd_sessions(duthost, neighbor_addrs)
remove_dut_ip(duthost, neighbor_devs, local_addrs, prefix_len)


@pytest.mark.parametrize('ipv6', [False, True], ids=['ipv4', 'ipv6'])
def test_bfd_multihop(request, rand_selected_dut, ptfhost, tbinfo, toggle_all_simulator_ports_to_rand_selected_tor_m, ipv6):
duthost = rand_selected_dut

bfd_session_cnt = int(request.config.getoption('--num_sessions'))
loopback_addr, ptf_intf, nexthop_ip, neighbor_addrs = get_neighbors_multihop(duthost, tbinfo, ipv6, count = bfd_session_cnt)
try:
cmd_buffer = ""
for neighbor in neighbor_addrs:
cmd_buffer += 'sudo ip route add {} via {} ;'.format(neighbor, nexthop_ip)
duthost.shell(cmd_buffer)

create_bfd_sessions_multihop(ptfhost, duthost, loopback_addr, ptf_intf, neighbor_addrs)

time.sleep(1)
for neighbor_addr in neighbor_addrs:
check_dut_bfd_status(duthost, neighbor_addr, "Up")

finally:
remove_bfd_sessions(duthost, neighbor_addrs)
cmd_buffer = ""
for neighbor in neighbor_addrs:
cmd_buffer += 'sudo ip route delete {} via {} ;'.format(neighbor, nexthop_ip)
duthost.shell(cmd_buffer)
ptfhost.command('supervisorctl stop bfd_responder')
ptfhost.file(path=BFD_RESPONDER_SCRIPT_DEST_PATH, state="absent")
10 changes: 10 additions & 0 deletions tests/templates/bfd_responder.conf.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[program:bfd_responder]
command=/usr/bin/python /opt/bfd_responder.py {{ bfd_responder_args }}
process_name=bfd_responder
stdout_logfile=/tmp/bfd_responder.out.log
stderr_logfile=/tmp/bfd_responder.err.log
redirect_stderr=false
autostart=false
autorestart=true
startsecs=1
numprocs=1