blob: 37f273bddfa474008edaace0bddf32068bae6c03 [file] [log] [blame]
Pierb0328e42018-03-27 11:29:42 -07001/*
2 * Copyright 2018-present Open Networking Foundation
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
17package org.onosproject.segmentrouting.mcast;
18
19import com.google.common.collect.ImmutableSet;
20import org.onlab.packet.Ethernet;
21import org.onlab.packet.IpAddress;
22import org.onlab.packet.MacAddress;
23import org.onlab.packet.VlanId;
24import org.onosproject.cluster.NodeId;
25import org.onosproject.core.ApplicationId;
26import org.onosproject.mcast.api.McastRoute;
27import org.onosproject.net.ConnectPoint;
28import org.onosproject.net.DeviceId;
29import org.onosproject.net.HostId;
30import org.onosproject.net.PortNumber;
31import org.onosproject.net.config.basics.McastConfig;
32import org.onosproject.net.flow.DefaultTrafficSelector;
33import org.onosproject.net.flow.DefaultTrafficTreatment;
34import org.onosproject.net.flow.TrafficSelector;
35import org.onosproject.net.flow.TrafficTreatment;
36import org.onosproject.net.flow.criteria.Criteria;
37import org.onosproject.net.flow.criteria.VlanIdCriterion;
38import org.onosproject.net.flow.instructions.Instructions;
39import org.onosproject.net.flowobjective.DefaultFilteringObjective;
40import org.onosproject.net.flowobjective.DefaultForwardingObjective;
41import org.onosproject.net.flowobjective.DefaultNextObjective;
42import org.onosproject.net.flowobjective.DefaultObjectiveContext;
43import org.onosproject.net.flowobjective.FilteringObjective;
44import org.onosproject.net.flowobjective.ForwardingObjective;
45import org.onosproject.net.flowobjective.NextObjective;
46import org.onosproject.net.flowobjective.ObjectiveContext;
47import org.onosproject.segmentrouting.SegmentRoutingManager;
48import org.onosproject.segmentrouting.SegmentRoutingService;
49import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
50import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
51import org.slf4j.Logger;
52
53import java.util.Collection;
54import java.util.Collections;
55import java.util.Map;
56import java.util.Set;
57import java.util.stream.Collectors;
58
59import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
60import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
61
62/**
63 * Utility class for Multicast Handler.
64 */
65class McastUtils {
66
67 // Internal reference to the log
68 private final Logger log;
69 // Internal reference to SR Manager
70 private SegmentRoutingManager srManager;
71 // Internal reference to the app id
72 private ApplicationId coreAppId;
73
74 /**
75 * Builds a new McastUtils object.
76 *
77 * @param srManager the SR manager
78 * @param coreAppId the core application id
79 * @param log log reference of the McastHandler
80 */
81 McastUtils(SegmentRoutingManager srManager, ApplicationId coreAppId, Logger log) {
82 this.srManager = srManager;
83 this.coreAppId = coreAppId;
84 this.log = log;
85 }
86
87 /**
88 * Given a connect point define a leader for it.
89 *
90 * @param source the source connect point
91 * @return true if this instance is the leader, otherwise false
92 */
93 boolean isLeader(ConnectPoint source) {
94 // Continue only when we have the mastership on the operation
95 if (!srManager.mastershipService.isLocalMaster(source.deviceId())) {
96 // When the source is available we just check the mastership
97 if (srManager.deviceService.isAvailable(source.deviceId())) {
98 return false;
99 }
100 // Fallback with Leadership service
101 // source id is used a topic
102 NodeId leader = srManager.leadershipService.runForLeadership(
103 source.deviceId().toString()).leaderNodeId();
104 // Verify if this node is the leader
105 if (!srManager.clusterService.getLocalNode().id().equals(leader)) {
106 return false;
107 }
108 }
109 // Done
110 return true;
111 }
112
113 /**
114 * Get router mac using application config and the connect point.
115 *
116 * @param deviceId the device id
117 * @param port the port number
118 * @return the router mac if the port is configured, otherwise null
119 */
120 private MacAddress getRouterMac(DeviceId deviceId, PortNumber port) {
121 // Do nothing if the port is configured as suppressed
122 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
123 SegmentRoutingAppConfig appConfig = srManager.cfgService
124 .getConfig(srManager.appId(), SegmentRoutingAppConfig.class);
125 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
126 log.info("Ignore suppressed port {}", connectPoint);
127 return MacAddress.NONE;
128 }
129 // Get the router mac using the device configuration
130 MacAddress routerMac;
131 try {
132 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
133 } catch (DeviceConfigNotFoundException dcnfe) {
134 log.warn("Fail to push filtering objective since device is not configured. Abort");
135 return MacAddress.NONE;
136 }
137 return routerMac;
138 }
139
140 /**
141 * Adds filtering objective for given device and port.
142 *
143 * @param deviceId device ID
144 * @param port ingress port number
145 * @param assignedVlan assigned VLAN ID
146 * @param mcastIp the group address
147 * @param mcastRole the role of the device
148 */
149 void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
150 IpAddress mcastIp, McastRole mcastRole) {
151
152 MacAddress routerMac = getRouterMac(deviceId, port);
153 if (routerMac.equals(MacAddress.NONE)) {
154 return;
155 }
156
157 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(port, assignedVlan, mcastIp,
158 routerMac, mcastRole);
159 ObjectiveContext context = new DefaultObjectiveContext(
160 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
161 deviceId, port.toLong(), assignedVlan),
162 (objective, error) ->
163 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
164 deviceId, port.toLong(), assignedVlan, error));
165 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
166 }
167
168 /**
169 * Removes filtering objective for given device and port.
170 *
171 * @param deviceId device ID
172 * @param port ingress port number
173 * @param assignedVlan assigned VLAN ID
174 * @param mcastIp multicast IP address
175 * @param mcastRole the multicast role of the device
176 */
177 void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
178 IpAddress mcastIp, McastRole mcastRole) {
179
180 MacAddress routerMac = getRouterMac(deviceId, port);
181 if (routerMac.equals(MacAddress.NONE)) {
182 return;
183 }
184
185 FilteringObjective.Builder filtObjBuilder =
186 filterObjBuilder(port, assignedVlan, mcastIp, routerMac, mcastRole);
187 ObjectiveContext context = new DefaultObjectiveContext(
188 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
189 deviceId, port.toLong(), assignedVlan),
190 (objective, error) ->
191 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
192 deviceId, port.toLong(), assignedVlan, error));
193 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
194 }
195
196 /**
197 * Gets assigned VLAN according to the value in the meta.
198 *
199 * @param nextObjective nextObjective to analyze
200 * @return assigned VLAN ID
201 */
202 VlanId assignedVlanFromNext(NextObjective nextObjective) {
203 return ((VlanIdCriterion) nextObjective.meta().getCriterion(VLAN_VID)).vlanId();
204 }
205
206 /**
207 * Gets ingress VLAN from McastConfig.
208 *
209 * @return ingress VLAN or VlanId.NONE if not configured
210 */
211 private VlanId ingressVlan() {
212 McastConfig mcastConfig =
213 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
214 return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
215 }
216
217 /**
218 * Gets egress VLAN from McastConfig.
219 *
220 * @return egress VLAN or VlanId.NONE if not configured
221 */
222 private VlanId egressVlan() {
223 McastConfig mcastConfig =
224 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
225 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
226 }
227
228 /**
229 * Gets assigned VLAN according to the value of egress VLAN.
230 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
231 *
232 * @param cp connect point; Can be null if not specified
233 * @return assigned VLAN ID
234 */
235 VlanId assignedVlan(ConnectPoint cp) {
236 // Use the egressVlan if it is tagged
237 if (!egressVlan().equals(VlanId.NONE)) {
238 return egressVlan();
239 }
240 // Reuse unicast VLAN if the port has subnet configured
241 if (cp != null) {
242 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
243 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
244 }
245 // Use DEFAULT_VLAN if none of the above matches
246 return SegmentRoutingManager.INTERNAL_VLAN;
247 }
248
249 /**
250 * Gets source connect point of given multicast group.
251 *
252 * @param mcastIp multicast IP
253 * @return source connect point or null if not found
254 */
255 // FIXME To be addressed with multiple sources support
256 ConnectPoint getSource(IpAddress mcastIp) {
257 // FIXME we should support different types of routes
258 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
259 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
260 .findFirst().orElse(null);
261 return mcastRoute == null ? null : srManager.multicastRouteService.sources(mcastRoute)
262 .stream()
263 .findFirst().orElse(null);
264 }
265
266 /**
267 * Gets sinks of given multicast group.
268 *
269 * @param mcastIp multicast IP
270 * @return map of sinks or empty map if not found
271 */
272 Map<HostId, Set<ConnectPoint>> getSinks(IpAddress mcastIp) {
273 // FIXME we should support different types of routes
274 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
275 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
276 .findFirst().orElse(null);
277 return mcastRoute == null ?
278 Collections.emptyMap() :
279 srManager.multicastRouteService.routeData(mcastRoute).sinks();
280 }
281
282 /**
283 * Get sinks affected by this egress device.
284 *
285 * @param egressDevice the egress device
286 * @param mcastIp the mcast ip address
287 * @return the map of the sinks affected
288 */
289 Map<HostId, Set<ConnectPoint>> getAffectedSinks(DeviceId egressDevice,
290 IpAddress mcastIp) {
291 return getSinks(mcastIp).entrySet()
292 .stream()
293 .filter(hostIdSetEntry -> hostIdSetEntry.getValue().stream()
294 .map(ConnectPoint::deviceId)
295 .anyMatch(deviceId -> deviceId.equals(egressDevice))
296 ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
297 }
298
299 /**
300 * Creates a next objective builder for multicast.
301 *
302 * @param mcastIp multicast group
303 * @param assignedVlan assigned VLAN ID
304 * @param outPorts set of output port numbers
305 * @param nextId the next id
306 * @return next objective builder
307 */
308 NextObjective.Builder nextObjBuilder(IpAddress mcastIp, VlanId assignedVlan,
309 Set<PortNumber> outPorts, Integer nextId) {
310 // If nextId is null allocate a new one
311 if (nextId == null) {
312 nextId = srManager.flowObjectiveService.allocateNextId();
313 }
314 // Build the meta selector with the fwd objective info
315 TrafficSelector metadata =
316 DefaultTrafficSelector.builder()
317 .matchVlanId(assignedVlan)
318 .matchIPDst(mcastIp.toIpPrefix())
319 .build();
320 // Define the nextobjective type
321 NextObjective.Builder nextObjBuilder = DefaultNextObjective
322 .builder().withId(nextId)
323 .withType(NextObjective.Type.BROADCAST)
324 .fromApp(srManager.appId())
325 .withMeta(metadata);
326 // Add the output ports
327 outPorts.forEach(port -> {
328 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
329 if (egressVlan().equals(VlanId.NONE)) {
330 tBuilder.popVlan();
331 }
332 tBuilder.setOutput(port);
333 nextObjBuilder.addTreatment(tBuilder.build());
334 });
335 // Done return the complete builder
336 return nextObjBuilder;
337 }
338
339 /**
340 * Creates a forwarding objective builder for multicast.
341 *
342 * @param mcastIp multicast group
343 * @param assignedVlan assigned VLAN ID
344 * @param nextId next ID of the L3 multicast group
345 * @return forwarding objective builder
346 */
347 ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
348 VlanId assignedVlan, int nextId) {
349 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
350 // Let's the matching on the group address
351 // TODO SSM support in future
352 if (mcastIp.isIp6()) {
353 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
354 sbuilder.matchIPv6Dst(mcastIp.toIpPrefix());
355 } else {
356 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
357 sbuilder.matchIPDst(mcastIp.toIpPrefix());
358 }
359 // Then build the meta selector
360 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
361 metabuilder.matchVlanId(assignedVlan);
362 // Finally return the completed builder
363 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
364 fwdBuilder.withSelector(sbuilder.build())
365 .withMeta(metabuilder.build())
366 .nextStep(nextId)
367 .withFlag(ForwardingObjective.Flag.SPECIFIC)
368 .fromApp(srManager.appId())
369 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
370 return fwdBuilder;
371 }
372
373 /**
374 * Creates a filtering objective builder for multicast.
375 *
376 * @param ingressPort ingress port of the multicast stream
377 * @param assignedVlan assigned VLAN ID
378 * @param mcastIp the group address
379 * @param routerMac router MAC. This is carried in metadata and used from some switches that
380 * need to put unicast entry before multicast entry in TMAC table.
381 * @param mcastRole the Multicast role
382 * @return filtering objective builder
383 */
384 private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort, VlanId assignedVlan,
385 IpAddress mcastIp, MacAddress routerMac, McastRole mcastRole) {
386 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
387 // Let's add the in port matching and the priority
388 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
389 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
390 // According to the mcast role we match on the proper vlan
391 // If the role is null we are on the transit or on the egress
392 if (mcastRole == null) {
393 filtBuilder.addCondition(Criteria.matchVlanId(egressVlan()));
394 } else {
395 filtBuilder.addCondition(Criteria.matchVlanId(ingressVlan()));
396 }
397 // According to the IP type we set the proper match on the mac address
398 if (mcastIp.isIp4()) {
399 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
400 MacAddress.IPV4_MULTICAST_MASK));
401 } else {
402 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
403 MacAddress.IPV6_MULTICAST_MASK));
404 }
405 // We finally build the meta treatment
406 TrafficTreatment tt = DefaultTrafficTreatment.builder()
407 .pushVlan().setVlanId(assignedVlan)
408 .setEthDst(routerMac)
409 .build();
410 filtBuilder.withMeta(tt);
411 // Done, we return a permit filtering objective
412 return filtBuilder.permit().fromApp(srManager.appId());
413 }
414
415 /**
416 * Gets output ports information from treatments.
417 *
418 * @param treatments collection of traffic treatments
419 * @return set of output port numbers
420 */
421 Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
422 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
423 treatments.forEach(treatment -> treatment.allInstructions().stream()
424 .filter(instr -> instr instanceof Instructions.OutputInstruction)
425 .forEach(instr -> builder.add(((Instructions.OutputInstruction) instr).port())));
426 return builder.build();
427 }
428}