f8274038087218ece0d64310e5065a0aacbf81ae
[onosfw.git] /
1 /*
2  * Copyright 2015 Open Networking Laboratory
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 package org.onosproject.segmentrouting;
17
18 import org.onlab.packet.Ethernet;
19 import org.onlab.packet.Ip4Address;
20 import org.onlab.packet.Ip4Prefix;
21 import org.onlab.packet.IpPrefix;
22 import org.onlab.packet.MacAddress;
23 import org.onlab.packet.MplsLabel;
24 import org.onlab.packet.VlanId;
25 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
26 import org.onosproject.segmentrouting.config.DeviceConfiguration;
27 import org.onosproject.segmentrouting.grouphandler.NeighborSet;
28 import org.onosproject.net.DeviceId;
29 import org.onosproject.net.Link;
30 import org.onosproject.net.Port;
31 import org.onosproject.net.PortNumber;
32 import org.onosproject.net.flow.DefaultTrafficSelector;
33 import org.onosproject.net.flow.DefaultTrafficTreatment;
34 import org.onosproject.net.flow.TrafficSelector;
35 import org.onosproject.net.flow.TrafficTreatment;
36 import org.onosproject.net.flow.criteria.Criteria;
37 import org.onosproject.net.flowobjective.DefaultFilteringObjective;
38 import org.onosproject.net.flowobjective.DefaultForwardingObjective;
39 import org.onosproject.net.flowobjective.FilteringObjective;
40 import org.onosproject.net.flowobjective.ForwardingObjective;
41 import org.onosproject.net.flowobjective.Objective;
42 import org.onosproject.net.flowobjective.ObjectiveError;
43 import org.onosproject.net.flowobjective.ForwardingObjective.Builder;
44 import org.onosproject.net.flowobjective.ForwardingObjective.Flag;
45 import org.onosproject.net.flowobjective.ObjectiveContext;
46 import org.slf4j.Logger;
47 import org.slf4j.LoggerFactory;
48
49 import java.util.ArrayList;
50 import java.util.HashSet;
51 import java.util.List;
52 import java.util.Set;
53 import java.util.concurrent.atomic.AtomicLong;
54
55 import static com.google.common.base.Preconditions.checkNotNull;
56
57 public class RoutingRulePopulator {
58
59     private static final Logger log = LoggerFactory
60             .getLogger(RoutingRulePopulator.class);
61
62     private AtomicLong rulePopulationCounter;
63     private SegmentRoutingManager srManager;
64     private DeviceConfiguration config;
65
66     private static final int HIGHEST_PRIORITY = 0xffff;
67     private static final long OFPP_MAX = 0xffffff00L;
68
69
70     /**
71      * Creates a RoutingRulePopulator object.
72      *
73      * @param srManager segment routing manager reference
74      */
75     public RoutingRulePopulator(SegmentRoutingManager srManager) {
76         this.srManager = srManager;
77         this.config = checkNotNull(srManager.deviceConfiguration);
78         this.rulePopulationCounter = new AtomicLong(0);
79     }
80
81     /**
82      * Resets the population counter.
83      */
84     public void resetCounter() {
85         rulePopulationCounter.set(0);
86     }
87
88     /**
89      * Returns the number of rules populated.
90      *
91      * @return number of rules
92      */
93     public long getCounter() {
94         return rulePopulationCounter.get();
95     }
96
97     /**
98      * Populates IP flow rules for specific hosts directly connected to the
99      * switch.
100      *
101      * @param deviceId switch ID to set the rules
102      * @param hostIp host IP address
103      * @param hostMac host MAC address
104      * @param outPort port where the host is connected
105      */
106     public void populateIpRuleForHost(DeviceId deviceId, Ip4Address hostIp,
107                                       MacAddress hostMac, PortNumber outPort) {
108         MacAddress deviceMac;
109         try {
110             deviceMac = config.getDeviceMac(deviceId);
111         } catch (DeviceConfigNotFoundException e) {
112             log.warn(e.getMessage() + " Aborting populateIpRuleForHost.");
113             return;
114         }
115
116         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
117         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
118
119         sbuilder.matchIPDst(IpPrefix.valueOf(hostIp, IpPrefix.MAX_INET_MASK_LENGTH));
120         sbuilder.matchEthType(Ethernet.TYPE_IPV4);
121
122         tbuilder.deferred()
123                 .setEthDst(hostMac)
124                 .setEthSrc(deviceMac)
125                 .setOutput(outPort);
126
127         TrafficTreatment treatment = tbuilder.build();
128         TrafficSelector selector = sbuilder.build();
129
130         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective
131                 .builder().fromApp(srManager.appId).makePermanent()
132                 .withSelector(selector).withTreatment(treatment)
133                 .withPriority(100).withFlag(ForwardingObjective.Flag.SPECIFIC);
134
135         log.debug("Installing IPv4 forwarding objective "
136                 + "for host {} in switch {}", hostIp, deviceId);
137         srManager.flowObjectiveService.
138             forward(deviceId,
139                     fwdBuilder.
140                     add(new SRObjectiveContext(deviceId,
141                                            SRObjectiveContext.ObjectiveType.FORWARDING)));
142         rulePopulationCounter.incrementAndGet();
143     }
144
145     /**
146      * Populates IP flow rules for the subnets of the destination router.
147      *
148      * @param deviceId switch ID to set the rules
149      * @param subnets subnet information
150      * @param destSw destination switch ID
151      * @param nextHops next hop switch ID list
152      * @return true if all rules are set successfully, false otherwise
153      */
154     public boolean populateIpRuleForSubnet(DeviceId deviceId,
155                                            Set<Ip4Prefix> subnets,
156                                            DeviceId destSw,
157                                            Set<DeviceId> nextHops) {
158
159         for (IpPrefix subnet : subnets) {
160             if (!populateIpRuleForRouter(deviceId, subnet, destSw, nextHops)) {
161                 return false;
162             }
163         }
164
165         return true;
166     }
167
168     /**
169      * Populates IP flow rules for the router IP address.
170      *
171      * @param deviceId target device ID to set the rules
172      * @param ipPrefix the IP address of the destination router
173      * @param destSw device ID of the destination router
174      * @param nextHops next hop switch ID list
175      * @return true if all rules are set successfully, false otherwise
176      */
177     public boolean populateIpRuleForRouter(DeviceId deviceId,
178                                            IpPrefix ipPrefix, DeviceId destSw,
179                                            Set<DeviceId> nextHops) {
180         int segmentId;
181         try {
182             segmentId = config.getSegmentId(destSw);
183         } catch (DeviceConfigNotFoundException e) {
184             log.warn(e.getMessage() + " Aborting populateIpRuleForRouter.");
185             return false;
186         }
187
188         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
189         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
190
191         sbuilder.matchIPDst(ipPrefix);
192         sbuilder.matchEthType(Ethernet.TYPE_IPV4);
193
194         NeighborSet ns = null;
195
196         // If the next hop is the same as the final destination, then MPLS label
197         // is not set.
198         if (nextHops.size() == 1 && nextHops.toArray()[0].equals(destSw)) {
199             tbuilder.deferred().decNwTtl();
200             ns = new NeighborSet(nextHops);
201         } else {
202             tbuilder.deferred().copyTtlOut();
203             ns = new NeighborSet(nextHops, segmentId);
204         }
205
206         TrafficTreatment treatment = tbuilder.build();
207         TrafficSelector selector = sbuilder.build();
208
209         if (srManager.getNextObjectiveId(deviceId, ns) <= 0) {
210             log.warn("No next objective in {} for ns: {}", deviceId, ns);
211             return false;
212         }
213
214         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective
215                 .builder()
216                 .fromApp(srManager.appId)
217                 .makePermanent()
218                 .nextStep(srManager.getNextObjectiveId(deviceId, ns))
219                 .withTreatment(treatment)
220                 .withSelector(selector)
221                 .withPriority(100)
222                 .withFlag(ForwardingObjective.Flag.SPECIFIC);
223         log.debug("Installing IPv4 forwarding objective "
224                         + "for router IP/subnet {} in switch {}",
225                 ipPrefix,
226                 deviceId);
227         srManager.flowObjectiveService.
228             forward(deviceId,
229                     fwdBuilder.
230                     add(new SRObjectiveContext(deviceId,
231                                                SRObjectiveContext.ObjectiveType.FORWARDING)));
232         rulePopulationCounter.incrementAndGet();
233
234         return true;
235     }
236
237     /**
238      * Populates MPLS flow rules to all routers.
239      *
240      * @param deviceId target device ID of the switch to set the rules
241      * @param destSwId destination switch device ID
242      * @param nextHops next hops switch ID list
243      * @return true if all rules are set successfully, false otherwise
244      */
245     public boolean populateMplsRule(DeviceId deviceId, DeviceId destSwId,
246                                     Set<DeviceId> nextHops) {
247         int segmentId;
248         try {
249             segmentId = config.getSegmentId(destSwId);
250         } catch (DeviceConfigNotFoundException e) {
251             log.warn(e.getMessage() + " Aborting populateMplsRule.");
252             return false;
253         }
254
255         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
256         List<ForwardingObjective.Builder> fwdObjBuilders = new ArrayList<>();
257
258         // TODO Handle the case of Bos == false
259         sbuilder.matchMplsLabel(MplsLabel.mplsLabel(segmentId));
260         sbuilder.matchEthType(Ethernet.MPLS_UNICAST);
261
262         // If the next hop is the destination router, do PHP
263         if (nextHops.size() == 1 && destSwId.equals(nextHops.toArray()[0])) {
264             log.debug("populateMplsRule: Installing MPLS forwarding objective for "
265                     + "label {} in switch {} with PHP",
266                     segmentId,
267                     deviceId);
268
269             ForwardingObjective.Builder fwdObjBosBuilder =
270                     getMplsForwardingObjective(deviceId,
271                                                destSwId,
272                                                nextHops,
273                                                true,
274                                                true);
275             // TODO: Check with Sangho on why we need this
276             ForwardingObjective.Builder fwdObjNoBosBuilder =
277                     getMplsForwardingObjective(deviceId,
278                                                destSwId,
279                                                nextHops,
280                                                true,
281                                                false);
282             if (fwdObjBosBuilder != null) {
283                 fwdObjBuilders.add(fwdObjBosBuilder);
284             } else {
285                 log.warn("Failed to set MPLS rules.");
286                 return false;
287             }
288         } else {
289             log.debug("Installing MPLS forwarding objective for "
290                     + "label {} in switch {} without PHP",
291                     segmentId,
292                     deviceId);
293
294             ForwardingObjective.Builder fwdObjBosBuilder =
295                     getMplsForwardingObjective(deviceId,
296                                                destSwId,
297                                                nextHops,
298                                                false,
299                                                true);
300             // TODO: Check with Sangho on why we need this
301             ForwardingObjective.Builder fwdObjNoBosBuilder =
302                     getMplsForwardingObjective(deviceId,
303                                                destSwId,
304                                                nextHops,
305                                                false,
306                                                false);
307             if (fwdObjBosBuilder != null) {
308                 fwdObjBuilders.add(fwdObjBosBuilder);
309             } else {
310                 log.warn("Failed to set MPLS rules.");
311                 return false;
312             }
313         }
314
315         TrafficSelector selector = sbuilder.build();
316         for (ForwardingObjective.Builder fwdObjBuilder : fwdObjBuilders) {
317             ((Builder) ((Builder) fwdObjBuilder.fromApp(srManager.appId)
318                     .makePermanent()).withSelector(selector)
319                     .withPriority(100))
320                     .withFlag(ForwardingObjective.Flag.SPECIFIC);
321             srManager.flowObjectiveService.
322                 forward(deviceId,
323                         fwdObjBuilder.
324                         add(new SRObjectiveContext(deviceId,
325                                                    SRObjectiveContext.ObjectiveType.FORWARDING)));
326             rulePopulationCounter.incrementAndGet();
327         }
328
329         return true;
330     }
331
332     private ForwardingObjective.Builder getMplsForwardingObjective(DeviceId deviceId,
333                                                                    DeviceId destSw,
334                                                                    Set<DeviceId> nextHops,
335                                                                    boolean phpRequired,
336                                                                    boolean isBos) {
337         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective
338                 .builder().withFlag(ForwardingObjective.Flag.SPECIFIC);
339         DeviceId nextHop = (DeviceId) nextHops.toArray()[0];
340
341         boolean isEdge;
342         MacAddress srcMac;
343         MacAddress dstMac;
344         try {
345             isEdge = config.isEdgeDevice(deviceId);
346             srcMac = config.getDeviceMac(deviceId);
347             dstMac = config.getDeviceMac(nextHop);
348         } catch (DeviceConfigNotFoundException e) {
349             log.warn(e.getMessage() + " Aborting getMplsForwardingObjective");
350             return null;
351         }
352
353         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
354
355         if (phpRequired) {
356             log.debug("getMplsForwardingObjective: php required");
357             tbuilder.deferred().copyTtlIn();
358             if (isBos) {
359                 tbuilder.deferred().popMpls(Ethernet.TYPE_IPV4).decNwTtl();
360             } else {
361                 tbuilder.deferred().popMpls(Ethernet.MPLS_UNICAST).decMplsTtl();
362             }
363         } else {
364             log.debug("getMplsForwardingObjective: php not required");
365             tbuilder.deferred().decMplsTtl();
366         }
367
368         if (!isECMPSupportedInTransitRouter() && !isEdge) {
369             PortNumber port = selectOnePort(deviceId, nextHops);
370             if (port == null) {
371                 log.warn("No link from {} to {}", deviceId, nextHops);
372                 return null;
373             }
374             tbuilder.deferred()
375                     .setEthSrc(srcMac)
376                     .setEthDst(dstMac)
377                     .setOutput(port);
378             fwdBuilder.withTreatment(tbuilder.build());
379         } else {
380             NeighborSet ns = new NeighborSet(nextHops);
381             fwdBuilder.withTreatment(tbuilder.build());
382             fwdBuilder.nextStep(srManager
383                     .getNextObjectiveId(deviceId, ns));
384         }
385
386         return fwdBuilder;
387     }
388
389     private boolean isECMPSupportedInTransitRouter() {
390
391         // TODO: remove this function when objectives subsystem is supported.
392         return false;
393     }
394
395     /**
396      * Creates a filtering objective to permit all untagged packets with a
397      * dstMac corresponding to the router's MAC address. For those pipelines
398      * that need to internally assign vlans to untagged packets, this method
399      * provides per-subnet vlan-ids as metadata.
400      * <p>
401      * Note that the vlan assignment is only done by the master-instance for a switch.
402      * However we send the filtering objective from slave-instances as well, so
403      * that drivers can obtain other information (like Router MAC and IP).
404      *
405      * @param deviceId  the switch dpid for the router
406      */
407     public void populateRouterMacVlanFilters(DeviceId deviceId) {
408         log.debug("Installing per-port filtering objective for untagged "
409                 + "packets in device {}", deviceId);
410
411         MacAddress deviceMac;
412         try {
413             deviceMac = config.getDeviceMac(deviceId);
414         } catch (DeviceConfigNotFoundException e) {
415             log.warn(e.getMessage() + " Aborting populateRouterMacVlanFilters.");
416             return;
417         }
418
419         for (Port port : srManager.deviceService.getPorts(deviceId)) {
420             if (port.number().toLong() > 0 && port.number().toLong() < OFPP_MAX) {
421                 Ip4Prefix portSubnet = config.getPortSubnet(deviceId, port.number());
422                 VlanId assignedVlan = (portSubnet == null)
423                         ? VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET)
424                         : srManager.getSubnetAssignedVlanId(deviceId, portSubnet);
425
426
427
428                 FilteringObjective.Builder fob = DefaultFilteringObjective.builder();
429                 fob.withKey(Criteria.matchInPort(port.number()))
430                 .addCondition(Criteria.matchEthDst(deviceMac))
431                 .addCondition(Criteria.matchVlanId(VlanId.NONE));
432                 // vlan assignment is valid only if this instance is master
433                 if (srManager.mastershipService.isLocalMaster(deviceId)) {
434                     TrafficTreatment tt = DefaultTrafficTreatment.builder()
435                             .pushVlan().setVlanId(assignedVlan).build();
436                     fob.setMeta(tt);
437                 }
438                 fob.permit().fromApp(srManager.appId);
439                 srManager.flowObjectiveService.
440                 filter(deviceId, fob.add(new SRObjectiveContext(deviceId,
441                                       SRObjectiveContext.ObjectiveType.FILTER)));
442             }
443         }
444     }
445
446     /**
447      * Creates a forwarding objective to punt all IP packets, destined to the
448      * router's port IP addresses, to the controller. Note that the input
449      * port should not be matched on, as these packets can come from any input.
450      * Furthermore, these are applied only by the master instance.
451      *
452      * @param deviceId the switch dpid for the router
453      */
454     public void populateRouterIpPunts(DeviceId deviceId) {
455         Ip4Address routerIp;
456         try {
457             routerIp = config.getRouterIp(deviceId);
458         } catch (DeviceConfigNotFoundException e) {
459             log.warn(e.getMessage() + " Aborting populateRouterIpPunts.");
460             return;
461         }
462
463         if (!srManager.mastershipService.isLocalMaster(deviceId)) {
464             log.debug("Not installing port-IP punts - not the master for dev:{} ",
465                       deviceId);
466             return;
467         }
468         ForwardingObjective.Builder puntIp = DefaultForwardingObjective.builder();
469         Set<Ip4Address> allIps = new HashSet<Ip4Address>(config.getPortIPs(deviceId));
470         allIps.add(routerIp);
471         for (Ip4Address ipaddr : allIps) {
472             TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
473             TrafficTreatment.Builder treatment = DefaultTrafficTreatment.builder();
474             selector.matchEthType(Ethernet.TYPE_IPV4);
475             selector.matchIPDst(IpPrefix.valueOf(ipaddr,
476                                                  IpPrefix.MAX_INET_MASK_LENGTH));
477             treatment.setOutput(PortNumber.CONTROLLER);
478             puntIp.withSelector(selector.build());
479             puntIp.withTreatment(treatment.build());
480             puntIp.withFlag(Flag.VERSATILE)
481                 .withPriority(HIGHEST_PRIORITY)
482                 .makePermanent()
483                 .fromApp(srManager.appId);
484             log.debug("Installing forwarding objective to punt port IP addresses");
485             srManager.flowObjectiveService.
486                 forward(deviceId,
487                         puntIp.add(new SRObjectiveContext(deviceId,
488                                            SRObjectiveContext.ObjectiveType.FORWARDING)));
489         }
490     }
491
492     private PortNumber selectOnePort(DeviceId srcId, Set<DeviceId> destIds) {
493
494         Set<Link> links = srManager.linkService.getDeviceLinks(srcId);
495         for (DeviceId destId: destIds) {
496             for (Link link : links) {
497                 if (link.dst().deviceId().equals(destId)) {
498                     return link.src().port();
499                 } else if (link.src().deviceId().equals(destId)) {
500                     return link.dst().port();
501                 }
502             }
503         }
504
505         return null;
506     }
507
508     private static class SRObjectiveContext implements ObjectiveContext {
509         enum ObjectiveType {
510             FILTER,
511             FORWARDING
512         }
513         final DeviceId deviceId;
514         final ObjectiveType type;
515
516         SRObjectiveContext(DeviceId deviceId, ObjectiveType type) {
517             this.deviceId = deviceId;
518             this.type = type;
519         }
520         @Override
521         public void onSuccess(Objective objective) {
522             log.debug("{} objective operation successful in device {}",
523                       type.name(), deviceId);
524         }
525
526         @Override
527         public void onError(Objective objective, ObjectiveError error) {
528             log.warn("{} objective {} operation failed with error: {} in device {}",
529                      type.name(), objective, error, deviceId);
530         }
531     }
532
533 }