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