blob: 295115881930afc8cfda17aec1c4a57136c5323d [file] [log] [blame]
Daniele Moro80889562021-09-08 10:09:26 +02001from distutils.util import strtobool
Daniele Moro522023c2021-10-15 17:30:33 +02002import copy
Daniele Moro80889562021-09-08 10:09:26 +02003
4FALSE = '0'
5TRUE = '1'
6DIR_UPLINK = '1'
7DIR_DOWNLINK = '2'
8IFACE_ACCESS = '1'
9IFACE_CORE = '2'
10TUNNEL_SPORT = '2152'
11TUNNEL_TYPE_GPDU = '3'
12
13UE_PORT = 400
14PDN_PORT = 800
15GPDU_PORT = 2152
16
Daniele Moro49a843c2022-01-05 14:36:32 +010017N_FLOWS_PER_UE = 5
Daniele Moroc6811a82021-10-12 11:29:41 +020018
Daniele Moroa1975192022-01-21 18:01:19 +010019DEFAULT_APP_ID = 0
Daniele Moro80889562021-09-08 10:09:26 +020020
21class UP4:
22 """
23 Utility that manages interaction with UP4 via a P4RuntimeCliDriver available
24 in the cluster. Additionally, can verify connectivity by crafting GTP packets
Daniele Moro522023c2021-10-15 17:30:33 +020025 via Scapy with an HostDriver component, specified via <enodebs>, <pdn_host>,
Daniele Moro80889562021-09-08 10:09:26 +020026 and <router_mac> parameters.
27
28 Example params file:
29 <UP4>
30 <pdn_host>Compute1</pdn_host> # Needed to verify connectivity with scapy
Daniele Moro522023c2021-10-15 17:30:33 +020031 <enodebs> # List of emulated eNodeBs
32 <enode_1>
33 <host>Compute1</host> # Host that emulates this eNodeB
34 <interface>eno3</interface> # Name of the linux interface to use on the host, if not specified take the default
35 <enb_address>10.32.11.122</enb_address> # IP address of the eNodeB
36 <ues>ue3</ues> # Emulated ues connected to this eNB
37 </enode_1>
38 <enodeb_2>
39 <host>Compute3</host>
40 <enb_address>10.32.11.194</enb_address>
41 <ues>ue1,ue2</ues>
42 </enodeb_2>
43 </enodebs>
44 <enodeb_host>Compute3</enodeb_host>
Daniele Moro80889562021-09-08 10:09:26 +020045 <router_mac>00:00:0A:4C:1C:46</router_mac> # Needed to verify connectivity with scapy
46 <s1u_address>10.32.11.126</s1u_address>
Daniele Moro80889562021-09-08 10:09:26 +020047 <ues>
48 <ue2>
Daniele Moro80889562021-09-08 10:09:26 +020049 <ue_address>10.240.0.2</ue_address>
50 <teid>200</teid>
51 <up_id>20</up_id>
52 <down_id>21</down_id>
Carmelo Cascone848d1f52022-01-27 18:15:58 -080053 <!-- TC 0 means BEST EFFORT -->
54 <tc>2</tc>
Daniele Moro80889562021-09-08 10:09:26 +020055 <five_g>False</five_g>
56 </ue2>
57 </ues>
Daniele Moro522023c2021-10-15 17:30:33 +020058 <switch_to_kill>Leaf2</switch_to_kill> # Component name of the switch to kill in CASE 5
59 <enodebs_fail>enodeb_1</enodebs_fail> # List of eNodeBs that should fail traffic forwarding in CASE 5
Daniele Moro80889562021-09-08 10:09:26 +020060 </UP4>
61 """
62
63 def __init__(self):
64 self.s1u_address = None
Daniele Moro522023c2021-10-15 17:30:33 +020065 self.enodebs = None
Daniele Moro80889562021-09-08 10:09:26 +020066 self.pdn_host = None
67 self.pdn_interface = None
68 self.router_mac = None
Daniele Moro522023c2021-10-15 17:30:33 +020069 self.emulated_ues = {}
Daniele Moro80889562021-09-08 10:09:26 +020070 self.up4_client = None
Daniele Moro522023c2021-10-15 17:30:33 +020071 self.no_host = False
Daniele Moro80889562021-09-08 10:09:26 +020072
Daniele Moro954e2282021-09-22 17:32:03 +020073 def setup(self, p4rt_client, no_host=False):
74 """
75 Set up P4RT and scapy on eNB and PDN hosts
76 :param p4rt_client: a P4RuntimeCliDriver component
77 :param no_host: True if you don't want to start scapy on the hosts
78 :return:
79 """
Daniele Moro80889562021-09-08 10:09:26 +020080 self.s1u_address = main.params["UP4"]["s1u_address"]
Daniele Moro80889562021-09-08 10:09:26 +020081 self.emulated_ues = main.params["UP4"]['ues']
82 self.up4_client = p4rt_client
Daniele Moro522023c2021-10-15 17:30:33 +020083 self.no_host = no_host
Daniele Moro80889562021-09-08 10:09:26 +020084
85 # Optional Parameters
Daniele Moro522023c2021-10-15 17:30:33 +020086
87 self.enodebs = copy.deepcopy((main.params["UP4"]["enodebs"]))
88 for enb in self.enodebs.values():
89 enb["ues"] = enb["ues"].split(",")
90 enb["host"] = getattr(main, enb["host"])
91 # If interface not provided by the params, use the default in the host
92 if "interface" not in enb.keys():
93 enb["interface"] = enb["host"].interfaces[0]["name"]
94 if "pdn_host" in main.params["UP4"]:
95 self.pdn_host = getattr(main, main.params["UP4"]["pdn_host"])
96 self.pdn_interface = self.pdn_host.interfaces[0]
97 self.router_mac = main.params["UP4"].get("router_mac", None)
Daniele Moro80889562021-09-08 10:09:26 +020098
99 # Start components
100 self.up4_client.startP4RtClient()
Daniele Moro522023c2021-10-15 17:30:33 +0200101 if not self.no_host:
102 if self.enodebs is not None:
103 for enb in self.enodebs.values():
104 enb["host"].startScapy(ifaceName=enb["interface"],
Daniele Moro49a843c2022-01-05 14:36:32 +0100105 enableGtp=True)
Daniele Moro522023c2021-10-15 17:30:33 +0200106 if self.pdn_host is not None:
107 self.pdn_host.startScapy(ifaceName=self.pdn_interface["name"])
Daniele Moro80889562021-09-08 10:09:26 +0200108
109 def teardown(self):
110 self.up4_client.stopP4RtClient()
Daniele Moro522023c2021-10-15 17:30:33 +0200111 if not self.no_host:
112 if self.enodebs is not None:
113 for enb in self.enodebs.values():
114 enb["host"].stopScapy()
115 if self.pdn_host is not None:
116 self.pdn_host.stopScapy()
Daniele Moro80889562021-09-08 10:09:26 +0200117
118 def attachUes(self):
Daniele Moro522023c2021-10-15 17:30:33 +0200119 for (name, ue) in self.emulated_ues.items():
Daniele Moro80889562021-09-08 10:09:26 +0200120 ue = UP4.__sanitizeUeData(ue)
Daniele Moro522023c2021-10-15 17:30:33 +0200121 self.attachUe(name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200122
123 def detachUes(self):
Daniele Moro522023c2021-10-15 17:30:33 +0200124 for (name, ue) in self.emulated_ues.items():
Daniele Moro249d6e72021-09-20 10:32:54 +0200125 ue = UP4.__sanitizeUeData(ue)
Daniele Moro522023c2021-10-15 17:30:33 +0200126 self.detachUe(name, **ue)
Daniele Moro80889562021-09-08 10:09:26 +0200127
Daniele Moro522023c2021-10-15 17:30:33 +0200128 def testUpstreamTraffic(self, enb_names=None, shouldFail=False):
129 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200130 main.log.error(
131 "Need eNodeB and PDN host params to generate scapy traffic")
132 return
133 # Scapy filter needs to start before sending traffic
Daniele Moro522023c2021-10-15 17:30:33 +0200134 if enb_names is None or enb_names == []:
135 enodebs = self.enodebs.values()
136 else:
137 enodebs = [self.enodebs[enb] for enb in enb_names]
Daniele Moro80889562021-09-08 10:09:26 +0200138 pkt_filter_upstream = ""
Daniele Moro522023c2021-10-15 17:30:33 +0200139 ues = []
140 for enb in enodebs:
141 for ue_name in enb["ues"]:
142 ue = self.emulated_ues[ue_name]
143 if "ue_address" in ue:
144 ues.append(ue)
145 if len(pkt_filter_upstream) != 0:
146 pkt_filter_upstream += " or "
147 pkt_filter_upstream += "src host " + ue["ue_address"]
Daniele Moro80889562021-09-08 10:09:26 +0200148 pkt_filter_upstream = "ip and udp dst port %s and (%s) and dst host %s" % \
149 (PDN_PORT, pkt_filter_upstream,
150 self.pdn_interface["ips"][0])
151 main.log.info("Start listening on %s intf %s" %
152 (self.pdn_host.name, self.pdn_interface["name"]))
153 main.log.debug("BPF Filter Upstream: \n %s" % pkt_filter_upstream)
154 self.pdn_host.startFilter(ifaceName=self.pdn_interface["name"],
Daniele Moro522023c2021-10-15 17:30:33 +0200155 sniffCount=len(ues),
Daniele Moro80889562021-09-08 10:09:26 +0200156 pktFilter=pkt_filter_upstream)
157
158 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200159 "Sending %d packets from eNodeB host" % len(ues))
160 for enb in enodebs:
161 for ue_name in enb["ues"]:
162 main.log.info(ue_name)
163 ue = self.emulated_ues[ue_name]
164 main.log.info(str(ue))
165 UP4.buildGtpPacket(enb["host"],
166 src_ip_outer=enb["enb_address"],
167 dst_ip_outer=self.s1u_address,
168 src_ip_inner=ue["ue_address"],
169 dst_ip_inner=self.pdn_interface["ips"][0],
170 src_udp_inner=UE_PORT,
171 dst_udp_inner=PDN_PORT,
172 teid=int(ue["teid"]))
173 enb["host"].sendPacket(iface=enb["interface"])
Daniele Moro80889562021-09-08 10:09:26 +0200174
Daniele Morobf53dec2021-09-13 18:11:56 +0200175 packets = UP4.checkFilterAndGetPackets(self.pdn_host)
Daniele Moro80889562021-09-08 10:09:26 +0200176 fail = False
Daniele Moro522023c2021-10-15 17:30:33 +0200177 if len(ues) != packets.count('Ether'):
Daniele Moro80889562021-09-08 10:09:26 +0200178 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200179 msg = "Failed to capture packets in PDN.\n" + str(packets)
Daniele Moro80889562021-09-08 10:09:26 +0200180 else:
181 msg = "Correctly captured packet in PDN. "
182 # We expect exactly 1 packet per UE
Daniele Moro522023c2021-10-15 17:30:33 +0200183 pktsFiltered = [packets.count("src=" + ue["ue_address"]) for ue in ues]
Daniele Moro80889562021-09-08 10:09:26 +0200184 if pktsFiltered.count(1) != len(pktsFiltered):
185 fail = True
Daniele Moro49a843c2022-01-05 14:36:32 +0100186 msg += "\nError on the number of packets per UE in downstream.\n" + str(
187 packets)
Daniele Moro80889562021-09-08 10:09:26 +0200188 else:
Daniele Moroc6811a82021-10-12 11:29:41 +0200189 msg += "\nOne packet per UE in upstream. "
Daniele Moro80889562021-09-08 10:09:26 +0200190
191 utilities.assert_equal(
Daniele Moro522023c2021-10-15 17:30:33 +0200192 expect=shouldFail, actual=fail, onpass=msg, onfail=msg)
Daniele Moro80889562021-09-08 10:09:26 +0200193
Daniele Moro522023c2021-10-15 17:30:33 +0200194 def testDownstreamTraffic(self, enb_names=None, shouldFail=False):
195 if self.enodebs is None or self.pdn_host is None:
Daniele Moro80889562021-09-08 10:09:26 +0200196 main.log.error(
197 "Need eNodeB and PDN host params to generate scapy traffic")
198 return
Daniele Moro522023c2021-10-15 17:30:33 +0200199 if enb_names is None or enb_names == []:
200 enodebs = self.enodebs.values()
201 else:
202 enodebs = [self.enodebs[enb] for enb in enb_names]
203 pkt_filter_downstream = "ip and udp src port %d and udp dst port %d and src host %s" % (
204 GPDU_PORT, GPDU_PORT, self.s1u_address)
205 ues = []
206 for enb in enodebs:
Daniele Moro49a843c2022-01-05 14:36:32 +0100207 filter_down = pkt_filter_downstream + " and dst host %s" % enb[
208 "enb_address"]
Daniele Moro522023c2021-10-15 17:30:33 +0200209 main.log.info("Start listening on %s intf %s" % (
210 enb["host"], enb["interface"]))
211 main.log.debug("BPF Filter Downstream: \n %s" % filter_down)
212 enb["host"].startFilter(ifaceName=enb["interface"],
213 sniffCount=len(enb["ues"]),
214 pktFilter=filter_down)
215 ues.extend([self.emulated_ues[ue_name] for ue_name in enb["ues"]])
Daniele Moro80889562021-09-08 10:09:26 +0200216
217 main.log.info(
Daniele Moro522023c2021-10-15 17:30:33 +0200218 "Sending %d packets from PDN host" % len(ues))
219 for ue in ues:
Daniele Moro80889562021-09-08 10:09:26 +0200220 # From PDN we have to set dest MAC, otherwise scapy will do ARP
221 # request for the UE IP address.
Daniele Morobf53dec2021-09-13 18:11:56 +0200222 UP4.buildUdpPacket(self.pdn_host,
223 dst_eth=self.router_mac,
224 src_ip=self.pdn_interface["ips"][0],
225 dst_ip=ue["ue_address"],
226 src_udp=PDN_PORT,
227 dst_udp=UE_PORT)
Daniele Moro80889562021-09-08 10:09:26 +0200228 self.pdn_host.sendPacket(iface=self.pdn_interface["name"])
Daniele Moro522023c2021-10-15 17:30:33 +0200229 packets = ""
230 for enb in enodebs:
231 pkt = UP4.checkFilterAndGetPackets(enb["host"])
232 packets += pkt
Daniele Moro80889562021-09-08 10:09:26 +0200233 # The BPF filter might capture non-GTP packets because we can't filter
234 # GTP header in BPF. For this reason, check that the captured packets
235 # are from the expected tunnels.
236 # TODO: check inner UDP and IP fields as well
237 # FIXME: with newer scapy TEID becomes teid (required for Scapy 2.4.5)
Daniele Moro49a843c2022-01-05 14:36:32 +0100238 pktsFiltered = [packets.count("TEID=" + hex(int(ue["teid"])) + "L ")
239 for ue in ues]
Daniele Moro522023c2021-10-15 17:30:33 +0200240 main.log.info("PACKETS: " + str(packets))
241 main.log.info("PKTs Filtered: " + str(pktsFiltered))
Daniele Moro80889562021-09-08 10:09:26 +0200242 fail = False
Daniele Moro522023c2021-10-15 17:30:33 +0200243 if len(ues) != sum(pktsFiltered):
Daniele Moro80889562021-09-08 10:09:26 +0200244 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200245 msg = "Failed to capture packets in eNodeB.\n" + str(packets)
Daniele Moro80889562021-09-08 10:09:26 +0200246 else:
247 msg = "Correctly captured packets in eNodeB. "
248 # We expect exactly 1 packet per UE
249 if pktsFiltered.count(1) != len(pktsFiltered):
250 fail = True
Daniele Moro49a843c2022-01-05 14:36:32 +0100251 msg += "\nError on the number of packets per GTP TEID in downstream.\n" + str(
252 packets)
Daniele Moro80889562021-09-08 10:09:26 +0200253 else:
Daniele Moroc6811a82021-10-12 11:29:41 +0200254 msg += "\nOne packet per GTP TEID in downstream. "
Daniele Moro80889562021-09-08 10:09:26 +0200255
256 utilities.assert_equal(
Daniele Moro522023c2021-10-15 17:30:33 +0200257 expect=shouldFail, actual=fail, onpass=msg, onfail=msg)
Daniele Moro80889562021-09-08 10:09:26 +0200258
Daniele Moro49a843c2022-01-05 14:36:32 +0100259 def readUeSessionsNumber(self):
Daniele Moro954e2282021-09-22 17:32:03 +0200260 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100261 Read the UE session tables and return the number of entries
Daniele Moro954e2282021-09-22 17:32:03 +0200262
Daniele Moro49a843c2022-01-05 14:36:32 +0100263 :return: Number of entries in the UE session tables
Daniele Moro954e2282021-09-22 17:32:03 +0200264 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100265 tableName = 'PreQosPipe.sessions_uplink'
266 nUeSess = self.up4_client.readNumberTableEntries(tableName)
267 tableName = 'PreQosPipe.sessions_downlink'
268 nUeSess += self.up4_client.readNumberTableEntries(tableName)
269 return nUeSess
Daniele Moro954e2282021-09-22 17:32:03 +0200270
Daniele Moro49a843c2022-01-05 14:36:32 +0100271 def readTerminationsNumber(self):
Daniele Moro954e2282021-09-22 17:32:03 +0200272 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100273 Read the terminations and return the number of entities
Daniele Moro954e2282021-09-22 17:32:03 +0200274
Daniele Moro49a843c2022-01-05 14:36:32 +0100275 :return: Number of terminations entities
Daniele Moro954e2282021-09-22 17:32:03 +0200276 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100277 tableName = 'PreQosPipe.terminations_uplink'
278 nTerm = self.up4_client.readNumberTableEntries(tableName)
279 tableName = 'PreQosPipe.terminations_downlink'
280 nTerm += self.up4_client.readNumberTableEntries(tableName)
281 return nTerm
Daniele Moro954e2282021-09-22 17:32:03 +0200282
283 def verifyUesFlowNumberP4rt(self):
284 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100285 Verify via P4RT CLI that the number of UE sessions and terminations
286 is the expected one
Daniele Moro954e2282021-09-22 17:32:03 +0200287
Daniele Moro49a843c2022-01-05 14:36:32 +0100288 :return: True if the number of UE sessions and terminations is expected,
289 False otherwise
Daniele Moro954e2282021-09-22 17:32:03 +0200290 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100291 nUeSessions = self.readUeSessionsNumber()
292 nTerminations = self.readTerminationsNumber()
293 return nUeSessions == nTerminations == len(self.emulated_ues) * 2
Daniele Moro954e2282021-09-22 17:32:03 +0200294
295 def verifyNoUesFlowNumberP4rt(self, preInstalledUes=0):
296 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100297 Verify via P4RT CLI that there is no UE sessions and terminations installed.
Daniele Moro954e2282021-09-22 17:32:03 +0200298
Daniele Moro49a843c2022-01-05 14:36:32 +0100299 :param preInstalledUes: Number of UEs whose UE sessions and terminations
300 are still programmed
Daniele Moro954e2282021-09-22 17:32:03 +0200301 :return:
302 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100303 return self.readUeSessionsNumber() == self.readTerminationsNumber() == preInstalledUes * 2
Daniele Moro954e2282021-09-22 17:32:03 +0200304
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200305 def verifyNoUesFlow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200306 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100307 Verify that no UE session, terminations are installed in ONOS.
Daniele Morobf53dec2021-09-13 18:11:56 +0200308
309 :param onosCli: An instance of a OnosCliDriver
310 :param retries: number of retries
311 :return:
312 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100313 retValue = utilities.retry(f=UP4.__verifyNoUeSessionAndTerminationOnos,
Daniele Morobf53dec2021-09-13 18:11:56 +0200314 retValue=False,
315 args=[onosCli],
Daniele Moro6dfbfef2021-09-28 22:44:19 +0200316 sleep=5,
Daniele Morobf53dec2021-09-13 18:11:56 +0200317 attempts=retries)
318 utilities.assert_equal(expect=True,
319 actual=retValue,
Daniele Moro49a843c2022-01-05 14:36:32 +0100320 onpass="No UE session and terminations in ONOS",
321 onfail="Stale UE session or terminations")
Daniele Morobf53dec2021-09-13 18:11:56 +0200322
323 @staticmethod
Daniele Moro49a843c2022-01-05 14:36:32 +0100324 def __verifyNoUeSessionAndTerminationOnos(onosCli):
Daniele Morobf53dec2021-09-13 18:11:56 +0200325 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100326 Verify that no UE session, terminations are installed in ONOS
Daniele Morobf53dec2021-09-13 18:11:56 +0200327
328 :param onosCli: An instance of a OnosCliDriver
329 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100330 sessions = onosCli.sendline(cmdStr="up4:read-entities -s",
331 showResponse=True,
332 noExit=True, expectJson=False)
333 terminations = onosCli.sendline(cmdStr="up4:read-entities -t",
334 showResponse=True,
335 noExit=True, expectJson=False)
336 return sessions == "" and terminations == ""
Daniele Morobf53dec2021-09-13 18:11:56 +0200337
Daniele Moroc6811a82021-10-12 11:29:41 +0200338 def verifyUp4Flow(self, onosCli, retries=10):
Daniele Morobf53dec2021-09-13 18:11:56 +0200339 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100340 Verify UE session, terminations and GTP tunnel peers installed via UP4
341 using the ONOS CLI.
Daniele Morobf53dec2021-09-13 18:11:56 +0200342
343 :param onosCli: An instance of a OnosCliDriver
Daniele Moroc6811a82021-10-12 11:29:41 +0200344 :param retries: Number of retries
Daniele Morobf53dec2021-09-13 18:11:56 +0200345 """
Daniele Moro49a843c2022-01-05 14:36:32 +0100346 failString = ""
Daniele Moroc6811a82021-10-12 11:29:41 +0200347 retValue = utilities.retry(f=self.__internalVerifyUp4Flow,
348 retValue=False,
349 args=[onosCli, failString],
350 sleep=5,
351 attempts=retries)
352 utilities.assert_equal(
353 expect=True,
354 actual=retValue,
Daniele Moro49a843c2022-01-05 14:36:32 +0100355 onpass="Correct UE session, terminations and GTP tunnel peers in ONOS",
356 onfail="Wrong UE session, terminations and GTP tunnel peers in ONOS. " +
357 "Missing:\n" + failString
Daniele Moroc6811a82021-10-12 11:29:41 +0200358 )
359
360 def __internalVerifyUp4Flow(self, onosCli, failMsg=""):
Daniele Moro49a843c2022-01-05 14:36:32 +0100361 sessions = onosCli.sendline(cmdStr="up4:read-entities -s",
362 showResponse=True,
363 noExit=True, expectJson=False)
364 terminations = onosCli.sendline(cmdStr="up4:read-entities -t",
365 showResponse=True,
366 noExit=True, expectJson=False)
367 tunn_peer = onosCli.sendline(cmdStr="up4:read-entities -g",
368 showResponse=True,
369 noExit=True, expectJson=False)
Daniele Morobf53dec2021-09-13 18:11:56 +0200370 fail = False
Daniele Moro522023c2021-10-15 17:30:33 +0200371 for (ue_name, ue) in self.emulated_ues.items():
Daniele Moro49a843c2022-01-05 14:36:32 +0100372 if sessions.count(self.upUeSessionOnosString(**ue)) != 1:
373 failMsg += self.upUeSessionOnosString(**ue) + "\n"
Daniele Morobf53dec2021-09-13 18:11:56 +0200374 fail = True
Daniele Moro49a843c2022-01-05 14:36:32 +0100375 if sessions.count(self.downUeSessionOnosString(**ue)) != 1:
376 failMsg += self.downUeSessionOnosString(**ue) + "\n"
Daniele Morobf53dec2021-09-13 18:11:56 +0200377 fail = True
Daniele Moro49a843c2022-01-05 14:36:32 +0100378 if terminations.count(self.upTerminationOnosString(**ue)) != 1:
379 failMsg += self.upTerminationOnosString(**ue) + "\n"
Daniele Morobf53dec2021-09-13 18:11:56 +0200380 fail = True
Daniele Moro49a843c2022-01-05 14:36:32 +0100381 if terminations.count(self.downTerminationOnosString(**ue)) != 1:
382 failMsg += self.downTerminationOnosString(**ue) + "\n"
383 fail = True
384 if tunn_peer.count(self.gtpTunnelPeerOnosString(ue_name, **ue)) != 1:
385 failMsg += self.gtpTunnelPeerOnosString(ue_name, **ue) + "\n"
Daniele Morobf53dec2021-09-13 18:11:56 +0200386 fail = True
Daniele Moroc6811a82021-10-12 11:29:41 +0200387 return not fail
Daniele Morobf53dec2021-09-13 18:11:56 +0200388
Daniele Moro49a843c2022-01-05 14:36:32 +0100389 def upUeSessionOnosString(self, teid=None, teid_up=None, **kwargs):
Daniele Morobf53dec2021-09-13 18:11:56 +0200390 if teid is not None:
391 teid_up = teid
Daniele Moro49a843c2022-01-05 14:36:32 +0100392 return "UESessionUL{{Match(tun_dst_addr={}, TEID={}) -> (FWD)}}".format(
393 self.s1u_address, teid_up)
Daniele Morobf53dec2021-09-13 18:11:56 +0200394
Daniele Moro49a843c2022-01-05 14:36:32 +0100395 def downUeSessionOnosString(self, ue_address, down_id=None,
396 tunn_peer_id=None,
397 **kwargs):
Daniele Morobf53dec2021-09-13 18:11:56 +0200398 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100399 tunn_peer_id = down_id
400 return "UESessionDL{{Match(ue_addr={}) -> (FWD, tun_peer={})}}".format(
401 ue_address, tunn_peer_id)
402
403 def upTerminationOnosString(self, ue_address, up_id=None, ctr_id_up=None,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800404 tc=None, **kwargs):
Daniele Moro49a843c2022-01-05 14:36:32 +0100405 if up_id is not None:
406 ctr_id_up = up_id
407 return "TerminationUL{{Match(ue_addr={})->(CTR_ID={}, TC={})}}".format(
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800408 ue_address, ctr_id_up, tc)
Daniele Moro49a843c2022-01-05 14:36:32 +0100409
410 def downTerminationOnosString(self, ue_address, teid=None, down_id=None,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800411 ctr_id_down=None, teid_down=None, tc=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100412 **kwargs):
413 if down_id is not None:
Daniele Morobf53dec2021-09-13 18:11:56 +0200414 ctr_id_down = down_id
Daniele Morobf53dec2021-09-13 18:11:56 +0200415 if teid is not None:
416 teid_down = teid
Daniele Moro49a843c2022-01-05 14:36:32 +0100417 return "TerminationDL{{Match(ue_addr={})->(TEID={}, CTR_ID={}, QFI={}, TC={})}}".format(
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800418 ue_address, teid_down, ctr_id_down, tc, tc)
Daniele Morobf53dec2021-09-13 18:11:56 +0200419
Daniele Moro49a843c2022-01-05 14:36:32 +0100420 def gtpTunnelPeerOnosString(self, ue_name, down_id=None, tunn_peer_id=None,
421 **kwargs):
422 if down_id is not None:
423 tunn_peer_id = down_id
424 enb_address = self.__getEnbAddress(ue_name)
425 return "GTP-Tunnel-Peer({} -> src:{}, dst:{} srcPort:{})".format(
426 tunn_peer_id, self.s1u_address, enb_address, GPDU_PORT)
Daniele Morobf53dec2021-09-13 18:11:56 +0200427
Daniele Moro80889562021-09-08 10:09:26 +0200428 @staticmethod
429 def __sanitizeUeData(ue):
Daniele Moro249d6e72021-09-20 10:32:54 +0200430 if "five_g" in ue and type(ue["five_g"]) != bool:
Daniele Moro80889562021-09-08 10:09:26 +0200431 ue["five_g"] = bool(strtobool(ue["five_g"]))
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800432 if "tc" in ue and ue["tc"] == "":
433 ue["tc"] = 0
Daniele Moro80889562021-09-08 10:09:26 +0200434 return ue
435
Daniele Moro49a843c2022-01-05 14:36:32 +0100436 def attachUe(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200437 teid=None, up_id=None, down_id=None,
438 teid_up=None, teid_down=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100439 ctr_id_up=None, ctr_id_down=None,
440 tunn_peer_id=None,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800441 tc=None, five_g=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200442 self.__programUp4Rules(ue_name,
Daniele Moro80889562021-09-08 10:09:26 +0200443 ue_address,
444 teid, up_id, down_id,
445 teid_up, teid_down,
Daniele Moro49a843c2022-01-05 14:36:32 +0100446 ctr_id_up, ctr_id_down,
447 tunn_peer_id,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800448 tc, five_g, action="program")
Daniele Moro80889562021-09-08 10:09:26 +0200449
Daniele Moro49a843c2022-01-05 14:36:32 +0100450 def detachUe(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200451 teid=None, up_id=None, down_id=None,
452 teid_up=None, teid_down=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100453 ctr_id_up=None, ctr_id_down=None,
454 tunn_peer_id=None,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800455 tc=None, five_g=False):
Daniele Moro522023c2021-10-15 17:30:33 +0200456 self.__programUp4Rules(ue_name,
Daniele Moro80889562021-09-08 10:09:26 +0200457 ue_address,
458 teid, up_id, down_id,
459 teid_up, teid_down,
Daniele Moro49a843c2022-01-05 14:36:32 +0100460 ctr_id_up, ctr_id_down,
461 tunn_peer_id,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800462 tc, five_g, action="clear")
Daniele Moro80889562021-09-08 10:09:26 +0200463
Daniele Moro49a843c2022-01-05 14:36:32 +0100464 def __programUp4Rules(self, ue_name, ue_address,
Daniele Moro80889562021-09-08 10:09:26 +0200465 teid=None, up_id=None, down_id=None,
Daniele Moro49a843c2022-01-05 14:36:32 +0100466 teid_up=None, teid_down=None, ctr_id_up=None,
467 ctr_id_down=None, tunn_peer_id=None,
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800468 tc=0, five_g=False, app_id=DEFAULT_APP_ID,
Daniele Moroa1975192022-01-21 18:01:19 +0100469 action="program"):
Daniele Moro80889562021-09-08 10:09:26 +0200470 if up_id is not None:
Daniele Moro80889562021-09-08 10:09:26 +0200471 ctr_id_up = up_id
472 if down_id is not None:
Daniele Moro49a843c2022-01-05 14:36:32 +0100473 tunn_peer_id = down_id
Daniele Moro80889562021-09-08 10:09:26 +0200474 ctr_id_down = down_id
475 if teid is not None:
476 teid_up = teid
477 teid_down = teid
478
479 entries = []
480
Daniele Moro522023c2021-10-15 17:30:33 +0200481 # Retrieve eNobeB address from eNodeB list
482 enb_address = self.__getEnbAddress(ue_name)
483
Daniele Moro80889562021-09-08 10:09:26 +0200484 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100485 # UE Session Entries
Daniele Moro80889562021-09-08 10:09:26 +0200486 # ========================#
487
488 # Uplink
Daniele Moro49a843c2022-01-05 14:36:32 +0100489 tableName = 'PreQosPipe.sessions_uplink'
490 actionName = 'PreQosPipe.set_session_uplink'
Daniele Moro80889562021-09-08 10:09:26 +0200491 matchFields = {}
492 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200493 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100494 matchFields['n3_address'] = str(self.s1u_address)
Daniele Moro80889562021-09-08 10:09:26 +0200495 matchFields['teid'] = str(teid_up)
Daniele Moro49a843c2022-01-05 14:36:32 +0100496 if five_g:
497 # TODO: currently QFI match is unsupported in TNA
498 main.log.warn("Matching on QFI is currently unsupported in TNA")
Daniele Moro80889562021-09-08 10:09:26 +0200499 if not self.__add_entry(tableName, actionName, matchFields,
500 actionParams, entries, action):
501 return False
502
503 # Downlink
Daniele Moro49a843c2022-01-05 14:36:32 +0100504 tableName = 'PreQosPipe.sessions_downlink'
505 actionName = 'PreQosPipe.set_session_downlink'
Daniele Moro80889562021-09-08 10:09:26 +0200506 matchFields = {}
507 actionParams = {}
Daniele Moro80889562021-09-08 10:09:26 +0200508 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100509 matchFields['ue_address'] = str(ue_address)
Daniele Moro80889562021-09-08 10:09:26 +0200510 # Action params
Daniele Moro49a843c2022-01-05 14:36:32 +0100511 actionParams['tunnel_peer_id'] = str(tunn_peer_id)
Daniele Moro80889562021-09-08 10:09:26 +0200512 if not self.__add_entry(tableName, actionName, matchFields,
513 actionParams, entries, action):
514 return False
515
516 # ========================#
Daniele Moro49a843c2022-01-05 14:36:32 +0100517 # Terminations Entries
Daniele Moro80889562021-09-08 10:09:26 +0200518 # ========================#
519
520 # Uplink
Daniele Moro49a843c2022-01-05 14:36:32 +0100521 tableName = 'PreQosPipe.terminations_uplink'
522 actionName = 'PreQosPipe.uplink_term_fwd'
Daniele Moro80889562021-09-08 10:09:26 +0200523 matchFields = {}
524 actionParams = {}
525
526 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100527 matchFields['ue_address'] = str(ue_address)
Daniele Moroa1975192022-01-21 18:01:19 +0100528 matchFields['app_id'] = str(app_id)
Daniele Moro80889562021-09-08 10:09:26 +0200529 # Action params
Daniele Moro49a843c2022-01-05 14:36:32 +0100530 actionParams['ctr_idx'] = str(ctr_id_up)
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800531 actionParams['tc'] = str(tc)
Daniele Moro80889562021-09-08 10:09:26 +0200532 if not self.__add_entry(tableName, actionName, matchFields,
533 actionParams, entries, action):
534 return False
535
536 # Downlink
Daniele Moro49a843c2022-01-05 14:36:32 +0100537 tableName = 'PreQosPipe.terminations_downlink'
538 actionName = 'PreQosPipe.downlink_term_fwd'
Daniele Moro80889562021-09-08 10:09:26 +0200539 matchFields = {}
540 actionParams = {}
541
542 # Match fields
Daniele Moro49a843c2022-01-05 14:36:32 +0100543 matchFields['ue_address'] = str(ue_address)
Daniele Moroa1975192022-01-21 18:01:19 +0100544 matchFields['app_id'] = str(app_id)
Daniele Moro80889562021-09-08 10:09:26 +0200545 # Action params
Daniele Moro49a843c2022-01-05 14:36:32 +0100546 actionParams['teid'] = str(teid_down)
547 actionParams['ctr_idx'] = str(ctr_id_down)
548 # 1-1 mapping between QFI and TC
Carmelo Cascone848d1f52022-01-27 18:15:58 -0800549 actionParams['tc'] = str(tc)
550 actionParams['qfi'] = str(tc)
Daniele Moro49a843c2022-01-05 14:36:32 +0100551 if not self.__add_entry(tableName, actionName, matchFields,
552 actionParams, entries, action):
553 return False
554
555 # ========================#
556 # Tunnel Peer Entry
557 # ========================#
558 tableName = 'PreQosPipe.tunnel_peers'
559 actionName = 'PreQosPipe.load_tunnel_param'
560 matchFields = {}
561 actionParams = {}
562 # Match fields
563 matchFields['tunnel_peer_id'] = str(tunn_peer_id)
564 # Action params
Daniele Moro80889562021-09-08 10:09:26 +0200565 actionParams['src_addr'] = str(self.s1u_address)
Daniele Moro522023c2021-10-15 17:30:33 +0200566 actionParams['dst_addr'] = str(enb_address)
Daniele Moro80889562021-09-08 10:09:26 +0200567 actionParams['sport'] = TUNNEL_SPORT
568 if not self.__add_entry(tableName, actionName, matchFields,
569 actionParams, entries, action):
570 return False
571 if action == "program":
572 main.log.info("All entries added successfully.")
573 elif action == "clear":
574 self.__clear_entries(entries)
575
576 def __add_entry(self, tableName, actionName, matchFields, actionParams,
577 entries, action):
578 if action == "program":
579 self.up4_client.buildP4RtTableEntry(
580 tableName=tableName, actionName=actionName,
581 actionParams=actionParams, matchFields=matchFields)
582 if self.up4_client.pushTableEntry(debug=True) == main.TRUE:
583 main.log.info("*** Entry added.")
584 else:
585 main.log.error("Error during table insertion")
586 self.__clear_entries(entries)
587 return False
588 entries.append({"tableName": tableName, "actionName": actionName,
589 "matchFields": matchFields,
590 "actionParams": actionParams})
591 return True
592
593 def __clear_entries(self, entries):
594 for i, entry in enumerate(entries):
595 self.up4_client.buildP4RtTableEntry(**entry)
596 if self.up4_client.deleteTableEntry(debug=True) == main.TRUE:
597 main.log.info(
598 "*** Entry %d of %d deleted." % (i + 1, len(entries)))
599 else:
600 main.log.error("Error during table delete")
Daniele Morobf53dec2021-09-13 18:11:56 +0200601
Daniele Moro522023c2021-10-15 17:30:33 +0200602 def __getEnbAddress(self, ue_name):
603 for enb in self.enodebs.values():
604 if ue_name in enb["ues"]:
605 return enb["enb_address"]
606 main.log.error("Missing eNodeB address!")
607 return ""
608
Daniele Morobf53dec2021-09-13 18:11:56 +0200609 @staticmethod
610 def buildGtpPacket(host, src_ip_outer, dst_ip_outer, src_ip_inner,
611 dst_ip_inner, src_udp_inner, dst_udp_inner, teid):
612 host.buildEther()
613 host.buildIP(src=src_ip_outer, dst=dst_ip_outer)
614 host.buildUDP(ipVersion=4, dport=GPDU_PORT)
615 # FIXME: With newer scapy TEID becomes teid (required for Scapy 2.4.5)
616 host.buildGTP(gtp_type=0xFF, TEID=teid)
617 host.buildIP(overGtp=True, src=src_ip_inner, dst=dst_ip_inner)
618 host.buildUDP(ipVersion=4, overGtp=True, sport=src_udp_inner,
619 dport=dst_udp_inner)
620
621 @staticmethod
622 def buildUdpPacket(host, src_ip, dst_ip, src_udp, dst_udp, src_eth=None,
623 dst_eth=None):
624 host.buildEther(src=src_eth, dst=dst_eth)
625 host.buildIP(src=src_ip, dst=dst_ip)
626 host.buildUDP(ipVersion=4, sport=src_udp, dport=dst_udp)
627
628 @staticmethod
629 def checkFilterAndGetPackets(host):
630 finished = host.checkFilter()
631 if finished:
632 packets = host.readPackets(detailed=True)
633 for p in packets.splitlines():
634 main.log.debug(p)
635 # We care only of the last line from readPackets
636 return packets.splitlines()[-1]
637 else:
638 kill = host.killFilter()
639 main.log.debug(kill)
640 return ""