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