-
Notifications
You must be signed in to change notification settings - Fork 781
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
Bfd test multihop #6032
Changes from 4 commits
5c15ba1
3b4fce1
4d28da6
d3f09fc
22c3ea3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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)) | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This print is incompatible with python3. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Incompatible with python3. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,13 @@ | |
pytest.mark.topology('t1', 't1-lag', 't1-64-lag') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 = [] | ||
|
@@ -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") | ||
|
||
|
@@ -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) | ||
|
@@ -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" | ||
}) | ||
|
@@ -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) | ||
|
||
|
||
|
@@ -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: | ||
|
@@ -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") |
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.