blob: bb285d4dcfc20eb77114a30a657e76a12094e549 [file] [log] [blame]
Daniele Moro80889562021-09-08 10:09:26 +02001from distutils.util import strtobool
Jon Halla4a79312022-01-25 17:16:53 -08002import ipaddress as ip
Daniele Moro522023c2021-10-15 17:30:33 +02003import copy
Jon Halla4a79312022-01-25 17:16:53 -08004import re
Jon Hall909fc242022-03-08 16:07:30 -08005import pexpect
Daniele Moro80889562021-09-08 10:09:26 +02006
7FALSE = '0'
8TRUE = '1'
9DIR_UPLINK = '1'
10DIR_DOWNLINK = '2'
11IFACE_ACCESS = '1'
12IFACE_CORE = '2'
13TUNNEL_SPORT = '2152'
14TUNNEL_TYPE_GPDU = '3'
15
16UE_PORT = 400
Daniele Morobef0c7e2022-02-16 17:47:13 -080017DEFAULT_PDN_PORT = 800
Daniele Moro80889562021-09-08 10:09:26 +020018GPDU_PORT = 2152
19
Daniele Moroa1975192022-01-21 18:01:19 +010020DEFAULT_APP_ID = 0
Jon Halla4a79312022-01-25 17:16:53 -080021DEFAULT_APP_NAME = "Default"
Daniele Moro4650ffb2022-02-15 22:44:18 +010022DEFAULT_SESSION_METER_IDX = 0
23DEFAULT_APP_METER_IDX = 0
24
Daniele Moro80889562021-09-08 10:09:26 +020025
26class UP4:
27 """
28 Utility that manages interaction with UP4 via a P4RuntimeCliDriver available
29 in the cluster. Additionally, can verify connectivity by crafting GTP packets
Daniele Moro522023c2021-10-15 17:30:33 +020030 via Scapy with an HostDriver component, specified via <enodebs>, <pdn_host>,
Daniele Moro80889562021-09-08 10:09:26 +020031 and <router_mac> parameters.
32
33 Example params file:
34 <UP4>
35 <pdn_host>Compute1</pdn_host> # Needed to verify connectivity with scapy
Daniele Moro522023c2021-10-15 17:30:33 +020036 <enodebs> # List of emulated eNodeBs
37 <enode_1>
38 <host>Compute1</host> # Host that emulates this eNodeB
39 <interface>eno3</interface> # Name of the linux interface to use on the host, if not specified take the default
40 <enb_address>10.32.11.122</enb_address> # IP address of the eNodeB
41 <ues>ue3</ues> # Emulated ues connected to this eNB
42 </enode_1>
43 <enodeb_2>
44 <host>Compute3</host>
45 <enb_address>10.32.11.194</enb_address>
46 <ues>ue1,ue2</ues>
47 </enodeb_2>
48 </enodebs>
49 <enodeb_host>Compute3</enodeb_host>
Daniele Moro80889562021-09-08 10:09:26 +020050 <router_mac>00:00:0A:4C:1C:46</router_mac> # Needed to verify connectivity with scapy
51 <s1u_address>10.32.11.126</s1u_address>
Daniele Morocc4ecda2022-02-25 23:27:25 +010052 <slice_id>1</slice_id> # Mobile SLICE ID, used when pushing application filtering entries
Daniele Moro80889562021-09-08 10:09:26 +020053 <ues>
54 <ue2>
Daniele Moro80889562021-09-08 10:09:26 +020055 <ue_address>10.240.0.2</ue_address>
56 <teid>200</teid>
57 <up_id>20</up_id>
58 <down_id>21</down_id>
Carmelo Cascone848d1f52022-01-27 18:15:58 -080059 <!-- TC 0 means BEST EFFORT -->
60 <tc>2</tc>
Daniele Moro80889562021-09-08 10:09:26 +020061 <five_g>False</five_g>
62 </ue2>
63 </ues>
Daniele Moro522023c2021-10-15 17:30:33 +020064 <switch_to_kill>Leaf2</switch_to_kill> # Component name of the switch to kill in CASE 5
65 <enodebs_fail>enodeb_1</enodebs_fail> # List of eNodeBs that should fail traffic forwarding in CASE 5
Daniele Moro80889562021-09-08 10:09:26 +020066 </UP4>
67 """
68
69 def __init__(self):
70 self.s1u_address = None
Daniele Moro522023c2021-10-15 17:30:33 +020071 self.enodebs = None
Daniele Moro80889562021-09-08 10:09:26 +020072 self.pdn_host = None
73 self.pdn_interface = None
74 self.router_mac = None
Daniele Moro522023c2021-10-15 17:30:33 +020075 self.emulated_ues = {}
Jon Halla4a79312022-01-25 17:16:53 -080076 self.DEFAULT_APP = {DEFAULT_APP_NAME: {'ip_prefix': None, 'ip_proto': None, 'port_range': None,
77 'app_id': DEFAULT_APP_ID, 'action': 'allow' } }
78 self.app_filters = self.DEFAULT_APP
Daniele Moro80889562021-09-08 10:09:26 +020079 self.up4_client = None
Jon Halla4a79312022-01-25 17:16:53 -080080 self.mock_smf = None
Daniele Moro522023c2021-10-15 17:30:33 +020081 self.no_host = False
Daniele Morocc4ecda2022-02-25 23:27:25 +010082 self.slice_id = None
Jon Halla4a79312022-01-25 17:16:53 -080083 self.next_seid = 1
Daniele Moro80889562021-09-08 10:09:26 +020084
Jon Halla4a79312022-01-25 17:16:53 -080085 def setup(self, p4rt_client, mock_smf=None, no_host=False, pfcpAddress=None, pfcpPort=8805, app_filters=True):
Daniele Moro954e2282021-09-22 17:32:03 +020086 """
Jon Halla4a79312022-01-25 17:16:53 -080087 Set up P4RT and scapy on eNB and PDN hosts. If mock_smf is not None, will also connect
88 to the pfcp agent using a mock smf and the test will use this to create ue sessions instead of P4RT
Daniele Moro954e2282021-09-22 17:32:03 +020089 :param p4rt_client: a P4RuntimeCliDriver component
Jon Halla4a79312022-01-25 17:16:53 -080090 :param mock_smf: The optional mocksmfdriver component to use
Daniele Moro954e2282021-09-22 17:32:03 +020091 :param no_host: True if you don't want to start scapy on the hosts
Jon Halla4a79312022-01-25 17:16:53 -080092 :param pfcpAddress: Address of the PFCP agent to connect to when using mock smf
93 :param pfcpPort: Port of the PFCP agent to connect to when using mock smf
94 :param app_filters: If True, will add app_filters defined in the params file, else use the default application filter
Daniele Moro954e2282021-09-22 17:32:03 +020095 :return:
96 """
Daniele Moro80889562021-09-08 10:09:26 +020097 self.s1u_address = main.params["UP4"]["s1u_address"]
Daniele Moro80889562021-09-08 10:09:26 +020098 self.emulated_ues = main.params["UP4"]['ues']
Jon Halla4a79312022-01-25 17:16:53 -080099 self.app_filters = main.params["UP4"]['app_filters'] if app_filters else self.DEFAULT_APP
Daniele Morocc4ecda2022-02-25 23:27:25 +0100100 self.slice_id = main.params["UP4"]['slice_id']
Daniele Moro80889562021-09-08 10:09:26 +0200101 self.up4_client = p4rt_client
Jon Halla4a79312022-01-25 17:16:53 -0800102 self.mock_smf = mock_smf
Daniele Moro522023c2021-10-15 17:30:33 +0200103 self.no_host = no_host
Daniele Moro80889562021-09-08 10:09:26 +0200104
105 # Optional Parameters
Daniele Moro522023c2021-10-15 17:30:33 +0200106
107 self.enodebs = copy.deepcopy((main.params["UP4"]["enodebs"]))
108 for enb in self.enodebs.values():
109 enb["ues"] = enb["ues"].split(",")
110 enb["host"] = getattr(main, enb["host"])
111 # If interface not provided by the params, use the default in the host
112 if "interface" not in enb.keys():
113 enb["interface"] = enb["host"].interfaces[0]["name"]
114 if "pdn_host" in main.params["UP4"]:
115 self.pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
116 self.pdn_interface = self.pdn_host.interfaces[0]
117 self.router_mac = main.params["UP4"].get("router_mac", None)
Daniele Moro80889562021-09-08 10:09:26 +0200118
Jon Halla601dde2022-02-28 14:22:07 -0800119 for app_filter in self.app_filters.values():
120 if app_filter.get('slice_id', None) is None:
121 app_filter['slice_id'] = self.slice_id
Daniele Moro80889562021-09-08 10:09:26 +0200122 # Start components
Jon Halla4a79312022-01-25 17:16:53 -0800123 if self.up4_client:
124 self.up4_client.startP4RtClient()
125 if self.mock_smf:
126 if not pfcpAddress:
127 pfcpAddress = self.mock_smf.kubectlGetServiceIP( "pfcp-agent" )
Jon Hall909fc242022-03-08 16:07:30 -0800128 self.startMockSmfPcap(self.mock_smf)
Jon Halla4a79312022-01-25 17:16:53 -0800129 self.mock_smf.startSMF()
130 self.mock_smf.configure(self.s1u_address, pfcpAddress, pfcpPort)
131 # TODO Start pcap on mock_smf host
132 self.mock_smf.associate()
Daniele Moro522023c2021-10-15 17:30:33 +0200133 if not self.no_host:
134 if self.enodebs is not None:
135 for enb in self.enodebs.values():
136 enb["host"].startScapy(ifaceName=enb["interface"],
Daniele Moro49a843c2022-01-05 14:36:32 +0100137 enableGtp=True)
Daniele Moro522023c2021-10-15 17:30:33 +0200138 if self.pdn_host is not None:
139 self.pdn_host.startScapy(ifaceName=self.pdn_interface["name"])
Daniele Morocc4ecda2022-02-25 23:27:25 +0100140 # TODO: configure interfaces table. Currently, we rely on netcfg or
141 # PFCP agent to push interface entries, but we should explicitly push
142 # them here
Daniele Moro80889562021-09-08 10:09:26 +0200143
Jon Hall909fc242022-03-08 16:07:30 -0800144 def startMockSmfPcap(self, smfComponent, pcapIface="eth0"):
145 compName = "smf-pcap"
146 # Create another component/bash session for tshark
147 main.Network.copyComponent(smfComponent.name, compName)
148 pcap = getattr(main, compName)
149 pcapFile = "%s/CASE%s-%s" % (main.logdir, main.CurrentTestCaseNumber, compName)
150 commands = ['touch %s.pcap' % pcapFile,
151 'chmod o=rw %s.pcap' % pcapFile]
152 for command in commands:
153 pcap.handle.sendline(command)
154 pcap.handle.expect(pcap.prompt)
155 main.log.debug("%s: %s" % (pcap.name, str(pcap.handle.before)))
156 pcap.handle.sendline("sudo /usr/bin/tshark -i %s -w %s.pcap &> %s.log" % (pcapIface, pcapFile, pcapFile))
157 i = pcap.handle.expect(["password", pexpect.TIMEOUT], timeout=5)
158 if i == 0:
159 pcap.handle.sendline(pcap.pwd if pcap.pwd else "jenkins")
160 pcap.preDisconnect = pcap.exitFromProcess
161
Daniele Moro80889562021-09-08 10:09:26 +0200162 def teardown(self):
Jon Halla4a79312022-01-25 17:16:53 -0800163 if self.up4_client:
164 self.up4_client.stopP4RtClient()
165 if self.mock_smf:
166 self.mock_smf.disassociate()
167 self.mock_smf.stop()
Daniele Moro522023c2021-10-15 17:30:33 +0200168 if not self.no_host:
169 if self.enodebs is not None:
170 for enb in self.enodebs.values():
171 enb["host"].stopScapy()
172 if self.pdn_host is not None:
173 self.pdn_host.stopScapy()
Daniele Moro80889562021-09-08 10:09:26 +0200174
175 def attachUes(self):
Jon Halla4a79312022-01-25 17:16:53 -0800176 filter_desc = []
177 for app_name, app_filter in sorted(self.app_filters.items(), key=lambda (k, v): int(v.get('priority', 0))):
178 if self.mock_smf:
179 if app_name != DEFAULT_APP_NAME:
180 filter_desc.append(self.__pfcpSDFString(**app_filter))
181 self.app_filters[app_name]['app_id'] = None
182 self.app_filters[app_name]['priority'] = None
183
184 else:
185 self.insertAppFilter(**app_filter)
186 for (ue_name, ue) in self.emulated_ues.items():
187 ue = UP4.__sanitizeUeData(ue, smf=True if self.mock_smf else False)
188 if self.mock_smf:
189 if not ue.get("seid"):
190 ue["seid"] = self.next_seid
191 self.next_seid = self.next_seid + 1
192 self.emulated_ues[ue_name]["seid"] = ue["seid"]
193 self.smfAttachUe(ue_name, sdf=filter_desc[-1:], **ue) # TODO: pass whole filter list once multi app support is fixed
194 else:
195 self.attachUe(ue_name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200196
197 def detachUes(self):
Jon Halla4a79312022-01-25 17:16:53 -0800198 if not self.mock_smf:
199 for app_filter in self.app_filters.values():
200 self.removeAppFilter(**app_filter)
201 for (ue_name, ue) in self.emulated_ues.items():
202 ue = UP4.__sanitizeUeData(ue, smf=True if self.mock_smf else False)
203 if self.mock_smf:
204 self.smfDetachUe(ue_name, **ue)
205 else:
206 self.detachUe(ue_name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200207
Daniele Morobef0c7e2022-02-16 17:47:13 -0800208 def __pickPortFromRange(self, range):
209 if range is None or len(range) == 0:
210 return DEFAULT_PDN_PORT
211 # First port in range
212 return int(range.split('..')[0])
213
214 def testUpstreamTraffic(self, enb_names=None, app_filter_name=None, shouldFail=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200215 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200216 main.log.error(
217 "Need eNodeB and PDN host params to generate scapy traffic")
218 return
Daniele Moro522023c2021-10-15 17:30:33 +0200219 if enb_names is None or enb_names == []:
220 enodebs = self.enodebs.values()
221 else:
222 enodebs = [self.enodebs[enb] for enb in enb_names]
Daniele Morobef0c7e2022-02-16 17:47:13 -0800223
Jon Halla4a79312022-01-25 17:16:53 -0800224 port_range = None
225 app_filter_should_drop = False
226 if app_filter_name and app_filter_name != DEFAULT_APP_NAME:
227 app_filter = self.app_filters[app_filter_name]
228 port_range = app_filter.get("port_range", None)
229 app_filter_should_drop = app_filter["action"] != "allow"
230 pdn_port = self.__pickPortFromRange(port_range)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800231
Daniele Moro80889562021-09-08 10:09:26 +0200232 pkt_filter_upstream = ""
Daniele Moro522023c2021-10-15 17:30:33 +0200233 ues = []
234 for enb in enodebs:
235 for ue_name in enb["ues"]:
236 ue = self.emulated_ues[ue_name]
237 if "ue_address" in ue:
238 ues.append(ue)
239 if len(pkt_filter_upstream) != 0:
240 pkt_filter_upstream += " or "
241 pkt_filter_upstream += "src host " + ue["ue_address"]
Daniele Moro80889562021-09-08 10:09:26 +0200242 pkt_filter_upstream = "ip and udp dst port %s and (%s) and dst host %s" % \
Daniele Morobef0c7e2022-02-16 17:47:13 -0800243 (pdn_port, pkt_filter_upstream,
Daniele Moro80889562021-09-08 10:09:26 +0200244 self.pdn_interface["ips"][0])
245 main.log.info("Start listening on %s intf %s" %
246 (self.pdn_host.name, self.pdn_interface["name"]))
247 main.log.debug("BPF Filter Upstream: \n %s" % pkt_filter_upstream)
248 self.pdn_host.startFilter(ifaceName=self.pdn_interface["name"],
Daniele Moro522023c2021-10-15 17:30:33 +0200249 sniffCount=len(ues),
Daniele Moro80889562021-09-08 10:09:26 +0200250 pktFilter=pkt_filter_upstream)
251
252 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200253 "Sending %d packets from eNodeB host" % len(ues))
254 for enb in enodebs:
255 for ue_name in enb["ues"]:
256 main.log.info(ue_name)
257 ue = self.emulated_ues[ue_name]
258 main.log.info(str(ue))
259 UP4.buildGtpPacket(enb["host"],
260 src_ip_outer=enb["enb_address"],
261 dst_ip_outer=self.s1u_address,
262 src_ip_inner=ue["ue_address"],
263 dst_ip_inner=self.pdn_interface["ips"][0],
264 src_udp_inner=UE_PORT,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800265 dst_udp_inner=pdn_port,
Daniele Moro522023c2021-10-15 17:30:33 +0200266 teid=int(ue["teid"]))
267 enb["host"].sendPacket(iface=enb["interface"])
Daniele Moro80889562021-09-08 10:09:26 +0200268
Daniele Morobf53dec2021-09-13 18:11:56 +0200269 packets = UP4.checkFilterAndGetPackets(self.pdn_host)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800270 if app_filter_should_drop:
271 expected_pkt_count = 0
272 else:
273 # We expect exactly 1 packet per UE.
274 expected_pkt_count = len(ues)
275 actual_pkt_count = packets.count('Ether')
Daniele Moro80889562021-09-08 10:09:26 +0200276 fail = False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800277 if expected_pkt_count != actual_pkt_count:
Daniele Moro80889562021-09-08 10:09:26 +0200278 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800279 msg = "Received %d packets (expected %d)\n%s\n" % (
280 actual_pkt_count, expected_pkt_count, str(packets)
281 )
Daniele Moro80889562021-09-08 10:09:26 +0200282 else:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800283 msg = "Received %d packets (expected %d)\n" % (
284 actual_pkt_count, expected_pkt_count
285 )
Daniele Moro80889562021-09-08 10:09:26 +0200286
Daniele Morobef0c7e2022-02-16 17:47:13 -0800287 if expected_pkt_count > 0:
288 # Make sure the captured packets are from the expected UE addresses.
289 for ue in ues:
290 ue_pkt_count = packets.count("src=" + ue["ue_address"])
291 if ue_pkt_count != 1:
292 fail = True
293 msg += "Received %d packet(s) from UE %s (expected 1)\n" % (
294 ue_pkt_count, ue["ue_address"]
295 )
Daniele Moro80889562021-09-08 10:09:26 +0200296 utilities.assert_equal(
Daniele Morobef0c7e2022-02-16 17:47:13 -0800297 expect=shouldFail, actual=fail, onpass=msg, onfail=msg
298 )
Daniele Moro80889562021-09-08 10:09:26 +0200299
Daniele Morobef0c7e2022-02-16 17:47:13 -0800300 def testDownstreamTraffic(self, enb_names=None, app_filter_name=None, shouldFail=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200301 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200302 main.log.error(
303 "Need eNodeB and PDN host params to generate scapy traffic")
304 return
Daniele Moro522023c2021-10-15 17:30:33 +0200305 if enb_names is None or enb_names == []:
306 enodebs = self.enodebs.values()
307 else:
308 enodebs = [self.enodebs[enb] for enb in enb_names]
Daniele Morobef0c7e2022-02-16 17:47:13 -0800309
Jon Halla4a79312022-01-25 17:16:53 -0800310 port_range = None
311 app_filter_should_drop = False
312 if app_filter_name and app_filter_name != DEFAULT_APP_NAME:
313 app_filter = self.app_filters[app_filter_name]
314 port_range = app_filter.get("port_range", None)
315 app_filter_should_drop = app_filter["action"] != "allow"
316 pdn_port = self.__pickPortFromRange(port_range)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800317
Daniele Moro522023c2021-10-15 17:30:33 +0200318 pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s" % (
319 GPDU_PORT, GPDU_PORT, self.s1u_address)
320 ues = []
321 for enb in enodebs:
Daniele Moro49a843c2022-01-05 14:36:32 +0100322 filter_down = pkt_filter_downstream + " and dst host %s" % enb[
323 "enb_address"]
Daniele Moro522023c2021-10-15 17:30:33 +0200324 main.log.info("Start listening on %s intf %s" % (
325 enb["host"], enb["interface"]))
326 main.log.debug("BPF Filter Downstream: \n %s" % filter_down)
327 enb["host"].startFilter(ifaceName=enb["interface"],
328 sniffCount=len(enb["ues"]),
329 pktFilter=filter_down)
330 ues.extend([self.emulated_ues[ue_name] for ue_name in enb["ues"]])
Daniele Moro80889562021-09-08 10:09:26 +0200331
332 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200333 "Sending %d packets from PDN host" % len(ues))
334 for ue in ues:
Daniele Moro80889562021-09-08 10:09:26 +0200335 # From PDN we have to set dest MAC, otherwise scapy will do ARP
336 # request for the UE IP address.
Daniele Morobf53dec2021-09-13 18:11:56 +0200337 UP4.buildUdpPacket(self.pdn_host,
338 dst_eth=self.router_mac,
339 src_ip=self.pdn_interface["ips"][0],
340 dst_ip=ue["ue_address"],
Daniele Morobef0c7e2022-02-16 17:47:13 -0800341 src_udp=pdn_port,
Daniele Morobf53dec2021-09-13 18:11:56 +0200342 dst_udp=UE_PORT)
Daniele Moro80889562021-09-08 10:09:26 +0200343 self.pdn_host.sendPacket(iface=self.pdn_interface["name"])
Daniele Moro522023c2021-10-15 17:30:33 +0200344 packets = ""
345 for enb in enodebs:
346 pkt = UP4.checkFilterAndGetPackets(enb["host"])
347 packets += pkt
Daniele Moro80889562021-09-08 10:09:26 +0200348 # The BPF filter might capture non-GTP packets because we can't filter
349 # GTP header in BPF. For this reason, check that the captured packets
350 # are from the expected tunnels.
Daniele Morobef0c7e2022-02-16 17:47:13 -0800351 # TODO: check inner IP fields as well
Daniele Moro80889562021-09-08 10:09:26 +0200352 # FIXME: with newer scapy TEID becomes teid (required for Scapy 2.4.5)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800353 downlink_teids = [int(ue["teid"]) + 1 for ue in ues]
354 # Number of GTP packets from expected TEID per UEs
355 gtp_pkts = [
356 packets.count("TEID=" + hex(teid) + "L ") for teid in downlink_teids
357 ]
358 # Number of packets from the expected PDN port
359 app_pkts = packets.count("UDP sport=" + str(pdn_port))
360 if app_filter_should_drop:
361 expected_pkt_count = 0
362 else:
363 # We expect exactly 1 packet per UE.
364 expected_pkt_count = len(ues)
365
Daniele Moro80889562021-09-08 10:09:26 +0200366 fail = False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800367 if expected_pkt_count != sum(gtp_pkts):
Daniele Moro80889562021-09-08 10:09:26 +0200368 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800369 msg = "Received %d packets (expected %d) from TEIDs %s\n%s\n" % (
370 sum(gtp_pkts), expected_pkt_count, downlink_teids, str(packets)
371 )
Daniele Moro80889562021-09-08 10:09:26 +0200372 else:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800373 msg = "Received %d packets (expected %d) from TEIDs %s\n" % (
374 sum(gtp_pkts), expected_pkt_count, downlink_teids
375 )
376 if expected_pkt_count != app_pkts:
Daniele Moro80889562021-09-08 10:09:26 +0200377 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800378 msg += "Received %d packets (expected %d) from PDN port %s\n%s\n" % (
379 sum(gtp_pkts), expected_pkt_count, pdn_port, str(packets)
380 )
Daniele Moro80889562021-09-08 10:09:26 +0200381 else:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800382 msg += "Received %d packets (expected %d) from PDN port %s\n" % (
383 sum(gtp_pkts), expected_pkt_count, pdn_port
384 )
385 if expected_pkt_count > 0:
386 if gtp_pkts.count(1) != len(gtp_pkts):
387 fail = True
388 msg += "Received %s packet(s) per UE (expected %s)\n%s\n" % (
389 gtp_pkts, [1] * len(gtp_pkts), packets
390 )
391 else:
392 msg += "Received %s packet(s) per UE (expected %s)\n" % (
393 gtp_pkts, [1] * len(gtp_pkts)
394 )
Daniele Moro80889562021-09-08 10:09:26 +0200395
396 utilities.assert_equal(
Daniele Morobef0c7e2022-02-16 17:47:13 -0800397 expect=shouldFail, actual=fail, onpass=msg, onfail=msg
398 )
Daniele Moro80889562021-09-08 10:09:26 +0200399
Daniele Moro49a843c2022-01-05 14:36:32 +0100400 def readUeSessionsNumber(self):
Daniele Moro954e2282021-09-22 17:32:03 +0200401 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100402 Read the UE session tables and return the number of entries
Daniele Moro954e2282021-09-22 17:32:03 +0200403
Daniele Moro49a843c2022-01-05 14:36:32 +0100404 :return: Number of entries in the UE session tables
Daniele Moro954e2282021-09-22 17:32:03 +0200405 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100406 tableName = 'PreQosPipe.sessions_uplink'
407 nUeSess = self.up4_client.readNumberTableEntries(tableName)
408 tableName = 'PreQosPipe.sessions_downlink'
409 nUeSess += self.up4_client.readNumberTableEntries(tableName)
410 return nUeSess
Daniele Moro954e2282021-09-22 17:32:03 +0200411
Daniele Moro49a843c2022-01-05 14:36:32 +0100412 def readTerminationsNumber(self):
Daniele Moro954e2282021-09-22 17:32:03 +0200413 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100414 Read the terminations and return the number of entities
Daniele Moro954e2282021-09-22 17:32:03 +0200415
Daniele Moro49a843c2022-01-05 14:36:32 +0100416 :return: Number of terminations entities
Daniele Moro954e2282021-09-22 17:32:03 +0200417 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100418 tableName = 'PreQosPipe.terminations_uplink'
419 nTerm = self.up4_client.readNumberTableEntries(tableName)
420 tableName = 'PreQosPipe.terminations_downlink'
421 nTerm += self.up4_client.readNumberTableEntries(tableName)
422 return nTerm
Daniele Moro954e2282021-09-22 17:32:03 +0200423
424 def verifyUesFlowNumberP4rt(self):
425 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100426 Verify via P4RT CLI that the number of UE sessions and terminations
427 is the expected one
Daniele Moro954e2282021-09-22 17:32:03 +0200428
Daniele Moro49a843c2022-01-05 14:36:32 +0100429 :return: True if the number of UE sessions and terminations is expected,
430 False otherwise
Daniele Moro954e2282021-09-22 17:32:03 +0200431 """
Daniele Morobef0c7e2022-02-16 17:47:13 -0800432 return self.readUeSessionsNumber() == len(self.emulated_ues) * 2 and \
433 self.readTerminationsNumber() == len(self.emulated_ues) * 2 * len(self.app_filters)
Daniele Moro954e2282021-09-22 17:32:03 +0200434
435 def verifyNoUesFlowNumberP4rt(self, preInstalledUes=0):
436 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100437 Verify via P4RT CLI that there is no UE sessions and terminations installed.
Daniele Moro954e2282021-09-22 17:32:03 +0200438
Daniele Moro49a843c2022-01-05 14:36:32 +0100439 :param preInstalledUes: Number of UEs whose UE sessions and terminations
440 are still programmed
Daniele Moro954e2282021-09-22 17:32:03 +0200441 :return:
442 """
Daniele Morobef0c7e2022-02-16 17:47:13 -0800443 return self.readUeSessionsNumber() == preInstalledUes * 2 and \
444 self.readTerminationsNumber() == preInstalledUes * 2 * len(self.app_filters)
Daniele Moro954e2282021-09-22 17:32:03 +0200445
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200446 def verifyNoUesFlow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200447 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100448 Verify that no UE session, terminations are installed in ONOS.
Daniele Morobf53dec2021-09-13 18:11:56 +0200449
450 :param onosCli: An instance of a OnosCliDriver
451 :param retries: number of retries
452 :return:
453 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100454 retValue = utilities.retry(f=UP4.__verifyNoUeSessionAndTerminationOnos,
Daniele Morobf53dec2021-09-13 18:11:56 +0200455 retValue=False,
456 args=[onosCli],
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200457 sleep=5,
Daniele Morobf53dec2021-09-13 18:11:56 +0200458 attempts=retries)
459 utilities.assert_equal(expect=True,
460 actual=retValue,
Daniele Moro49a843c2022-01-05 14:36:32 +0100461 onpass="No UE session and terminations in ONOS",
462 onfail="Stale UE session or terminations")
Daniele Morobf53dec2021-09-13 18:11:56 +0200463
464 @staticmethod
Daniele Moro49a843c2022-01-05 14:36:32 +0100465 def __verifyNoUeSessionAndTerminationOnos(onosCli):
Daniele Morobf53dec2021-09-13 18:11:56 +0200466 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100467 Verify that no UE session, terminations are installed in ONOS
Daniele Morobf53dec2021-09-13 18:11:56 +0200468
469 :param onosCli: An instance of a OnosCliDriver
470 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100471 sessions = onosCli.sendline(cmdStr="up4:read-entities -s",
472 showResponse=True,
473 noExit=True, expectJson=False)
474 terminations = onosCli.sendline(cmdStr="up4:read-entities -t",
475 showResponse=True,
476 noExit=True, expectJson=False)
477 return sessions == "" and terminations == ""
Daniele Morobf53dec2021-09-13 18:11:56 +0200478
Daniele Moroc6811a82021-10-12 11:29:41 +0200479 def verifyUp4Flow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200480 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100481 Verify UE session, terminations and GTP tunnel peers installed via UP4
482 using the ONOS CLI.
Daniele Morobf53dec2021-09-13 18:11:56 +0200483
484 :param onosCli: An instance of a OnosCliDriver
Daniele Moroc6811a82021-10-12 11:29:41 +0200485 :param retries: Number of retries
Daniele Morobf53dec2021-09-13 18:11:56 +0200486 """
Daniele Morobef0c7e2022-02-16 17:47:13 -0800487 failString = []
Daniele Moroc6811a82021-10-12 11:29:41 +0200488 retValue = utilities.retry(f=self.__internalVerifyUp4Flow,
489 retValue=False,
490 args=[onosCli, failString],
491 sleep=5,
492 attempts=retries)
493 utilities.assert_equal(
494 expect=True,
495 actual=retValue,
Daniele Moro49a843c2022-01-05 14:36:32 +0100496 onpass="Correct UE session, terminations and GTP tunnel peers in ONOS",
Jon Halla601dde2022-02-28 14:22:07 -0800497 onfail="Wrong Application Filters, UE sessions, terminations and/or GTP tunnel peers in ONOS. " +
Daniele Morobef0c7e2022-02-16 17:47:13 -0800498 "Missing:\n" + '\n'.join(failString)
Daniele Moroc6811a82021-10-12 11:29:41 +0200499 )
500
Daniele Morobef0c7e2022-02-16 17:47:13 -0800501 def __internalVerifyUp4Flow(self, onosCli, failMsg=[]):
502 # Need to pass a list, so it's an object and we can use failMsg to
503 # return a string values from this method.
504
505 # Cleanup failMsg if any remaining from previous runs
506 del failMsg[:]
507 applications = onosCli.sendline(cmdStr="up4:read-entities -f",
508 showResponse=True,
509 noExit=True, expectJson=False)
Daniele Moro49a843c2022-01-05 14:36:32 +0100510 sessions = onosCli.sendline(cmdStr="up4:read-entities -s",
511 showResponse=True,
512 noExit=True, expectJson=False)
513 terminations = onosCli.sendline(cmdStr="up4:read-entities -t",
514 showResponse=True,
515 noExit=True, expectJson=False)
516 tunn_peer = onosCli.sendline(cmdStr="up4:read-entities -g",
517 showResponse=True,
518 noExit=True, expectJson=False)
Daniele Morobf53dec2021-09-13 18:11:56 +0200519 fail = False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800520 for app_filter in self.app_filters.values():
521 if not UP4.__defaultApp(**app_filter):
Jon Halla4a79312022-01-25 17:16:53 -0800522 if len(re.findall(self.appFilterOnosString(**app_filter), applications)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800523 failMsg.append(self.appFilterOnosString(**app_filter))
524 fail = True
Daniele Moro522023c2021-10-15 17:30:33 +0200525 for (ue_name, ue) in self.emulated_ues.items():
Jon Halla4a79312022-01-25 17:16:53 -0800526 if len(re.findall(self.upUeSessionOnosString(**ue), sessions)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800527 failMsg.append(self.upUeSessionOnosString(**ue))
Daniele Morobf53dec2021-09-13 18:11:56 +0200528 fail = True
Jon Halla4a79312022-01-25 17:16:53 -0800529 if len(re.findall(self.downUeSessionOnosString(**ue), sessions)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800530 failMsg.append(self.downUeSessionOnosString(**ue))
Daniele Morobf53dec2021-09-13 18:11:56 +0200531 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800532 for app_filter in self.app_filters.values():
Jon Halla4a79312022-01-25 17:16:53 -0800533 if len(re.findall(self.upTerminationOnosString(app_filter=app_filter, **ue), terminations)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800534 failMsg.append(self.upTerminationOnosString(app_filter=app_filter, **ue))
535 fail = True
Jon Halla4a79312022-01-25 17:16:53 -0800536 if len(re.findall(self.downTerminationOnosString(app_filter=app_filter, **ue), terminations)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800537 failMsg.append(self.downTerminationOnosString(app_filter=app_filter, **ue))
538 fail = True
Jon Halla4a79312022-01-25 17:16:53 -0800539 if len(re.findall(self.gtpTunnelPeerOnosString(ue_name, **ue), tunn_peer)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800540 failMsg.append(self.gtpTunnelPeerOnosString(ue_name, **ue))
Daniele Morobf53dec2021-09-13 18:11:56 +0200541 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200542 return not fail
Daniele Morobf53dec2021-09-13 18:11:56 +0200543
Jon Halla4a79312022-01-25 17:16:53 -0800544 def appFilterOnosString(self, app_id=None, priority=None, slice_id=None, ip_proto=None, ip_prefix=None, port_range=None, **kwargs):
545 if app_id is None:
546 app_id = "\d+"
547 if priority is None:
548 priority = "\d+"
Jon Halla601dde2022-02-28 14:22:07 -0800549 if slice_id is None:
550 slice_id = self.slice_id
551 matches = []
552 if ip_prefix:
553 matches.append("ip_prefix=%s" % ip_prefix)
554 if port_range:
Jon Halla4a79312022-01-25 17:16:53 -0800555 matches.append("l4_port_range=\[%s\]" % port_range)
Jon Halla601dde2022-02-28 14:22:07 -0800556 if ip_proto:
557 matches.append("ip_proto=%s" % ip_proto)
558 matches.append("slice_id=%s" % slice_id)
559
Jon Halla4a79312022-01-25 17:16:53 -0800560 return "UpfApplication\(priority=%s, Match\(%s\) -> Action\(app_id=%s\)\)" % (
Daniele Morobef0c7e2022-02-16 17:47:13 -0800561 priority,
Jon Halla601dde2022-02-28 14:22:07 -0800562 ", ".join(matches),
Daniele Morobef0c7e2022-02-16 17:47:13 -0800563 app_id
564 )
565
Daniele Moro4650ffb2022-02-15 22:44:18 +0100566 def upUeSessionOnosString(self, teid=None, teid_up=None, sess_meter_idx=DEFAULT_SESSION_METER_IDX, **kwargs):
Jon Halla4a79312022-01-25 17:16:53 -0800567 if teid_up is None and teid is not None:
Daniele Morobf53dec2021-09-13 18:11:56 +0200568 teid_up = teid
Jon Halla4a79312022-01-25 17:16:53 -0800569 if sess_meter_idx is None:
570 sess_meter_idx = "\d+"
571 return "UpfSessionUL\(Match\(tun_dst_addr={}, teid={}\) -> Action\(FWD, session_meter_idx={}\)\)".format(
Daniele Moro4650ffb2022-02-15 22:44:18 +0100572 self.s1u_address, teid_up, sess_meter_idx)
Daniele Morobf53dec2021-09-13 18:11:56 +0200573
Daniele Moro49a843c2022-01-05 14:36:32 +0100574 def downUeSessionOnosString(self, ue_address, down_id=None,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800575 tunn_peer_id=None, sess_meter_idx=DEFAULT_SESSION_METER_IDX,
Daniele Moro49a843c2022-01-05 14:36:32 +0100576 **kwargs):
Daniele Morobf53dec2021-09-13 18:11:56 +0200577 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100578 tunn_peer_id = down_id
Jon Halla4a79312022-01-25 17:16:53 -0800579 if tunn_peer_id is None:
580 tunn_peer_id = "\d+"
581 if sess_meter_idx is None:
582 sess_meter_idx = "\d+"
583 return "UpfSessionDL\(Match\(ue_addr={}\) -> Action\(FWD, tun_peer={}, session_meter_idx={}\)\)".format(
Daniele Moro4650ffb2022-02-15 22:44:18 +0100584 ue_address, tunn_peer_id, sess_meter_idx)
Daniele Moro49a843c2022-01-05 14:36:32 +0100585
Daniele Morobef0c7e2022-02-16 17:47:13 -0800586 def upTerminationOnosString(self, ue_address, app_filter, up_id=None,
Daniele Moro4650ffb2022-02-15 22:44:18 +0100587 ctr_id_up=None, tc=None, app_meter_idx=DEFAULT_APP_METER_IDX, **kwargs):
Daniele Moro49a843c2022-01-05 14:36:32 +0100588 if up_id is not None:
589 ctr_id_up = up_id
Jon Halla4a79312022-01-25 17:16:53 -0800590 if ctr_id_up is None:
591 ctr_id_up = "\d+"
592 if tc is None or int(tc) == 0:
593 tc = "(?:0|null)"
594 if app_meter_idx is None:
595 app_meter_idx = "\d+"
596 app_id = app_filter["app_id"]
597 if app_id is None:
598 app_id = "\d+"
599 if app_filter["action"] == "deny":
600 return "UpfTerminationUL\(Match\(ue_addr={}, app_id={}\) -> Action\(DROP, ctr_id={}, tc=null, app_meter_idx=0\)\)".format(
601 ue_address, app_id, ctr_id_up)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800602 else:
Jon Halla4a79312022-01-25 17:16:53 -0800603 return "UpfTerminationUL\(Match\(ue_addr={}, app_id={}\) -> Action\(FWD, ctr_id={}, tc={}, app_meter_idx={}\)\)".format(
604 ue_address, app_id, ctr_id_up, tc, app_meter_idx)
Daniele Moro49a843c2022-01-05 14:36:32 +0100605
Daniele Morobef0c7e2022-02-16 17:47:13 -0800606 def downTerminationOnosString(self, ue_address, app_filter, teid=None,
Daniele Moro1f7ca6f2022-01-27 11:58:50 +0100607 down_id=None, ctr_id_down=None, teid_down=None,
Daniele Moro4650ffb2022-02-15 22:44:18 +0100608 tc=None, app_meter_idx=DEFAULT_APP_METER_IDX,
609 **kwargs):
Daniele Moro49a843c2022-01-05 14:36:32 +0100610 if down_id is not None:
Daniele Morobf53dec2021-09-13 18:11:56 +0200611 ctr_id_down = down_id
Jon Halla4a79312022-01-25 17:16:53 -0800612 if ctr_id_down is None:
613 ctr_id_down = "\d+"
614 if teid_down is None and teid is not None:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800615 teid_down = int(teid) + 1
Jon Halla4a79312022-01-25 17:16:53 -0800616 if tc is None or int(tc) == 0:
617 tc = "(?:0|null)"
618 if app_meter_idx is None:
619 app_meter_idx = "\d+"
620 app_id = app_filter["app_id"]
621 if app_id is None:
622 app_id = "\d+"
623 if app_filter["action"] == "deny":
624 return "UpfTerminationDL\(Match\(ue_addr={}, app_id={}\) -> Action\(DROP, teid=null, ctr_id={}, qfi=null, tc=null, app_meter_idx=0\)\)".format(
625 ue_address, app_id, ctr_id_down)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800626 else:
Jon Halla4a79312022-01-25 17:16:53 -0800627 return "UpfTerminationDL\(Match\(ue_addr={}, app_id={}\) -> Action\(FWD, teid={}, ctr_id={}, qfi={}, tc={}, app_meter_idx={}\)\)".format(
628 ue_address, app_id, teid_down, ctr_id_down, tc, tc, app_meter_idx)
Daniele Morobf53dec2021-09-13 18:11:56 +0200629
Daniele Moro49a843c2022-01-05 14:36:32 +0100630 def gtpTunnelPeerOnosString(self, ue_name, down_id=None, tunn_peer_id=None,
631 **kwargs):
632 if down_id is not None:
633 tunn_peer_id = down_id
Jon Halla4a79312022-01-25 17:16:53 -0800634 if tunn_peer_id is None:
635 tunn_peer_id = "\d+"
Daniele Moro49a843c2022-01-05 14:36:32 +0100636 enb_address = self.__getEnbAddress(ue_name)
Jon Halla4a79312022-01-25 17:16:53 -0800637 return "UpfGtpTunnelPeer\(tunn_peer_id={} -> src={}, dst={} src_port={}\)".format(
Daniele Moro49a843c2022-01-05 14:36:32 +0100638 tunn_peer_id, self.s1u_address, enb_address, GPDU_PORT)
Daniele Morobf53dec2021-09-13 18:11:56 +0200639
Daniele Moro80889562021-09-08 10:09:26 +0200640 @staticmethod
Jon Halla4a79312022-01-25 17:16:53 -0800641 def __sanitizeUeData(ue, smf=False):
Daniele Moro249d6e72021-09-20 10:32:54 +0200642 if "five_g" in ue and type(ue["five_g"]) != bool:
Daniele Moro80889562021-09-08 10:09:26 +0200643 ue["five_g"] = bool(strtobool(ue["five_g"]))
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800644 if "tc" in ue and ue["tc"] == "":
645 ue["tc"] = 0
Jon Halla4a79312022-01-25 17:16:53 -0800646 if smf:
647 ue["up_id"] = None
648 ue["down_id"] = None
Daniele Moro80889562021-09-08 10:09:26 +0200649 return ue
650
Daniele Morobef0c7e2022-02-16 17:47:13 -0800651 def insertAppFilter(self, **kwargs):
652 if not UP4.__defaultApp(**kwargs):
653 self.__programAppFilter(op="program", **kwargs)
654
655 def removeAppFilter(self, **kwargs):
656 if not UP4.__defaultApp(**kwargs):
657 self.__programAppFilter(op="clear", **kwargs)
658
Jon Halla4a79312022-01-25 17:16:53 -0800659 def smfAttachUe(self, ue_name, ue_address, prefix_len=31, seid=1, teid=None, sdf=[], **kwargs):
660 enb_addr = None
661 for enb_name, enb in self.enodebs.items():
662 if ue_name in enb["ues"]:
663 enb_addr = enb["enb_address"]
664 break
665 assert enb_addr, "Unknown eNodeB address"
666 ue_address = str(ip.ip_address(unicode(ue_address)) - 1)
667 base = seid
668
669 create = self.mock_smf.create(ue_pool="%s/%s" % (ue_address, prefix_len),
670 gnb_addr=enb_addr,
671 base_id=base,
672 sdf=sdf)
673 modify = self.mock_smf.modify(ue_pool="%s/%s" % (ue_address, prefix_len),
674 gnb_addr=enb_addr,
675 base_id=seid)
676 self.emulated_ues[ue_name]['seid'] = base
677 self.emulated_ues[ue_name]['teid'] = base
678 self.emulated_ues[ue_name]['sess_meter_idx'] = None
679 self.emulated_ues[ue_name]['app_meter_idx'] = None
680 return create and modify
681
682 def smfDetachUe(self, ue_name, ue_address, prefix_len=31, seid=1, teid=None, **kwargs):
683 enb_addr = None
684 for enb_name, enb in self.enodebs.items():
685 if ue_name in enb["ues"]:
686 enb_addr = enb["enb_address"]
687 break
688 assert enb_addr, "Unknown eNodeB address"
689 ue_address = str( ip.ip_address( unicode( ue_address ) ) - 1 )
690
691 return self.mock_smf.delete( base_id=seid )
692
Daniele Moro49a843c2022-01-05 14:36:32 +0100693 def attachUe(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200694 teid=None, up_id=None, down_id=None,
695 teid_up=None, teid_down=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100696 ctr_id_up=None, ctr_id_down=None,
697 tunn_peer_id=None,
Jon Halla4a79312022-01-25 17:16:53 -0800698 tc=None, five_g=False, **kwargs):
Daniele Morobef0c7e2022-02-16 17:47:13 -0800699 self.__programUeRules(ue_name,
700 ue_address,
701 teid, up_id, down_id,
702 teid_up, teid_down,
703 ctr_id_up, ctr_id_down,
704 tunn_peer_id,
705 tc, five_g, op="program")
Daniele Moro80889562021-09-08 10:09:26 +0200706
Daniele Moro49a843c2022-01-05 14:36:32 +0100707 def detachUe(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200708 teid=None, up_id=None, down_id=None,
709 teid_up=None, teid_down=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100710 ctr_id_up=None, ctr_id_down=None,
711 tunn_peer_id=None,
Jon Halla4a79312022-01-25 17:16:53 -0800712 tc=None, five_g=False, **kwargs):
Daniele Morobef0c7e2022-02-16 17:47:13 -0800713 self.__programUeRules(ue_name,
714 ue_address,
715 teid, up_id, down_id,
716 teid_up, teid_down,
717 ctr_id_up, ctr_id_down,
718 tunn_peer_id,
719 tc, five_g, op="clear")
Daniele Moro80889562021-09-08 10:09:26 +0200720
Jon Halla601dde2022-02-28 14:22:07 -0800721 def __programAppFilter(self, app_id, slice_id, ip_prefix=None, ip_proto=None,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800722 port_range=None, priority=0, op="program", **kwargs):
723
724 entries = []
725
726 tableName = 'PreQosPipe.applications'
727 actionName = 'PreQosPipe.set_app_id'
728 actionParams = {'app_id': str(app_id)}
Daniele Morocc4ecda2022-02-25 23:27:25 +0100729 matchFields = {
Jon Halla601dde2022-02-28 14:22:07 -0800730 'slice_id': str(slice_id)
Daniele Morocc4ecda2022-02-25 23:27:25 +0100731 }
Daniele Morobef0c7e2022-02-16 17:47:13 -0800732 if ip_prefix:
733 matchFields['app_ip_addr'] = str(ip_prefix)
734 if ip_proto:
735 matchFields['app_ip_proto'] = str(ip_proto)
736 if port_range:
737 matchFields['app_l4_port'] = str(port_range)
738
739 if not self.__add_entry(tableName, actionName, matchFields,
740 actionParams, entries, op, priority):
741 return False
742
743 if op == "program":
744 main.log.info("Application entry added successfully.")
745 elif op == "clear":
746 self.__clear_entries(entries)
747
Jon Halla4a79312022-01-25 17:16:53 -0800748 def __pfcpSDFString(self, ip_prefix=None, ip_proto=None, port_range=None,
749 action="allow", **kwargs):
750 if ip_proto is None or str(ip_proto) == "255":
751 protoString = "ip"
752 elif str(ip_proto) == "6":
753 descList.append("tcp")
754 elif str(ip_proto) == "17":
755 protoString = "udp"
756 else:
757 # TODO: is there a python library for this? Can we just pass the number?
758 raise NotImplemented
759 if port_range is None:
760 port_range = "any"
761 if ip_prefix is None:
762 ip_prefix = "any"
763 return "--app-filter '%s:%s:%s:%s'" % ( protoString,
764 ip_prefix,
765 port_range.replace("..","-"),
766 action )
767
Daniele Morobef0c7e2022-02-16 17:47:13 -0800768 def __programUeRules(self, ue_name, ue_address,
769 teid=None, up_id=None, down_id=None,
770 teid_up=None, teid_down=None, ctr_id_up=None,
771 ctr_id_down=None, tunn_peer_id=None,
772 tc=0, five_g=False,
773 op="program"):
Daniele Moro80889562021-09-08 10:09:26 +0200774 if up_id is not None:
Daniele Moro80889562021-09-08 10:09:26 +0200775 ctr_id_up = up_id
776 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100777 tunn_peer_id = down_id
Daniele Moro80889562021-09-08 10:09:26 +0200778 ctr_id_down = down_id
779 if teid is not None:
780 teid_up = teid
Daniele Morobef0c7e2022-02-16 17:47:13 -0800781 teid_down = int(teid) + 1
Daniele Moro80889562021-09-08 10:09:26 +0200782
783 entries = []
784
Daniele Moro522023c2021-10-15 17:30:33 +0200785 # Retrieve eNobeB address from eNodeB list
786 enb_address = self.__getEnbAddress(ue_name)
787
Daniele Moro80889562021-09-08 10:09:26 +0200788 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100789 # UE Session Entries
Daniele Moro80889562021-09-08 10:09:26 +0200790 # ========================#
791
792 # Uplink
Daniele Moro49a843c2022-01-05 14:36:32 +0100793 tableName = 'PreQosPipe.sessions_uplink'
794 actionName = 'PreQosPipe.set_session_uplink'
Daniele Moro80889562021-09-08 10:09:26 +0200795 matchFields = {}
Daniele Morobef0c7e2022-02-16 17:47:13 -0800796 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200797 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100798 matchFields['n3_address'] = str(self.s1u_address)
Daniele Moro80889562021-09-08 10:09:26 +0200799 matchFields['teid'] = str(teid_up)
Daniele Moro4650ffb2022-02-15 22:44:18 +0100800 # Action params
801 actionParams["session_meter_idx"] = str(DEFAULT_SESSION_METER_IDX)
Daniele Moro49a843c2022-01-05 14:36:32 +0100802 if five_g:
803 # TODO: currently QFI match is unsupported in TNA
804 main.log.warn("Matching on QFI is currently unsupported in TNA")
Daniele Moro80889562021-09-08 10:09:26 +0200805 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800806 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200807 return False
808
809 # Downlink
Daniele Moro49a843c2022-01-05 14:36:32 +0100810 tableName = 'PreQosPipe.sessions_downlink'
811 actionName = 'PreQosPipe.set_session_downlink'
Daniele Moro80889562021-09-08 10:09:26 +0200812 matchFields = {}
813 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200814 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100815 matchFields['ue_address'] = str(ue_address)
Daniele Moro80889562021-09-08 10:09:26 +0200816 # Action params
Daniele Moro49a843c2022-01-05 14:36:32 +0100817 actionParams['tunnel_peer_id'] = str(tunn_peer_id)
Daniele Moro4650ffb2022-02-15 22:44:18 +0100818 actionParams["session_meter_idx"] = str(DEFAULT_SESSION_METER_IDX)
Daniele Moro80889562021-09-08 10:09:26 +0200819 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800820 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200821 return False
822
823 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100824 # Terminations Entries
Daniele Moro80889562021-09-08 10:09:26 +0200825 # ========================#
826
Daniele Morobef0c7e2022-02-16 17:47:13 -0800827 # Insert one termination entry per app filtering rule,
Daniele Moro80889562021-09-08 10:09:26 +0200828
Daniele Morobef0c7e2022-02-16 17:47:13 -0800829 # Uplink
830 for f in self.app_filters.values():
831 tableName = 'PreQosPipe.terminations_uplink'
832 matchFields = {}
833 actionParams = {}
834
835 # Match fields
836 matchFields['ue_address'] = str(ue_address)
837 matchFields['app_id'] = str(f["app_id"])
838
839 # Action params
840 if f['action'] == 'allow':
841 actionName = 'PreQosPipe.uplink_term_fwd'
842 actionParams['app_meter_idx'] = str(DEFAULT_APP_METER_IDX)
843 actionParams['tc'] = str(tc)
844 else:
845 actionName = 'PreQosPipe.uplink_term_drop'
846 actionParams['ctr_idx'] = str(ctr_id_up)
847 if not self.__add_entry(
848 tableName, actionName, matchFields, actionParams, entries, op
849 ):
850 return False
Daniele Moro80889562021-09-08 10:09:26 +0200851
852 # Downlink
Daniele Morobef0c7e2022-02-16 17:47:13 -0800853 for f in self.app_filters.values():
854 tableName = 'PreQosPipe.terminations_downlink'
855 matchFields = {}
856 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200857
Daniele Morobef0c7e2022-02-16 17:47:13 -0800858 # Match fields
859 matchFields['ue_address'] = str(ue_address)
860 matchFields['app_id'] = str(f["app_id"])
861
862 # Action params
863 if f['action'] == 'allow':
864 actionName = 'PreQosPipe.downlink_term_fwd'
865 actionParams['teid'] = str(teid_down)
866 # 1-1 mapping between QFI and TC
867 actionParams['tc'] = str(tc)
868 actionParams['qfi'] = str(tc)
869 actionParams['app_meter_idx'] = str(DEFAULT_APP_METER_IDX)
870 else:
871 actionName = 'PreQosPipe.downlink_term_drop'
872 actionParams['ctr_idx'] = str(ctr_id_down)
873
874 if not self.__add_entry(tableName, actionName, matchFields,
875 actionParams, entries, op):
876 return False
Daniele Moro49a843c2022-01-05 14:36:32 +0100877
878 # ========================#
879 # Tunnel Peer Entry
880 # ========================#
881 tableName = 'PreQosPipe.tunnel_peers'
882 actionName = 'PreQosPipe.load_tunnel_param'
883 matchFields = {}
884 actionParams = {}
885 # Match fields
886 matchFields['tunnel_peer_id'] = str(tunn_peer_id)
887 # Action params
Daniele Moro80889562021-09-08 10:09:26 +0200888 actionParams['src_addr'] = str(self.s1u_address)
Daniele Moro522023c2021-10-15 17:30:33 +0200889 actionParams['dst_addr'] = str(enb_address)
Daniele Moro80889562021-09-08 10:09:26 +0200890 actionParams['sport'] = TUNNEL_SPORT
891 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800892 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200893 return False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800894 if op == "program":
Daniele Moro80889562021-09-08 10:09:26 +0200895 main.log.info("All entries added successfully.")
Daniele Morobef0c7e2022-02-16 17:47:13 -0800896 elif op == "clear":
Daniele Moro80889562021-09-08 10:09:26 +0200897 self.__clear_entries(entries)
898
899 def __add_entry(self, tableName, actionName, matchFields, actionParams,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800900 entries, op, priority=0):
901 if op == "program":
Daniele Moro80889562021-09-08 10:09:26 +0200902 self.up4_client.buildP4RtTableEntry(
903 tableName=tableName, actionName=actionName,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800904 actionParams=actionParams, matchFields=matchFields, priority=priority)
Daniele Moro80889562021-09-08 10:09:26 +0200905 if self.up4_client.pushTableEntry(debug=True) == main.TRUE:
906 main.log.info("*** Entry added.")
907 else:
908 main.log.error("Error during table insertion")
909 self.__clear_entries(entries)
910 return False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800911 entries.append({
912 "tableName": tableName, "actionName": actionName,
913 "matchFields": matchFields,
914 "actionParams": actionParams,
915 "priority": priority
916 })
Daniele Moro80889562021-09-08 10:09:26 +0200917 return True
918
919 def __clear_entries(self, entries):
920 for i, entry in enumerate(entries):
921 self.up4_client.buildP4RtTableEntry(**entry)
922 if self.up4_client.deleteTableEntry(debug=True) == main.TRUE:
923 main.log.info(
924 "*** Entry %d of %d deleted." % (i + 1, len(entries)))
925 else:
926 main.log.error("Error during table delete")
Daniele Morobf53dec2021-09-13 18:11:56 +0200927
Daniele Moro522023c2021-10-15 17:30:33 +0200928 def __getEnbAddress(self, ue_name):
929 for enb in self.enodebs.values():
930 if ue_name in enb["ues"]:
931 return enb["enb_address"]
932 main.log.error("Missing eNodeB address!")
933 return ""
934
Daniele Morobf53dec2021-09-13 18:11:56 +0200935 @staticmethod
936 def buildGtpPacket(host, src_ip_outer, dst_ip_outer, src_ip_inner,
937 dst_ip_inner, src_udp_inner, dst_udp_inner, teid):
938 host.buildEther()
939 host.buildIP(src=src_ip_outer, dst=dst_ip_outer)
940 host.buildUDP(ipVersion=4, dport=GPDU_PORT)
941 # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
942 host.buildGTP(gtp_type=0xFF, TEID=teid)
943 host.buildIP(overGtp=True, src=src_ip_inner, dst=dst_ip_inner)
944 host.buildUDP(ipVersion=4, overGtp=True, sport=src_udp_inner,
945 dport=dst_udp_inner)
946
947 @staticmethod
948 def buildUdpPacket(host, src_ip, dst_ip, src_udp, dst_udp, src_eth=None,
949 dst_eth=None):
950 host.buildEther(src=src_eth, dst=dst_eth)
951 host.buildIP(src=src_ip, dst=dst_ip)
952 host.buildUDP(ipVersion=4, sport=src_udp, dport=dst_udp)
953
954 @staticmethod
955 def checkFilterAndGetPackets(host):
956 finished = host.checkFilter()
957 if finished:
958 packets = host.readPackets(detailed=True)
959 for p in packets.splitlines():
960 main.log.debug(p)
961 # We care only of the last line from readPackets
962 return packets.splitlines()[-1]
963 else:
964 kill = host.killFilter()
965 main.log.debug(kill)
966 return ""
Daniele Morobef0c7e2022-02-16 17:47:13 -0800967
968 @staticmethod
969 def __defaultApp(ip_prefix=None, ip_proto=None, port_range=None, **kwargs):
970 if ip_prefix is None and ip_proto is None and port_range is None:
971 return True
972 return False
973