blob: 604f868ba896ee6f5655d5ed0a4a40e5914f9cec [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
Pier96f63cb2018-04-17 16:29:56 +020019import com.google.common.collect.ImmutableMap;
Pierb0328e42018-03-27 11:29:42 -070020import com.google.common.collect.ImmutableSet;
Pier96f63cb2018-04-17 16:29:56 +020021import com.google.common.collect.Maps;
22import com.google.common.hash.HashFunction;
23import com.google.common.hash.Hashing;
Pierb0328e42018-03-27 11:29:42 -070024import org.onlab.packet.Ethernet;
25import org.onlab.packet.IpAddress;
26import org.onlab.packet.MacAddress;
27import org.onlab.packet.VlanId;
28import org.onosproject.cluster.NodeId;
29import org.onosproject.core.ApplicationId;
30import org.onosproject.mcast.api.McastRoute;
31import org.onosproject.net.ConnectPoint;
32import org.onosproject.net.DeviceId;
33import org.onosproject.net.HostId;
34import org.onosproject.net.PortNumber;
35import org.onosproject.net.config.basics.McastConfig;
36import org.onosproject.net.flow.DefaultTrafficSelector;
37import org.onosproject.net.flow.DefaultTrafficTreatment;
38import org.onosproject.net.flow.TrafficSelector;
39import org.onosproject.net.flow.TrafficTreatment;
40import org.onosproject.net.flow.criteria.Criteria;
41import org.onosproject.net.flow.criteria.VlanIdCriterion;
42import org.onosproject.net.flow.instructions.Instructions;
43import org.onosproject.net.flowobjective.DefaultFilteringObjective;
44import org.onosproject.net.flowobjective.DefaultForwardingObjective;
45import org.onosproject.net.flowobjective.DefaultNextObjective;
46import org.onosproject.net.flowobjective.DefaultObjectiveContext;
47import org.onosproject.net.flowobjective.FilteringObjective;
48import org.onosproject.net.flowobjective.ForwardingObjective;
49import org.onosproject.net.flowobjective.NextObjective;
50import org.onosproject.net.flowobjective.ObjectiveContext;
51import org.onosproject.segmentrouting.SegmentRoutingManager;
52import org.onosproject.segmentrouting.SegmentRoutingService;
53import org.onosproject.segmentrouting.config.DeviceConfigNotFoundException;
54import org.onosproject.segmentrouting.config.SegmentRoutingAppConfig;
55import org.slf4j.Logger;
56
57import java.util.Collection;
Pierb0328e42018-03-27 11:29:42 -070058import java.util.Map;
59import java.util.Set;
60import java.util.stream.Collectors;
61
62import static org.onosproject.net.flow.criteria.Criterion.Type.VLAN_VID;
63import static org.onosproject.segmentrouting.SegmentRoutingManager.INTERNAL_VLAN;
64
65/**
66 * Utility class for Multicast Handler.
67 */
68class McastUtils {
69
70 // Internal reference to the log
71 private final Logger log;
72 // Internal reference to SR Manager
73 private SegmentRoutingManager srManager;
74 // Internal reference to the app id
75 private ApplicationId coreAppId;
Pier96f63cb2018-04-17 16:29:56 +020076 // Hashing function for the multicast hasher
77 private static final HashFunction HASH_FN = Hashing.md5();
78 // Read only cache of the Mcast leader
79 private Map<IpAddress, NodeId> mcastLeaderCache;
Pierb0328e42018-03-27 11:29:42 -070080
81 /**
82 * Builds a new McastUtils object.
83 *
84 * @param srManager the SR manager
85 * @param coreAppId the core application id
86 * @param log log reference of the McastHandler
87 */
88 McastUtils(SegmentRoutingManager srManager, ApplicationId coreAppId, Logger log) {
89 this.srManager = srManager;
90 this.coreAppId = coreAppId;
91 this.log = log;
Pier96f63cb2018-04-17 16:29:56 +020092 this.mcastLeaderCache = Maps.newConcurrentMap();
Pierb0328e42018-03-27 11:29:42 -070093 }
94
95 /**
Pier477e0062018-04-20 14:14:34 +020096 * Clean up when deactivating the application.
97 */
98 public void terminate() {
99 mcastLeaderCache.clear();
100 }
101
102 /**
Pierb0328e42018-03-27 11:29:42 -0700103 * Get router mac using application config and the connect point.
104 *
105 * @param deviceId the device id
106 * @param port the port number
107 * @return the router mac if the port is configured, otherwise null
108 */
109 private MacAddress getRouterMac(DeviceId deviceId, PortNumber port) {
110 // Do nothing if the port is configured as suppressed
111 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
112 SegmentRoutingAppConfig appConfig = srManager.cfgService
113 .getConfig(srManager.appId(), SegmentRoutingAppConfig.class);
114 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
115 log.info("Ignore suppressed port {}", connectPoint);
116 return MacAddress.NONE;
117 }
118 // Get the router mac using the device configuration
119 MacAddress routerMac;
120 try {
121 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
122 } catch (DeviceConfigNotFoundException dcnfe) {
123 log.warn("Fail to push filtering objective since device is not configured. Abort");
124 return MacAddress.NONE;
125 }
126 return routerMac;
127 }
128
129 /**
130 * Adds filtering objective for given device and port.
131 *
132 * @param deviceId device ID
133 * @param port ingress port number
134 * @param assignedVlan assigned VLAN ID
135 * @param mcastIp the group address
136 * @param mcastRole the role of the device
137 */
138 void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
139 IpAddress mcastIp, McastRole mcastRole) {
140
141 MacAddress routerMac = getRouterMac(deviceId, port);
142 if (routerMac.equals(MacAddress.NONE)) {
143 return;
144 }
145
146 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(port, assignedVlan, mcastIp,
147 routerMac, mcastRole);
148 ObjectiveContext context = new DefaultObjectiveContext(
149 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
150 deviceId, port.toLong(), assignedVlan),
151 (objective, error) ->
152 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
153 deviceId, port.toLong(), assignedVlan, error));
154 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
155 }
156
157 /**
158 * Removes filtering objective for given device and port.
159 *
160 * @param deviceId device ID
161 * @param port ingress port number
162 * @param assignedVlan assigned VLAN ID
163 * @param mcastIp multicast IP address
164 * @param mcastRole the multicast role of the device
165 */
166 void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
167 IpAddress mcastIp, McastRole mcastRole) {
168
169 MacAddress routerMac = getRouterMac(deviceId, port);
170 if (routerMac.equals(MacAddress.NONE)) {
171 return;
172 }
173
174 FilteringObjective.Builder filtObjBuilder =
175 filterObjBuilder(port, assignedVlan, mcastIp, routerMac, mcastRole);
176 ObjectiveContext context = new DefaultObjectiveContext(
177 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
178 deviceId, port.toLong(), assignedVlan),
179 (objective, error) ->
180 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
181 deviceId, port.toLong(), assignedVlan, error));
182 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
183 }
184
185 /**
186 * Gets assigned VLAN according to the value in the meta.
187 *
188 * @param nextObjective nextObjective to analyze
189 * @return assigned VLAN ID
190 */
191 VlanId assignedVlanFromNext(NextObjective nextObjective) {
192 return ((VlanIdCriterion) nextObjective.meta().getCriterion(VLAN_VID)).vlanId();
193 }
194
195 /**
196 * Gets ingress VLAN from McastConfig.
197 *
198 * @return ingress VLAN or VlanId.NONE if not configured
199 */
200 private VlanId ingressVlan() {
201 McastConfig mcastConfig =
202 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
203 return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
204 }
205
206 /**
207 * Gets egress VLAN from McastConfig.
208 *
209 * @return egress VLAN or VlanId.NONE if not configured
210 */
211 private VlanId egressVlan() {
212 McastConfig mcastConfig =
213 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
214 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
215 }
216
217 /**
218 * Gets assigned VLAN according to the value of egress VLAN.
219 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
220 *
221 * @param cp connect point; Can be null if not specified
222 * @return assigned VLAN ID
223 */
224 VlanId assignedVlan(ConnectPoint cp) {
225 // Use the egressVlan if it is tagged
226 if (!egressVlan().equals(VlanId.NONE)) {
227 return egressVlan();
228 }
229 // Reuse unicast VLAN if the port has subnet configured
230 if (cp != null) {
231 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
232 return (untaggedVlan != null) ? untaggedVlan : INTERNAL_VLAN;
233 }
234 // Use DEFAULT_VLAN if none of the above matches
235 return SegmentRoutingManager.INTERNAL_VLAN;
236 }
237
238 /**
239 * Gets source connect point of given multicast group.
240 *
241 * @param mcastIp multicast IP
242 * @return source connect point or null if not found
Pierb1fe7382018-04-17 17:25:22 +0200243 *
244 * @deprecated in 1.12 ("Magpie") release.
Pierb0328e42018-03-27 11:29:42 -0700245 */
Pierb1fe7382018-04-17 17:25:22 +0200246 @Deprecated
Pierb0328e42018-03-27 11:29:42 -0700247 ConnectPoint getSource(IpAddress mcastIp) {
Pierb0328e42018-03-27 11:29:42 -0700248 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
249 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
250 .findFirst().orElse(null);
251 return mcastRoute == null ? null : srManager.multicastRouteService.sources(mcastRoute)
252 .stream()
253 .findFirst().orElse(null);
254 }
255
256 /**
Pierb1fe7382018-04-17 17:25:22 +0200257 * Gets sources connect points of given multicast group.
258 *
259 * @param mcastIp multicast IP
260 * @return sources connect points or empty set if not found
261 */
262 Set<ConnectPoint> getSources(IpAddress mcastIp) {
Pier3e793752018-04-19 16:47:06 +0200263 // TODO we should support different types of routes
Pierb1fe7382018-04-17 17:25:22 +0200264 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
265 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
266 .findFirst().orElse(null);
267 return mcastRoute == null ? ImmutableSet.of() :
268 srManager.multicastRouteService.sources(mcastRoute);
269 }
270
271 /**
Pierb0328e42018-03-27 11:29:42 -0700272 * Gets sinks of given multicast group.
273 *
274 * @param mcastIp multicast IP
275 * @return map of sinks or empty map if not found
276 */
277 Map<HostId, Set<ConnectPoint>> getSinks(IpAddress mcastIp) {
Pier3e793752018-04-19 16:47:06 +0200278 // TODO we should support different types of routes
Pierb0328e42018-03-27 11:29:42 -0700279 McastRoute mcastRoute = srManager.multicastRouteService.getRoutes().stream()
280 .filter(mcastRouteInternal -> mcastRouteInternal.group().equals(mcastIp))
281 .findFirst().orElse(null);
282 return mcastRoute == null ?
Pierb1fe7382018-04-17 17:25:22 +0200283 ImmutableMap.of() :
Pierb0328e42018-03-27 11:29:42 -0700284 srManager.multicastRouteService.routeData(mcastRoute).sinks();
285 }
286
287 /**
288 * Get sinks affected by this egress device.
289 *
290 * @param egressDevice the egress device
291 * @param mcastIp the mcast ip address
292 * @return the map of the sinks affected
293 */
294 Map<HostId, Set<ConnectPoint>> getAffectedSinks(DeviceId egressDevice,
Pier3e793752018-04-19 16:47:06 +0200295 IpAddress mcastIp) {
Pierb0328e42018-03-27 11:29:42 -0700296 return getSinks(mcastIp).entrySet()
297 .stream()
298 .filter(hostIdSetEntry -> hostIdSetEntry.getValue().stream()
299 .map(ConnectPoint::deviceId)
300 .anyMatch(deviceId -> deviceId.equals(egressDevice))
301 ).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
302 }
303
304 /**
305 * Creates a next objective builder for multicast.
306 *
307 * @param mcastIp multicast group
308 * @param assignedVlan assigned VLAN ID
309 * @param outPorts set of output port numbers
310 * @param nextId the next id
311 * @return next objective builder
312 */
313 NextObjective.Builder nextObjBuilder(IpAddress mcastIp, VlanId assignedVlan,
314 Set<PortNumber> outPorts, Integer nextId) {
315 // If nextId is null allocate a new one
316 if (nextId == null) {
317 nextId = srManager.flowObjectiveService.allocateNextId();
318 }
319 // Build the meta selector with the fwd objective info
320 TrafficSelector metadata =
321 DefaultTrafficSelector.builder()
322 .matchVlanId(assignedVlan)
323 .matchIPDst(mcastIp.toIpPrefix())
324 .build();
325 // Define the nextobjective type
326 NextObjective.Builder nextObjBuilder = DefaultNextObjective
327 .builder().withId(nextId)
328 .withType(NextObjective.Type.BROADCAST)
329 .fromApp(srManager.appId())
330 .withMeta(metadata);
331 // Add the output ports
332 outPorts.forEach(port -> {
333 TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
334 if (egressVlan().equals(VlanId.NONE)) {
335 tBuilder.popVlan();
336 }
337 tBuilder.setOutput(port);
338 nextObjBuilder.addTreatment(tBuilder.build());
339 });
340 // Done return the complete builder
341 return nextObjBuilder;
342 }
343
344 /**
345 * Creates a forwarding objective builder for multicast.
346 *
347 * @param mcastIp multicast group
348 * @param assignedVlan assigned VLAN ID
349 * @param nextId next ID of the L3 multicast group
350 * @return forwarding objective builder
351 */
352 ForwardingObjective.Builder fwdObjBuilder(IpAddress mcastIp,
353 VlanId assignedVlan, int nextId) {
354 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
355 // Let's the matching on the group address
356 // TODO SSM support in future
357 if (mcastIp.isIp6()) {
358 sbuilder.matchEthType(Ethernet.TYPE_IPV6);
359 sbuilder.matchIPv6Dst(mcastIp.toIpPrefix());
360 } else {
361 sbuilder.matchEthType(Ethernet.TYPE_IPV4);
362 sbuilder.matchIPDst(mcastIp.toIpPrefix());
363 }
364 // Then build the meta selector
365 TrafficSelector.Builder metabuilder = DefaultTrafficSelector.builder();
366 metabuilder.matchVlanId(assignedVlan);
367 // Finally return the completed builder
368 ForwardingObjective.Builder fwdBuilder = DefaultForwardingObjective.builder();
369 fwdBuilder.withSelector(sbuilder.build())
370 .withMeta(metabuilder.build())
371 .nextStep(nextId)
372 .withFlag(ForwardingObjective.Flag.SPECIFIC)
373 .fromApp(srManager.appId())
374 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
375 return fwdBuilder;
376 }
377
378 /**
379 * Creates a filtering objective builder for multicast.
380 *
381 * @param ingressPort ingress port of the multicast stream
382 * @param assignedVlan assigned VLAN ID
383 * @param mcastIp the group address
384 * @param routerMac router MAC. This is carried in metadata and used from some switches that
385 * need to put unicast entry before multicast entry in TMAC table.
386 * @param mcastRole the Multicast role
387 * @return filtering objective builder
388 */
389 private FilteringObjective.Builder filterObjBuilder(PortNumber ingressPort, VlanId assignedVlan,
390 IpAddress mcastIp, MacAddress routerMac, McastRole mcastRole) {
391 FilteringObjective.Builder filtBuilder = DefaultFilteringObjective.builder();
392 // Let's add the in port matching and the priority
393 filtBuilder.withKey(Criteria.matchInPort(ingressPort))
394 .withPriority(SegmentRoutingService.DEFAULT_PRIORITY);
395 // According to the mcast role we match on the proper vlan
396 // If the role is null we are on the transit or on the egress
397 if (mcastRole == null) {
398 filtBuilder.addCondition(Criteria.matchVlanId(egressVlan()));
399 } else {
400 filtBuilder.addCondition(Criteria.matchVlanId(ingressVlan()));
401 }
402 // According to the IP type we set the proper match on the mac address
403 if (mcastIp.isIp4()) {
404 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV4_MULTICAST,
405 MacAddress.IPV4_MULTICAST_MASK));
406 } else {
407 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
408 MacAddress.IPV6_MULTICAST_MASK));
409 }
410 // We finally build the meta treatment
411 TrafficTreatment tt = DefaultTrafficTreatment.builder()
412 .pushVlan().setVlanId(assignedVlan)
413 .setEthDst(routerMac)
414 .build();
415 filtBuilder.withMeta(tt);
416 // Done, we return a permit filtering objective
417 return filtBuilder.permit().fromApp(srManager.appId());
418 }
419
420 /**
421 * Gets output ports information from treatments.
422 *
423 * @param treatments collection of traffic treatments
424 * @return set of output port numbers
425 */
426 Set<PortNumber> getPorts(Collection<TrafficTreatment> treatments) {
427 ImmutableSet.Builder<PortNumber> builder = ImmutableSet.builder();
428 treatments.forEach(treatment -> treatment.allInstructions().stream()
429 .filter(instr -> instr instanceof Instructions.OutputInstruction)
430 .forEach(instr -> builder.add(((Instructions.OutputInstruction) instr).port())));
431 return builder.build();
432 }
Pier96f63cb2018-04-17 16:29:56 +0200433
434 /**
435 * Returns the hash of the group address.
436 *
437 * @param ipAddress the ip address
438 * @return the hash of the address
439 */
440 private Long hasher(IpAddress ipAddress) {
441 return HASH_FN.newHasher()
442 .putBytes(ipAddress.toOctets())
443 .hash()
444 .asLong();
445 }
446
447 /**
448 * Given a multicast group define a leader for it.
449 *
450 * @param mcastIp the group address
451 * @return true if the instance is the leader of the group
452 */
453 boolean isLeader(IpAddress mcastIp) {
454 // Get our id
455 final NodeId currentNodeId = srManager.clusterService.getLocalNode().id();
456 // Get the leader for this group using the ip address as key
457 final NodeId leader = srManager.workPartitionService.getLeader(mcastIp, this::hasher);
458 // If there is not a leader, let's send an error
459 if (leader == null) {
460 log.error("Fail to elect a leader for {}.", mcastIp);
461 return false;
462 }
463 // Update cache and return operation result
464 mcastLeaderCache.put(mcastIp, leader);
465 return currentNodeId.equals(leader);
466 }
467
468 /**
469 * Given a multicast group withdraw its leader.
470 *
471 * @param mcastIp the group address
472 */
473 void withdrawLeader(IpAddress mcastIp) {
474 // For now just update the cache
475 mcastLeaderCache.remove(mcastIp);
476 }
477
478 Map<IpAddress, NodeId> getMcastLeaders(IpAddress mcastIp) {
479 // If mcast ip is present
480 if (mcastIp != null) {
481 return mcastLeaderCache.entrySet().stream()
482 .filter(entry -> entry.getKey().equals(mcastIp))
483 .collect(Collectors.toMap(Map.Entry::getKey,
484 Map.Entry::getValue));
485 }
486 // Otherwise take all the groups
487 return ImmutableMap.copyOf(mcastLeaderCache);
488 }
Pierb0328e42018-03-27 11:29:42 -0700489}