blob: f041171efb7c40fd8262fdac33c0feafd12d7220 [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
27from common.frameio.frameio import hexify
28from voltha.protos import third_party
29from voltha.core.flow_decomposer import *
30_ = third_party
31
32
33def ipv4int2str(ipv4int):
34 return '{}.{}.{}.{}'.format(
35 (ipv4int >> 24) & 0xff,
36 (ipv4int >> 16) & 0xff,
37 (ipv4int >> 8) & 0xff,
38 ipv4int & 0xff
39 )
40
41
42class SimDevice(object):
43
44 def __init__(self, name, logical_port_no):
45 self.name = name
46 self.logical_port_no = logical_port_no
47 self.links = dict()
48 self.flows = list()
49 self.log = structlog.get_logger(name=name,
50 logical_port_no=logical_port_no)
51
52 def link(self, port, egress_fun):
53 self.links.setdefault(port, []).append(egress_fun)
54
55 def ingress(self, port, frame):
56 self.log.debug('ingress', ingress_port=port)
57 outcome = self.process_frame(port, frame)
58 if outcome is not None:
59 egress_port, egress_frame = outcome
60 forwarded = 0
61 links = self.links.get(egress_port)
62 if links is not None:
63 for fun in links:
64 forwarded += 1
65 self.log.debug('forwarding', egress_port=egress_port)
66 fun(egress_port, egress_frame)
67 if not forwarded:
68 self.log.debug('no-one-to-forward-to', egress_port=egress_port)
69 else:
70 self.log.debug('dropped')
71
72 def install_flows(self, flows):
73 # store flows in precedence order so we can roll down on frame arrival
74 self.flows = sorted(flows, key=lambda fm: fm.priority, reverse=True)
75
76 def process_frame(self, ingress_port, ingress_frame):
77 for flow in self.flows:
78 if self.is_match(flow, ingress_port, ingress_frame):
79 egress_port, egress_frame = self.process_actions(
80 flow, ingress_frame)
81 return egress_port, egress_frame
82 return None
83
84 @staticmethod
85 def is_match(flow, ingress_port, frame):
86
87 def get_non_shim_ether_type(f):
88 if f.haslayer(Dot1Q):
89 f = f.getlayer(Dot1Q)
90 return f.type
91
92 def get_vlan_pcp(f):
93 if f.haslayer(Dot1Q):
94 return f.getlayer(Dot1Q).prio
95
96 def get_ip_proto(f):
97 if f.haslayer(IP):
98 return f.getlayer(IP).proto
99
100 def get_ipv4_dst(f):
101 if f.haslayer(IP):
102 return f.getlayer(IP).dst
103
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800104 def get_udp_src(f):
105 if f.haslayer(UDP):
106 return f.getlayer(UDP).sport
107
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800108 def get_udp_dst(f):
109 if f.haslayer(UDP):
110 return f.getlayer(UDP).dport
111
112 for field in get_ofb_fields(flow):
113
114 if field.type == IN_PORT:
115 if field.port != ingress_port:
116 return False
117
118 elif field.type == ETH_TYPE:
119 if field.eth_type != get_non_shim_ether_type(frame):
120 return False
121
122 elif field.type == IP_PROTO:
123 if field.ip_proto != get_ip_proto(frame):
124 return False
125
126 elif field.type == VLAN_VID:
127 expected_vlan = field.vlan_vid
128 tagged = frame.haslayer(Dot1Q)
129 if bool(expected_vlan & 4096) != bool(tagged):
130 return False
131 if tagged:
132 actual_vid = frame.getlayer(Dot1Q).vlan
133 if actual_vid != expected_vlan & 4095:
134 return False
135
136 elif field.type == VLAN_PCP:
137 if field.vlan_pcp != get_vlan_pcp(frame):
138 return False
139
140 elif field.type == IPV4_DST:
141 if ipv4int2str(field.ipv4_dst) != get_ipv4_dst(frame):
142 return False
143
Zsolt Haraszti3578a1c2017-01-10 15:29:02 -0800144 elif field.type == UDP_SRC:
145 if field.udp_src != get_udp_src(frame):
146 return False
147
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800148 elif field.type == UDP_DST:
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800149 if field.udp_dst != get_udp_dst(frame):
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800150 return False
151
Zsolt Haraszti6a5107c2017-01-09 23:42:41 -0800152 elif field.type == METADATA:
153 pass # safe to ignore
154
Zsolt Haraszti656ecc62016-12-28 15:08:23 -0800155 else:
156 raise NotImplementedError('field.type=%d' % field.type)
157
158 return True
159
160 @staticmethod
161 def process_actions(flow, frame):
162 egress_port = None
163 for action in get_actions(flow):
164
165 if action.type == OUTPUT:
166 egress_port = action.output.port
167
168 elif action.type == POP_VLAN:
169 if frame.haslayer(Dot1Q):
170 shim = frame.getlayer(Dot1Q)
171 frame = Ether(
172 src=frame.src,
173 dst=frame.dst,
174 type=shim.type) / shim.payload
175
176 elif action.type == PUSH_VLAN:
177 frame = (
178 Ether(src=frame.src, dst=frame.dst,
179 type=action.push.ethertype) /
180 Dot1Q(type=frame.type) /
181 frame.payload
182 )
183
184 elif action.type == SET_FIELD:
185 assert (action.set_field.field.oxm_class ==
186 ofp.OFPXMC_OPENFLOW_BASIC)
187 field = action.set_field.field.ofb_field
188
189 if field.type == VLAN_VID:
190 shim = frame.getlayer(Dot1Q)
191 shim.vlan = field.vlan_vid & 4095
192
193 elif field.type == VLAN_PCP:
194 shim = frame.getlayer(Dot1Q)
195 shim.prio = field.vlan_pcp
196
197 else:
198 raise NotImplementedError('set_field.field.type=%d'
199 % field.type)
200
201 else:
202 raise NotImplementedError('action.type=%d' % action.type)
203
204 return egress_port, frame
205
206
207class PonSim(object):
208
209 def __init__(self, onus, egress_fun):
210 self.egress_fun = egress_fun
211
212 # Create OLT and hook NNI port up for egress
213 self.olt = SimDevice('olt', 0)
214 self.olt.link(2, lambda _, frame: self.egress_fun(0, frame))
215 self.devices = dict()
216 self.devices[0] = self.olt
217
218 # Create ONUs of the requested number and hook them up with OLT
219 # and with egress fun
220 def mk_egress_fun(port_no):
221 return lambda _, frame: self.egress_fun(port_no, frame)
222
223 def mk_onu_ingress(onu):
224 return lambda _, frame: onu.ingress(1, frame)
225
226 for i in range(onus):
227 port_no = 128 + i
228 onu = SimDevice('onu%d' % i, port_no)
229 onu.link(1, lambda _, frame: self.olt.ingress(1, frame))
230 onu.link(2, mk_egress_fun(port_no))
231 self.olt.link(1, mk_onu_ingress(onu))
232 self.devices[port_no] = onu
233
234 def get_ports(self):
235 return sorted(self.devices.keys())
236
237 def olt_install_flows(self, flows):
238 self.olt.install_flows(flows)
239
240 def onu_install_flows(self, onu_port, flows):
241 self.devices[onu_port].install_flows(flows)
242
243 def ingress(self, port, frame):
244 if not isinstance(frame, Packet):
245 frame = Ether(frame)
246 self.devices[port].ingress(2, frame)
247