blob: 461fe1526f7364637ce790b384be781a4670a3c4 [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>
Daniele Morob8404e82022-02-25 00:17:28 +010062 <max_bps>200000000</max_bps>
Daniele Moro80889562021-09-08 10:09:26 +020063 </ue2>
64 </ues>
Daniele Moro522023c2021-10-15 17:30:33 +020065 <switch_to_kill>Leaf2</switch_to_kill> # Component name of the switch to kill in CASE 5
66 <enodebs_fail>enodeb_1</enodebs_fail> # List of eNodeBs that should fail traffic forwarding in CASE 5
Daniele Moro80889562021-09-08 10:09:26 +020067 </UP4>
68 """
69
70 def __init__(self):
71 self.s1u_address = None
Daniele Moro522023c2021-10-15 17:30:33 +020072 self.enodebs = None
Daniele Moro80889562021-09-08 10:09:26 +020073 self.pdn_host = None
74 self.pdn_interface = None
75 self.router_mac = None
Daniele Moro522023c2021-10-15 17:30:33 +020076 self.emulated_ues = {}
Jon Halla4a79312022-01-25 17:16:53 -080077 self.DEFAULT_APP = {DEFAULT_APP_NAME: {'ip_prefix': None, 'ip_proto': None, 'port_range': None,
78 'app_id': DEFAULT_APP_ID, 'action': 'allow' } }
79 self.app_filters = self.DEFAULT_APP
Daniele Moro80889562021-09-08 10:09:26 +020080 self.up4_client = None
Jon Halla4a79312022-01-25 17:16:53 -080081 self.mock_smf = None
Daniele Moro522023c2021-10-15 17:30:33 +020082 self.no_host = False
Daniele Morocc4ecda2022-02-25 23:27:25 +010083 self.slice_id = None
Jon Halla4a79312022-01-25 17:16:53 -080084 self.next_seid = 1
Daniele Moro80889562021-09-08 10:09:26 +020085
Jon Halla4a79312022-01-25 17:16:53 -080086 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 +020087 """
Jon Halla4a79312022-01-25 17:16:53 -080088 Set up P4RT and scapy on eNB and PDN hosts. If mock_smf is not None, will also connect
89 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 +020090 :param p4rt_client: a P4RuntimeCliDriver component
Jon Halla4a79312022-01-25 17:16:53 -080091 :param mock_smf: The optional mocksmfdriver component to use
Daniele Moro954e2282021-09-22 17:32:03 +020092 :param no_host: True if you don't want to start scapy on the hosts
Jon Halla4a79312022-01-25 17:16:53 -080093 :param pfcpAddress: Address of the PFCP agent to connect to when using mock smf
94 :param pfcpPort: Port of the PFCP agent to connect to when using mock smf
95 :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 +020096 :return:
97 """
Daniele Moro80889562021-09-08 10:09:26 +020098 self.s1u_address = main.params["UP4"]["s1u_address"]
Daniele Moro80889562021-09-08 10:09:26 +020099 self.emulated_ues = main.params["UP4"]['ues']
Jon Halla4a79312022-01-25 17:16:53 -0800100 self.app_filters = main.params["UP4"]['app_filters'] if app_filters else self.DEFAULT_APP
Daniele Morocc4ecda2022-02-25 23:27:25 +0100101 self.slice_id = main.params["UP4"]['slice_id']
Daniele Moro80889562021-09-08 10:09:26 +0200102 self.up4_client = p4rt_client
Jon Halla4a79312022-01-25 17:16:53 -0800103 self.mock_smf = mock_smf
Daniele Moro522023c2021-10-15 17:30:33 +0200104 self.no_host = no_host
Daniele Moro80889562021-09-08 10:09:26 +0200105
106 # Optional Parameters
Daniele Moro522023c2021-10-15 17:30:33 +0200107
108 self.enodebs = copy.deepcopy((main.params["UP4"]["enodebs"]))
109 for enb in self.enodebs.values():
110 enb["ues"] = enb["ues"].split(",")
111 enb["host"] = getattr(main, enb["host"])
112 # If interface not provided by the params, use the default in the host
113 if "interface" not in enb.keys():
114 enb["interface"] = enb["host"].interfaces[0]["name"]
115 if "pdn_host" in main.params["UP4"]:
116 self.pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
117 self.pdn_interface = self.pdn_host.interfaces[0]
118 self.router_mac = main.params["UP4"].get("router_mac", None)
Daniele Moro80889562021-09-08 10:09:26 +0200119
Jon Halla601dde2022-02-28 14:22:07 -0800120 for app_filter in self.app_filters.values():
121 if app_filter.get('slice_id', None) is None:
122 app_filter['slice_id'] = self.slice_id
Daniele Moro80889562021-09-08 10:09:26 +0200123 # Start components
Jon Halla4a79312022-01-25 17:16:53 -0800124 if self.up4_client:
125 self.up4_client.startP4RtClient()
126 if self.mock_smf:
127 if not pfcpAddress:
128 pfcpAddress = self.mock_smf.kubectlGetServiceIP( "pfcp-agent" )
Jon Hall909fc242022-03-08 16:07:30 -0800129 self.startMockSmfPcap(self.mock_smf)
Jon Halla4a79312022-01-25 17:16:53 -0800130 self.mock_smf.startSMF()
131 self.mock_smf.configure(self.s1u_address, pfcpAddress, pfcpPort)
132 # TODO Start pcap on mock_smf host
133 self.mock_smf.associate()
Daniele Moro522023c2021-10-15 17:30:33 +0200134 if not self.no_host:
135 if self.enodebs is not None:
136 for enb in self.enodebs.values():
137 enb["host"].startScapy(ifaceName=enb["interface"],
Daniele Moro49a843c2022-01-05 14:36:32 +0100138 enableGtp=True)
Daniele Moro522023c2021-10-15 17:30:33 +0200139 if self.pdn_host is not None:
140 self.pdn_host.startScapy(ifaceName=self.pdn_interface["name"])
Daniele Moro80889562021-09-08 10:09:26 +0200141
Jon Hall909fc242022-03-08 16:07:30 -0800142 def startMockSmfPcap(self, smfComponent, pcapIface="eth0"):
143 compName = "smf-pcap"
144 # Create another component/bash session for tshark
145 main.Network.copyComponent(smfComponent.name, compName)
146 pcap = getattr(main, compName)
147 pcapFile = "%s/CASE%s-%s" % (main.logdir, main.CurrentTestCaseNumber, compName)
148 commands = ['touch %s.pcap' % pcapFile,
149 'chmod o=rw %s.pcap' % pcapFile]
150 for command in commands:
151 pcap.handle.sendline(command)
152 pcap.handle.expect(pcap.prompt)
153 main.log.debug("%s: %s" % (pcap.name, str(pcap.handle.before)))
154 pcap.handle.sendline("sudo /usr/bin/tshark -i %s -w %s.pcap &> %s.log" % (pcapIface, pcapFile, pcapFile))
155 i = pcap.handle.expect(["password", pexpect.TIMEOUT], timeout=5)
156 if i == 0:
157 pcap.handle.sendline(pcap.pwd if pcap.pwd else "jenkins")
158 pcap.preDisconnect = pcap.exitFromProcess
159
Daniele Moro80889562021-09-08 10:09:26 +0200160 def teardown(self):
Jon Halla4a79312022-01-25 17:16:53 -0800161 if self.up4_client:
162 self.up4_client.stopP4RtClient()
163 if self.mock_smf:
164 self.mock_smf.disassociate()
165 self.mock_smf.stop()
Daniele Moro522023c2021-10-15 17:30:33 +0200166 if not self.no_host:
167 if self.enodebs is not None:
168 for enb in self.enodebs.values():
169 enb["host"].stopScapy()
170 if self.pdn_host is not None:
171 self.pdn_host.stopScapy()
Daniele Moro80889562021-09-08 10:09:26 +0200172
173 def attachUes(self):
Jon Halla4a79312022-01-25 17:16:53 -0800174 filter_desc = []
175 for app_name, app_filter in sorted(self.app_filters.items(), key=lambda (k, v): int(v.get('priority', 0))):
176 if self.mock_smf:
177 if app_name != DEFAULT_APP_NAME:
178 filter_desc.append(self.__pfcpSDFString(**app_filter))
179 self.app_filters[app_name]['app_id'] = None
180 self.app_filters[app_name]['priority'] = None
181
182 else:
183 self.insertAppFilter(**app_filter)
184 for (ue_name, ue) in self.emulated_ues.items():
185 ue = UP4.__sanitizeUeData(ue, smf=True if self.mock_smf else False)
186 if self.mock_smf:
187 if not ue.get("seid"):
188 ue["seid"] = self.next_seid
189 self.next_seid = self.next_seid + 1
190 self.emulated_ues[ue_name]["seid"] = ue["seid"]
191 self.smfAttachUe(ue_name, sdf=filter_desc[-1:], **ue) # TODO: pass whole filter list once multi app support is fixed
192 else:
193 self.attachUe(ue_name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200194
195 def detachUes(self):
Jon Halla4a79312022-01-25 17:16:53 -0800196 if not self.mock_smf:
197 for app_filter in self.app_filters.values():
198 self.removeAppFilter(**app_filter)
199 for (ue_name, ue) in self.emulated_ues.items():
200 ue = UP4.__sanitizeUeData(ue, smf=True if self.mock_smf else False)
201 if self.mock_smf:
202 self.smfDetachUe(ue_name, **ue)
203 else:
204 self.detachUe(ue_name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200205
Daniele Morobef0c7e2022-02-16 17:47:13 -0800206 def __pickPortFromRange(self, range):
207 if range is None or len(range) == 0:
208 return DEFAULT_PDN_PORT
209 # First port in range
210 return int(range.split('..')[0])
211
212 def testUpstreamTraffic(self, enb_names=None, app_filter_name=None, shouldFail=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200213 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200214 main.log.error(
215 "Need eNodeB and PDN host params to generate scapy traffic")
216 return
Daniele Moro522023c2021-10-15 17:30:33 +0200217 if enb_names is None or enb_names == []:
218 enodebs = self.enodebs.values()
219 else:
220 enodebs = [self.enodebs[enb] for enb in enb_names]
Daniele Morobef0c7e2022-02-16 17:47:13 -0800221
Jon Halla4a79312022-01-25 17:16:53 -0800222 port_range = None
223 app_filter_should_drop = False
224 if app_filter_name and app_filter_name != DEFAULT_APP_NAME:
225 app_filter = self.app_filters[app_filter_name]
226 port_range = app_filter.get("port_range", None)
227 app_filter_should_drop = app_filter["action"] != "allow"
228 pdn_port = self.__pickPortFromRange(port_range)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800229
Daniele Moro80889562021-09-08 10:09:26 +0200230 pkt_filter_upstream = ""
Daniele Moro522023c2021-10-15 17:30:33 +0200231 ues = []
232 for enb in enodebs:
233 for ue_name in enb["ues"]:
234 ue = self.emulated_ues[ue_name]
235 if "ue_address" in ue:
236 ues.append(ue)
237 if len(pkt_filter_upstream) != 0:
238 pkt_filter_upstream += " or "
239 pkt_filter_upstream += "src host " + ue["ue_address"]
Daniele Moro80889562021-09-08 10:09:26 +0200240 pkt_filter_upstream = "ip and udp dst port %s and (%s) and dst host %s" % \
Daniele Morobef0c7e2022-02-16 17:47:13 -0800241 (pdn_port, pkt_filter_upstream,
Daniele Moro80889562021-09-08 10:09:26 +0200242 self.pdn_interface["ips"][0])
243 main.log.info("Start listening on %s intf %s" %
244 (self.pdn_host.name, self.pdn_interface["name"]))
245 main.log.debug("BPF Filter Upstream: \n %s" % pkt_filter_upstream)
246 self.pdn_host.startFilter(ifaceName=self.pdn_interface["name"],
Daniele Moro522023c2021-10-15 17:30:33 +0200247 sniffCount=len(ues),
Daniele Moro80889562021-09-08 10:09:26 +0200248 pktFilter=pkt_filter_upstream)
249
250 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200251 "Sending %d packets from eNodeB host" % len(ues))
252 for enb in enodebs:
253 for ue_name in enb["ues"]:
254 main.log.info(ue_name)
255 ue = self.emulated_ues[ue_name]
256 main.log.info(str(ue))
257 UP4.buildGtpPacket(enb["host"],
258 src_ip_outer=enb["enb_address"],
259 dst_ip_outer=self.s1u_address,
260 src_ip_inner=ue["ue_address"],
261 dst_ip_inner=self.pdn_interface["ips"][0],
262 src_udp_inner=UE_PORT,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800263 dst_udp_inner=pdn_port,
Daniele Moro522023c2021-10-15 17:30:33 +0200264 teid=int(ue["teid"]))
265 enb["host"].sendPacket(iface=enb["interface"])
Daniele Moro80889562021-09-08 10:09:26 +0200266
Daniele Morobf53dec2021-09-13 18:11:56 +0200267 packets = UP4.checkFilterAndGetPackets(self.pdn_host)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800268 if app_filter_should_drop:
269 expected_pkt_count = 0
270 else:
271 # We expect exactly 1 packet per UE.
272 expected_pkt_count = len(ues)
273 actual_pkt_count = packets.count('Ether')
Daniele Moro80889562021-09-08 10:09:26 +0200274 fail = False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800275 if expected_pkt_count != actual_pkt_count:
Daniele Moro80889562021-09-08 10:09:26 +0200276 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800277 msg = "Received %d packets (expected %d)\n%s\n" % (
278 actual_pkt_count, expected_pkt_count, str(packets)
279 )
Daniele Moro80889562021-09-08 10:09:26 +0200280 else:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800281 msg = "Received %d packets (expected %d)\n" % (
282 actual_pkt_count, expected_pkt_count
283 )
Daniele Moro80889562021-09-08 10:09:26 +0200284
Daniele Morobef0c7e2022-02-16 17:47:13 -0800285 if expected_pkt_count > 0:
286 # Make sure the captured packets are from the expected UE addresses.
287 for ue in ues:
288 ue_pkt_count = packets.count("src=" + ue["ue_address"])
289 if ue_pkt_count != 1:
290 fail = True
291 msg += "Received %d packet(s) from UE %s (expected 1)\n" % (
292 ue_pkt_count, ue["ue_address"]
293 )
Daniele Moro80889562021-09-08 10:09:26 +0200294 utilities.assert_equal(
Daniele Morobef0c7e2022-02-16 17:47:13 -0800295 expect=shouldFail, actual=fail, onpass=msg, onfail=msg
296 )
Daniele Moro80889562021-09-08 10:09:26 +0200297
Daniele Morobef0c7e2022-02-16 17:47:13 -0800298 def testDownstreamTraffic(self, enb_names=None, app_filter_name=None, shouldFail=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200299 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200300 main.log.error(
301 "Need eNodeB and PDN host params to generate scapy traffic")
302 return
Daniele Moro522023c2021-10-15 17:30:33 +0200303 if enb_names is None or enb_names == []:
304 enodebs = self.enodebs.values()
305 else:
306 enodebs = [self.enodebs[enb] for enb in enb_names]
Daniele Morobef0c7e2022-02-16 17:47:13 -0800307
Jon Halla4a79312022-01-25 17:16:53 -0800308 port_range = None
309 app_filter_should_drop = False
310 if app_filter_name and app_filter_name != DEFAULT_APP_NAME:
311 app_filter = self.app_filters[app_filter_name]
312 port_range = app_filter.get("port_range", None)
313 app_filter_should_drop = app_filter["action"] != "allow"
314 pdn_port = self.__pickPortFromRange(port_range)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800315
Daniele Moro522023c2021-10-15 17:30:33 +0200316 pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s" % (
317 GPDU_PORT, GPDU_PORT, self.s1u_address)
318 ues = []
319 for enb in enodebs:
Daniele Moro49a843c2022-01-05 14:36:32 +0100320 filter_down = pkt_filter_downstream + " and dst host %s" % enb[
321 "enb_address"]
Daniele Moro522023c2021-10-15 17:30:33 +0200322 main.log.info("Start listening on %s intf %s" % (
323 enb["host"], enb["interface"]))
324 main.log.debug("BPF Filter Downstream: \n %s" % filter_down)
325 enb["host"].startFilter(ifaceName=enb["interface"],
326 sniffCount=len(enb["ues"]),
327 pktFilter=filter_down)
328 ues.extend([self.emulated_ues[ue_name] for ue_name in enb["ues"]])
Daniele Moro80889562021-09-08 10:09:26 +0200329
330 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200331 "Sending %d packets from PDN host" % len(ues))
332 for ue in ues:
Daniele Moro80889562021-09-08 10:09:26 +0200333 # From PDN we have to set dest MAC, otherwise scapy will do ARP
334 # request for the UE IP address.
Daniele Morobf53dec2021-09-13 18:11:56 +0200335 UP4.buildUdpPacket(self.pdn_host,
336 dst_eth=self.router_mac,
337 src_ip=self.pdn_interface["ips"][0],
338 dst_ip=ue["ue_address"],
Daniele Morobef0c7e2022-02-16 17:47:13 -0800339 src_udp=pdn_port,
Daniele Morobf53dec2021-09-13 18:11:56 +0200340 dst_udp=UE_PORT)
Daniele Moro80889562021-09-08 10:09:26 +0200341 self.pdn_host.sendPacket(iface=self.pdn_interface["name"])
Daniele Moro522023c2021-10-15 17:30:33 +0200342 packets = ""
343 for enb in enodebs:
344 pkt = UP4.checkFilterAndGetPackets(enb["host"])
345 packets += pkt
Daniele Moro80889562021-09-08 10:09:26 +0200346 # The BPF filter might capture non-GTP packets because we can't filter
347 # GTP header in BPF. For this reason, check that the captured packets
348 # are from the expected tunnels.
Daniele Morobef0c7e2022-02-16 17:47:13 -0800349 # TODO: check inner IP fields as well
Daniele Moro80889562021-09-08 10:09:26 +0200350 # FIXME: with newer scapy TEID becomes teid (required for Scapy 2.4.5)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800351 downlink_teids = [int(ue["teid"]) + 1 for ue in ues]
352 # Number of GTP packets from expected TEID per UEs
353 gtp_pkts = [
354 packets.count("TEID=" + hex(teid) + "L ") for teid in downlink_teids
355 ]
356 # Number of packets from the expected PDN port
357 app_pkts = packets.count("UDP sport=" + str(pdn_port))
358 if app_filter_should_drop:
359 expected_pkt_count = 0
360 else:
361 # We expect exactly 1 packet per UE.
362 expected_pkt_count = len(ues)
363
Daniele Moro80889562021-09-08 10:09:26 +0200364 fail = False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800365 if expected_pkt_count != sum(gtp_pkts):
Daniele Moro80889562021-09-08 10:09:26 +0200366 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800367 msg = "Received %d packets (expected %d) from TEIDs %s\n%s\n" % (
368 sum(gtp_pkts), expected_pkt_count, downlink_teids, str(packets)
369 )
Daniele Moro80889562021-09-08 10:09:26 +0200370 else:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800371 msg = "Received %d packets (expected %d) from TEIDs %s\n" % (
372 sum(gtp_pkts), expected_pkt_count, downlink_teids
373 )
374 if expected_pkt_count != app_pkts:
Daniele Moro80889562021-09-08 10:09:26 +0200375 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800376 msg += "Received %d packets (expected %d) from PDN port %s\n%s\n" % (
377 sum(gtp_pkts), expected_pkt_count, pdn_port, str(packets)
378 )
Daniele Moro80889562021-09-08 10:09:26 +0200379 else:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800380 msg += "Received %d packets (expected %d) from PDN port %s\n" % (
381 sum(gtp_pkts), expected_pkt_count, pdn_port
382 )
383 if expected_pkt_count > 0:
384 if gtp_pkts.count(1) != len(gtp_pkts):
385 fail = True
386 msg += "Received %s packet(s) per UE (expected %s)\n%s\n" % (
387 gtp_pkts, [1] * len(gtp_pkts), packets
388 )
389 else:
390 msg += "Received %s packet(s) per UE (expected %s)\n" % (
391 gtp_pkts, [1] * len(gtp_pkts)
392 )
Daniele Moro80889562021-09-08 10:09:26 +0200393
394 utilities.assert_equal(
Daniele Morobef0c7e2022-02-16 17:47:13 -0800395 expect=shouldFail, actual=fail, onpass=msg, onfail=msg
396 )
Daniele Moro80889562021-09-08 10:09:26 +0200397
Daniele Moro49a843c2022-01-05 14:36:32 +0100398 def readUeSessionsNumber(self):
Daniele Moro954e2282021-09-22 17:32:03 +0200399 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100400 Read the UE session tables and return the number of entries
Daniele Moro954e2282021-09-22 17:32:03 +0200401
Daniele Moro49a843c2022-01-05 14:36:32 +0100402 :return: Number of entries in the UE session tables
Daniele Moro954e2282021-09-22 17:32:03 +0200403 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100404 tableName = 'PreQosPipe.sessions_uplink'
405 nUeSess = self.up4_client.readNumberTableEntries(tableName)
406 tableName = 'PreQosPipe.sessions_downlink'
407 nUeSess += self.up4_client.readNumberTableEntries(tableName)
408 return nUeSess
Daniele Moro954e2282021-09-22 17:32:03 +0200409
Daniele Moro49a843c2022-01-05 14:36:32 +0100410 def readTerminationsNumber(self):
Daniele Moro954e2282021-09-22 17:32:03 +0200411 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100412 Read the terminations and return the number of entities
Daniele Moro954e2282021-09-22 17:32:03 +0200413
Daniele Moro49a843c2022-01-05 14:36:32 +0100414 :return: Number of terminations entities
Daniele Moro954e2282021-09-22 17:32:03 +0200415 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100416 tableName = 'PreQosPipe.terminations_uplink'
417 nTerm = self.up4_client.readNumberTableEntries(tableName)
418 tableName = 'PreQosPipe.terminations_downlink'
419 nTerm += self.up4_client.readNumberTableEntries(tableName)
420 return nTerm
Daniele Moro954e2282021-09-22 17:32:03 +0200421
422 def verifyUesFlowNumberP4rt(self):
423 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100424 Verify via P4RT CLI that the number of UE sessions and terminations
425 is the expected one
Daniele Moro954e2282021-09-22 17:32:03 +0200426
Daniele Moro49a843c2022-01-05 14:36:32 +0100427 :return: True if the number of UE sessions and terminations is expected,
428 False otherwise
Daniele Moro954e2282021-09-22 17:32:03 +0200429 """
Daniele Morobef0c7e2022-02-16 17:47:13 -0800430 return self.readUeSessionsNumber() == len(self.emulated_ues) * 2 and \
431 self.readTerminationsNumber() == len(self.emulated_ues) * 2 * len(self.app_filters)
Daniele Moro954e2282021-09-22 17:32:03 +0200432
433 def verifyNoUesFlowNumberP4rt(self, preInstalledUes=0):
434 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100435 Verify via P4RT CLI that there is no UE sessions and terminations installed.
Daniele Moro954e2282021-09-22 17:32:03 +0200436
Daniele Moro49a843c2022-01-05 14:36:32 +0100437 :param preInstalledUes: Number of UEs whose UE sessions and terminations
438 are still programmed
Daniele Moro954e2282021-09-22 17:32:03 +0200439 :return:
440 """
Daniele Morobef0c7e2022-02-16 17:47:13 -0800441 return self.readUeSessionsNumber() == preInstalledUes * 2 and \
442 self.readTerminationsNumber() == preInstalledUes * 2 * len(self.app_filters)
Daniele Moro954e2282021-09-22 17:32:03 +0200443
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200444 def verifyNoUesFlow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200445 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100446 Verify that no UE session, terminations are installed in ONOS.
Daniele Morobf53dec2021-09-13 18:11:56 +0200447
448 :param onosCli: An instance of a OnosCliDriver
449 :param retries: number of retries
450 :return:
451 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100452 retValue = utilities.retry(f=UP4.__verifyNoUeSessionAndTerminationOnos,
Daniele Morobf53dec2021-09-13 18:11:56 +0200453 retValue=False,
454 args=[onosCli],
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200455 sleep=5,
Daniele Morobf53dec2021-09-13 18:11:56 +0200456 attempts=retries)
457 utilities.assert_equal(expect=True,
458 actual=retValue,
Daniele Moro49a843c2022-01-05 14:36:32 +0100459 onpass="No UE session and terminations in ONOS",
460 onfail="Stale UE session or terminations")
Daniele Morobf53dec2021-09-13 18:11:56 +0200461
462 @staticmethod
Daniele Moro49a843c2022-01-05 14:36:32 +0100463 def __verifyNoUeSessionAndTerminationOnos(onosCli):
Daniele Morobf53dec2021-09-13 18:11:56 +0200464 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100465 Verify that no UE session, terminations are installed in ONOS
Daniele Morobf53dec2021-09-13 18:11:56 +0200466
467 :param onosCli: An instance of a OnosCliDriver
468 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100469 sessions = onosCli.sendline(cmdStr="up4:read-entities -s",
470 showResponse=True,
471 noExit=True, expectJson=False)
472 terminations = onosCli.sendline(cmdStr="up4:read-entities -t",
473 showResponse=True,
474 noExit=True, expectJson=False)
475 return sessions == "" and terminations == ""
Daniele Morobf53dec2021-09-13 18:11:56 +0200476
Daniele Moroc6811a82021-10-12 11:29:41 +0200477 def verifyUp4Flow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200478 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100479 Verify UE session, terminations and GTP tunnel peers installed via UP4
480 using the ONOS CLI.
Daniele Morobf53dec2021-09-13 18:11:56 +0200481
482 :param onosCli: An instance of a OnosCliDriver
Daniele Moroc6811a82021-10-12 11:29:41 +0200483 :param retries: Number of retries
Daniele Morobf53dec2021-09-13 18:11:56 +0200484 """
Daniele Morobef0c7e2022-02-16 17:47:13 -0800485 failString = []
Daniele Moroc6811a82021-10-12 11:29:41 +0200486 retValue = utilities.retry(f=self.__internalVerifyUp4Flow,
487 retValue=False,
488 args=[onosCli, failString],
489 sleep=5,
490 attempts=retries)
491 utilities.assert_equal(
492 expect=True,
493 actual=retValue,
Daniele Moro49a843c2022-01-05 14:36:32 +0100494 onpass="Correct UE session, terminations and GTP tunnel peers in ONOS",
Jon Halla601dde2022-02-28 14:22:07 -0800495 onfail="Wrong Application Filters, UE sessions, terminations and/or GTP tunnel peers in ONOS. " +
Daniele Morobef0c7e2022-02-16 17:47:13 -0800496 "Missing:\n" + '\n'.join(failString)
Daniele Moroc6811a82021-10-12 11:29:41 +0200497 )
498
Daniele Morobef0c7e2022-02-16 17:47:13 -0800499 def __internalVerifyUp4Flow(self, onosCli, failMsg=[]):
500 # Need to pass a list, so it's an object and we can use failMsg to
501 # return a string values from this method.
502
503 # Cleanup failMsg if any remaining from previous runs
504 del failMsg[:]
505 applications = onosCli.sendline(cmdStr="up4:read-entities -f",
506 showResponse=True,
507 noExit=True, expectJson=False)
Daniele Moro49a843c2022-01-05 14:36:32 +0100508 sessions = onosCli.sendline(cmdStr="up4:read-entities -s",
509 showResponse=True,
510 noExit=True, expectJson=False)
511 terminations = onosCli.sendline(cmdStr="up4:read-entities -t",
512 showResponse=True,
513 noExit=True, expectJson=False)
514 tunn_peer = onosCli.sendline(cmdStr="up4:read-entities -g",
515 showResponse=True,
516 noExit=True, expectJson=False)
Daniele Morobf53dec2021-09-13 18:11:56 +0200517 fail = False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800518 for app_filter in self.app_filters.values():
519 if not UP4.__defaultApp(**app_filter):
Jon Halla4a79312022-01-25 17:16:53 -0800520 if len(re.findall(self.appFilterOnosString(**app_filter), applications)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800521 failMsg.append(self.appFilterOnosString(**app_filter))
522 fail = True
Daniele Moro522023c2021-10-15 17:30:33 +0200523 for (ue_name, ue) in self.emulated_ues.items():
Jon Halla4a79312022-01-25 17:16:53 -0800524 if len(re.findall(self.upUeSessionOnosString(**ue), sessions)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800525 failMsg.append(self.upUeSessionOnosString(**ue))
Daniele Morobf53dec2021-09-13 18:11:56 +0200526 fail = True
Jon Halla4a79312022-01-25 17:16:53 -0800527 if len(re.findall(self.downUeSessionOnosString(**ue), sessions)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800528 failMsg.append(self.downUeSessionOnosString(**ue))
Daniele Morobf53dec2021-09-13 18:11:56 +0200529 fail = True
Daniele Morobef0c7e2022-02-16 17:47:13 -0800530 for app_filter in self.app_filters.values():
Jon Halla4a79312022-01-25 17:16:53 -0800531 if len(re.findall(self.upTerminationOnosString(app_filter=app_filter, **ue), terminations)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800532 failMsg.append(self.upTerminationOnosString(app_filter=app_filter, **ue))
533 fail = True
Jon Halla4a79312022-01-25 17:16:53 -0800534 if len(re.findall(self.downTerminationOnosString(app_filter=app_filter, **ue), terminations)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800535 failMsg.append(self.downTerminationOnosString(app_filter=app_filter, **ue))
536 fail = True
Jon Halla4a79312022-01-25 17:16:53 -0800537 if len(re.findall(self.gtpTunnelPeerOnosString(ue_name, **ue), tunn_peer)) != 1:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800538 failMsg.append(self.gtpTunnelPeerOnosString(ue_name, **ue))
Daniele Morobf53dec2021-09-13 18:11:56 +0200539 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200540 return not fail
Daniele Morobf53dec2021-09-13 18:11:56 +0200541
Jon Halla4a79312022-01-25 17:16:53 -0800542 def appFilterOnosString(self, app_id=None, priority=None, slice_id=None, ip_proto=None, ip_prefix=None, port_range=None, **kwargs):
543 if app_id is None:
544 app_id = "\d+"
545 if priority is None:
546 priority = "\d+"
Jon Halla601dde2022-02-28 14:22:07 -0800547 if slice_id is None:
548 slice_id = self.slice_id
549 matches = []
550 if ip_prefix:
551 matches.append("ip_prefix=%s" % ip_prefix)
552 if port_range:
Jon Halla4a79312022-01-25 17:16:53 -0800553 matches.append("l4_port_range=\[%s\]" % port_range)
Jon Halla601dde2022-02-28 14:22:07 -0800554 if ip_proto:
555 matches.append("ip_proto=%s" % ip_proto)
556 matches.append("slice_id=%s" % slice_id)
557
Jon Halla4a79312022-01-25 17:16:53 -0800558 return "UpfApplication\(priority=%s, Match\(%s\) -> Action\(app_id=%s\)\)" % (
Daniele Morobef0c7e2022-02-16 17:47:13 -0800559 priority,
Jon Halla601dde2022-02-28 14:22:07 -0800560 ", ".join(matches),
Daniele Morobef0c7e2022-02-16 17:47:13 -0800561 app_id
562 )
563
Daniele Morob8404e82022-02-25 00:17:28 +0100564 def upUeSessionOnosString(self, teid=None, teid_up=None, up_id=None,
565 sess_meter_idx=None, max_bps=None, **kwargs):
Jon Halla4a79312022-01-25 17:16:53 -0800566 if teid_up is None and teid is not None:
Daniele Morobf53dec2021-09-13 18:11:56 +0200567 teid_up = teid
Daniele Morob8404e82022-02-25 00:17:28 +0100568 if up_id is not None:
569 if max_bps is not None:
570 sess_meter_idx = up_id
571 else:
572 sess_meter_idx = DEFAULT_SESSION_METER_IDX
Jon Halla4a79312022-01-25 17:16:53 -0800573 if sess_meter_idx is None:
574 sess_meter_idx = "\d+"
575 return "UpfSessionUL\(Match\(tun_dst_addr={}, teid={}\) -> Action\(FWD, session_meter_idx={}\)\)".format(
Daniele Moro4650ffb2022-02-15 22:44:18 +0100576 self.s1u_address, teid_up, sess_meter_idx)
Daniele Morobf53dec2021-09-13 18:11:56 +0200577
Daniele Moro49a843c2022-01-05 14:36:32 +0100578 def downUeSessionOnosString(self, ue_address, down_id=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100579 tunn_peer_id=None, sess_meter_idx=None, max_bps=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100580 **kwargs):
Daniele Morobf53dec2021-09-13 18:11:56 +0200581 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100582 tunn_peer_id = down_id
Daniele Morob8404e82022-02-25 00:17:28 +0100583 if max_bps is not None:
584 sess_meter_idx = down_id
585 else:
586 sess_meter_idx = DEFAULT_SESSION_METER_IDX
Jon Halla4a79312022-01-25 17:16:53 -0800587 if tunn_peer_id is None:
588 tunn_peer_id = "\d+"
589 if sess_meter_idx is None:
590 sess_meter_idx = "\d+"
591 return "UpfSessionDL\(Match\(ue_addr={}\) -> Action\(FWD, tun_peer={}, session_meter_idx={}\)\)".format(
Daniele Moro4650ffb2022-02-15 22:44:18 +0100592 ue_address, tunn_peer_id, sess_meter_idx)
Daniele Moro49a843c2022-01-05 14:36:32 +0100593
Daniele Morobef0c7e2022-02-16 17:47:13 -0800594 def upTerminationOnosString(self, ue_address, app_filter, up_id=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100595 ctr_id_up=None, tc=None, app_meter_idx=None, **kwargs):
Daniele Moro49a843c2022-01-05 14:36:32 +0100596 if up_id is not None:
597 ctr_id_up = up_id
Daniele Morob8404e82022-02-25 00:17:28 +0100598 if "max_bps" in app_filter:
599 app_meter_idx = int(up_id) + int(app_filter["app_id"])
600 else:
601 app_meter_idx = DEFAULT_APP_METER_IDX
Jon Halla4a79312022-01-25 17:16:53 -0800602 if ctr_id_up is None:
603 ctr_id_up = "\d+"
604 if tc is None or int(tc) == 0:
605 tc = "(?:0|null)"
606 if app_meter_idx is None:
607 app_meter_idx = "\d+"
608 app_id = app_filter["app_id"]
609 if app_id is None:
610 app_id = "\d+"
611 if app_filter["action"] == "deny":
612 return "UpfTerminationUL\(Match\(ue_addr={}, app_id={}\) -> Action\(DROP, ctr_id={}, tc=null, app_meter_idx=0\)\)".format(
613 ue_address, app_id, ctr_id_up)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800614 else:
Jon Halla4a79312022-01-25 17:16:53 -0800615 return "UpfTerminationUL\(Match\(ue_addr={}, app_id={}\) -> Action\(FWD, ctr_id={}, tc={}, app_meter_idx={}\)\)".format(
616 ue_address, app_id, ctr_id_up, tc, app_meter_idx)
Daniele Moro49a843c2022-01-05 14:36:32 +0100617
Daniele Morobef0c7e2022-02-16 17:47:13 -0800618 def downTerminationOnosString(self, ue_address, app_filter, teid=None,
Daniele Moro1f7ca6f2022-01-27 11:58:50 +0100619 down_id=None, ctr_id_down=None, teid_down=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100620 tc=None, app_meter_idx=None,
Daniele Moro4650ffb2022-02-15 22:44:18 +0100621 **kwargs):
Daniele Moro49a843c2022-01-05 14:36:32 +0100622 if down_id is not None:
Daniele Morobf53dec2021-09-13 18:11:56 +0200623 ctr_id_down = down_id
Jon Halla4a79312022-01-25 17:16:53 -0800624 if ctr_id_down is None:
625 ctr_id_down = "\d+"
626 if teid_down is None and teid is not None:
Daniele Morobef0c7e2022-02-16 17:47:13 -0800627 teid_down = int(teid) + 1
Daniele Morob8404e82022-02-25 00:17:28 +0100628 if "max_bps" in app_filter:
629 app_meter_idx = int(down_id) + int(app_filter["app_id"])
630 else:
631 app_meter_idx = DEFAULT_APP_METER_IDX
Jon Halla4a79312022-01-25 17:16:53 -0800632 if tc is None or int(tc) == 0:
633 tc = "(?:0|null)"
634 if app_meter_idx is None:
635 app_meter_idx = "\d+"
636 app_id = app_filter["app_id"]
637 if app_id is None:
638 app_id = "\d+"
639 if app_filter["action"] == "deny":
640 return "UpfTerminationDL\(Match\(ue_addr={}, app_id={}\) -> Action\(DROP, teid=null, ctr_id={}, qfi=null, tc=null, app_meter_idx=0\)\)".format(
641 ue_address, app_id, ctr_id_down)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800642 else:
Jon Halla4a79312022-01-25 17:16:53 -0800643 return "UpfTerminationDL\(Match\(ue_addr={}, app_id={}\) -> Action\(FWD, teid={}, ctr_id={}, qfi={}, tc={}, app_meter_idx={}\)\)".format(
644 ue_address, app_id, teid_down, ctr_id_down, tc, tc, app_meter_idx)
Daniele Morobf53dec2021-09-13 18:11:56 +0200645
Daniele Moro49a843c2022-01-05 14:36:32 +0100646 def gtpTunnelPeerOnosString(self, ue_name, down_id=None, tunn_peer_id=None,
647 **kwargs):
648 if down_id is not None:
649 tunn_peer_id = down_id
Jon Halla4a79312022-01-25 17:16:53 -0800650 if tunn_peer_id is None:
651 tunn_peer_id = "\d+"
Daniele Moro49a843c2022-01-05 14:36:32 +0100652 enb_address = self.__getEnbAddress(ue_name)
Jon Halla4a79312022-01-25 17:16:53 -0800653 return "UpfGtpTunnelPeer\(tunn_peer_id={} -> src={}, dst={} src_port={}\)".format(
Daniele Moro49a843c2022-01-05 14:36:32 +0100654 tunn_peer_id, self.s1u_address, enb_address, GPDU_PORT)
Daniele Morobf53dec2021-09-13 18:11:56 +0200655
Daniele Moro80889562021-09-08 10:09:26 +0200656 @staticmethod
Jon Halla4a79312022-01-25 17:16:53 -0800657 def __sanitizeUeData(ue, smf=False):
Daniele Moro249d6e72021-09-20 10:32:54 +0200658 if "five_g" in ue and type(ue["five_g"]) != bool:
Daniele Moro80889562021-09-08 10:09:26 +0200659 ue["five_g"] = bool(strtobool(ue["five_g"]))
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800660 if "tc" in ue and ue["tc"] == "":
661 ue["tc"] = 0
Daniele Morob8404e82022-02-25 00:17:28 +0100662 if "max_bps" in ue:
663 if ue["max_bps"] == "" or ue["max_bps"] is None:
664 ue["max_bps"] = None
665 else:
666 ue["max_bps"] = int(ue["max_bps"])
Jon Halla4a79312022-01-25 17:16:53 -0800667 if smf:
668 ue["up_id"] = None
669 ue["down_id"] = None
Daniele Moro80889562021-09-08 10:09:26 +0200670 return ue
671
Daniele Morobef0c7e2022-02-16 17:47:13 -0800672 def insertAppFilter(self, **kwargs):
673 if not UP4.__defaultApp(**kwargs):
674 self.__programAppFilter(op="program", **kwargs)
675
676 def removeAppFilter(self, **kwargs):
677 if not UP4.__defaultApp(**kwargs):
678 self.__programAppFilter(op="clear", **kwargs)
679
Jon Halla4a79312022-01-25 17:16:53 -0800680 def smfAttachUe(self, ue_name, ue_address, prefix_len=31, seid=1, teid=None, sdf=[], **kwargs):
681 enb_addr = None
682 for enb_name, enb in self.enodebs.items():
683 if ue_name in enb["ues"]:
684 enb_addr = enb["enb_address"]
685 break
686 assert enb_addr, "Unknown eNodeB address"
687 ue_address = str(ip.ip_address(unicode(ue_address)) - 1)
688 base = seid
689
690 create = self.mock_smf.create(ue_pool="%s/%s" % (ue_address, prefix_len),
691 gnb_addr=enb_addr,
692 base_id=base,
693 sdf=sdf)
694 modify = self.mock_smf.modify(ue_pool="%s/%s" % (ue_address, prefix_len),
695 gnb_addr=enb_addr,
696 base_id=seid)
697 self.emulated_ues[ue_name]['seid'] = base
698 self.emulated_ues[ue_name]['teid'] = base
699 self.emulated_ues[ue_name]['sess_meter_idx'] = None
700 self.emulated_ues[ue_name]['app_meter_idx'] = None
701 return create and modify
702
703 def smfDetachUe(self, ue_name, ue_address, prefix_len=31, seid=1, teid=None, **kwargs):
704 enb_addr = None
705 for enb_name, enb in self.enodebs.items():
706 if ue_name in enb["ues"]:
707 enb_addr = enb["enb_address"]
708 break
709 assert enb_addr, "Unknown eNodeB address"
710 ue_address = str( ip.ip_address( unicode( ue_address ) ) - 1 )
711
712 return self.mock_smf.delete( base_id=seid )
713
Daniele Moro49a843c2022-01-05 14:36:32 +0100714 def attachUe(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200715 teid=None, up_id=None, down_id=None,
716 teid_up=None, teid_down=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100717 ctr_id_up=None, ctr_id_down=None,
718 tunn_peer_id=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100719 tc=None, five_g=False, max_bps=None,
720 sess_meter_idx_up=None, sess_meter_idx_down=None, **kwargs):
Daniele Morobef0c7e2022-02-16 17:47:13 -0800721 self.__programUeRules(ue_name,
722 ue_address,
723 teid, up_id, down_id,
724 teid_up, teid_down,
725 ctr_id_up, ctr_id_down,
726 tunn_peer_id,
Daniele Morob8404e82022-02-25 00:17:28 +0100727 tc, five_g, max_bps,
728 sess_meter_idx_up, sess_meter_idx_down,
729 op="program")
Daniele Moro80889562021-09-08 10:09:26 +0200730
Daniele Moro49a843c2022-01-05 14:36:32 +0100731 def detachUe(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200732 teid=None, up_id=None, down_id=None,
733 teid_up=None, teid_down=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100734 ctr_id_up=None, ctr_id_down=None,
735 tunn_peer_id=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100736 tc=None, five_g=False, max_bps=None,
737 sess_meter_idx_up=None, sess_meter_idx_down=None, **kwargs):
Daniele Morobef0c7e2022-02-16 17:47:13 -0800738 self.__programUeRules(ue_name,
739 ue_address,
740 teid, up_id, down_id,
741 teid_up, teid_down,
742 ctr_id_up, ctr_id_down,
743 tunn_peer_id,
Daniele Morob8404e82022-02-25 00:17:28 +0100744 tc, five_g, max_bps,
745 sess_meter_idx_up, sess_meter_idx_down,
746 op="clear")
Daniele Moro80889562021-09-08 10:09:26 +0200747
Jon Halla601dde2022-02-28 14:22:07 -0800748 def __programAppFilter(self, app_id, slice_id, ip_prefix=None, ip_proto=None,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800749 port_range=None, priority=0, op="program", **kwargs):
750
751 entries = []
752
753 tableName = 'PreQosPipe.applications'
754 actionName = 'PreQosPipe.set_app_id'
755 actionParams = {'app_id': str(app_id)}
Daniele Morocc4ecda2022-02-25 23:27:25 +0100756 matchFields = {
Jon Halla601dde2022-02-28 14:22:07 -0800757 'slice_id': str(slice_id)
Daniele Morocc4ecda2022-02-25 23:27:25 +0100758 }
Daniele Morobef0c7e2022-02-16 17:47:13 -0800759 if ip_prefix:
760 matchFields['app_ip_addr'] = str(ip_prefix)
761 if ip_proto:
762 matchFields['app_ip_proto'] = str(ip_proto)
763 if port_range:
764 matchFields['app_l4_port'] = str(port_range)
765
766 if not self.__add_entry(tableName, actionName, matchFields,
767 actionParams, entries, op, priority):
768 return False
769
770 if op == "program":
771 main.log.info("Application entry added successfully.")
772 elif op == "clear":
773 self.__clear_entries(entries)
774
Jon Halla4a79312022-01-25 17:16:53 -0800775 def __pfcpSDFString(self, ip_prefix=None, ip_proto=None, port_range=None,
776 action="allow", **kwargs):
777 if ip_proto is None or str(ip_proto) == "255":
778 protoString = "ip"
779 elif str(ip_proto) == "6":
780 descList.append("tcp")
781 elif str(ip_proto) == "17":
782 protoString = "udp"
783 else:
784 # TODO: is there a python library for this? Can we just pass the number?
785 raise NotImplemented
786 if port_range is None:
787 port_range = "any"
788 if ip_prefix is None:
789 ip_prefix = "any"
790 return "--app-filter '%s:%s:%s:%s'" % ( protoString,
791 ip_prefix,
792 port_range.replace("..","-"),
793 action )
794
Daniele Morobef0c7e2022-02-16 17:47:13 -0800795 def __programUeRules(self, ue_name, ue_address,
796 teid=None, up_id=None, down_id=None,
797 teid_up=None, teid_down=None, ctr_id_up=None,
798 ctr_id_down=None, tunn_peer_id=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100799 tc=0, five_g=False, max_bps=None,
800 sess_meter_idx_up=None, sess_meter_idx_down=None,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800801 op="program"):
Daniele Morob8404e82022-02-25 00:17:28 +0100802 if max_bps is None:
803 sess_meter_idx_up = DEFAULT_SESSION_METER_IDX
804 sess_meter_idx_down = DEFAULT_SESSION_METER_IDX
Daniele Moro80889562021-09-08 10:09:26 +0200805 if up_id is not None:
Daniele Moro80889562021-09-08 10:09:26 +0200806 ctr_id_up = up_id
Daniele Morob8404e82022-02-25 00:17:28 +0100807 if max_bps is not None:
808 sess_meter_idx_up = int(up_id)
Daniele Moro80889562021-09-08 10:09:26 +0200809 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100810 tunn_peer_id = down_id
Daniele Moro80889562021-09-08 10:09:26 +0200811 ctr_id_down = down_id
Daniele Morob8404e82022-02-25 00:17:28 +0100812 if max_bps is not None:
813 sess_meter_idx_down = int(down_id)
Daniele Moro80889562021-09-08 10:09:26 +0200814 if teid is not None:
815 teid_up = teid
Daniele Morobef0c7e2022-02-16 17:47:13 -0800816 teid_down = int(teid) + 1
Daniele Moro80889562021-09-08 10:09:26 +0200817
818 entries = []
819
Daniele Moro522023c2021-10-15 17:30:33 +0200820 # Retrieve eNobeB address from eNodeB list
821 enb_address = self.__getEnbAddress(ue_name)
822
Daniele Moro80889562021-09-08 10:09:26 +0200823 # ========================#
Daniele Morob8404e82022-02-25 00:17:28 +0100824 # Session Meters
825 # ========================#
826 if max_bps is not None:
827 if not self.__mod_meter(
828 'PreQosPipe.session_meter',
829 sess_meter_idx_up,
830 max_bps,
831 op
832 ):
833 return False
834 if not self.__mod_meter(
835 'PreQosPipe.session_meter',
836 sess_meter_idx_down,
837 max_bps,
838 op
839 ):
840 return False
841
842 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100843 # UE Session Entries
Daniele Moro80889562021-09-08 10:09:26 +0200844 # ========================#
845
846 # Uplink
Daniele Moro49a843c2022-01-05 14:36:32 +0100847 tableName = 'PreQosPipe.sessions_uplink'
848 actionName = 'PreQosPipe.set_session_uplink'
Daniele Moro80889562021-09-08 10:09:26 +0200849 matchFields = {}
Daniele Morobef0c7e2022-02-16 17:47:13 -0800850 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200851 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100852 matchFields['n3_address'] = str(self.s1u_address)
Daniele Moro80889562021-09-08 10:09:26 +0200853 matchFields['teid'] = str(teid_up)
Daniele Moro4650ffb2022-02-15 22:44:18 +0100854 # Action params
Daniele Morob8404e82022-02-25 00:17:28 +0100855 actionParams["session_meter_idx"] = str(sess_meter_idx_up)
Daniele Moro49a843c2022-01-05 14:36:32 +0100856 if five_g:
857 # TODO: currently QFI match is unsupported in TNA
858 main.log.warn("Matching on QFI is currently unsupported in TNA")
Daniele Moro80889562021-09-08 10:09:26 +0200859 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800860 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200861 return False
862
863 # Downlink
Daniele Moro49a843c2022-01-05 14:36:32 +0100864 tableName = 'PreQosPipe.sessions_downlink'
865 actionName = 'PreQosPipe.set_session_downlink'
Daniele Moro80889562021-09-08 10:09:26 +0200866 matchFields = {}
867 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200868 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100869 matchFields['ue_address'] = str(ue_address)
Daniele Moro80889562021-09-08 10:09:26 +0200870 # Action params
Daniele Moro49a843c2022-01-05 14:36:32 +0100871 actionParams['tunnel_peer_id'] = str(tunn_peer_id)
Daniele Morob8404e82022-02-25 00:17:28 +0100872 actionParams["session_meter_idx"] = str(sess_meter_idx_down)
Daniele Moro80889562021-09-08 10:09:26 +0200873 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800874 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200875 return False
876
877 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100878 # Terminations Entries
Daniele Moro80889562021-09-08 10:09:26 +0200879 # ========================#
880
Daniele Morobef0c7e2022-02-16 17:47:13 -0800881 # Insert one termination entry per app filtering rule,
Daniele Moro80889562021-09-08 10:09:26 +0200882
Daniele Morobef0c7e2022-02-16 17:47:13 -0800883 # Uplink
884 for f in self.app_filters.values():
Daniele Morob8404e82022-02-25 00:17:28 +0100885 if "max_bps" in f:
886 app_meter_idx_up = sess_meter_idx_up + int(f['app_id'])
887 if not self.__mod_meter(
888 'PreQosPipe.app_meter',
889 app_meter_idx_up,
890 int(f["max_bps"]),
891 op
892 ):
893 return False
894 else:
895 app_meter_idx_up = DEFAULT_APP_METER_IDX
Daniele Morobef0c7e2022-02-16 17:47:13 -0800896 tableName = 'PreQosPipe.terminations_uplink'
897 matchFields = {}
898 actionParams = {}
899
900 # Match fields
901 matchFields['ue_address'] = str(ue_address)
902 matchFields['app_id'] = str(f["app_id"])
903
904 # Action params
905 if f['action'] == 'allow':
906 actionName = 'PreQosPipe.uplink_term_fwd'
Daniele Morob8404e82022-02-25 00:17:28 +0100907 actionParams['app_meter_idx'] = str(app_meter_idx_up)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800908 actionParams['tc'] = str(tc)
909 else:
910 actionName = 'PreQosPipe.uplink_term_drop'
911 actionParams['ctr_idx'] = str(ctr_id_up)
912 if not self.__add_entry(
913 tableName, actionName, matchFields, actionParams, entries, op
914 ):
915 return False
Daniele Moro80889562021-09-08 10:09:26 +0200916
917 # Downlink
Daniele Morobef0c7e2022-02-16 17:47:13 -0800918 for f in self.app_filters.values():
Daniele Morob8404e82022-02-25 00:17:28 +0100919 if "max_bps" in f:
920 app_meter_idx_down = sess_meter_idx_down + int(f['app_id'])
921 if not self.__mod_meter(
922 'PreQosPipe.app_meter',
923 app_meter_idx_down,
924 int(f["max_bps"]),
925 op
926 ):
927 return False
928 else:
929 app_meter_idx_down = DEFAULT_APP_METER_IDX
Daniele Morobef0c7e2022-02-16 17:47:13 -0800930 tableName = 'PreQosPipe.terminations_downlink'
931 matchFields = {}
932 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200933
Daniele Morobef0c7e2022-02-16 17:47:13 -0800934 # Match fields
935 matchFields['ue_address'] = str(ue_address)
936 matchFields['app_id'] = str(f["app_id"])
937
938 # Action params
939 if f['action'] == 'allow':
940 actionName = 'PreQosPipe.downlink_term_fwd'
941 actionParams['teid'] = str(teid_down)
942 # 1-1 mapping between QFI and TC
943 actionParams['tc'] = str(tc)
944 actionParams['qfi'] = str(tc)
Daniele Morob8404e82022-02-25 00:17:28 +0100945 actionParams['app_meter_idx'] = str(app_meter_idx_down)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800946 else:
947 actionName = 'PreQosPipe.downlink_term_drop'
948 actionParams['ctr_idx'] = str(ctr_id_down)
949
950 if not self.__add_entry(tableName, actionName, matchFields,
951 actionParams, entries, op):
952 return False
Daniele Moro49a843c2022-01-05 14:36:32 +0100953
954 # ========================#
955 # Tunnel Peer Entry
956 # ========================#
957 tableName = 'PreQosPipe.tunnel_peers'
958 actionName = 'PreQosPipe.load_tunnel_param'
959 matchFields = {}
960 actionParams = {}
961 # Match fields
962 matchFields['tunnel_peer_id'] = str(tunn_peer_id)
963 # Action params
Daniele Moro80889562021-09-08 10:09:26 +0200964 actionParams['src_addr'] = str(self.s1u_address)
Daniele Moro522023c2021-10-15 17:30:33 +0200965 actionParams['dst_addr'] = str(enb_address)
Daniele Moro80889562021-09-08 10:09:26 +0200966 actionParams['sport'] = TUNNEL_SPORT
967 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800968 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200969 return False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800970 if op == "program":
Daniele Moro80889562021-09-08 10:09:26 +0200971 main.log.info("All entries added successfully.")
Daniele Morobef0c7e2022-02-16 17:47:13 -0800972 elif op == "clear":
Daniele Moro80889562021-09-08 10:09:26 +0200973 self.__clear_entries(entries)
974
975 def __add_entry(self, tableName, actionName, matchFields, actionParams,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800976 entries, op, priority=0):
977 if op == "program":
Daniele Moro80889562021-09-08 10:09:26 +0200978 self.up4_client.buildP4RtTableEntry(
979 tableName=tableName, actionName=actionName,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800980 actionParams=actionParams, matchFields=matchFields, priority=priority)
Daniele Moro80889562021-09-08 10:09:26 +0200981 if self.up4_client.pushTableEntry(debug=True) == main.TRUE:
982 main.log.info("*** Entry added.")
983 else:
984 main.log.error("Error during table insertion")
985 self.__clear_entries(entries)
986 return False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800987 entries.append({
988 "tableName": tableName, "actionName": actionName,
989 "matchFields": matchFields,
990 "actionParams": actionParams,
991 "priority": priority
992 })
Daniele Moro80889562021-09-08 10:09:26 +0200993 return True
994
Daniele Morob8404e82022-02-25 00:17:28 +0100995 def __mod_meter(self, name, index, max_bps, op):
996 cir = 0
997 cburst = 0
998 pir = max_bps // 8
999 # TRex/DPDK can generate burst of 32 packets, considering MTU=1500, 32x1500B=48KB
1000 # Burst must be greater than 48KB
1001 pburst = 100000
1002 if op == "program":
1003 self.up4_client.buildP4RtMeterEntry(
1004 meterName=name, index=index, cir=cir, cburst=cburst, pir=pir,
1005 pburst=pburst
1006 )
1007 else:
1008 # in case of "clear" don't specify bands to clear meters
1009 self.up4_client.buildP4RtMeterEntry(meterName=name, index=index)
1010 if self.up4_client.modifyMeterEntry(debug=True) == main.TRUE:
1011 main.log.info("*** Meter modified.")
1012 else:
1013 main.log.error("Error during meter modification")
1014 return False
1015 return True
1016
Daniele Moro80889562021-09-08 10:09:26 +02001017 def __clear_entries(self, entries):
1018 for i, entry in enumerate(entries):
1019 self.up4_client.buildP4RtTableEntry(**entry)
1020 if self.up4_client.deleteTableEntry(debug=True) == main.TRUE:
1021 main.log.info(
1022 "*** Entry %d of %d deleted." % (i + 1, len(entries)))
1023 else:
1024 main.log.error("Error during table delete")
Daniele Morobf53dec2021-09-13 18:11:56 +02001025
Daniele Moro522023c2021-10-15 17:30:33 +02001026 def __getEnbAddress(self, ue_name):
1027 for enb in self.enodebs.values():
1028 if ue_name in enb["ues"]:
1029 return enb["enb_address"]
1030 main.log.error("Missing eNodeB address!")
1031 return ""
1032
Daniele Morobf53dec2021-09-13 18:11:56 +02001033 @staticmethod
1034 def buildGtpPacket(host, src_ip_outer, dst_ip_outer, src_ip_inner,
1035 dst_ip_inner, src_udp_inner, dst_udp_inner, teid):
1036 host.buildEther()
1037 host.buildIP(src=src_ip_outer, dst=dst_ip_outer)
1038 host.buildUDP(ipVersion=4, dport=GPDU_PORT)
1039 # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
1040 host.buildGTP(gtp_type=0xFF, TEID=teid)
1041 host.buildIP(overGtp=True, src=src_ip_inner, dst=dst_ip_inner)
1042 host.buildUDP(ipVersion=4, overGtp=True, sport=src_udp_inner,
1043 dport=dst_udp_inner)
1044
1045 @staticmethod
1046 def buildUdpPacket(host, src_ip, dst_ip, src_udp, dst_udp, src_eth=None,
1047 dst_eth=None):
1048 host.buildEther(src=src_eth, dst=dst_eth)
1049 host.buildIP(src=src_ip, dst=dst_ip)
1050 host.buildUDP(ipVersion=4, sport=src_udp, dport=dst_udp)
1051
1052 @staticmethod
1053 def checkFilterAndGetPackets(host):
1054 finished = host.checkFilter()
1055 if finished:
1056 packets = host.readPackets(detailed=True)
1057 for p in packets.splitlines():
1058 main.log.debug(p)
1059 # We care only of the last line from readPackets
1060 return packets.splitlines()[-1]
1061 else:
1062 kill = host.killFilter()
1063 main.log.debug(kill)
1064 return ""
Daniele Morobef0c7e2022-02-16 17:47:13 -08001065
1066 @staticmethod
1067 def __defaultApp(ip_prefix=None, ip_proto=None, port_range=None, **kwargs):
1068 if ip_prefix is None and ip_proto is None and port_range is None:
1069 return True
1070 return False
1071