Skip to content

Commit b91b9b9

Browse files
committed
Bfd test multihop (sonic-net#6032)
Cherry pick of PR sonic-net#6032 for 202012
1 parent 104a62a commit b91b9b9

File tree

4 files changed

+327
-24
lines changed

4 files changed

+327
-24
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
import binascii
2+
import socket
3+
import struct
4+
import select
5+
import json
6+
import argparse
7+
import os.path
8+
from fcntl import ioctl
9+
import logging
10+
logging.getLogger("scapy.runtime").setLevel(logging.ERROR)
11+
import scapy.all as scapy2
12+
scapy2.conf.use_pcap=True
13+
from scapy.contrib.bfd import BFD
14+
15+
def get_if(iff, cmd):
16+
s = socket.socket()
17+
ifreq = ioctl(s, cmd, struct.pack("16s16x",iff))
18+
s.close()
19+
20+
return ifreq
21+
22+
def get_mac(iff):
23+
SIOCGIFHWADDR = 0x8927 # Get hardware address
24+
return get_if(iff, SIOCGIFHWADDR)[18:24]
25+
26+
27+
class Interface(object):
28+
29+
def __init__(self, iface):
30+
self.iface = iface
31+
self.socket = None
32+
self.mac_address = get_mac(iface)
33+
34+
def __del__(self):
35+
if self.socket:
36+
self.socket.close()
37+
38+
def bind(self):
39+
self.socket = scapy2.conf.L2listen(iface=self.iface, filter="udp port 4784")
40+
41+
def handler(self):
42+
return self.socket
43+
44+
def recv(self):
45+
sniffed = self.socket.recv()
46+
pkt = sniffed[0]
47+
str_pkt = str(pkt).encode("HEX")
48+
binpkt = binascii.unhexlify(str_pkt)
49+
return binpkt
50+
51+
def send(self, data):
52+
scapy2.sendp(data, iface=self.iface)
53+
54+
def mac(self):
55+
return self.mac_address
56+
57+
def name(self):
58+
return self.iface
59+
60+
61+
class Poller(object):
62+
def __init__(self, interfaces, responder):
63+
self.responder = responder
64+
self.mapping = {}
65+
for interface in interfaces:
66+
self.mapping[interface.handler()] = interface
67+
68+
def poll(self):
69+
handlers = self.mapping.keys()
70+
while True:
71+
(rdlist, _, _) = select.select(handlers, [], [])
72+
for handler in rdlist:
73+
self.responder.action(self.mapping[handler])
74+
75+
76+
class BFDResponder(object):
77+
def __init__(self, sessions):
78+
self.sessions = sessions
79+
return
80+
81+
def action(self, interface):
82+
data = interface.recv()
83+
mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state = self.extract_bfd_info(data)
84+
if ip_dst not in self.sessions:
85+
return
86+
session = self.sessions[ip_dst]
87+
88+
if bfd_state== 3L:
89+
interface.send(session["pkt"])
90+
return
91+
92+
if bfd_state == 2L:
93+
return
94+
session = self.sessions[ip_dst]
95+
session["other_disc"] = bfd_remote_disc
96+
bfd_pkt_init = self.craft_bfd_packet( session, data, mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, 2L )
97+
print("sending INIT", bfd_pkt_init)
98+
interface.send(bfd_pkt_init)
99+
bfd_pkt_init.payload.payload.payload.load.sta =3L
100+
session["pkt"] = bfd_pkt_init
101+
return
102+
103+
def extract_bfd_info(self, data):
104+
# remote_mac, remote_ip, request_ip, op_type
105+
ether = scapy2.Ether(data)
106+
mac_src= ether.src
107+
mac_dst = ether.dst
108+
ip_src = ether.payload.src
109+
ip_dst = ether.payload.dst
110+
bfdpkt = BFD(ether.payload.payload.payload.load)
111+
bfd_remote_disc =bfdpkt.my_discriminator
112+
bfd_state = bfdpkt.sta
113+
return mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state
114+
115+
def craft_bfd_packet(self, session, data, mac_src, mac_dst, ip_src, ip_dst, bfd_remote_disc, bfd_state):
116+
ethpart = scapy2.Ether(data)
117+
bfdpart = BFD(ethpart.payload.payload.payload.load)
118+
bfdpart.my_discriminator = session["my_disc"]
119+
bfdpart.your_discriminator = bfd_remote_disc
120+
bfdpart.sta = bfd_state
121+
122+
ethpart.payload.payload.payload.load = bfdpart
123+
ethpart.src = mac_dst
124+
ethpart.dst = mac_src
125+
ethpart.payload.src= ip_dst
126+
ethpart.payload.dst= ip_src
127+
return ethpart
128+
129+
def parse_args():
130+
parser = argparse.ArgumentParser(description='ARP autoresponder')
131+
parser.add_argument('--conf', '-c', type=str, dest='conf', default='/tmp/from_t1.json', help='path to json file with configuration')
132+
args = parser.parse_args()
133+
134+
return args
135+
136+
def main():
137+
args = parse_args()
138+
139+
if not os.path.exists(args.conf):
140+
print("Can't find file %s" % args.conf)
141+
return
142+
143+
with open(args.conf) as fp:
144+
data = json.load(fp)
145+
146+
# generate ip_sets. every ip address will have it's own uniq mac address
147+
sessions = {}
148+
local_disc_base = 0xcdba0000
149+
local_src_port = 14000
150+
ifaces = {}
151+
for bfd in data:
152+
curr_session = {}
153+
curr_session["local"] = bfd["local_addr"]
154+
curr_session["remote"] = bfd["neighbor_addr"]
155+
curr_session["intf"] = bfd["ptf_intf"]
156+
curr_session["multihop"] = bfd["multihop"]
157+
curr_session["my_disc"] = local_disc_base
158+
curr_session["other_disc"] = 0x00
159+
curr_session["mac"] = get_mac( str( bfd["ptf_intf"]))
160+
curr_session["src_port"] = local_src_port
161+
curr_session["pkt"] = ""
162+
if bfd["ptf_intf"] not in ifaces:
163+
ifaces[curr_session["intf"]] = curr_session["mac"]
164+
165+
local_disc_base +=1
166+
local_src_port +=1
167+
sessions[curr_session["local"]] = curr_session
168+
ifaceobjs =[]
169+
for iface_name in ifaces.keys():
170+
iface = Interface(str(iface_name))
171+
iface.bind()
172+
ifaceobjs.append(iface)
173+
174+
resp = BFDResponder(sessions)
175+
176+
p = Poller(ifaceobjs, resp)
177+
p.poll()
178+
179+
return
180+
181+
if __name__ == '__main__':
182+
main()

tests/bfd/test_bfd.py

+135-23
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,11 @@
1414
from tests.common import constants
1515

1616
pytestmark = [
17-
pytest.mark.topology('t0', 't1', 't1-lag')
17+
pytest.mark.topology('t1')
1818
]
1919

20-
def skip_201911_and_older(duthost):
21-
""" Skip the current test if the DUT version is 201911 or older.
22-
"""
23-
if parse_version(duthost.kernel_version) <= parse_version('4.9.0'):
24-
pytest.skip("Test not supported for 201911 images or older. Skipping the test")
25-
20+
BFD_RESPONDER_SCRIPT_SRC_PATH = '../ansible/roles/test/files/helpers/bfd_responder.py'
21+
BFD_RESPONDER_SCRIPT_DEST_PATH = '/opt/bfd_responder.py'
2622

2723
def is_dualtor(tbinfo):
2824
"""Check if the testbed is dualtor."""
@@ -96,6 +92,47 @@ def get_neighbors(duthost, tbinfo, ipv6=False, count=1):
9692
return [t1_ipv4_pattern.format(idx * 2) for idx in indices], 31, [t1_ipv4_pattern.format(idx * 2 + 1) for idx in indices], [t0_intfs[_] for _ in indices], [ptf_ports[_] for _ in indices]
9793

9894

95+
def get_loopback_intf(mg_facts, ipv6):
96+
ipv6idx = 0 if mg_facts['minigraph_lo_interfaces'][0]['prefixlen'] == 128 else 1
97+
if ipv6:
98+
return mg_facts['minigraph_lo_interfaces'][ipv6idx]['addr']
99+
else:
100+
return mg_facts['minigraph_lo_interfaces'][(ipv6idx+1) %2]['addr']
101+
102+
def get_neighbors_multihop(duthost, tbinfo, ipv6=False, count=1):
103+
mg_facts = duthost.get_extended_minigraph_facts(tbinfo)
104+
t0_ipv4_pattern = '4.{}.{}.1'
105+
t0_ipv6_pattern = '3000:3000:{:x}::3000'
106+
t0_intfs = get_t0_intfs(mg_facts)
107+
ptf_ports = [mg_facts['minigraph_ptf_indices'][port] for port in t0_intfs]
108+
loopback_addr = get_loopback_intf( mg_facts, ipv6 )
109+
110+
index = random.sample(list(range(len(t0_intfs))), k=1)[0]
111+
port_intf = t0_intfs[index]
112+
ptf_intf = ptf_ports[index]
113+
nexthop_ip = ""
114+
neighbour_dev_name = mg_facts['minigraph_neighbors'][port_intf]['name']
115+
for bgpinfo in mg_facts['minigraph_bgp']:
116+
if bgpinfo['name'] == neighbour_dev_name:
117+
nexthop_ip = bgpinfo['addr']
118+
if ipv6 and ":" not in nexthop_ip :
119+
nexthop_ip = ""
120+
continue
121+
break
122+
if nexthop_ip =="":
123+
assert False
124+
neighbor_addrs = []
125+
idx2 =0
126+
for idx in range(1, count):
127+
if idx %250 ==0:
128+
idx2 +=1
129+
if ipv6:
130+
neighbor_addrs.append(t0_ipv6_pattern.format(idx))
131+
else:
132+
neighbor_addrs.append(t0_ipv4_pattern.format((idx%250),idx2))
133+
134+
return loopback_addr, ptf_intf, nexthop_ip, neighbor_addrs
135+
99136
def init_ptf_bfd(ptfhost):
100137
ptfhost.shell("bfdd-beacon")
101138

@@ -117,7 +154,13 @@ def del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6=Fa
117154
if ipv6:
118155
ptfhost.shell("ip -6 addr del {}/{} dev eth{}".format(neighbor_addrs[idx], prefix_len, neighbor_interfaces[idx]), module_ignore_errors=True)
119156
else:
120-
ptfhost.shell("ip addr del {}/{} dev eth{}".format(neighbor_addrs[idx], prefix_len, neighbor_interfaces[idx]), module_ignore_errors=True)
157+
cmd_buffer += "ip addr del {}/{} dev eth{} ;".format(neighbor_addrs[idx], prefix_len,
158+
neighbor_interfaces[idx])
159+
if idx % 50 == 0:
160+
ptfhost.shell(cmd_buffer, module_ignore_errors=True)
161+
cmd_buffer = ""
162+
if cmd_buffer != "":
163+
ptfhost.shell(cmd_buffer, module_ignore_errors=True)
121164

122165

123166
def check_ptf_bfd_status(ptfhost, neighbor_addr, local_addr, expected_state):
@@ -157,8 +200,51 @@ def create_bfd_sessions(ptfhost, duthost, local_addrs, neighbor_addrs):
157200
if result['rc'] != 0:
158201
pytest.fail('Failed to apply BFD session configuration file: {}'.format(result['stderr']))
159202

203+
def create_bfd_sessions_multihop(ptfhost, duthost, loopback_addr, ptf_intf, neighbor_addrs):
204+
# Create a tempfile for BFD sessions
205+
bfd_file_dir = duthost.shell('mktemp')['stdout']
206+
ptf_file_dir = ptfhost.shell('mktemp')['stdout']
207+
bfd_config = []
208+
ptf_config = []
209+
for neighbor_addr in neighbor_addrs:
210+
bfd_config.append({
211+
"BFD_SESSION_TABLE:default:default:{}".format(neighbor_addr): {
212+
"local_addr": loopback_addr,
213+
"multihop" : "true"
214+
},
215+
"OP": "SET"
216+
})
217+
ptf_config.append(
218+
{
219+
"neighbor_addr": loopback_addr,
220+
"local_addr" : neighbor_addr,
221+
"multihop" : "true",
222+
"ptf_intf" : "eth{}".format(ptf_intf)
223+
}
224+
)
225+
226+
# Copy json file to DUT
227+
duthost.copy(content=json.dumps(bfd_config, indent=4), dest=bfd_file_dir, verbose=False)
228+
229+
# Apply BFD sessions with swssconfig
230+
result = duthost.shell('docker exec -i swss swssconfig /dev/stdin < {}'.format(bfd_file_dir),
231+
module_ignore_errors=True)
232+
if result['rc'] != 0:
233+
pytest.fail('Failed to apply BFD session configuration file: {}'.format(result['stderr']))
234+
# Copy json file to PTF
235+
ptfhost.copy(content=json.dumps(ptf_config, indent=4), dest=ptf_file_dir, verbose=False)
160236

161-
def remove_bfd_sessions(duthost, local_addrs, neighbor_addrs):
237+
ptfhost.copy(src=BFD_RESPONDER_SCRIPT_SRC_PATH, dest=BFD_RESPONDER_SCRIPT_DEST_PATH)
238+
239+
extra_vars = {"bfd_responder_args" : "-c {}".format(ptf_file_dir)}
240+
ptfhost.host.options["variable_manager"].extra_vars.update(extra_vars)
241+
242+
ptfhost.template(src='templates/bfd_responder.conf.j2', dest='/etc/supervisor/conf.d/bfd_responder.conf')
243+
ptfhost.command('supervisorctl reread')
244+
ptfhost.command('supervisorctl update')
245+
ptfhost.command('supervisorctl start bfd_responder')
246+
247+
def remove_bfd_sessions(duthost, neighbor_addrs):
162248
# Create a tempfile for BFD sessions
163249
bfd_file_dir = duthost.shell('mktemp')['stdout']
164250
bfd_config = []
@@ -167,7 +253,6 @@ def remove_bfd_sessions(duthost, local_addrs, neighbor_addrs):
167253
duthost.shell("sonic-db-cli APPL_DB hmset 'BFD_SESSION_TABLE:default:default:{}' local_addr {}".format(neighbor_addr, local_addrs[idx]))
168254
bfd_config.append({
169255
"BFD_SESSION_TABLE:default:default:{}".format(neighbor_addr): {
170-
"local_addr": local_addrs[idx]
171256
},
172257
"OP": "DEL"
173258
})
@@ -218,10 +303,9 @@ def test_bfd(rand_selected_dut, ptfhost, tbinfo, toggle_all_simulator_ports_to_r
218303
check_ptf_bfd_status(ptfhost, neighbor_addr, local_addrs[idx], "Up")
219304
finally:
220305
stop_ptf_bfd(ptfhost)
221-
del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6=False)
222-
remove_bfd_sessions(duthost, local_addrs, neighbor_addrs)
223-
if 't1' in tbinfo['topo']['name']:
224-
remove_dut_ip(duthost, neighbor_devs, local_addrs, prefix_len)
306+
del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6)
307+
remove_bfd_sessions(duthost, neighbor_addrs)
308+
remove_dut_ip(duthost, neighbor_devs, local_addrs, prefix_len)
225309

