a07a15d29900b7d3ff91a703e2e08c862724ea6f
[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.EthType;
19 import org.onlab.packet.Ethernet;
20 import org.onlab.packet.Ip4Address;
21 import org.onlab.packet.Ip4Prefix;
22 import org.onlab.packet.IpPrefix;
23 import org.onlab.packet.MacAddress;
24 import org.onlab.packet.MplsLabel;
25 import org.onlab.packet.VlanId;
26 import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
27 import org.onosproject.segmentrouting.config.DeviceConfiguration;
28 import org.onosproject.segmentrouting.grouphandler.NeighborSet;
29 import org.onosproject.net.DeviceId;
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         // setup metadata to pass to nextObjective - indicate the vlan on egress
231         // if needed by the switch pipeline. Since neighbor sets are always to
232         // other neighboring routers, there is no subnet assigned on those ports.
233         TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder(selector);
234         metabuilder.matchVlanId(
235             VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET));
236
237         int nextId = srManager.getNextObjectiveId(deviceId, ns, metabuilder.build());
238         if (nextId <= 0) {
239             log.warn("No next objective in {} for ns: {}", deviceId, ns);
240             return false;
241         }
242
243         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective
244                 .builder()
245                 .fromApp(srManager.appId)
246                 .makePermanent()
247                 .nextStep(nextId)
248                 .withSelector(selector)
249                 .withPriority(100)
250                 .withFlag(ForwardingObjective.Flag.SPECIFIC);
251         if (treatment != null) {
252             fwdBuilder.withTreatment(treatment);
253         }
254         log.debug("Installing IPv4 forwarding objective "
255                         + "for router IP/subnet {} in switch {}",
256                 ipPrefix,
257                 deviceId);
258         srManager.flowObjectiveService.
259             forward(deviceId,
260                     fwdBuilder.
261                     add(new SRObjectiveContext(deviceId,
262                                                SRObjectiveContext.ObjectiveType.FORWARDING)));
263         rulePopulationCounter.incrementAndGet();
264
265         return true;
266     }
267
268     /**
269      * Populates MPLS flow rules to all routers.
270      *
271      * @param deviceId target device ID of the switch to set the rules
272      * @param destSwId destination switch device ID
273      * @param nextHops next hops switch ID list
274      * @return true if all rules are set successfully, false otherwise
275      */
276     public boolean populateMplsRule(DeviceId deviceId, DeviceId destSwId,
277                                     Set<DeviceId> nextHops) {
278         int segmentId;
279         try {
280             segmentId = config.getSegmentId(destSwId);
281         } catch (DeviceConfigNotFoundException e) {
282             log.warn(e.getMessage() + " Aborting populateMplsRule.");
283             return false;
284         }
285
286         TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
287         List<ForwardingObjective.Builder> fwdObjBuilders = new ArrayList<>();
288
289         // TODO Handle the case of Bos == false
290         sbuilder.matchEthType(Ethernet.MPLS_UNICAST);
291         sbuilder.matchMplsLabel(MplsLabel.mplsLabel(segmentId));
292         TrafficSelector selector = sbuilder.build();
293
294         // setup metadata to pass to nextObjective - indicate the vlan on egress
295         // if needed by the switch pipeline. Since mpls next-hops are always to
296         // other neighboring routers, there is no subnet assigned on those ports.
297         TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder(selector);
298         metabuilder.matchVlanId(
299             VlanId.vlanId(SegmentRoutingManager.ASSIGNED_VLAN_NO_SUBNET));
300
301         // If the next hop is the destination router for the segment, do pop
302         if (nextHops.size() == 1 && destSwId.equals(nextHops.toArray()[0])) {
303             log.debug("populateMplsRule: Installing MPLS forwarding objective for "
304                     + "label {} in switch {} with pop", segmentId, deviceId);
305
306             // bos pop case (php)
307             ForwardingObjective.Builder fwdObjBosBuilder =
308                     getMplsForwardingObjective(deviceId,
309                                                nextHops,
310                                                true,
311                                                true,
312                                                metabuilder.build());
313             if (fwdObjBosBuilder == null) {
314                 return false;
315             }
316             fwdObjBuilders.add(fwdObjBosBuilder);
317
318             // XXX not-bos pop case,  SR app multi-label not implemented yet
319             /*ForwardingObjective.Builder fwdObjNoBosBuilder =
320                     getMplsForwardingObjective(deviceId,
321                                                nextHops,
322                                                true,
323                                                false);*/
324
325         } else {
326             // next hop is not destination, SR CONTINUE case (swap with self)
327             log.debug("Installing MPLS forwarding objective for "
328                     + "label {} in switch {} without pop", segmentId, deviceId);
329
330             // continue case with bos - this does get triggered in edge routers
331             // and in core routers - driver can handle depending on availability
332             // of MPLS ECMP or not
333             ForwardingObjective.Builder fwdObjBosBuilder =
334                     getMplsForwardingObjective(deviceId,
335                                                nextHops,
336                                                false,
337                                                true,
338                                                metabuilder.build());
339             if (fwdObjBosBuilder == null) {
340                 return false;
341             }
342             fwdObjBuilders.add(fwdObjBosBuilder);
343
344             // XXX continue case with not-bos - SR app multi label not implemented yet
345             // also requires MPLS ECMP
346             /*ForwardingObjective.Builder fwdObjNoBosBuilder =
347                     getMplsForwardingObjective(deviceId,
348                                                nextHops,
349                                                false,
350                                                false); */
351
352         }
353
354         for (ForwardingObjective.Builder fwdObjBuilder : fwdObjBuilders) {
355             ((Builder) ((Builder) fwdObjBuilder.fromApp(srManager.appId)
356                     .makePermanent()).withSelector(selector)
357                     .withPriority(100))
358                     .withFlag(ForwardingObjective.Flag.SPECIFIC);
359             srManager.flowObjectiveService.
360                 forward(deviceId,
361                         fwdObjBuilder.
362                         add(new SRObjectiveContext(deviceId,
363                                     SRObjectiveContext.ObjectiveType.FORWARDING)));
364             rulePopulationCounter.incrementAndGet();
365         }
366
367         return true;
368     }
369
370     private ForwardingObjective.Builder getMplsForwardingObjective(
371                                              DeviceId deviceId,
372                                              Set<DeviceId> nextHops,
373                                              boolean phpRequired,
374                                              boolean isBos,
375                                              TrafficSelector meta) {
376
377         ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective
378                 .builder().withFlag(ForwardingObjective.Flag.SPECIFIC);
379
380         TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
381
382         if (phpRequired) {
383             // php case - pop should always be flow-action
384             log.debug("getMplsForwardingObjective: php required");
385             tbuilder.deferred().copyTtlIn();
386             if (isBos) {
387                 tbuilder.deferred().popMpls(EthType.EtherType.IPV4.ethType())
388                     .decNwTtl();
389             } else {
390                 tbuilder.deferred().popMpls(EthType.EtherType.MPLS_UNICAST.ethType())
391                     .decMplsTtl();
392             }
393         } else {
394             // swap with self case - SR CONTINUE
395             log.debug("getMplsForwardingObjective: php not required");
396             tbuilder.deferred().decMplsTtl();
397         }
398
399         // All forwarding is via ECMP group, the metadata informs the driver
400         // that the next-Objective will be used by MPLS flows. In other words,
401         // MPLS ECMP is requested. It is up to the driver to decide if these
402         // packets will be hashed or not.
403         fwdBuilder.withTreatment(tbuilder.build());
404         NeighborSet ns = new NeighborSet(nextHops);
405         log.debug("Trying to get a nextObjid for mpls rule on device:{} to ns:{}",
406                  deviceId, ns);
407
408         int nextId = srManager.getNextObjectiveId(deviceId, ns, meta);
409         if (nextId <= 0) {
410             log.warn("No next objective in {} for ns: {}", deviceId, ns);
411             return null;
412         }
413
414         fwdBuilder.nextStep(nextId);
415         return fwdBuilder;
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 static class SRObjectiveContext implements ObjectiveContext {
556         enum ObjectiveType {
557             FILTER,
558             FORWARDING
559         }
560         final DeviceId deviceId;
561         final ObjectiveType type;
562
563         SRObjectiveContext(DeviceId deviceId, ObjectiveType type) {
564             this.deviceId = deviceId;
565             this.type = type;
566         }
567         @Override
568         public void onSuccess(Objective objective) {
569             log.debug("{} objective operation successful in device {}",
570                       type.name(), deviceId);
571         }
572
573         @Override
574         public void onError(Objective objective, ObjectiveError error) {
575             log.warn("{} objective {} operation failed with error: {} in device {}",
576                      type.name(), objective, error, deviceId);
577         }
578     }
579
580 }