blob: 3f89de7cf99e5ca06622a6d0140d91e3917ae3b6 [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"]
Daniele Moroa7200882022-03-25 11:12:54 +0100191 self.smfAttachUe(ue_name, sdf=filter_desc, **ue)
Jon Halla4a79312022-01-25 17:16:53 -0800192 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
Daniele Moroa7200882022-03-25 11:12:54 +0100775 def __pfcpSDFString(self, ip_prefix=None, ip_proto=None, port_range=None, priority=0,
Jon Halla4a79312022-01-25 17:16:53 -0800776 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
Daniele Moroa7200882022-03-25 11:12:54 +0100786 precedence = (1<<32) - priority # MAX_UINT32 - priority
Jon Halla4a79312022-01-25 17:16:53 -0800787 if port_range is None:
788 port_range = "any"
789 if ip_prefix is None:
790 ip_prefix = "any"
Daniele Moroa7200882022-03-25 11:12:54 +0100791 return "--app-filter '%s:%s:%s:%s:%s'" % ( protoString,
Jon Halla4a79312022-01-25 17:16:53 -0800792 ip_prefix,
793 port_range.replace("..","-"),
Daniele Moroa7200882022-03-25 11:12:54 +0100794 action,
795 precedence )
Jon Halla4a79312022-01-25 17:16:53 -0800796
Daniele Morobef0c7e2022-02-16 17:47:13 -0800797 def __programUeRules(self, ue_name, ue_address,
798 teid=None, up_id=None, down_id=None,
799 teid_up=None, teid_down=None, ctr_id_up=None,
800 ctr_id_down=None, tunn_peer_id=None,
Daniele Morob8404e82022-02-25 00:17:28 +0100801 tc=0, five_g=False, max_bps=None,
802 sess_meter_idx_up=None, sess_meter_idx_down=None,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800803 op="program"):
Daniele Morob8404e82022-02-25 00:17:28 +0100804 if max_bps is None:
805 sess_meter_idx_up = DEFAULT_SESSION_METER_IDX
806 sess_meter_idx_down = DEFAULT_SESSION_METER_IDX
Daniele Moro80889562021-09-08 10:09:26 +0200807 if up_id is not None:
Daniele Moro80889562021-09-08 10:09:26 +0200808 ctr_id_up = up_id
Daniele Morob8404e82022-02-25 00:17:28 +0100809 if max_bps is not None:
810 sess_meter_idx_up = int(up_id)
Daniele Moro80889562021-09-08 10:09:26 +0200811 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100812 tunn_peer_id = down_id
Daniele Moro80889562021-09-08 10:09:26 +0200813 ctr_id_down = down_id
Daniele Morob8404e82022-02-25 00:17:28 +0100814 if max_bps is not None:
815 sess_meter_idx_down = int(down_id)
Daniele Moro80889562021-09-08 10:09:26 +0200816 if teid is not None:
817 teid_up = teid
Daniele Morobef0c7e2022-02-16 17:47:13 -0800818 teid_down = int(teid) + 1
Daniele Moro80889562021-09-08 10:09:26 +0200819
820 entries = []
821
Daniele Moro522023c2021-10-15 17:30:33 +0200822 # Retrieve eNobeB address from eNodeB list
823 enb_address = self.__getEnbAddress(ue_name)
824
Daniele Moro80889562021-09-08 10:09:26 +0200825 # ========================#
Daniele Morob8404e82022-02-25 00:17:28 +0100826 # Session Meters
827 # ========================#
828 if max_bps is not None:
829 if not self.__mod_meter(
830 'PreQosPipe.session_meter',
831 sess_meter_idx_up,
832 max_bps,
833 op
834 ):
835 return False
836 if not self.__mod_meter(
837 'PreQosPipe.session_meter',
838 sess_meter_idx_down,
839 max_bps,
840 op
841 ):
842 return False
843
844 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100845 # UE Session Entries
Daniele Moro80889562021-09-08 10:09:26 +0200846 # ========================#
847
848 # Uplink
Daniele Moro49a843c2022-01-05 14:36:32 +0100849 tableName = 'PreQosPipe.sessions_uplink'
850 actionName = 'PreQosPipe.set_session_uplink'
Daniele Moro80889562021-09-08 10:09:26 +0200851 matchFields = {}
Daniele Morobef0c7e2022-02-16 17:47:13 -0800852 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200853 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100854 matchFields['n3_address'] = str(self.s1u_address)
Daniele Moro80889562021-09-08 10:09:26 +0200855 matchFields['teid'] = str(teid_up)
Daniele Moro4650ffb2022-02-15 22:44:18 +0100856 # Action params
Daniele Morob8404e82022-02-25 00:17:28 +0100857 actionParams["session_meter_idx"] = str(sess_meter_idx_up)
Daniele Moro49a843c2022-01-05 14:36:32 +0100858 if five_g:
859 # TODO: currently QFI match is unsupported in TNA
860 main.log.warn("Matching on QFI is currently unsupported in TNA")
Daniele Moro80889562021-09-08 10:09:26 +0200861 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800862 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200863 return False
864
865 # Downlink
Daniele Moro49a843c2022-01-05 14:36:32 +0100866 tableName = 'PreQosPipe.sessions_downlink'
867 actionName = 'PreQosPipe.set_session_downlink'
Daniele Moro80889562021-09-08 10:09:26 +0200868 matchFields = {}
869 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200870 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100871 matchFields['ue_address'] = str(ue_address)
Daniele Moro80889562021-09-08 10:09:26 +0200872 # Action params
Daniele Moro49a843c2022-01-05 14:36:32 +0100873 actionParams['tunnel_peer_id'] = str(tunn_peer_id)
Daniele Morob8404e82022-02-25 00:17:28 +0100874 actionParams["session_meter_idx"] = str(sess_meter_idx_down)
Daniele Moro80889562021-09-08 10:09:26 +0200875 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800876 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200877 return False
878
879 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100880 # Terminations Entries
Daniele Moro80889562021-09-08 10:09:26 +0200881 # ========================#
882
Daniele Morobef0c7e2022-02-16 17:47:13 -0800883 # Insert one termination entry per app filtering rule,
Daniele Moro80889562021-09-08 10:09:26 +0200884
Daniele Morobef0c7e2022-02-16 17:47:13 -0800885 # Uplink
886 for f in self.app_filters.values():
Daniele Morob8404e82022-02-25 00:17:28 +0100887 if "max_bps" in f:
888 app_meter_idx_up = sess_meter_idx_up + int(f['app_id'])
889 if not self.__mod_meter(
890 'PreQosPipe.app_meter',
891 app_meter_idx_up,
892 int(f["max_bps"]),
893 op
894 ):
895 return False
896 else:
897 app_meter_idx_up = DEFAULT_APP_METER_IDX
Daniele Morobef0c7e2022-02-16 17:47:13 -0800898 tableName = 'PreQosPipe.terminations_uplink'
899 matchFields = {}
900 actionParams = {}
901
902 # Match fields
903 matchFields['ue_address'] = str(ue_address)
904 matchFields['app_id'] = str(f["app_id"])
905
906 # Action params
907 if f['action'] == 'allow':
908 actionName = 'PreQosPipe.uplink_term_fwd'
Daniele Morob8404e82022-02-25 00:17:28 +0100909 actionParams['app_meter_idx'] = str(app_meter_idx_up)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800910 actionParams['tc'] = str(tc)
911 else:
912 actionName = 'PreQosPipe.uplink_term_drop'
913 actionParams['ctr_idx'] = str(ctr_id_up)
914 if not self.__add_entry(
915 tableName, actionName, matchFields, actionParams, entries, op
916 ):
917 return False
Daniele Moro80889562021-09-08 10:09:26 +0200918
919 # Downlink
Daniele Morobef0c7e2022-02-16 17:47:13 -0800920 for f in self.app_filters.values():
Daniele Morob8404e82022-02-25 00:17:28 +0100921 if "max_bps" in f:
922 app_meter_idx_down = sess_meter_idx_down + int(f['app_id'])
923 if not self.__mod_meter(
924 'PreQosPipe.app_meter',
925 app_meter_idx_down,
926 int(f["max_bps"]),
927 op
928 ):
929 return False
930 else:
931 app_meter_idx_down = DEFAULT_APP_METER_IDX
Daniele Morobef0c7e2022-02-16 17:47:13 -0800932 tableName = 'PreQosPipe.terminations_downlink'
933 matchFields = {}
934 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200935
Daniele Morobef0c7e2022-02-16 17:47:13 -0800936 # Match fields
937 matchFields['ue_address'] = str(ue_address)
938 matchFields['app_id'] = str(f["app_id"])
939
940 # Action params
941 if f['action'] == 'allow':
942 actionName = 'PreQosPipe.downlink_term_fwd'
943 actionParams['teid'] = str(teid_down)
944 # 1-1 mapping between QFI and TC
945 actionParams['tc'] = str(tc)
946 actionParams['qfi'] = str(tc)
Daniele Morob8404e82022-02-25 00:17:28 +0100947 actionParams['app_meter_idx'] = str(app_meter_idx_down)
Daniele Morobef0c7e2022-02-16 17:47:13 -0800948 else:
949 actionName = 'PreQosPipe.downlink_term_drop'
950 actionParams['ctr_idx'] = str(ctr_id_down)
951
952 if not self.__add_entry(tableName, actionName, matchFields,
953 actionParams, entries, op):
954 return False
Daniele Moro49a843c2022-01-05 14:36:32 +0100955
956 # ========================#
957 # Tunnel Peer Entry
958 # ========================#
959 tableName = 'PreQosPipe.tunnel_peers'
960 actionName = 'PreQosPipe.load_tunnel_param'
961 matchFields = {}
962 actionParams = {}
963 # Match fields
964 matchFields['tunnel_peer_id'] = str(tunn_peer_id)
965 # Action params
Daniele Moro80889562021-09-08 10:09:26 +0200966 actionParams['src_addr'] = str(self.s1u_address)
Daniele Moro522023c2021-10-15 17:30:33 +0200967 actionParams['dst_addr'] = str(enb_address)
Daniele Moro80889562021-09-08 10:09:26 +0200968 actionParams['sport'] = TUNNEL_SPORT
969 if not self.__add_entry(tableName, actionName, matchFields,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800970 actionParams, entries, op):
Daniele Moro80889562021-09-08 10:09:26 +0200971 return False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800972 if op == "program":
Daniele Moro80889562021-09-08 10:09:26 +0200973 main.log.info("All entries added successfully.")
Daniele Morobef0c7e2022-02-16 17:47:13 -0800974 elif op == "clear":
Daniele Moro80889562021-09-08 10:09:26 +0200975 self.__clear_entries(entries)
976
977 def __add_entry(self, tableName, actionName, matchFields, actionParams,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800978 entries, op, priority=0):
979 if op == "program":
Daniele Moro80889562021-09-08 10:09:26 +0200980 self.up4_client.buildP4RtTableEntry(
981 tableName=tableName, actionName=actionName,
Daniele Morobef0c7e2022-02-16 17:47:13 -0800982 actionParams=actionParams, matchFields=matchFields, priority=priority)
Daniele Moro80889562021-09-08 10:09:26 +0200983 if self.up4_client.pushTableEntry(debug=True) == main.TRUE:
984 main.log.info("*** Entry added.")
985 else:
986 main.log.error("Error during table insertion")
987 self.__clear_entries(entries)
988 return False
Daniele Morobef0c7e2022-02-16 17:47:13 -0800989 entries.append({
990 "tableName": tableName, "actionName": actionName,
991 "matchFields": matchFields,
992 "actionParams": actionParams,
993 "priority": priority
994 })
Daniele Moro80889562021-09-08 10:09:26 +0200995 return True
996
Daniele Morob8404e82022-02-25 00:17:28 +0100997 def __mod_meter(self, name, index, max_bps, op):
998 cir = 0
999 cburst = 0
1000 pir = max_bps // 8
1001 # TRex/DPDK can generate burst of 32 packets, considering MTU=1500, 32x1500B=48KB
1002 # Burst must be greater than 48KB
1003 pburst = 100000
1004 if op == "program":
1005 self.up4_client.buildP4RtMeterEntry(
1006 meterName=name, index=index, cir=cir, cburst=cburst, pir=pir,
1007 pburst=pburst
1008 )
1009 else:
1010 # in case of "clear" don't specify bands to clear meters
1011 self.up4_client.buildP4RtMeterEntry(meterName=name, index=index)
1012 if self.up4_client.modifyMeterEntry(debug=True) == main.TRUE:
1013 main.log.info("*** Meter modified.")
1014 else:
1015 main.log.error("Error during meter modification")
1016 return False
1017 return True
1018
Daniele Moro80889562021-09-08 10:09:26 +02001019 def __clear_entries(self, entries):
1020 for i, entry in enumerate(entries):
1021 self.up4_client.buildP4RtTableEntry(**entry)
1022 if self.up4_client.deleteTableEntry(debug=True) == main.TRUE:
1023 main.log.info(
1024 "*** Entry %d of %d deleted." % (i + 1, len(entries)))
1025 else:
1026 main.log.error("Error during table delete")
Daniele Morobf53dec2021-09-13 18:11:56 +02001027
Daniele Moro522023c2021-10-15 17:30:33 +02001028 def __getEnbAddress(self, ue_name):
1029 for enb in self.enodebs.values():
1030 if ue_name in enb["ues"]:
1031 return enb["enb_address"]
1032 main.log.error("Missing eNodeB address!")
1033 return ""
1034
Daniele Morobf53dec2021-09-13 18:11:56 +02001035 @staticmethod
1036 def buildGtpPacket(host, src_ip_outer, dst_ip_outer, src_ip_inner,
1037 dst_ip_inner, src_udp_inner, dst_udp_inner, teid):
1038 host.buildEther()
1039 host.buildIP(src=src_ip_outer, dst=dst_ip_outer)
1040 host.buildUDP(ipVersion=4, dport=GPDU_PORT)
1041 # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
1042 host.buildGTP(gtp_type=0xFF, TEID=teid)
1043 host.buildIP(overGtp=True, src=src_ip_inner, dst=dst_ip_inner)
1044 host.buildUDP(ipVersion=4, overGtp=True, sport=src_udp_inner,
1045 dport=dst_udp_inner)
1046
1047 @staticmethod
1048 def buildUdpPacket(host, src_ip, dst_ip, src_udp, dst_udp, src_eth=None,
1049 dst_eth=None):
1050 host.buildEther(src=src_eth, dst=dst_eth)
1051 host.buildIP(src=src_ip, dst=dst_ip)
1052 host.buildUDP(ipVersion=4, sport=src_udp, dport=dst_udp)
1053
1054 @staticmethod
1055 def checkFilterAndGetPackets(host):
1056 finished = host.checkFilter()
1057 if finished:
1058 packets = host.readPackets(detailed=True)
1059 for p in packets.splitlines():
1060 main.log.debug(p)
1061 # We care only of the last line from readPackets
1062 return packets.splitlines()[-1]
1063 else:
1064 kill = host.killFilter()
1065 main.log.debug(kill)
1066 return ""
Daniele Morobef0c7e2022-02-16 17:47:13 -08001067
1068 @staticmethod
1069 def __defaultApp(ip_prefix=None, ip_proto=None, port_range=None, **kwargs):
1070 if ip_prefix is None and ip_proto is None and port_range is None:
1071 return True
1072 return False
1073