226310

227311
@pytest.mark.skip(reason="Test may currently fail due to lack of hardware support")
@@ -238,11 +322,12 @@ def test_bfd_ipv6(rand_selected_dut, ptfhost, tbinfo, toggle_all_simulator_ports
238322
add_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6=True)
239323
create_bfd_sessions(ptfhost, duthost, local_addrs, neighbor_addrs)
240324

241-
time.sleep(1)
242-
243-
for idx, neighbor_addr in enumerate(neighbor_addrs):
244-
check_dut_bfd_status(duthost, neighbor_addr, "Up")
245-
check_ptf_bfd_status(ptfhost, neighbor_addr, local_addrs[idx], "Up")
325+
time.sleep(10)
326+
bfd_state = ptfhost.shell("bfdd-control status")
327+
dut_state = duthost.shell("show bfd summary")
328+
for itr in local_addrs:
329+
assert itr in bfd_state['stdout']
330+
assert itr in dut_state['stdout']
246331

247332
update_idx = random.choice(range(bfd_session_cnt))
248333
update_bfd_session_state(ptfhost, neighbor_addrs[update_idx], local_addrs[idx], "down")
@@ -256,7 +341,34 @@ def test_bfd_ipv6(rand_selected_dut, ptfhost, tbinfo, toggle_all_simulator_ports
256341
check_ptf_bfd_status(ptfhost, neighbor_addr, local_addrs[idx], "Up")
257342
finally:
258343
stop_ptf_bfd(ptfhost)
259-
del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6=True)
260-
remove_bfd_sessions(duthost, local_addrs, neighbor_addrs)
261-
if 't1' in tbinfo['topo']['name']:
262-
remove_dut_ip(duthost, neighbor_devs, local_addrs, prefix_len)
344+
del_ipaddr(ptfhost, neighbor_addrs, prefix_len, neighbor_interfaces, ipv6)
345+
remove_bfd_sessions(duthost, neighbor_addrs)
346+
remove_dut_ip(duthost, neighbor_devs, local_addrs, prefix_len)
347+
348+
349+
@pytest.mark.parametrize('ipv6', [False, True], ids=['ipv4', 'ipv6'])
350+
def test_bfd_multihop(request, rand_selected_dut, ptfhost, tbinfo, toggle_all_simulator_ports_to_rand_selected_tor_m, ipv6):
351+
duthost = rand_selected_dut
352+
353+
bfd_session_cnt = int(request.config.getoption('--num_sessions'))
354+
loopback_addr, ptf_intf, nexthop_ip, neighbor_addrs = get_neighbors_multihop(duthost, tbinfo, ipv6, count = bfd_session_cnt)
355+
try:
356+
cmd_buffer = ""
357+
for neighbor in neighbor_addrs:
358+
cmd_buffer += 'sudo ip route add {} via {} ;'.format(neighbor, nexthop_ip)
359+
duthost.shell(cmd_buffer)
360+
361+
create_bfd_sessions_multihop(ptfhost, duthost, loopback_addr, ptf_intf, neighbor_addrs)
362+
363+
time.sleep(1)
364+
for neighbor_addr in neighbor_addrs:
365+
check_dut_bfd_status(duthost, neighbor_addr, "Up")
366+
367+
finally:
368+
remove_bfd_sessions(duthost, neighbor_addrs)
369+
cmd_buffer = ""
370+
for neighbor in neighbor_addrs:
371+
cmd_buffer += 'sudo ip route delete {} via {} ;'.format(neighbor, nexthop_ip)
372+
duthost.shell(cmd_buffer)
373+
ptfhost.command('supervisorctl stop bfd_responder')
374+
ptfhost.file(path=BFD_RESPONDER_SCRIPT_DEST_PATH, state="absent")

0 commit comments

Comments
 (0)