blob: 07939846e8bb7cd650b19039dfd55145227f588c [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;
Pierb0328e42018-03-27 11:29:42 -070063
64/**
65 * Utility class for Multicast Handler.
66 */
67class McastUtils {
68
69 // Internal reference to the log
70 private final Logger log;
71 // Internal reference to SR Manager
72 private SegmentRoutingManager srManager;
73 // Internal reference to the app id
74 private ApplicationId coreAppId;
Pier96f63cb2018-04-17 16:29:56 +020075 // Hashing function for the multicast hasher
76 private static final HashFunction HASH_FN = Hashing.md5();
77 // Read only cache of the Mcast leader
78 private Map<IpAddress, NodeId> mcastLeaderCache;
Pierb0328e42018-03-27 11:29:42 -070079
80 /**
81 * Builds a new McastUtils object.
82 *
83 * @param srManager the SR manager
84 * @param coreAppId the core application id
85 * @param log log reference of the McastHandler
86 */
87 McastUtils(SegmentRoutingManager srManager, ApplicationId coreAppId, Logger log) {
88 this.srManager = srManager;
89 this.coreAppId = coreAppId;
90 this.log = log;
Pier96f63cb2018-04-17 16:29:56 +020091 this.mcastLeaderCache = Maps.newConcurrentMap();
Pierb0328e42018-03-27 11:29:42 -070092 }
93
94 /**
Pier477e0062018-04-20 14:14:34 +020095 * Clean up when deactivating the application.
96 */
97 public void terminate() {
98 mcastLeaderCache.clear();
99 }
100
101 /**
Pierb0328e42018-03-27 11:29:42 -0700102 * Get router mac using application config and the connect point.
103 *
104 * @param deviceId the device id
105 * @param port the port number
106 * @return the router mac if the port is configured, otherwise null
107 */
108 private MacAddress getRouterMac(DeviceId deviceId, PortNumber port) {
109 // Do nothing if the port is configured as suppressed
110 ConnectPoint connectPoint = new ConnectPoint(deviceId, port);
111 SegmentRoutingAppConfig appConfig = srManager.cfgService
112 .getConfig(srManager.appId(), SegmentRoutingAppConfig.class);
113 if (appConfig != null && appConfig.suppressSubnet().contains(connectPoint)) {
114 log.info("Ignore suppressed port {}", connectPoint);
115 return MacAddress.NONE;
116 }
117 // Get the router mac using the device configuration
118 MacAddress routerMac;
119 try {
120 routerMac = srManager.deviceConfiguration().getDeviceMac(deviceId);
121 } catch (DeviceConfigNotFoundException dcnfe) {
122 log.warn("Fail to push filtering objective since device is not configured. Abort");
123 return MacAddress.NONE;
124 }
125 return routerMac;
126 }
127
128 /**
129 * Adds filtering objective for given device and port.
130 *
131 * @param deviceId device ID
132 * @param port ingress port number
133 * @param assignedVlan assigned VLAN ID
134 * @param mcastIp the group address
135 * @param mcastRole the role of the device
136 */
137 void addFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
138 IpAddress mcastIp, McastRole mcastRole) {
139
140 MacAddress routerMac = getRouterMac(deviceId, port);
141 if (routerMac.equals(MacAddress.NONE)) {
142 return;
143 }
144
145 FilteringObjective.Builder filtObjBuilder = filterObjBuilder(port, assignedVlan, mcastIp,
146 routerMac, mcastRole);
147 ObjectiveContext context = new DefaultObjectiveContext(
148 (objective) -> log.debug("Successfully add filter on {}/{}, vlan {}",
149 deviceId, port.toLong(), assignedVlan),
150 (objective, error) ->
151 log.warn("Failed to add filter on {}/{}, vlan {}: {}",
152 deviceId, port.toLong(), assignedVlan, error));
153 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.add(context));
154 }
155
156 /**
157 * Removes filtering objective for given device and port.
158 *
159 * @param deviceId device ID
160 * @param port ingress port number
161 * @param assignedVlan assigned VLAN ID
162 * @param mcastIp multicast IP address
163 * @param mcastRole the multicast role of the device
164 */
165 void removeFilterToDevice(DeviceId deviceId, PortNumber port, VlanId assignedVlan,
166 IpAddress mcastIp, McastRole mcastRole) {
167
168 MacAddress routerMac = getRouterMac(deviceId, port);
169 if (routerMac.equals(MacAddress.NONE)) {
170 return;
171 }
172
173 FilteringObjective.Builder filtObjBuilder =
174 filterObjBuilder(port, assignedVlan, mcastIp, routerMac, mcastRole);
175 ObjectiveContext context = new DefaultObjectiveContext(
176 (objective) -> log.debug("Successfully removed filter on {}/{}, vlan {}",
177 deviceId, port.toLong(), assignedVlan),
178 (objective, error) ->
179 log.warn("Failed to remove filter on {}/{}, vlan {}: {}",
180 deviceId, port.toLong(), assignedVlan, error));
181 srManager.flowObjectiveService.filter(deviceId, filtObjBuilder.remove(context));
182 }
183
184 /**
185 * Gets assigned VLAN according to the value in the meta.
186 *
187 * @param nextObjective nextObjective to analyze
188 * @return assigned VLAN ID
189 */
190 VlanId assignedVlanFromNext(NextObjective nextObjective) {
191 return ((VlanIdCriterion) nextObjective.meta().getCriterion(VLAN_VID)).vlanId();
192 }
193
194 /**
195 * Gets ingress VLAN from McastConfig.
196 *
197 * @return ingress VLAN or VlanId.NONE if not configured
198 */
199 private VlanId ingressVlan() {
200 McastConfig mcastConfig =
201 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
202 return (mcastConfig != null) ? mcastConfig.ingressVlan() : VlanId.NONE;
203 }
204
205 /**
206 * Gets egress VLAN from McastConfig.
207 *
208 * @return egress VLAN or VlanId.NONE if not configured
209 */
210 private VlanId egressVlan() {
211 McastConfig mcastConfig =
212 srManager.cfgService.getConfig(coreAppId, McastConfig.class);
213 return (mcastConfig != null) ? mcastConfig.egressVlan() : VlanId.NONE;
214 }
215
216 /**
217 * Gets assigned VLAN according to the value of egress VLAN.
218 * If connect point is specified, try to reuse the assigned VLAN on the connect point.
219 *
220 * @param cp connect point; Can be null if not specified
221 * @return assigned VLAN ID
222 */
223 VlanId assignedVlan(ConnectPoint cp) {
224 // Use the egressVlan if it is tagged
225 if (!egressVlan().equals(VlanId.NONE)) {
226 return egressVlan();
227 }
228 // Reuse unicast VLAN if the port has subnet configured
229 if (cp != null) {
230 VlanId untaggedVlan = srManager.getInternalVlanId(cp);
Saurav Das09c2c4d2018-08-13 15:34:26 -0700231 return (untaggedVlan != null) ? untaggedVlan
232 : srManager.getDefaultInternalVlan();
Pierb0328e42018-03-27 11:29:42 -0700233 }
234 // Use DEFAULT_VLAN if none of the above matches
Saurav Das09c2c4d2018-08-13 15:34:26 -0700235 return srManager.getDefaultInternalVlan();
Pierb0328e42018-03-27 11:29:42 -0700236 }
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,
Charles Chan056e0c12018-05-10 22:19:49 +0000390 IpAddress mcastIp, MacAddress routerMac, McastRole mcastRole) {
Pierb0328e42018-03-27 11:29:42 -0700391 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,
Charles Chan056e0c12018-05-10 22:19:49 +0000405 MacAddress.IPV4_MULTICAST_MASK));
Pierb0328e42018-03-27 11:29:42 -0700406 } else {
407 filtBuilder.addCondition(Criteria.matchEthDstMasked(MacAddress.IPV6_MULTICAST,
Charles Chan056e0c12018-05-10 22:19:49 +0000408 MacAddress.IPV6_MULTICAST_MASK));
Pierb0328e42018-03-27 11:29:42 -0700409 }
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}