blob: 437290e261fa3a47ba4794d46ad27d312bdc12b0 [file] [log] [blame]
Zsolt Haraszti656ecc62016-12-28 15:08:23 -08001#
2# Copyright 2016 the original author or authors.
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16
17"""
18Simple PON Simulator which would not be needed if openvswitch could do
19802.1ad (QinQ), which it cannot (the reason is beyond me), or if CPQD could
20handle 0-tagged packets (no comment).
21"""
22import structlog
23from scapy.layers.inet import IP, UDP
24from scapy.layers.l2 import Ether, Dot1Q
25from scapy.packet import Packet
26
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080027from voltha.protos import third_party
Sergio Slobodrian98eff412017-03-15 14:46:30 -040028from voltha.protos.ponsim_pb2 import PonSimMetrics, PonSimPortMetrics, \
Khen Nursimulu49792142017-03-17 12:34:05 -040029 PonSimPacketCounter
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080030from voltha.core.flow_decomposer import *
Sergio Slobodrian98eff412017-03-15 14:46:30 -040031from twisted.internet.task import LoopingCall
Khen Nursimulu49792142017-03-17 12:34:05 -040032
Zsolt Haraszti656ecc62016-12-28 15:08:23 -080033_ = third_party
34
35
36def ipv4int2str(ipv4int):
37 return '{}.{}.{}.{}'.format(
38 (ipv4int >> 24) & 0xff,
39 (ipv4int >> 16) & 0xff,
40 (ipv4int >> 8) & 0xff,
41 ipv4int & 0xff
42 )
43
44
Khen Nursimulu49792142017-03-17 12:34:05 -040045class _FlowMatchMask(object):
46 """
47 Enum of mask values based on flow match priority. For instance, a port
48 match has higher priority when match that a UDP match.
49 """
50 UDP_DST = 1
51 UDP_SRC = 2
52 IPV4_DST = 4
53 VLAN_PCP = 8
54 VLAN_VID = 16
55 IP_PROTO = 34
56 ETH_TYPE = 64
57 IN_PORT = 128
58
59
Sergio Slobodrian98eff412017-03-15 14:46:30 -040060class FrameIOCounter(object):
61 class SingleFrameCounter(object):
Khen Nursimulu49792142017-03-17 12:34:05 -040062 def __init__(self, name, min, max):
Sergio Slobodrian98eff412017-03-15 14:46:30 -040063 # Currently there are 2 values, one for the PON interface (port 1)
64 # and one for the Network Interface (port 2). This can be extended if
65 # the virtual devices extend the number of ports.
Khen Nursimulu49792142017-03-17 12:34:05 -040066 self.value = [0, 0] # {PON,NI}
Sergio Slobodrian98eff412017-03-15 14:46:30 -040067 self.name = name
Khen Nursimulu49792142017-03-17 12:34:05 -040068 self.min = min
Sergio Slobodrian98eff412017-03-15 14:46:30 -040069 self.max = max
70
71 def __init__(self, device):
72 self.device = device
Khen Nursimulu49792142017-03-17 12:34:05 -040073 self.tx_counters = dict(
74 tx_64=self.SingleFrameCounter("tx_64", 1, 64),
75 tx_65_127=self.SingleFrameCounter("tx_65_127", 65, 127),
76 tx_128_255=self.SingleFrameCounter("tx_128_255", 128, 255),
77 tx_256_511=self.SingleFrameCounter("tx_256_511", 256, 511),
78 tx_512_1023=self.SingleFrameCounter("tx_512_1023", 512, 1024),
79 tx_1024_1518=self.SingleFrameCounter("tx_1024_1518", 1024, 1518),
80 tx_1519_9k=self.SingleFrameCounter("tx_1519_9k", 1519, 9216),
Sergio Slobodrian98eff412017-03-15 14:46:30 -040081 )
82 self.rx_counters = dict(
Khen Nursimulu49792142017-03-17 12:34:05 -040083 rx_64=self.SingleFrameCounter("rx_64", 1, 64),
84 rx_65_127=self.SingleFrameCounter("rx_65_127", 65, 127),
85 rx_128_255=self.SingleFrameCounter("rx_128_255", 128, 255),
86 rx_256_511=self.SingleFrameCounter("rx_256_511", 256, 511),
87 rx_512_1023=self.SingleFrameCounter("rx_512_1023", 512, 1024),
88 rx_1024_1518=self.SingleFrameCounter("rx_1024_1518", 1024, 1518),
89 rx_1519_9k=self.SingleFrameCounter("rx_1519_9k", 1519, 9216)
Sergio Slobodrian98eff412017-03-15 14:46:30 -040090 )
91
92 def count_rx_frame(self, port, size):
93 log.info("counting-rx-frame", size=size, port=port)
Khen Nursimulu49792142017-03-17 12:34:05 -040094 for k, v in self.rx_counters.iteritems():
Sergio Slobodrian98eff412017-03-15 14:46:30 -040095 if size >= v.min and size <= v.max:
Khen Nursimulu49792142017-03-17 12:34:05 -040096 self.rx_counters[k].value[port - 1] += 1
Sergio Slobodrian98eff412017-03-15 14:46:30 -040097 return
98 log.warn("unsupported-packet-size", size=size)
99
100 def count_tx_frame(self, port, size):
101 for k, v in self.tx_counters.iteritems():
102 if size >= v.min and size <= v.max:
Khen Nursimulu49792142017-03-17 12:34:05 -0400103 self.tx_counters[k].value[port - 1] += 1
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400104 return
105 log.warn("unsupported-packet-size", size=size)
106
107 def log_counts(self):
Khen Nursimulu49792142017-03-17 12:34:05 -0400108 rx_ct_list = [(v.name, v.value[0], v.value[1]) for v in
109 self.rx_counters.values()]
110 tx_ct_list = [(v.name, v.value[0], v.value[1]) for v in
111 self.tx_counters.values()]
112 log.info("rx-counts", rx_ct_list=rx_ct_list)
113 log.info("tx-counts", tx_ct_list=tx_ct_list)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400114
115 def make_proto(self):
116 sim_metrics = PonSimMetrics(
Khen Nursimulu49792142017-03-17 12:34:05 -0400117 device=self.device
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400118 )
Khen Nursimulu49792142017-03-17 12:34:05 -0400119 pon_port_metrics = PonSimPortMetrics(
120 port_name="pon"
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400121 )
Khen Nursimulu49792142017-03-17 12:34:05 -0400122 ni_port_metrics = PonSimPortMetrics(
123 port_name="nni"
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400124 )
125 for c in sorted(self.rx_counters):
126 ctr = self.rx_counters[c]
127 pon_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400128 PonSimPacketCounter(name=ctr.name, value=ctr.value[0])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400129 ])
130 # Since they're identical keys, save some time and cheat
131 ni_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400132 PonSimPacketCounter(name=ctr.name, value=ctr.value[1])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400133 ])
134
135 for c in sorted(self.tx_counters):
136 ctr = self.tx_counters[c]
137 pon_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400138 PonSimPacketCounter(name=ctr.name, value=ctr.value[0])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400139 ])
140 # Since they're identical keys, save some time and cheat
141 ni_port_metrics.packets.extend([
Khen Nursimulu49792142017-03-17 12:34:05 -0400142 PonSimPacketCounter(name=ctr.name, value=ctr.value[1])
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400143 ])
144 sim_metrics.metrics.extend([pon_port_metrics])
145 sim_metrics.metrics.extend([ni_port_metrics])
146
147 return sim_metrics
148
149
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800150class SimDevice(object):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800151 def __init__(self, name, logical_port_no):
152 self.name = name
153 self.logical_port_no = logical_port_no
154 self.links = dict()
155 self.flows = list()
156 self.log = structlog.get_logger(name=name,
157 logical_port_no=logical_port_no)
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400158 self.counter = FrameIOCounter(name)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800159
160 def link(self, port, egress_fun):
161 self.links.setdefault(port, []).append(egress_fun)
162
163 def ingress(self, port, frame):
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400164 self.log.debug('ingress', ingress_port=port, name=self.name)
165 self.counter.count_rx_frame(port, len(frame["Ether"].payload))
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800166 outcome = self.process_frame(port, frame)
167 if outcome is not None:
168 egress_port, egress_frame = outcome
169 forwarded = 0
170 links = self.links.get(egress_port)
171 if links is not None:
Khen Nursimulu49792142017-03-17 12:34:05 -0400172 self.counter.count_tx_frame(egress_port,
173 len(egress_frame["Ether"].payload))
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800174 for fun in links:
175 forwarded += 1
176 self.log.debug('forwarding', egress_port=egress_port)
177 fun(egress_port, egress_frame)
178 if not forwarded:
179 self.log.debug('no-one-to-forward-to', egress_port=egress_port)
180 else:
181 self.log.debug('dropped')
182
183 def install_flows(self, flows):
184 # store flows in precedence order so we can roll down on frame arrival
185 self.flows = sorted(flows, key=lambda fm: fm.priority, reverse=True)
186
187 def process_frame(self, ingress_port, ingress_frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400188 matched_mask = 0
Khen Nursimulu4f940622017-03-17 17:45:02 -0400189 highest_priority = 0
Khen Nursimulu49792142017-03-17 12:34:05 -0400190 matched_flow = None
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800191 for flow in self.flows:
Khen Nursimulu4f940622017-03-17 17:45:02 -0400192 # flows are sorted by highest priority.
193 if matched_flow and flow.priority < highest_priority:
194 break
195
196 highest_priority = flow.priority
Khen Nursimulu49792142017-03-17 12:34:05 -0400197 current_mask = self.is_match(flow, ingress_port, ingress_frame)
198 if current_mask > matched_mask:
199 matched_mask = current_mask
200 matched_flow = flow
201
202 if matched_flow:
203 egress_port, egress_frame = self.process_actions(
204 matched_flow, ingress_frame)
205 return egress_port, egress_frame
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800206 return None
207
208 @staticmethod
209 def is_match(flow, ingress_port, frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400210 matched_mask = 0
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800211
212 def get_non_shim_ether_type(f):
213 if f.haslayer(Dot1Q):
214 f = f.getlayer(Dot1Q)
215 return f.type
216
217 def get_vlan_pcp(f):
218 if f.haslayer(Dot1Q):
219 return f.getlayer(Dot1Q).prio
220
221 def get_ip_proto(f):
222 if f.haslayer(IP):
223 return f.getlayer(IP).proto
224
225 def get_ipv4_dst(f):
226 if f.haslayer(IP):
227 return f.getlayer(IP).dst
228
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800229 def get_udp_src(f):
230 if f.haslayer(UDP):
231 return f.getlayer(UDP).sport
232
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800233 def get_udp_dst(f):
234 if f.haslayer(UDP):
235 return f.getlayer(UDP).dport
236
237 for field in get_ofb_fields(flow):
238
239 if field.type == IN_PORT:
240 if field.port != ingress_port:
Khen Nursimulu49792142017-03-17 12:34:05 -0400241 return 0
242 matched_mask |= _FlowMatchMask.IN_PORT
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800243
244 elif field.type == ETH_TYPE:
245 if field.eth_type != get_non_shim_ether_type(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400246 return 0
247 matched_mask |= _FlowMatchMask.ETH_TYPE
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800248
249 elif field.type == IP_PROTO:
250 if field.ip_proto != get_ip_proto(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400251 return 0
252 matched_mask |= _FlowMatchMask.IP_PROTO
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800253
254 elif field.type == VLAN_VID:
255 expected_vlan = field.vlan_vid
256 tagged = frame.haslayer(Dot1Q)
257 if bool(expected_vlan & 4096) != bool(tagged):
Khen Nursimulu49792142017-03-17 12:34:05 -0400258 return 0
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800259 if tagged:
260 actual_vid = frame.getlayer(Dot1Q).vlan
261 if actual_vid != expected_vlan & 4095:
Khen Nursimulu49792142017-03-17 12:34:05 -0400262 return 0
263 matched_mask |= _FlowMatchMask.VLAN_VID
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800264
265 elif field.type == VLAN_PCP:
266 if field.vlan_pcp != get_vlan_pcp(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400267 return 0
268 matched_mask |= _FlowMatchMask.VLAN_PCP
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800269
270 elif field.type == IPV4_DST:
271 if ipv4int2str(field.ipv4_dst) != get_ipv4_dst(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400272 return 0
273 matched_mask |= _FlowMatchMask.IPV4_DST
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800274
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800275 elif field.type == UDP_SRC:
276 if field.udp_src != get_udp_src(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400277 return 0
278 matched_mask |= _FlowMatchMask.UDP_SRC
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800279
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800280 elif field.type == UDP_DST:
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800281 if field.udp_dst != get_udp_dst(frame):
Khen Nursimulu49792142017-03-17 12:34:05 -0400282 return 0
283 matched_mask |= _FlowMatchMask.UDP_DST
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800284
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800285 elif field.type == METADATA:
286 pass # safe to ignore
287
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800288 else:
289 raise NotImplementedError('field.type=%d' % field.type)
290
Khen Nursimulu49792142017-03-17 12:34:05 -0400291 return matched_mask
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800292
293 @staticmethod
294 def process_actions(flow, frame):
295 egress_port = None
296 for action in get_actions(flow):
297
298 if action.type == OUTPUT:
299 egress_port = action.output.port
300
301 elif action.type == POP_VLAN:
302 if frame.haslayer(Dot1Q):
303 shim = frame.getlayer(Dot1Q)
304 frame = Ether(
305 src=frame.src,
306 dst=frame.dst,
307 type=shim.type) / shim.payload
308
309 elif action.type == PUSH_VLAN:
310 frame = (
311 Ether(src=frame.src, dst=frame.dst,
312 type=action.push.ethertype) /
313 Dot1Q(type=frame.type) /
314 frame.payload
315 )
316
317 elif action.type == SET_FIELD:
318 assert (action.set_field.field.oxm_class ==
319 ofp.OFPXMC_OPENFLOW_BASIC)
320 field = action.set_field.field.ofb_field
321
322 if field.type == VLAN_VID:
323 shim = frame.getlayer(Dot1Q)
324 shim.vlan = field.vlan_vid & 4095
325
326 elif field.type == VLAN_PCP:
327 shim = frame.getlayer(Dot1Q)
328 shim.prio = field.vlan_pcp
329
330 else:
331 raise NotImplementedError('set_field.field.type=%d'
332 % field.type)
333
334 else:
335 raise NotImplementedError('action.type=%d' % action.type)
336
337 return egress_port, frame
338
339
340class PonSim(object):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800341 def __init__(self, onus, egress_fun):
342 self.egress_fun = egress_fun
343
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400344 self.log = structlog.get_logger()
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800345 # Create OLT and hook NNI port up for egress
346 self.olt = SimDevice('olt', 0)
347 self.olt.link(2, lambda _, frame: self.egress_fun(0, frame))
348 self.devices = dict()
349 self.devices[0] = self.olt
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400350 # TODO: This can be removed, it's for debugging purposes
351 self.lc = LoopingCall(self.olt.counter.log_counts)
Khen Nursimulu49792142017-03-17 12:34:05 -0400352 self.lc.start(90) # To correlate with Kafka
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800353
354 # Create ONUs of the requested number and hook them up with OLT
355 # and with egress fun
356 def mk_egress_fun(port_no):
357 return lambda _, frame: self.egress_fun(port_no, frame)
358
359 def mk_onu_ingress(onu):
360 return lambda _, frame: onu.ingress(1, frame)
361
362 for i in range(onus):
363 port_no = 128 + i
364 onu = SimDevice('onu%d' % i, port_no)
Khen Nursimulu49792142017-03-17 12:34:05 -0400365 onu.link(1, lambda _, frame: self.olt.ingress(1,
366 frame)) # Send to the OLT
367 onu.link(2,
368 mk_egress_fun(port_no)) # Send from the ONU to the world
369 self.olt.link(1, mk_onu_ingress(onu)) # Internal send to the ONU
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800370 self.devices[port_no] = onu
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400371 for d in self.devices:
372 self.log.info("pon-sim-init", port=d, name=self.devices[d].name,
373 links=self.devices[d].links)
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800374
375 def get_ports(self):
376 return sorted(self.devices.keys())
377
Sergio Slobodrian98eff412017-03-15 14:46:30 -0400378 def get_stats(self):
379 return self.olt.counter.make_proto()
380
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800381 def olt_install_flows(self, flows):
382 self.olt.install_flows(flows)
383
384 def onu_install_flows(self, onu_port, flows):
385 self.devices[onu_port].install_flows(flows)
386
387 def ingress(self, port, frame):
388 if not isinstance(frame, Packet):
389 frame = Ether(frame)
390 self.devices[port].ingress(2, frame)