blob: 0f6c8a800788300b1801dd488b8cb59a89a4c5bb [file] [log] [blame]
Daniele Moro80889562021-09-08 10:09:26 +02001from distutils.util import strtobool
Daniele Moro522023c2021-10-15 17:30:33 +02002import copy
Daniele Moro80889562021-09-08 10:09:26 +02003
4FALSE = '0'
5TRUE = '1'
6DIR_UPLINK = '1'
7DIR_DOWNLINK = '2'
8IFACE_ACCESS = '1'
9IFACE_CORE = '2'
10TUNNEL_SPORT = '2152'
11TUNNEL_TYPE_GPDU = '3'
12
13UE_PORT = 400
14PDN_PORT = 800
15GPDU_PORT = 2152
16
Daniele Moroc6811a82021-10-12 11:29:41 +020017N_FLOWS_PER_UE = 4
18
Daniele Moro80889562021-09-08 10:09:26 +020019
20class UP4:
21 """
22 Utility that manages interaction with UP4 via a P4RuntimeCliDriver available
23 in the cluster. Additionally, can verify connectivity by crafting GTP packets
Daniele Moro522023c2021-10-15 17:30:33 +020024 via Scapy with an HostDriver component, specified via <enodebs>, <pdn_host>,
Daniele Moro80889562021-09-08 10:09:26 +020025 and <router_mac> parameters.
26
27 Example params file:
28 <UP4>
29 <pdn_host>Compute1</pdn_host> # Needed to verify connectivity with scapy
Daniele Moro522023c2021-10-15 17:30:33 +020030 <enodebs> # List of emulated eNodeBs
31 <enode_1>
32 <host>Compute1</host> # Host that emulates this eNodeB
33 <interface>eno3</interface> # Name of the linux interface to use on the host, if not specified take the default
34 <enb_address>10.32.11.122</enb_address> # IP address of the eNodeB
35 <ues>ue3</ues> # Emulated ues connected to this eNB
36 </enode_1>
37 <enodeb_2>
38 <host>Compute3</host>
39 <enb_address>10.32.11.194</enb_address>
40 <ues>ue1,ue2</ues>
41 </enodeb_2>
42 </enodebs>
43 <enodeb_host>Compute3</enodeb_host>
Daniele Moro80889562021-09-08 10:09:26 +020044 <router_mac>00:00:0A:4C:1C:46</router_mac> # Needed to verify connectivity with scapy
45 <s1u_address>10.32.11.126</s1u_address>
Daniele Moro80889562021-09-08 10:09:26 +020046 <ues>
47 <ue2>
48 <pfcp_session_id>100</pfcp_session_id>
49 <ue_address>10.240.0.2</ue_address>
50 <teid>200</teid>
51 <up_id>20</up_id>
52 <down_id>21</down_id>
53 <qfi>2</qfi>
54 <five_g>False</five_g>
55 </ue2>
56 </ues>
Daniele Moro522023c2021-10-15 17:30:33 +020057 <switch_to_kill>Leaf2</switch_to_kill> # Component name of the switch to kill in CASE 5
58 <enodebs_fail>enodeb_1</enodebs_fail> # List of eNodeBs that should fail traffic forwarding in CASE 5
Daniele Moro80889562021-09-08 10:09:26 +020059 </UP4>
60 """
61
62 def __init__(self):
63 self.s1u_address = None
Daniele Moro522023c2021-10-15 17:30:33 +020064 self.enodebs = None
Daniele Moro80889562021-09-08 10:09:26 +020065 self.pdn_host = None
66 self.pdn_interface = None
67 self.router_mac = None
Daniele Moro522023c2021-10-15 17:30:33 +020068 self.emulated_ues = {}
Daniele Moro80889562021-09-08 10:09:26 +020069 self.up4_client = None
Daniele Moro522023c2021-10-15 17:30:33 +020070 self.no_host = False
Daniele Moro80889562021-09-08 10:09:26 +020071
Daniele Moro954e2282021-09-22 17:32:03 +020072 def setup(self, p4rt_client, no_host=False):
73 """
74 Set up P4RT and scapy on eNB and PDN hosts
75 :param p4rt_client: a P4RuntimeCliDriver component
76 :param no_host: True if you don't want to start scapy on the hosts
77 :return:
78 """
Daniele Moro80889562021-09-08 10:09:26 +020079 self.s1u_address = main.params["UP4"]["s1u_address"]
Daniele Moro80889562021-09-08 10:09:26 +020080 self.emulated_ues = main.params["UP4"]['ues']
81 self.up4_client = p4rt_client
Daniele Moro522023c2021-10-15 17:30:33 +020082 self.no_host = no_host
Daniele Moro80889562021-09-08 10:09:26 +020083
84 # Optional Parameters
Daniele Moro522023c2021-10-15 17:30:33 +020085
86 self.enodebs = copy.deepcopy((main.params["UP4"]["enodebs"]))
87 for enb in self.enodebs.values():
88 enb["ues"] = enb["ues"].split(",")
89 enb["host"] = getattr(main, enb["host"])
90 # If interface not provided by the params, use the default in the host
91 if "interface" not in enb.keys():
92 enb["interface"] = enb["host"].interfaces[0]["name"]
93 if "pdn_host" in main.params["UP4"]:
94 self.pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
95 self.pdn_interface = self.pdn_host.interfaces[0]
96 self.router_mac = main.params["UP4"].get("router_mac", None)
Daniele Moro80889562021-09-08 10:09:26 +020097
98 # Start components
99 self.up4_client.startP4RtClient()
Daniele Moro522023c2021-10-15 17:30:33 +0200100 if not self.no_host:
101 if self.enodebs is not None:
102 for enb in self.enodebs.values():
103 enb["host"].startScapy(ifaceName=enb["interface"],
104 enableGtp=True)
105 if self.pdn_host is not None:
106 self.pdn_host.startScapy(ifaceName=self.pdn_interface["name"])
Daniele Moro80889562021-09-08 10:09:26 +0200107
108 def teardown(self):
109 self.up4_client.stopP4RtClient()
Daniele Moro522023c2021-10-15 17:30:33 +0200110 if not self.no_host:
111 if self.enodebs is not None:
112 for enb in self.enodebs.values():
113 enb["host"].stopScapy()
114 if self.pdn_host is not None:
115 self.pdn_host.stopScapy()
Daniele Moro80889562021-09-08 10:09:26 +0200116
117 def attachUes(self):
Daniele Moro522023c2021-10-15 17:30:33 +0200118 for (name, ue) in self.emulated_ues.items():
Daniele Moro80889562021-09-08 10:09:26 +0200119 ue = UP4.__sanitizeUeData(ue)
Daniele Moro522023c2021-10-15 17:30:33 +0200120 self.attachUe(name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200121
122 def detachUes(self):
Daniele Moro522023c2021-10-15 17:30:33 +0200123 for (name, ue) in self.emulated_ues.items():
Daniele Moro249d6e72021-09-20 10:32:54 +0200124 ue = UP4.__sanitizeUeData(ue)
Daniele Moro522023c2021-10-15 17:30:33 +0200125 self.detachUe(name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200126
Daniele Moro522023c2021-10-15 17:30:33 +0200127 def testUpstreamTraffic(self, enb_names=None, shouldFail=False):
128 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200129 main.log.error(
130 "Need eNodeB and PDN host params to generate scapy traffic")
131 return
132 # Scapy filter needs to start before sending traffic
Daniele Moro522023c2021-10-15 17:30:33 +0200133 if enb_names is None or enb_names == []:
134 enodebs = self.enodebs.values()
135 else:
136 enodebs = [self.enodebs[enb] for enb in enb_names]
Daniele Moro80889562021-09-08 10:09:26 +0200137 pkt_filter_upstream = ""
Daniele Moro522023c2021-10-15 17:30:33 +0200138 ues = []
139 for enb in enodebs:
140 for ue_name in enb["ues"]:
141 ue = self.emulated_ues[ue_name]
142 if "ue_address" in ue:
143 ues.append(ue)
144 if len(pkt_filter_upstream) != 0:
145 pkt_filter_upstream += " or "
146 pkt_filter_upstream += "src host " + ue["ue_address"]
Daniele Moro80889562021-09-08 10:09:26 +0200147 pkt_filter_upstream = "ip and udp dst port %s and (%s) and dst host %s" % \
148 (PDN_PORT, pkt_filter_upstream,
149 self.pdn_interface["ips"][0])
150 main.log.info("Start listening on %s intf %s" %
151 (self.pdn_host.name, self.pdn_interface["name"]))
152 main.log.debug("BPF Filter Upstream: \n %s" % pkt_filter_upstream)
153 self.pdn_host.startFilter(ifaceName=self.pdn_interface["name"],
Daniele Moro522023c2021-10-15 17:30:33 +0200154 sniffCount=len(ues),
Daniele Moro80889562021-09-08 10:09:26 +0200155 pktFilter=pkt_filter_upstream)
156
157 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200158 "Sending %d packets from eNodeB host" % len(ues))
159 for enb in enodebs:
160 for ue_name in enb["ues"]:
161 main.log.info(ue_name)
162 ue = self.emulated_ues[ue_name]
163 main.log.info(str(ue))
164 UP4.buildGtpPacket(enb["host"],
165 src_ip_outer=enb["enb_address"],
166 dst_ip_outer=self.s1u_address,
167 src_ip_inner=ue["ue_address"],
168 dst_ip_inner=self.pdn_interface["ips"][0],
169 src_udp_inner=UE_PORT,
170 dst_udp_inner=PDN_PORT,
171 teid=int(ue["teid"]))
172 enb["host"].sendPacket(iface=enb["interface"])
Daniele Moro80889562021-09-08 10:09:26 +0200173
Daniele Morobf53dec2021-09-13 18:11:56 +0200174 packets = UP4.checkFilterAndGetPackets(self.pdn_host)
Daniele Moro80889562021-09-08 10:09:26 +0200175 fail = False
Daniele Moro522023c2021-10-15 17:30:33 +0200176 if len(ues) != packets.count('Ether'):
Daniele Moro80889562021-09-08 10:09:26 +0200177 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200178 msg = "Failed to capture packets in PDN.\n" + str(packets)
Daniele Moro80889562021-09-08 10:09:26 +0200179 else:
180 msg = "Correctly captured packet in PDN. "
181 # We expect exactly 1 packet per UE
Daniele Moro522023c2021-10-15 17:30:33 +0200182 pktsFiltered = [packets.count("src=" + ue["ue_address"]) for ue in ues]
Daniele Moro80889562021-09-08 10:09:26 +0200183 if pktsFiltered.count(1) != len(pktsFiltered):
184 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200185 msg += "\nError on the number of packets per UE in downstream.\n" + str(packets)
Daniele Moro80889562021-09-08 10:09:26 +0200186 else:
Daniele Moroc6811a82021-10-12 11:29:41 +0200187 msg += "\nOne packet per UE in upstream. "
Daniele Moro80889562021-09-08 10:09:26 +0200188
189 utilities.assert_equal(
Daniele Moro522023c2021-10-15 17:30:33 +0200190 expect=shouldFail, actual=fail, onpass=msg, onfail=msg)
Daniele Moro80889562021-09-08 10:09:26 +0200191
Daniele Moro522023c2021-10-15 17:30:33 +0200192 def testDownstreamTraffic(self, enb_names=None, shouldFail=False):
193 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200194 main.log.error(
195 "Need eNodeB and PDN host params to generate scapy traffic")
196 return
Daniele Moro522023c2021-10-15 17:30:33 +0200197 if enb_names is None or enb_names == []:
198 enodebs = self.enodebs.values()
199 else:
200 enodebs = [self.enodebs[enb] for enb in enb_names]
201 pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s" % (
202 GPDU_PORT, GPDU_PORT, self.s1u_address)
203 ues = []
204 for enb in enodebs:
205 filter_down = pkt_filter_downstream + " and dst host %s" % enb["enb_address"]
206 main.log.info("Start listening on %s intf %s" % (
207 enb["host"], enb["interface"]))
208 main.log.debug("BPF Filter Downstream: \n %s" % filter_down)
209 enb["host"].startFilter(ifaceName=enb["interface"],
210 sniffCount=len(enb["ues"]),
211 pktFilter=filter_down)
212 ues.extend([self.emulated_ues[ue_name] for ue_name in enb["ues"]])
Daniele Moro80889562021-09-08 10:09:26 +0200213
214 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200215 "Sending %d packets from PDN host" % len(ues))
216 for ue in ues:
Daniele Moro80889562021-09-08 10:09:26 +0200217 # From PDN we have to set dest MAC, otherwise scapy will do ARP
218 # request for the UE IP address.
Daniele Morobf53dec2021-09-13 18:11:56 +0200219 UP4.buildUdpPacket(self.pdn_host,
220 dst_eth=self.router_mac,
221 src_ip=self.pdn_interface["ips"][0],
222 dst_ip=ue["ue_address"],
223 src_udp=PDN_PORT,
224 dst_udp=UE_PORT)
Daniele Moro80889562021-09-08 10:09:26 +0200225 self.pdn_host.sendPacket(iface=self.pdn_interface["name"])
Daniele Moro522023c2021-10-15 17:30:33 +0200226 packets = ""
227 for enb in enodebs:
228 pkt = UP4.checkFilterAndGetPackets(enb["host"])
229 packets += pkt
Daniele Moro80889562021-09-08 10:09:26 +0200230 # The BPF filter might capture non-GTP packets because we can't filter
231 # GTP header in BPF. For this reason, check that the captured packets
232 # are from the expected tunnels.
233 # TODO: check inner UDP and IP fields as well
234 # FIXME: with newer scapy TEID becomes teid (required for Scapy 2.4.5)
Daniele Moro522023c2021-10-15 17:30:33 +0200235 pktsFiltered= [packets.count("TEID=" + hex(int(ue["teid"])) + "L ")
236 for ue in ues]
237 main.log.info("PACKETS: " + str(packets))
238 main.log.info("PKTs Filtered: " + str(pktsFiltered))
Daniele Moro80889562021-09-08 10:09:26 +0200239 fail = False
Daniele Moro522023c2021-10-15 17:30:33 +0200240 if len(ues) != sum(pktsFiltered):
Daniele Moro80889562021-09-08 10:09:26 +0200241 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200242 msg = "Failed to capture packets in eNodeB.\n" + str(packets)
Daniele Moro80889562021-09-08 10:09:26 +0200243 else:
244 msg = "Correctly captured packets in eNodeB. "
245 # We expect exactly 1 packet per UE
246 if pktsFiltered.count(1) != len(pktsFiltered):
247 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200248 msg += "\nError on the number of packets per GTP TEID in downstream.\n" + str(packets)
Daniele Moro80889562021-09-08 10:09:26 +0200249 else:
Daniele Moroc6811a82021-10-12 11:29:41 +0200250 msg += "\nOne packet per GTP TEID in downstream. "
Daniele Moro80889562021-09-08 10:09:26 +0200251
252 utilities.assert_equal(
Daniele Moro522023c2021-10-15 17:30:33 +0200253 expect=shouldFail, actual=fail, onpass=msg, onfail=msg)
Daniele Moro80889562021-09-08 10:09:26 +0200254
Daniele Moro954e2282021-09-22 17:32:03 +0200255 def readPdrsNumber(self):
256 """
257 Read the PDRs table and return the number of entries in the PDRs table
258
259 :return: Number of entries in the PDRs table
260 """
261 tableName = 'PreQosPipe.pdrs'
262 return self.up4_client.readNumberTableEntries(tableName)
263
264 def readFarsNumber(self):
265 """
266 Read the FARs table and return the number of entries in the FARs table
267
268 :return: Number of entries in the FARs table
269 """
270 tableName = 'PreQosPipe.load_far_attributes'
271 return self.up4_client.readNumberTableEntries(tableName)
272
273 def verifyUesFlowNumberP4rt(self):
274 """
275 Verify via P4RT CLI that the number of PDRs and FARs is the expected one
276
277 :return: True if the number of PDRs and FARs is expected, False otherwise
278 """
279 nPdrs = self.readPdrsNumber()
280 nFars = self.readFarsNumber()
281 return nPdrs == nFars == len(self.emulated_ues) * 2
282
283 def verifyNoUesFlowNumberP4rt(self, preInstalledUes=0):
284 """
285 Verify via P4RT CLI that there is no PDRs and FARs installed.
286
287 :param preInstalledUes: Number of UEs whose PDRs and FARs are still programmed
288 :return:
289 """
290 return self.readPdrsNumber() == self.readFarsNumber() == preInstalledUes * 2
291
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200292 def verifyNoUesFlow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200293 """
294 Verify that no PDRs and FARs are installed in ONOS.
295
296 :param onosCli: An instance of a OnosCliDriver
297 :param retries: number of retries
298 :return:
299 """
300 retValue = utilities.retry(f=UP4.__verifyNoPdrsFarsOnos,
301 retValue=False,
302 args=[onosCli],
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200303 sleep=5,
Daniele Morobf53dec2021-09-13 18:11:56 +0200304 attempts=retries)
305 utilities.assert_equal(expect=True,
306 actual=retValue,
307 onpass="No PDRs and FARs in ONOS",
308 onfail="Stale PDRs or FARs")
309
310 @staticmethod
311 def __verifyNoPdrsFarsOnos(onosCli):
312 """
313 Verify that no PDRs and FARs are installed in ONOS
314
315 :param onosCli: An instance of a OnosCliDriver
316 """
317 pdrs = onosCli.sendline(cmdStr="up4:read-pdrs", showResponse=True,
318 noExit=True, expectJson=False)
319 fars = onosCli.sendline(cmdStr="up4:read-fars", showResponse=True,
320 noExit=True, expectJson=False)
321 return pdrs == "" and fars == ""
322
Daniele Moroc6811a82021-10-12 11:29:41 +0200323 def verifyUp4Flow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200324 """
325 Verify PDRs and FARs installed via UP4 using the ONOS CLI.
326
327 :param onosCli: An instance of a OnosCliDriver
Daniele Moroc6811a82021-10-12 11:29:41 +0200328 :param retries: Number of retries
Daniele Morobf53dec2021-09-13 18:11:56 +0200329 """
Daniele Moroc6811a82021-10-12 11:29:41 +0200330 failString=""
331 retValue = utilities.retry(f=self.__internalVerifyUp4Flow,
332 retValue=False,
333 args=[onosCli, failString],
334 sleep=5,
335 attempts=retries)
336 utilities.assert_equal(
337 expect=True,
338 actual=retValue,
339 onpass="Correct PDRs and FARs in ONOS",
340 onfail="Wrong PDRs and FARs in ONOS. Missing PDR/FAR:\n" + failString
341 )
342
343 def __internalVerifyUp4Flow(self, onosCli, failMsg=""):
Daniele Morobf53dec2021-09-13 18:11:56 +0200344 pdrs = onosCli.sendline(cmdStr="up4:read-pdrs", showResponse=True,
345 noExit=True, expectJson=False)
346 fars = onosCli.sendline(cmdStr="up4:read-fars", showResponse=True,
347 noExit=True, expectJson=False)
348 fail = False
Daniele Moro522023c2021-10-15 17:30:33 +0200349 for (ue_name, ue) in self.emulated_ues.items():
Daniele Morobf53dec2021-09-13 18:11:56 +0200350 if pdrs.count(self.upPdrOnosString(**ue)) != 1:
351 failMsg += self.upPdrOnosString(**ue) + "\n"
352 fail = True
353 if pdrs.count(self.downPdrOnosString(**ue)) != 1:
354 failMsg += self.downPdrOnosString(**ue) + "\n"
355 fail = True
356 if fars.count(self.upFarOnosString(**ue)) != 1:
357 failMsg += self.upFarOnosString(**ue) + "\n"
358 fail = True
Daniele Moro522023c2021-10-15 17:30:33 +0200359 if fars.count(self.downFarOnosString(ue_name, **ue)) != 1:
360 failMsg += self.downFarOnosString(ue_name, **ue) + "\n"
Daniele Morobf53dec2021-09-13 18:11:56 +0200361 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200362 return not fail
Daniele Morobf53dec2021-09-13 18:11:56 +0200363
364 def upPdrOnosString(self, pfcp_session_id, teid=None, up_id=None,
365 teid_up=None, far_id_up=None, ctr_id_up=None, qfi=None,
366 **kwargs):
367 # TODO: consider that with five_g the output might be different
368 if up_id is not None:
369 far_id_up = up_id
370 ctr_id_up = up_id
371 if teid is not None:
372 teid_up = teid
373 if qfi is not None:
374 return "PDR{{Match(Dst={}, TEID={}) -> LoadParams(SEID={}, FAR={}, CtrIdx={}, QFI={})}}".format(
375 self.s1u_address, hex(int(teid_up)), hex(int(pfcp_session_id)),
Daniele Moro3e4cf3b2021-09-21 18:53:22 +0200376 far_id_up, ctr_id_up, qfi)
Daniele Morobf53dec2021-09-13 18:11:56 +0200377 return "PDR{{Match(Dst={}, TEID={}) -> LoadParams(SEID={}, FAR={}, CtrIdx={})}}".format(
378 self.s1u_address, hex(int(teid_up)), hex(int(pfcp_session_id)),
379 far_id_up, ctr_id_up)
380
381 def downPdrOnosString(self, pfcp_session_id, ue_address, down_id=None,
Daniele Moro3e4cf3b2021-09-21 18:53:22 +0200382 far_id_down=None, ctr_id_down=None, qfi=None,
383 **kwargs):
Daniele Morobf53dec2021-09-13 18:11:56 +0200384 # TODO: consider that with five_g the output might be different
385 if down_id is not None:
386 far_id_down = down_id
387 ctr_id_down = down_id
Daniele Moro3e4cf3b2021-09-21 18:53:22 +0200388 if qfi is not None:
389 return "PDR{{Match(Dst={}, !GTP) -> LoadParams(SEID={}, FAR={}, CtrIdx={}, QFI={})}}".format(
390 ue_address, hex(int(pfcp_session_id)), far_id_down, ctr_id_down,
391 qfi)
Daniele Morobf53dec2021-09-13 18:11:56 +0200392 return "PDR{{Match(Dst={}, !GTP) -> LoadParams(SEID={}, FAR={}, CtrIdx={})}}".format(
393 ue_address, hex(int(pfcp_session_id)), far_id_down, ctr_id_down)
394
Daniele Moro522023c2021-10-15 17:30:33 +0200395 def downFarOnosString(self, ue_name, pfcp_session_id, teid=None, down_id=None,
Daniele Morobf53dec2021-09-13 18:11:56 +0200396 teid_down=None, far_id_down=None, **kwargs):
397 if down_id is not None:
398 far_id_down = down_id
399 if teid is not None:
400 teid_down = teid
Daniele Moro522023c2021-10-15 17:30:33 +0200401 enb_address = self.__getEnbAddress(ue_name)
Daniele Morobf53dec2021-09-13 18:11:56 +0200402 return "FAR{{Match(ID={}, SEID={}) -> Encap(Src={}, SPort={}, TEID={}, Dst={})}}".format(
403 far_id_down, hex(int(pfcp_session_id)), self.s1u_address, GPDU_PORT,
Daniele Moro522023c2021-10-15 17:30:33 +0200404 hex(int(teid_down)), enb_address)
Daniele Morobf53dec2021-09-13 18:11:56 +0200405
406 def upFarOnosString(self, pfcp_session_id, up_id=None, far_id_up=None,
407 **kwargs):
408 if up_id is not None:
409 far_id_up = up_id
410 return "FAR{{Match(ID={}, SEID={}) -> Forward()}}".format(
411 far_id_up, hex(int(pfcp_session_id)))
412
Daniele Moro80889562021-09-08 10:09:26 +0200413 @staticmethod
414 def __sanitizeUeData(ue):
Daniele Moro249d6e72021-09-20 10:32:54 +0200415 if "five_g" in ue and type(ue["five_g"]) != bool:
Daniele Moro80889562021-09-08 10:09:26 +0200416 ue["five_g"] = bool(strtobool(ue["five_g"]))
417 if "qfi" in ue and ue["qfi"] == "":
418 ue["qfi"] = None
419 return ue
420
Daniele Moro522023c2021-10-15 17:30:33 +0200421 def attachUe(self, ue_name, pfcp_session_id, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200422 teid=None, up_id=None, down_id=None,
423 teid_up=None, teid_down=None,
424 pdr_id_up=None, far_id_up=None, ctr_id_up=None,
425 pdr_id_down=None, far_id_down=None, ctr_id_down=None,
426 qfi=None, five_g=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200427 self.__programUp4Rules(ue_name,
428 pfcp_session_id,
Daniele Moro80889562021-09-08 10:09:26 +0200429 ue_address,
430 teid, up_id, down_id,
431 teid_up, teid_down,
432 pdr_id_up, far_id_up, ctr_id_up,
433 pdr_id_down, far_id_down, ctr_id_down,
434 qfi, five_g, action="program")
435
Daniele Moro522023c2021-10-15 17:30:33 +0200436 def detachUe(self, ue_name, pfcp_session_id, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200437 teid=None, up_id=None, down_id=None,
438 teid_up=None, teid_down=None,
439 pdr_id_up=None, far_id_up=None, ctr_id_up=None,
440 pdr_id_down=None, far_id_down=None, ctr_id_down=None,
441 qfi=None, five_g=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200442 self.__programUp4Rules(ue_name,
443 pfcp_session_id,
Daniele Moro80889562021-09-08 10:09:26 +0200444 ue_address,
445 teid, up_id, down_id,
446 teid_up, teid_down,
447 pdr_id_up, far_id_up, ctr_id_up,
448 pdr_id_down, far_id_down, ctr_id_down,
449 qfi, five_g, action="clear")
450
Daniele Moro522023c2021-10-15 17:30:33 +0200451 def __programUp4Rules(self, ue_name, pfcp_session_id, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200452 teid=None, up_id=None, down_id=None,
453 teid_up=None, teid_down=None,
454 pdr_id_up=None, far_id_up=None, ctr_id_up=None,
455 pdr_id_down=None, far_id_down=None, ctr_id_down=None,
456 qfi=None, five_g=False, action="program"):
457 if up_id is not None:
458 pdr_id_up = up_id
459 far_id_up = up_id
460 ctr_id_up = up_id
461 if down_id is not None:
462 pdr_id_down = down_id
463 far_id_down = down_id
464 ctr_id_down = down_id
465 if teid is not None:
466 teid_up = teid
467 teid_down = teid
468
469 entries = []
470
Daniele Moro522023c2021-10-15 17:30:33 +0200471 # Retrieve eNobeB address from eNodeB list
472 enb_address = self.__getEnbAddress(ue_name)
473
Daniele Moro80889562021-09-08 10:09:26 +0200474 # ========================#
475 # PDR Entries
476 # ========================#
477
478 # Uplink
479 tableName = 'PreQosPipe.pdrs'
480 actionName = ''
481 matchFields = {}
482 actionParams = {}
483 if qfi is None:
484 actionName = 'PreQosPipe.set_pdr_attributes'
485 else:
486 actionName = 'PreQosPipe.set_pdr_attributes_qos'
487 if five_g:
488 # TODO: currently QFI_MATCH is unsupported in TNA
489 matchFields['has_qfi'] = TRUE
490 matchFields["qfi"] = str(qfi)
491 actionParams['needs_qfi_push'] = FALSE
492 actionParams['qfi'] = str(qfi)
493 # Match fields
494 matchFields['src_iface'] = IFACE_ACCESS
495 matchFields['ue_addr'] = str(ue_address)
496 matchFields['teid'] = str(teid_up)
497 matchFields['tunnel_ipv4_dst'] = str(self.s1u_address)
498 # Action params
499 actionParams['id'] = str(pdr_id_up)
500 actionParams['fseid'] = str(pfcp_session_id)
501 actionParams['ctr_id'] = str(ctr_id_up)
502 actionParams['far_id'] = str(far_id_up)
503 actionParams['needs_gtpu_decap'] = TRUE
504 if not self.__add_entry(tableName, actionName, matchFields,
505 actionParams, entries, action):
506 return False
507
508 # Downlink
509 tableName = 'PreQosPipe.pdrs'
510 matchFields = {}
511 actionParams = {}
512 if qfi is None:
513 actionName = 'PreQosPipe.set_pdr_attributes'
514 else:
515 actionName = 'PreQosPipe.set_pdr_attributes_qos'
516 # TODO: currently QFI_PUSH is unsupported in TNA
517 actionParams['needs_qfi_push'] = TRUE if five_g else FALSE
518 actionParams['qfi'] = str(qfi)
519 # Match fields
520 matchFields['src_iface'] = IFACE_CORE
521 matchFields['ue_addr'] = str(ue_address)
522 # Action params
523 actionParams['id'] = str(pdr_id_down)
524 actionParams['fseid'] = str(pfcp_session_id)
525 actionParams['ctr_id'] = str(ctr_id_down)
526 actionParams['far_id'] = str(far_id_down)
527 actionParams['needs_gtpu_decap'] = FALSE
528 if not self.__add_entry(tableName, actionName, matchFields,
529 actionParams, entries, action):
530 return False
531
532 # ========================#
533 # FAR Entries
534 # ========================#
535
536 # Uplink
537 tableName = 'PreQosPipe.load_far_attributes'
538 actionName = 'PreQosPipe.load_normal_far_attributes'
539 matchFields = {}
540 actionParams = {}
541
542 # Match fields
543 matchFields['far_id'] = str(far_id_up)
544 matchFields['session_id'] = str(pfcp_session_id)
545 # Action params
546 actionParams['needs_dropping'] = FALSE
547 actionParams['notify_cp'] = FALSE
548 if not self.__add_entry(tableName, actionName, matchFields,
549 actionParams, entries, action):
550 return False
551
552 # Downlink
553 tableName = 'PreQosPipe.load_far_attributes'
554 actionName = 'PreQosPipe.load_tunnel_far_attributes'
555 matchFields = {}
556 actionParams = {}
557
558 # Match fields
559 matchFields['far_id'] = str(far_id_down)
560 matchFields['session_id'] = str(pfcp_session_id)
561 # Action params
562 actionParams['needs_dropping'] = FALSE
563 actionParams['notify_cp'] = FALSE
564 actionParams['needs_buffering'] = FALSE
565 actionParams['tunnel_type'] = TUNNEL_TYPE_GPDU
566 actionParams['src_addr'] = str(self.s1u_address)
Daniele Moro522023c2021-10-15 17:30:33 +0200567 actionParams['dst_addr'] = str(enb_address)
Daniele Moro80889562021-09-08 10:09:26 +0200568 actionParams['teid'] = str(teid_down)
569 actionParams['sport'] = TUNNEL_SPORT
570 if not self.__add_entry(tableName, actionName, matchFields,
571 actionParams, entries, action):
572 return False
573 if action == "program":
574 main.log.info("All entries added successfully.")
575 elif action == "clear":
576 self.__clear_entries(entries)
577
578 def __add_entry(self, tableName, actionName, matchFields, actionParams,
579 entries, action):
580 if action == "program":
581 self.up4_client.buildP4RtTableEntry(
582 tableName=tableName, actionName=actionName,
583 actionParams=actionParams, matchFields=matchFields)
584 if self.up4_client.pushTableEntry(debug=True) == main.TRUE:
585 main.log.info("*** Entry added.")
586 else:
587 main.log.error("Error during table insertion")
588 self.__clear_entries(entries)
589 return False
590 entries.append({"tableName": tableName, "actionName": actionName,
591 "matchFields": matchFields,
592 "actionParams": actionParams})
593 return True
594
595 def __clear_entries(self, entries):
596 for i, entry in enumerate(entries):
597 self.up4_client.buildP4RtTableEntry(**entry)
598 if self.up4_client.deleteTableEntry(debug=True) == main.TRUE:
599 main.log.info(
600 "*** Entry %d of %d deleted." % (i + 1, len(entries)))
601 else:
602 main.log.error("Error during table delete")
Daniele Morobf53dec2021-09-13 18:11:56 +0200603
Daniele Moro522023c2021-10-15 17:30:33 +0200604 def __getEnbAddress(self, ue_name):
605 for enb in self.enodebs.values():
606 if ue_name in enb["ues"]:
607 return enb["enb_address"]
608 main.log.error("Missing eNodeB address!")
609 return ""
610
Daniele Morobf53dec2021-09-13 18:11:56 +0200611 @staticmethod
612 def buildGtpPacket(host, src_ip_outer, dst_ip_outer, src_ip_inner,
613 dst_ip_inner, src_udp_inner, dst_udp_inner, teid):
614 host.buildEther()
615 host.buildIP(src=src_ip_outer, dst=dst_ip_outer)
616 host.buildUDP(ipVersion=4, dport=GPDU_PORT)
617 # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
618 host.buildGTP(gtp_type=0xFF, TEID=teid)
619 host.buildIP(overGtp=True, src=src_ip_inner, dst=dst_ip_inner)
620 host.buildUDP(ipVersion=4, overGtp=True, sport=src_udp_inner,
621 dport=dst_udp_inner)
622
623 @staticmethod
624 def buildUdpPacket(host, src_ip, dst_ip, src_udp, dst_udp, src_eth=None,
625 dst_eth=None):
626 host.buildEther(src=src_eth, dst=dst_eth)
627 host.buildIP(src=src_ip, dst=dst_ip)
628 host.buildUDP(ipVersion=4, sport=src_udp, dport=dst_udp)
629
630 @staticmethod
631 def checkFilterAndGetPackets(host):
632 finished = host.checkFilter()
633 if finished:
634 packets = host.readPackets(detailed=True)
635 for p in packets.splitlines():
636 main.log.debug(p)
637 # We care only of the last line from readPackets
638 return packets.splitlines()[-1]
639 else:
640 kill = host.killFilter()
641 main.log.debug(kill)
642 return ""