blob: bf87dc3509692a5ac32975e1784f30ed1ba49d84 [file] [log] [blame]
Saurav Dasdecd7a62015-05-16 22:39:47 -07001package org.onosproject.driver.pipeline;
2
3import static org.slf4j.LoggerFactory.getLogger;
4
5import java.util.ArrayList;
6import java.util.Collection;
7import java.util.Collections;
8import java.util.concurrent.ConcurrentHashMap;
9
10import org.onlab.osgi.ServiceDirectory;
11import org.onlab.packet.Ethernet;
12import org.onlab.packet.MacAddress;
13import org.onlab.packet.VlanId;
14import org.onlab.util.KryoNamespace;
15import org.onosproject.core.ApplicationId;
16import org.onosproject.core.CoreService;
17import org.onosproject.net.DeviceId;
18import org.onosproject.net.PortNumber;
19import org.onosproject.net.behaviour.NextGroup;
20import org.onosproject.net.behaviour.Pipeliner;
21import org.onosproject.net.behaviour.PipelinerContext;
22import org.onosproject.net.driver.AbstractHandlerBehaviour;
23import org.onosproject.net.flow.DefaultFlowRule;
24import org.onosproject.net.flow.DefaultTrafficSelector;
25import org.onosproject.net.flow.DefaultTrafficTreatment;
26import org.onosproject.net.flow.FlowRule;
27import org.onosproject.net.flow.FlowRuleOperations;
28import org.onosproject.net.flow.FlowRuleOperationsContext;
29import org.onosproject.net.flow.FlowRuleService;
30import org.onosproject.net.flow.TrafficSelector;
31import org.onosproject.net.flow.TrafficTreatment;
32import org.onosproject.net.flow.criteria.Criteria;
33import org.onosproject.net.flow.criteria.Criterion;
34import org.onosproject.net.flow.criteria.EthCriterion;
35import org.onosproject.net.flow.criteria.EthTypeCriterion;
36import org.onosproject.net.flow.criteria.IPCriterion;
37import org.onosproject.net.flow.criteria.PortCriterion;
38import org.onosproject.net.flow.criteria.VlanIdCriterion;
39import org.onosproject.net.flowobjective.FilteringObjective;
40import org.onosproject.net.flowobjective.FlowObjectiveStore;
41import org.onosproject.net.flowobjective.ForwardingObjective;
42import org.onosproject.net.flowobjective.NextObjective;
43import org.onosproject.net.flowobjective.Objective;
44import org.onosproject.net.flowobjective.ObjectiveError;
45import org.slf4j.Logger;
46import org.onosproject.store.serializers.KryoNamespaces;
47
48/**
49 * Simple 2-Table Pipeline for Software/NPU based routers. This pipeline
50 * does not forward IP traffic to next-hop groups. Instead it forwards traffic
51 * using OF FlowMod actions.
52 */
53public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipeliner {
54
55 protected static final int FILTER_TABLE = 0;
56 protected static final int FIB_TABLE = 1;
57
58 private static final int DROP_PRIORITY = 0;
59 private static final int DEFAULT_PRIORITY = 0x8000;
60 private static final int HIGHEST_PRIORITY = 0xffff;
61
62 private ServiceDirectory serviceDirectory;
63 protected FlowRuleService flowRuleService;
64 private CoreService coreService;
65 private FlowObjectiveStore flowObjectiveStore;
66 protected DeviceId deviceId;
67 protected ApplicationId appId;
68 private ApplicationId driverId;
69 private Collection<Filter> filters;
70 private Collection<ForwardingObjective> pendingVersatiles;
71
72 private KryoNamespace appKryo = new KryoNamespace.Builder()
73 .register(DummyGroup.class)
74 .register(KryoNamespaces.API)
75 .register(byte[].class)
76 .build();
77
78 private final Logger log = getLogger(getClass());
79
80 @Override
81 public void init(DeviceId deviceId, PipelinerContext context) {
82 this.serviceDirectory = context.directory();
83 this.deviceId = deviceId;
84 coreService = serviceDirectory.get(CoreService.class);
85 flowRuleService = serviceDirectory.get(FlowRuleService.class);
86 flowObjectiveStore = context.store();
87 driverId = coreService.registerApplication(
88 "org.onosproject.driver.OVSCorsaPipeline");
89 filters = Collections.newSetFromMap(new ConcurrentHashMap<Filter, Boolean>());
90 pendingVersatiles = Collections.newSetFromMap(
91 new ConcurrentHashMap<ForwardingObjective, Boolean>());
92 initializePipeline();
93 }
94
95 @Override
96 public void filter(FilteringObjective filteringObjective) {
97 if (filteringObjective.type() == FilteringObjective.Type.PERMIT) {
98 processFilter(filteringObjective,
99 filteringObjective.op() == Objective.Operation.ADD,
100 filteringObjective.appId());
101 } else {
102 fail(filteringObjective, ObjectiveError.UNSUPPORTED);
103 }
104 }
105
106 @Override
107 public void forward(ForwardingObjective fwd) {
108 Collection<FlowRule> rules;
109 FlowRuleOperations.Builder flowOpsBuilder = FlowRuleOperations.builder();
110
111 rules = processForward(fwd);
112 switch (fwd.op()) {
113 case ADD:
114 rules.stream()
115 .filter(rule -> rule != null)
116 .forEach(flowOpsBuilder::add);
117 break;
118 case REMOVE:
119 rules.stream()
120 .filter(rule -> rule != null)
121 .forEach(flowOpsBuilder::remove);
122 break;
123 default:
124 fail(fwd, ObjectiveError.UNKNOWN);
125 log.warn("Unknown forwarding type {}", fwd.op());
126 }
127
128
129 flowRuleService.apply(flowOpsBuilder.build(new FlowRuleOperationsContext() {
130 @Override
131 public void onSuccess(FlowRuleOperations ops) {
132 pass(fwd);
133 }
134
135 @Override
136 public void onError(FlowRuleOperations ops) {
137 fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED);
138 }
139 }));
140
141 }
142
143 @Override
144 public void next(NextObjective nextObjective) {
145 switch (nextObjective.type()) {
146 case SIMPLE:
147 Collection<TrafficTreatment> treatments = nextObjective.next();
148 if (treatments.size() != 1) {
149 log.error("Next Objectives of type Simple should only have a "
150 + "single Traffic Treatment. Next Objective Id:{}", nextObjective.id());
151 fail(nextObjective, ObjectiveError.BADPARAMS);
152 return;
153 }
154 processSimpleNextObjective(nextObjective);
155 break;
156 case HASHED:
157 case BROADCAST:
158 case FAILOVER:
159 fail(nextObjective, ObjectiveError.UNSUPPORTED);
160 log.warn("Unsupported next objective type {}", nextObjective.type());
161 break;
162 default:
163 fail(nextObjective, ObjectiveError.UNKNOWN);
164 log.warn("Unknown next objective type {}", nextObjective.type());
165 }
166 }
167
168 private void pass(Objective obj) {
169 if (obj.context().isPresent()) {
170 obj.context().get().onSuccess(obj);
171 }
172 }
173
174 private void fail(Objective obj, ObjectiveError error) {
175 if (obj.context().isPresent()) {
176 obj.context().get().onError(obj, error);
177 }
178 }
179
180
181 private void initializePipeline() {
182 //Drop rules for both tables
183 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
184 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
185 FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
186
187 treatment.drop();
188
189 FlowRule rule = DefaultFlowRule.builder()
190 .forDevice(deviceId)
191 .withSelector(selector.build())
192 .withTreatment(treatment.build())
193 .withPriority(DROP_PRIORITY)
194 .fromApp(driverId)
195 .makePermanent()
196 .forTable(FILTER_TABLE)
197 .build();
198 ops = ops.add(rule);
199
200 rule = DefaultFlowRule.builder().forDevice(deviceId)
201 .withSelector(selector.build())
202 .withTreatment(treatment.build())
203 .withPriority(DROP_PRIORITY)
204 .fromApp(driverId)
205 .makePermanent()
206 .forTable(FIB_TABLE)
207 .build();
208 ops = ops.add(rule);
209
210 flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
211 @Override
212 public void onSuccess(FlowRuleOperations ops) {
213 log.info("Provisioned drop rules in both tables");
214 }
215
216 @Override
217 public void onError(FlowRuleOperations ops) {
218 log.info("Failed to provision drop rules");
219 }
220 }));
221 }
222
223 private void processFilter(FilteringObjective filt, boolean install,
224 ApplicationId applicationId) {
225 // This driver only processes filtering criteria defined with switch
226 // ports as the key
227 PortCriterion p; EthCriterion e = null; VlanIdCriterion v = null;
228 Collection<IPCriterion> ips = new ArrayList<IPCriterion>();
229 if (!filt.key().equals(Criteria.dummy()) &&
230 filt.key().type() == Criterion.Type.IN_PORT) {
231 p = (PortCriterion) filt.key();
232 } else {
233 log.warn("No key defined in filtering objective from app: {}. Not"
234 + "processing filtering objective", applicationId);
235 fail(filt, ObjectiveError.UNKNOWN);
236 return;
237 }
238
239 // convert filtering conditions for switch-intfs into flowrules
240 FlowRuleOperations.Builder ops = FlowRuleOperations.builder();
241 for (Criterion c : filt.conditions()) {
242 if (c.type() == Criterion.Type.ETH_DST) {
243 e = (EthCriterion) c;
244 } else if (c.type() == Criterion.Type.VLAN_VID) {
245 v = (VlanIdCriterion) c;
246 } else if (c.type() == Criterion.Type.IPV4_DST) {
247 ips.add((IPCriterion) c);
248 } else {
249 log.error("Unsupported filter {}", c);
250 fail(filt, ObjectiveError.UNSUPPORTED);
251 return;
252 }
253 }
254
255 log.debug("adding Port/VLAN/MAC filtering rules in filter table: {}/{}/{}",
256 p.port(), v.vlanId(), e.mac());
257 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
258 TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
259 selector.matchInPort(p.port());
260 selector.matchVlanId(v.vlanId());
261 selector.matchEthDst(e.mac());
262 selector.matchEthType(Ethernet.TYPE_IPV4);
263 treatment.transition(FIB_TABLE);
264 FlowRule rule = DefaultFlowRule.builder()
265 .forDevice(deviceId)
266 .withSelector(selector.build())
267 .withTreatment(treatment.build())
268 .withPriority(DEFAULT_PRIORITY)
269 .fromApp(applicationId)
270 .makePermanent()
271 .forTable(FILTER_TABLE).build();
272 ops = ops.add(rule);
273
274 for (IPCriterion ipaddr : ips) {
275 log.debug("adding IP filtering rules in FIB table: {}", ipaddr.ip());
276 selector = DefaultTrafficSelector.builder();
277 treatment = DefaultTrafficTreatment.builder();
278 selector.matchEthType(Ethernet.TYPE_IPV4);
279 selector.matchIPDst(ipaddr.ip());
280 treatment.setOutput(PortNumber.CONTROLLER);
281 rule = DefaultFlowRule.builder()
282 .forDevice(deviceId)
283 .withSelector(selector.build())
284 .withTreatment(treatment.build())
285 .withPriority(HIGHEST_PRIORITY)
286 .fromApp(applicationId)
287 .makePermanent()
288 .forTable(FIB_TABLE).build();
289 ops = ops.add(rule);
290 }
291
292 // cache for later use
293 Filter filter = new Filter(p, e, v, ips);
294 filters.add(filter);
295 // apply any pending versatile forwarding objectives
296 for (ForwardingObjective fwd : pendingVersatiles) {
297 Collection<FlowRule> ret = processVersatilesWithFilters(filter, fwd);
298 for (FlowRule fr : ret) {
299 ops.add(fr);
300 }
301 }
302
303 ops = install ? ops.add(rule) : ops.remove(rule);
304 // apply filtering flow rules
305 flowRuleService.apply(ops.build(new FlowRuleOperationsContext() {
306 @Override
307 public void onSuccess(FlowRuleOperations ops) {
308 log.info("Applied filtering rules");
309 pass(filt);
310 }
311
312 @Override
313 public void onError(FlowRuleOperations ops) {
314 log.info("Failed to apply filtering rules");
315 fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED);
316 }
317 }));
318 }
319
320 private Collection<FlowRule> processForward(ForwardingObjective fwd) {
321 switch (fwd.flag()) {
322 case SPECIFIC:
323 return processSpecific(fwd);
324 case VERSATILE:
325 return processVersatile(fwd);
326 default:
327 fail(fwd, ObjectiveError.UNKNOWN);
328 log.warn("Unknown forwarding flag {}", fwd.flag());
329 }
330 return Collections.emptySet();
331 }
332
333 /**
334 * SoftRouter has a single versatile table - the filter table. All versatile
335 * flow rules must include the filtering rules.
336 *
337 * @param fwd The forwarding objective of type versatile
338 * @return A collection of flow rules meant to be delivered to the flowrule
339 * subsystem. May return empty collection in case of failures.
340 */
341 private Collection<FlowRule> processVersatile(ForwardingObjective fwd) {
342 if (filters.isEmpty()) {
343 pendingVersatiles.add(fwd);
344 return Collections.emptySet();
345 }
346 Collection<FlowRule> flowrules = new ArrayList<FlowRule>();
347 for (Filter filter : filters) {
348 flowrules.addAll(processVersatilesWithFilters(filter, fwd));
349 }
350 return flowrules;
351 }
352
353 private Collection<FlowRule> processVersatilesWithFilters(
354 Filter filt, ForwardingObjective fwd) {
355 log.info("Processing versatile forwarding objective");
356 Collection<FlowRule> flows = new ArrayList<FlowRule>();
357 TrafficSelector match = fwd.selector();
358 EthTypeCriterion ethType =
359 (EthTypeCriterion) match.getCriterion(Criterion.Type.ETH_TYPE);
360 if (ethType == null) {
361 log.error("Versatile forwarding objective must include ethType");
362 fail(fwd, ObjectiveError.UNKNOWN);
363 return Collections.emptySet();
364 }
365
366 if (ethType.ethType() == Ethernet.TYPE_ARP) {
367 // need to install ARP request & reply flow rules for each interface filter
368
369 // rule for ARP replies
370 TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
371 selector.matchInPort(filt.port());
372 selector.matchVlanId(filt.vlanId());
373 selector.matchEthDst(filt.mac());
374 selector.matchEthType(Ethernet.TYPE_ARP);
375 FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
376 .fromApp(fwd.appId())
377 .withPriority(fwd.priority())
378 .forDevice(deviceId)
379 .withSelector(selector.build())
380 .withTreatment(fwd.treatment())
381 .makePermanent()
382 .forTable(FILTER_TABLE);
383 flows.add(ruleBuilder.build());
384
385 //rule for ARP requests
386 selector = DefaultTrafficSelector.builder();
387 selector.matchInPort(filt.port());
388 selector.matchVlanId(filt.vlanId());
389 selector.matchEthDst(MacAddress.BROADCAST);
390 selector.matchEthType(Ethernet.TYPE_ARP);
391 ruleBuilder = DefaultFlowRule.builder()
392 .fromApp(fwd.appId())
393 .withPriority(fwd.priority())
394 .forDevice(deviceId)
395 .withSelector(selector.build())
396 .withTreatment(fwd.treatment())
397 .makePermanent()
398 .forTable(FILTER_TABLE);
399 flows.add(ruleBuilder.build());
400
401 return flows;
402 }
403 // not handling other versatile flows
404 return Collections.emptySet();
405 }
406
407 /**
408 * SoftRouter has a single specific table - the FIB Table. It emulates
409 * LPM matching of dstIP by using higher priority flows for longer prefixes.
410 * Flows are forwarded using flow-actions
411 *
412 * @param fwd The forwarding objective of type simple
413 * @return A collection of flow rules meant to be delivered to the flowrule
414 * subsystem. Typically the returned collection has a single flowrule.
415 * May return empty collection in case of failures.
416 *
417 */
418 private Collection<FlowRule> processSpecific(ForwardingObjective fwd) {
419 log.debug("Processing specific forwarding objective");
420 TrafficSelector selector = fwd.selector();
421 EthTypeCriterion ethType =
422 (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
423 // XXX currently supporting only the L3 unicast table
424 if (ethType == null || ethType.ethType() != Ethernet.TYPE_IPV4) {
425 fail(fwd, ObjectiveError.UNSUPPORTED);
426 return Collections.emptySet();
427 }
428
429 TrafficSelector filteredSelector =
430 DefaultTrafficSelector.builder()
431 .matchEthType(Ethernet.TYPE_IPV4)
432 .matchIPDst(((IPCriterion)
433 selector.getCriterion(Criterion.Type.IPV4_DST)).ip())
434 .build();
435
436 TrafficTreatment tt = null;
437 if (fwd.nextId() != null) {
438 NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId());
439 if (next == null) {
440 log.error("next-id {} does not exist in store", fwd.nextId());
441 return Collections.emptySet();
442 }
443 tt = appKryo.deserialize(next.data());
444 if (tt == null) {
445 log.error("Error in deserializing next-id {}", fwd.nextId());
446 return Collections.emptySet();
447 }
448 }
449
450 FlowRule.Builder ruleBuilder = DefaultFlowRule.builder()
451 .fromApp(fwd.appId())
452 .withPriority(fwd.priority())
453 .forDevice(deviceId)
454 .withSelector(filteredSelector)
455 .withTreatment(tt);
456
457 if (fwd.permanent()) {
458 ruleBuilder.makePermanent();
459 } else {
460 ruleBuilder.makeTemporary(fwd.timeout());
461 }
462
463 ruleBuilder.forTable(FIB_TABLE);
464 return Collections.singletonList(ruleBuilder.build());
465 }
466
467 /**
468 * Next Objectives are stored as dummy groups for retrieval later
469 * when Forwarding Objectives reference the next objective id. At that point
470 * the dummy group is fetched from the distributed store and the enclosed
471 * treatment is applied as a flow rule action.
472 *
473 * @param nextObjective the next objective of type simple
474 */
475 private void processSimpleNextObjective(NextObjective nextObj) {
476 // Simple next objective has a single treatment (not a collection)
477 flowObjectiveStore.putNextGroup(nextObj.id(),
478 new DummyGroup(nextObj.next().iterator().next()));
479 }
480
481 private class Filter {
482 private PortCriterion port;
483 private VlanIdCriterion vlan;
484 private EthCriterion eth;
485
486 @SuppressWarnings("unused")
487 private Collection<IPCriterion> ips;
488
489 public Filter(PortCriterion p, EthCriterion e, VlanIdCriterion v,
490 Collection<IPCriterion> ips) {
491 this.eth = e;
492 this.port = p;
493 this.vlan = v;
494 this.ips = ips;
495 }
496
497 public PortNumber port() {
498 return port.port();
499 }
500
501 public VlanId vlanId() {
502 return vlan.vlanId();
503 }
504
505 public MacAddress mac() {
506 return eth.mac();
507 }
508 }
509
510 private class DummyGroup implements NextGroup {
511 TrafficTreatment nextActions;
512
513 public DummyGroup(TrafficTreatment next) {
514 this.nextActions = next;
515 }
516
517 @Override
518 public byte[] data() {
519 return appKryo.serialize(nextActions);
520 }
521
522 }
523
524}