blob: 18cb221764d0d420f295a1631fdb50241377e649 [file] [log] [blame]
Tunahan Sezen03e55272020-04-18 09:18:53 +00001/*
2 * Copyright 2017-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 */
16package org.opencord.maclearner.app.impl;
17
18import com.google.common.collect.ImmutableMap;
19import com.google.common.collect.ImmutableSet;
20import com.google.common.collect.Maps;
21import com.google.common.collect.Sets;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000022import org.onlab.packet.EthType;
23import org.onlab.packet.IpAddress;
Tunahan Sezen03e55272020-04-18 09:18:53 +000024import org.onlab.packet.VlanId;
Andrea Campanellabccc74e2020-09-30 14:05:24 +020025import org.onlab.util.PredictableExecutor;
Tunahan Sezen03e55272020-04-18 09:18:53 +000026import org.onlab.util.Tools;
27import org.onosproject.cfg.ComponentConfigService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000028import org.onosproject.cluster.ClusterEvent;
29import org.onosproject.cluster.ClusterEventListener;
30import org.onosproject.cluster.ClusterService;
31import org.onosproject.cluster.ControllerNode;
Andrea Campanellada3be772022-03-14 16:17:05 +010032import org.onosproject.cluster.LeadershipService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000033import org.onosproject.cluster.NodeId;
Tunahan Sezen03e55272020-04-18 09:18:53 +000034import org.onosproject.core.ApplicationId;
Andrea Campanellada3be772022-03-14 16:17:05 +010035import org.onosproject.mastership.MastershipService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000036import org.onosproject.net.ConnectPoint;
37import org.onosproject.net.Device;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053038import org.onosproject.net.ElementId;
39import org.onosproject.net.Host;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000040import org.onosproject.net.HostId;
41import org.onosproject.net.HostLocation;
42import org.onosproject.net.Link;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053043import org.onosproject.net.Port;
Tunahan Sezen03e55272020-04-18 09:18:53 +000044import org.onosproject.net.device.DeviceEvent;
45import org.onosproject.net.device.DeviceListener;
46import org.onosproject.net.device.DeviceService;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053047import org.onosproject.net.flow.DefaultTrafficTreatment;
48import org.onosproject.net.flow.TrafficTreatment;
49import org.onosproject.net.host.HostService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000050import org.onosproject.net.link.LinkService;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053051import org.onosproject.net.packet.DefaultOutboundPacket;
52import org.onosproject.net.packet.OutboundPacket;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000053import org.onosproject.net.topology.Topology;
54import org.onosproject.net.topology.TopologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000055import org.onosproject.store.service.ConsistentMap;
56import org.onosproject.store.service.StorageService;
57import org.onosproject.store.service.Versioned;
58import org.opencord.maclearner.api.DefaultMacLearner;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000059import org.opencord.maclearner.api.MacLearnerHostLocationService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000060import org.opencord.maclearner.api.MacDeleteResult;
61import org.opencord.maclearner.api.MacLearnerEvent;
62import org.opencord.maclearner.api.MacLearnerKey;
63import org.opencord.maclearner.api.MacLearnerListener;
64import org.opencord.maclearner.api.MacLearnerProvider;
65import org.opencord.maclearner.api.MacLearnerProviderService;
66import org.opencord.maclearner.api.MacLearnerService;
67import org.opencord.maclearner.api.MacLearnerValue;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053068import org.opencord.sadis.BaseInformationService;
69import org.opencord.sadis.SadisService;
70import org.opencord.sadis.SubscriberAndDeviceInformation;
Tunahan Sezen03e55272020-04-18 09:18:53 +000071import org.osgi.service.component.ComponentContext;
72import org.osgi.service.component.annotations.Activate;
73import org.osgi.service.component.annotations.Component;
74import org.osgi.service.component.annotations.Deactivate;
75import org.osgi.service.component.annotations.Modified;
76import org.osgi.service.component.annotations.Reference;
77import org.onlab.packet.DHCP;
78import org.onlab.packet.Ethernet;
79import org.onlab.packet.IPv4;
80import org.onlab.packet.MacAddress;
81import org.onlab.packet.UDP;
82import org.onlab.packet.dhcp.DhcpOption;
83import org.onlab.util.KryoNamespace;
84import org.onosproject.core.CoreService;
85import org.onosproject.net.DeviceId;
86import org.onosproject.net.PortNumber;
87import org.onosproject.net.packet.PacketContext;
88import org.onosproject.net.packet.PacketProcessor;
89import org.onosproject.net.packet.PacketService;
90import org.onosproject.net.provider.AbstractListenerProviderRegistry;
91import org.onosproject.net.provider.AbstractProviderService;
92import org.onosproject.store.LogicalTimestamp;
93import org.onosproject.store.serializers.KryoNamespaces;
94import org.onosproject.store.service.Serializer;
95import org.onosproject.store.service.WallClockTimestamp;
Harsh Awasthi6bd73602022-01-31 18:40:25 +053096import org.osgi.service.component.annotations.ReferenceCardinality;
97import org.osgi.service.component.annotations.ReferencePolicy;
Tunahan Sezen03e55272020-04-18 09:18:53 +000098import org.slf4j.Logger;
99import org.slf4j.LoggerFactory;
100
101import java.net.URI;
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530102import java.nio.ByteBuffer;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000103import java.util.Date;
104import java.util.Dictionary;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000105import java.util.List;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000106import java.util.Map;
107import java.util.Optional;
108import java.util.Properties;
109import java.util.Set;
110import java.util.concurrent.ExecutorService;
111import java.util.concurrent.Executors;
112import java.util.concurrent.ScheduledExecutorService;
113import java.util.concurrent.ScheduledFuture;
114import java.util.concurrent.TimeUnit;
115import java.util.stream.Collectors;
116
117import static com.google.common.base.Strings.isNullOrEmpty;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000118import static java.util.stream.Collectors.toList;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000119import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
120import static org.onlab.util.Tools.groupedThreads;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000121import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING;
122import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000123import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION_DEFAULT;
124import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION;
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530125import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DHCP_FORWARD;
126import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.ENABLE_DHCP_FORWARD_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000127import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY;
128
129/**
130 * Mac Learner Service implementation.
131 */
132@Component(immediate = true,
133 property = {
134 CACHE_DURATION + ":Integer=" + CACHE_DURATION_DEFAULT,
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530135 AUTO_CLEAR_MAC_MAPPING + ":Boolean=" + AUTO_CLEAR_MAC_MAPPING_DEFAULT,
136 ENABLE_DHCP_FORWARD + ":Boolean=" + ENABLE_DHCP_FORWARD_DEFAULT
Tunahan Sezen03e55272020-04-18 09:18:53 +0000137 },
138 service = MacLearnerService.class
139)
140public class MacLearnerManager
141 extends AbstractListenerProviderRegistry<MacLearnerEvent, MacLearnerListener,
142 MacLearnerProvider, MacLearnerProviderService>
143 implements MacLearnerService {
144
145 private static final String MAC_LEARNER_APP = "org.opencord.maclearner";
146 private static final String MAC_LEARNER = "maclearner";
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000147 private static final String OLT_MANUFACTURER_KEY = "VOLTHA";
Tunahan Sezen03e55272020-04-18 09:18:53 +0000148 private ApplicationId appId;
149
150 private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
151 private ScheduledFuture scheduledFuture;
152
153 private final Logger log = LoggerFactory.getLogger(getClass());
154
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530155 protected BaseInformationService<SubscriberAndDeviceInformation> subsService;
156
157 @Reference(cardinality = ReferenceCardinality.OPTIONAL,
158 bind = "bindSadisService",
159 unbind = "unbindSadisService",
160 policy = ReferencePolicy.DYNAMIC)
161 protected volatile SadisService sadisService;
162
Tunahan Sezen03e55272020-04-18 09:18:53 +0000163 @Reference(cardinality = MANDATORY)
164 protected CoreService coreService;
165
166 @Reference(cardinality = MANDATORY)
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000167 protected ClusterService clusterService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000168
169 @Reference(cardinality = MANDATORY)
170 protected DeviceService deviceService;
171
172 @Reference(cardinality = MANDATORY)
Andrea Campanellada3be772022-03-14 16:17:05 +0100173 protected LeadershipService leadershipService;
174
175 @Reference(cardinality = MANDATORY)
176 protected MastershipService mastershipService;
177
178 @Reference(cardinality = MANDATORY)
Tunahan Sezen03e55272020-04-18 09:18:53 +0000179 protected PacketService packetService;
180
181 @Reference(cardinality = MANDATORY)
182 protected StorageService storageService;
183
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000184 @Reference(cardinality = MANDATORY)
185 protected TopologyService topologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000186
187 @Reference(cardinality = MANDATORY)
188 protected ComponentConfigService componentConfigService;
189
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000190 @Reference(cardinality = MANDATORY)
191 protected MacLearnerHostLocationService hostLocService;
192
193 @Reference(cardinality = MANDATORY)
194 protected LinkService linkService;
195
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530196 @Reference(cardinality = ReferenceCardinality.MANDATORY)
197 protected HostService hostService;
198
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000199 private final MacLearnerPacketProcessor macLearnerPacketProcessor =
Tunahan Sezen03e55272020-04-18 09:18:53 +0000200 new MacLearnerPacketProcessor();
201
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000202 private final DeviceListener deviceListener = new InternalDeviceListener();
203 private final ClusterEventListener clusterListener = new InternalClusterListener();
204
205 private ConsistentHasher hasher;
206 public static final int HASH_WEIGHT = 10;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000207
208 /**
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530209 * Enables Dhcp forwarding.
210 */
211 protected boolean enableDhcpForward = ENABLE_DHCP_FORWARD_DEFAULT;
212
213 /**
Tunahan Sezen03e55272020-04-18 09:18:53 +0000214 * Minimum duration of mapping, mapping can be exist until 2*cacheDuration because of cleanerTimer fixed rate.
215 */
216 protected int cacheDurationSec = CACHE_DURATION_DEFAULT;
217
218 /**
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000219 * Removes mappings from MAC Address Map for removed events.
Tunahan Sezen03e55272020-04-18 09:18:53 +0000220 */
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000221 protected boolean autoClearMacMapping = AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000222
223 private ConsistentMap<DeviceId, Set<PortNumber>> ignoredPortsMap;
224 private ConsistentMap<MacLearnerKey, MacLearnerValue> macAddressMap;
225
226 protected ExecutorService eventExecutor;
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200227 // Packet workers - 0 will leverage available processors
228 private static final int DEFAULT_THREADS = 0;
229 private PredictableExecutor packetWorkers;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000230
231 @Activate
232 public void activate() {
233 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/maclearner",
234 "events-%d", log));
235 appId = coreService.registerApplication(MAC_LEARNER_APP);
236 componentConfigService.registerProperties(getClass());
237 eventDispatcher.addSink(MacLearnerEvent.class, listenerRegistry);
238 macAddressMap = storageService.<MacLearnerKey, MacLearnerValue>consistentMapBuilder()
239 .withName(MAC_LEARNER)
240 .withSerializer(createSerializer())
241 .withApplicationId(appId)
242 .build();
243 ignoredPortsMap = storageService
244 .<DeviceId, Set<PortNumber>>consistentMapBuilder()
245 .withName("maclearner-ignored")
246 .withSerializer(createSerializer())
247 .withApplicationId(appId)
248 .build();
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200249 packetWorkers = new PredictableExecutor(DEFAULT_THREADS,
250 groupedThreads("onos/mac-learner-host-loc-provider",
251 "packet-worker-%d", log));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000252 //mac learner must process the packet before director processors
253 packetService.addProcessor(macLearnerPacketProcessor,
254 PacketProcessor.advisor(2));
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000255 List<NodeId> readyNodes = clusterService.getNodes().stream()
256 .filter(c -> clusterService.getState(c.id()) == ControllerNode.State.READY)
257 .map(ControllerNode::id)
258 .collect(toList());
259 hasher = new ConsistentHasher(readyNodes, HASH_WEIGHT);
260 clusterService.addListener(clusterListener);
261 deviceService.addListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000262 createSchedulerForClearMacMappings();
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530263
264 if (sadisService != null) {
265 subsService = sadisService.getSubscriberInfoService();
266 } else {
267 log.warn("Sadis is not running");
268 }
269
Tunahan Sezen03e55272020-04-18 09:18:53 +0000270 log.info("{} is started.", getClass().getSimpleName());
271 }
272
273 private Serializer createSerializer() {
274 return Serializer.using(KryoNamespace.newBuilder()
275 .register(KryoNamespace.newBuilder().build(MAC_LEARNER))
276 // not so robust way to avoid collision with other
277 // user supplied registrations
278 .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID + 100)
279 .register(KryoNamespaces.BASIC)
280 .register(LogicalTimestamp.class)
281 .register(WallClockTimestamp.class)
282 .register(MacLearnerKey.class)
283 .register(MacLearnerValue.class)
284 .register(DeviceId.class)
285 .register(URI.class)
286 .register(PortNumber.class)
287 .register(VlanId.class)
288 .register(MacAddress.class)
289 .build(MAC_LEARNER + "-ecmap"));
290 }
291
292 private void createSchedulerForClearMacMappings() {
293 scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(this::clearExpiredMacMappings,
294 0,
295 cacheDurationSec,
296 TimeUnit.SECONDS);
297 }
298
299 private void clearExpiredMacMappings() {
300 Date curDate = new Date();
301 for (Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>> entry : macAddressMap.entrySet()) {
Andrea Campanellada3be772022-03-14 16:17:05 +0100302 if (!isLocalLeader(entry.getKey().getDeviceId())) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000303 continue;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000304 }
305 if (curDate.getTime() - entry.getValue().value().getTimestamp() > cacheDurationSec * 1000) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000306 removeFromMacAddressMap(entry.getKey(), false);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000307 }
308 }
309 }
310
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000311 /**
312 * Determines if this instance should handle this device based on
313 * consistent hashing.
314 *
Andrea Campanellada3be772022-03-14 16:17:05 +0100315 * @param deviceId device ID
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000316 * @return true if this instance should handle the device, otherwise false
317 */
Andrea Campanellada3be772022-03-14 16:17:05 +0100318 public boolean isLocalLeader(DeviceId deviceId) {
319 if (deviceService.isAvailable(deviceId)) {
320 return mastershipService.isLocalMaster(deviceId);
321 } else {
322 // Fallback with Leadership service - device id is used as topic
323 NodeId leader = leadershipService.runForLeadership(
324 deviceId.toString()).leaderNodeId();
325 // Verify if this node is the leader
326 return clusterService.getLocalNode().id().equals(leader);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000327 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000328 }
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530329 protected void bindSadisService(SadisService service) {
330 this.subsService = service.getSubscriberInfoService();
331 log.info("Sadis service is loaded");
332 }
333
334 protected void unbindSadisService(SadisService service) {
335 this.subsService = null;
336 log.info("Sadis service is unloaded");
337 }
338
Tunahan Sezen03e55272020-04-18 09:18:53 +0000339 @Deactivate
340 public void deactivate() {
341 if (scheduledFuture != null) {
342 scheduledFuture.cancel(true);
343 }
344 packetService.removeProcessor(macLearnerPacketProcessor);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000345 clusterService.removeListener(clusterListener);
346 deviceService.removeListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000347 eventDispatcher.removeSink(MacLearnerEvent.class);
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200348 packetWorkers.shutdown();
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000349 if (eventExecutor != null) {
350 eventExecutor.shutdown();
351 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000352 componentConfigService.unregisterProperties(getClass(), false);
353 log.info("{} is stopped.", getClass().getSimpleName());
354 }
355
356 @Modified
357 public void modified(ComponentContext context) {
358 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
359
360 String cacheDuration = Tools.get(properties, CACHE_DURATION);
361 if (!isNullOrEmpty(cacheDuration)) {
362 int cacheDur = Integer.parseInt(cacheDuration.trim());
363 if (cacheDurationSec != cacheDur) {
364 setMacMappingCacheDuration(cacheDur);
365 }
366 }
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530367
368 Boolean o = Tools.isPropertyEnabled(properties, ENABLE_DHCP_FORWARD);
369 if (o != null) {
370 if (o != enableDhcpForward) {
371 log.info("Changing enableDhcpForward to: {} from {}", o, enableDhcpForward);
372 enableDhcpForward = o;
373 }
374 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000375 }
376
377 private Integer setMacMappingCacheDuration(Integer second) {
378 if (cacheDurationSec == second) {
379 log.info("Cache duration already: {}", second);
380 return second;
381 }
382 log.info("Changing cache duration to: {} second from {} second...", second, cacheDurationSec);
383 this.cacheDurationSec = second;
384 if (scheduledFuture != null) {
385 scheduledFuture.cancel(false);
386 }
387 createSchedulerForClearMacMappings();
388 return cacheDurationSec;
389 }
390
391 @Override
392 public void addPortToIgnore(DeviceId deviceId, PortNumber portNumber) {
393 log.info("Adding ignore port: {} {}", deviceId, portNumber);
394 Set<PortNumber> updatedPorts = Sets.newHashSet();
395 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
396 if (storedPorts == null || !storedPorts.value().contains(portNumber)) {
397 if (storedPorts != null) {
398 updatedPorts.addAll(storedPorts.value());
399 }
400 updatedPorts.add(portNumber);
401 ignoredPortsMap.put(deviceId, updatedPorts);
402 log.info("Port:{} of device: {} is added to ignoredPortsMap.", portNumber, deviceId);
403 deleteMacMappings(deviceId, portNumber);
404 } else {
405 log.warn("Port:{} of device: {} is already ignored.", portNumber, deviceId);
406 }
407 }
408
409 @Override
410 public void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber) {
411 log.info("Removing ignore port: {} {}", deviceId, portNumber);
412 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
413 if (storedPorts != null && storedPorts.value().contains(portNumber)) {
414 if (storedPorts.value().size() == 1) {
415 ignoredPortsMap.remove(deviceId);
416 } else {
417 Set<PortNumber> updatedPorts = Sets.newHashSet();
418 updatedPorts.addAll(storedPorts.value());
419 updatedPorts.remove(portNumber);
420 ignoredPortsMap.put(deviceId, updatedPorts);
421 }
422 log.info("Port:{} of device: {} is removed ignoredPortsMap.", portNumber, deviceId);
423 } else {
424 log.warn("Port:{} of device: {} is not found in ignoredPortsMap.", portNumber, deviceId);
425 }
426 }
427
428 @Override
429 public ImmutableMap<MacLearnerKey, MacAddress> getAllMappings() {
430 log.info("Getting all MAC Mappings");
431 Map<MacLearnerKey, MacAddress> immutableMap = Maps.newHashMap();
432 macAddressMap.entrySet().forEach(entry ->
433 immutableMap.put(entry.getKey(),
434 entry.getValue() != null ? entry.getValue().value().getMacAddress() : null));
435 return ImmutableMap.copyOf(immutableMap);
436 }
437
438 @Override
439 public Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
440 log.info("Getting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
441 Versioned<MacLearnerValue> value = macAddressMap.get(new MacLearnerKey(deviceId, portNumber, vlanId));
442 return value != null ? Optional.ofNullable(value.value().getMacAddress()) : Optional.empty();
443 }
444
445 @Override
446 public MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
447 log.info("Deleting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
448 MacLearnerKey key = new MacLearnerKey(deviceId, portNumber, vlanId);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000449 return removeFromMacAddressMap(key, true);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000450 }
451
452 @Override
453 public boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber) {
454 log.info("Deleting MAC mappings for: {} {}", deviceId, portNumber);
455 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
456 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId) &&
457 entry.getKey().getPortNumber().equals(portNumber))
458 .collect(Collectors.toSet());
459 if (entriesToDelete.isEmpty()) {
460 log.warn("MAC mapping not found for deviceId: {} and portNumber: {}", deviceId, portNumber);
461 return false;
462 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000463 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000464 return true;
465 }
466
467 @Override
468 public boolean deleteMacMappings(DeviceId deviceId) {
469 log.info("Deleting MAC mappings for: {}", deviceId);
470 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
471 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId))
472 .collect(Collectors.toSet());
473 if (entriesToDelete.isEmpty()) {
474 log.warn("MAC mapping not found for deviceId: {}", deviceId);
475 return false;
476 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000477 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000478 return true;
479 }
480
481 @Override
482 public ImmutableSet<DeviceId> getMappedDevices() {
483 Set<DeviceId> deviceIds = Sets.newHashSet();
484 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
485 deviceIds.add(entry.getKey().getDeviceId());
486 }
487 return ImmutableSet.copyOf(deviceIds);
488 }
489
490 @Override
491 public ImmutableSet<PortNumber> getMappedPorts() {
492 Set<PortNumber> portNumbers = Sets.newHashSet();
493 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
494 portNumbers.add(entry.getKey().getPortNumber());
495 }
496 return ImmutableSet.copyOf(portNumbers);
497 }
498
499 @Override
500 public ImmutableMap<DeviceId, Set<PortNumber>> getIgnoredPorts() {
501 log.info("Getting ignored ports");
502 Map<DeviceId, Set<PortNumber>> immutableMap = Maps.newHashMap();
503 ignoredPortsMap.forEach(entry -> immutableMap.put(entry.getKey(),
504 entry.getValue() != null ? entry.getValue().value() : Sets.newHashSet()));
505 return ImmutableMap.copyOf(immutableMap);
506 }
507
508 @Override
509 protected MacLearnerProviderService createProviderService(MacLearnerProvider provider) {
510 return new InternalMacLearnerProviderService(provider);
511 }
512
513 private static class InternalMacLearnerProviderService extends AbstractProviderService<MacLearnerProvider>
514 implements MacLearnerProviderService {
515
516 InternalMacLearnerProviderService(MacLearnerProvider provider) {
517 super(provider);
518 }
519 }
520
521 private void sendMacLearnerEvent(MacLearnerEvent.Type type, DeviceId deviceId,
522 PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
523 log.info("Sending MAC Learner Event: type: {} deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
524 type, deviceId, portNumber, vlanId.toShort(), macAddress);
525 DefaultMacLearner macLearner = new DefaultMacLearner(deviceId, portNumber, vlanId, macAddress);
526 MacLearnerEvent macLearnerEvent = new MacLearnerEvent(type, macLearner);
527 post(macLearnerEvent);
528 }
529
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000530 private boolean isOltDevice(Device device) {
531 return device.manufacturer().contains(OLT_MANUFACTURER_KEY);
532 }
533
Tunahan Sezen03e55272020-04-18 09:18:53 +0000534 private class MacLearnerPacketProcessor implements PacketProcessor {
535
536 @Override
537 public void process(PacketContext context) {
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200538 packetWorkers.submit(() -> processPacketInternal(context));
539 }
540
541 private void processPacketInternal(PacketContext context) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000542 // process the packet and get the payload
543 Ethernet packet = context.inPacket().parsed();
544
545 if (packet == null) {
546 log.warn("Packet is null");
547 return;
548 }
549
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000550 ConnectPoint cp = context.inPacket().receivedFrom();
551 DeviceId deviceId = cp.deviceId();
552 PortNumber sourcePort = cp.port();
553 MacAddress srcMac = packet.getSourceMAC();
554 MacAddress dstMac = packet.getDestinationMAC();
555
556 Device device = deviceService.getDevice(deviceId);
557 if (!isOltDevice(device)) { // not handle non OLT device packets
558 log.debug("Packet received from non-OLT device: {}. Returning.", deviceId);
559 return;
560 }
561
562 if (srcMac.isBroadcast() || srcMac.isMulticast()) {
563 log.debug("Broadcast or multicast packet received from: {}. Returning.", cp);
564 return;
565 }
566
567 // Ignore location probes
568 if (dstMac.isOnos() && !MacAddress.NONE.equals(dstMac)) {
569 log.debug("Location probe. cp: {}", cp);
570 return;
571 }
572
573 // If this arrived on control port, bail out.
574 if (cp.port().isLogical()) {
575 log.debug("Packet received from logical port: {}", cp);
576 return;
577 }
578
579 // If this is not an edge port, bail out.
580 Topology topology = topologyService.currentTopology();
581 if (topologyService.isInfrastructure(topology, cp)) {
582 log.debug("Packet received from non-edge port: {}", cp);
583 return;
584 }
585
586 VlanId vlan = VlanId.vlanId(packet.getVlanID());
587 VlanId outerVlan = VlanId.vlanId(packet.getQinQVID());
588 VlanId innerVlan = VlanId.NONE;
589 EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
590 // Set up values for double-tagged hosts
591 if (outerVlan.toShort() != Ethernet.VLAN_UNTAGGED) {
592 innerVlan = vlan;
593 vlan = outerVlan;
594 outerTpid = EthType.EtherType.lookup(packet.getQinQTPID()).ethType();
595 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000596
597 Versioned<Set<PortNumber>> ignoredPortsOfDevice = ignoredPortsMap.get(deviceId);
598 if (ignoredPortsOfDevice != null && ignoredPortsOfDevice.value().contains(sourcePort)) {
599 log.warn("Port Number: {} is in ignoredPortsMap. Returning", sourcePort);
600 return;
601 }
602
603 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
604 IPv4 ipv4Packet = (IPv4) packet.getPayload();
605
606 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
607 UDP udpPacket = (UDP) ipv4Packet.getPayload();
608 int udpSourcePort = udpPacket.getSourcePort();
609 if ((udpSourcePort == UDP.DHCP_CLIENT_PORT) || (udpSourcePort == UDP.DHCP_SERVER_PORT)) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000610 // Update host location
611 HostLocation hloc = new HostLocation(cp, System.currentTimeMillis());
612 HostLocation auxLocation = null;
613 Optional<Link> optLink = linkService.getDeviceLinks(deviceId).stream().findFirst();
614 if (optLink.isPresent()) {
615 Link link = optLink.get();
616 auxLocation = !link.src().deviceId().equals(deviceId) ?
617 new HostLocation(link.src(), System.currentTimeMillis()) :
618 new HostLocation(link.dst(), System.currentTimeMillis());
619 } else {
620 log.debug("Link not found for device {}", deviceId);
621 }
622 hostLocService.createOrUpdateHost(HostId.hostId(packet.getSourceMAC(), vlan),
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200623 packet.getSourceMAC(), packet.getDestinationMAC(),
624 vlan, innerVlan, outerTpid,
625 hloc, auxLocation, null);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000626 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
627 //This packet is dhcp.
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000628 processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlan);
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530629
630 if (enableDhcpForward) {
631 // Forward DHCP Packet to either uni or nni.
632 forwardDhcpPacket(packet, dhcpPayload, device, vlan);
633 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000634 }
635 }
636 }
637 }
638
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530639 /**
640 * Returns the connectPoint which is the uplink port of the OLT.
641 */
642 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
643
644 Device device = deviceService.getDevice(dId);
645
646 if (device == null) {
647 log.warn("Could not find device for device ID {}", dId);
648 return null;
649 }
650
651 SubscriberAndDeviceInformation deviceInfo = subsService.get(device.serialNumber());
652 if (deviceInfo != null) {
653 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
654 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
655 Port port = deviceService.getPort(device.id(), pNum);
656 if (port != null) {
657 return new ConnectPoint(device.id(), pNum);
658 } else {
659 log.warn("Unable to find Port in deviceService for deice ID : {}, port : {}", dId, pNum);
660 }
661 } else {
662 log.warn("Unable to find Sadis entry for device ID : {}, device serial : {}",
663 dId, device.serialNumber());
664 }
665
666 return null;
667 }
668
669 /***
670 * Forwards the packet to uni port or nni port based on the DHCP source port.
671 * Client DHCP packets are transparently forwarded to the nni port.
672 * Server DHCP replies are forwared to the respective uni port based on the (mac,vlan) lookup
673 */
674 private void forwardDhcpPacket(Ethernet packet, DHCP dhcpPayload, Device device, VlanId vlan) {
675 UDP udpPacket = (UDP) dhcpPayload.getParent();
676 int udpSourcePort = udpPacket.getSourcePort();
677 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
678
679 ConnectPoint destinationCp = null;
680
681 if (udpSourcePort == UDP.DHCP_CLIENT_PORT) {
682 destinationCp = getUplinkConnectPointOfOlt(device.id());
683 } else if (udpSourcePort == UDP.DHCP_SERVER_PORT) {
684 Host host = hostService.getHost(HostId.hostId(clientMacAddress, vlan));
685
686 ElementId elementId = host.location().elementId();
687 PortNumber portNumber = host.location().port();
688
689 destinationCp = new ConnectPoint(elementId, portNumber);
690 }
691
692 if (destinationCp == null) {
693 log.error("No connect point to send msg to DHCP message");
694 return;
695 }
696
697 if (log.isTraceEnabled()) {
698 VlanId printVlan = VlanId.NONE;
699
700 if (vlan != null) {
701 printVlan = vlan;
702 }
703
704 log.trace("Emitting : packet {}, with MAC {}, with VLAN {}, with connect point {}",
705 getDhcpPacketType(dhcpPayload), clientMacAddress, printVlan, destinationCp);
706 }
707
708 TrafficTreatment t = DefaultTrafficTreatment.builder()
709 .setOutput(destinationCp.port()).build();
710 OutboundPacket o = new DefaultOutboundPacket(destinationCp
711 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
712 packetService.emit(o);
713 }
714
Tunahan Sezen03e55272020-04-18 09:18:53 +0000715 //process the dhcp packet before forwarding
716 private void processDhcpPacket(PacketContext context, Ethernet packet,
717 DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
718 if (dhcpPayload == null) {
719 log.warn("DHCP payload is null");
720 return;
721 }
722
723 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
724
725 if (incomingPacketType == null) {
726 log.warn("Incoming packet type is null!");
727 return;
728 }
729
730 log.info("Received DHCP Packet of type {} from {}",
731 incomingPacketType, context.inPacket().receivedFrom());
732
733 if (incomingPacketType.equals(DHCP.MsgType.DHCPDISCOVER) ||
734 incomingPacketType.equals(DHCP.MsgType.DHCPREQUEST)) {
735 addToMacAddressMap(deviceId, sourcePort, vlanId, packet.getSourceMAC());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000736 } else if (incomingPacketType.equals(DHCP.MsgType.DHCPACK)) {
737 MacAddress hostMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
738 VlanId hostVlan = VlanId.vlanId(packet.getVlanID());
739 HostId hostId = HostId.hostId(hostMac, hostVlan);
740 hostLocService.updateHostIp(hostId, IpAddress.valueOf(dhcpPayload.getYourIPAddress()));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000741 }
742 }
743
744 // get type of the DHCP packet
745 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
746
747 for (DhcpOption option : dhcpPayload.getOptions()) {
748 if (option.getCode() == OptionCode_MessageType.getValue()) {
749 byte[] data = option.getData();
750 return DHCP.MsgType.getType(data[0]);
751 }
752 }
753 return null;
754 }
755
756 private void addToMacAddressMap(DeviceId deviceId, PortNumber portNumber,
757 VlanId vlanId, MacAddress macAddress) {
758 Versioned<MacLearnerValue> prevMacAddress =
759 macAddressMap.put(new MacLearnerKey(deviceId, portNumber, vlanId),
760 new MacLearnerValue(macAddress, new Date().getTime()));
761 if (prevMacAddress != null && !prevMacAddress.value().getMacAddress().equals(macAddress)) {
762 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
763 deviceId,
764 portNumber,
765 vlanId,
766 prevMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000767 }
768 if (prevMacAddress == null || !prevMacAddress.value().getMacAddress().equals(macAddress)) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000769 // Not sending event for already mapped
770 log.info("Mapped MAC: {} for port: {} of deviceId: {} and vlanId: {}",
771 macAddress, portNumber, deviceId, vlanId);
772 sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
773 }
774 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000775 }
776
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000777 private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey, boolean vanishHost) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000778 Versioned<MacLearnerValue> verMacAddress = macAddressMap.remove(macLearnerKey);
779 if (verMacAddress != null) {
780 log.info("Mapping removed. deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
781 macLearnerKey.getDeviceId(), macLearnerKey.getPortNumber(),
782 verMacAddress.value(), verMacAddress.value().getMacAddress());
783 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
784 macLearnerKey.getDeviceId(),
785 macLearnerKey.getPortNumber(),
786 macLearnerKey.getVlanId(),
787 verMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000788 if (vanishHost) {
789 hostLocService.vanishHost(verMacAddress.value().getMacAddress(), macLearnerKey.getVlanId());
790 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000791 return MacDeleteResult.SUCCESSFUL;
792 } else {
793 log.warn("MAC not removed, because mapping not found for deviceId: {} and portNumber: {} and vlanId: {}",
794 macLearnerKey.getDeviceId(),
795 macLearnerKey.getPortNumber(),
796 macLearnerKey.getVlanId());
797 return MacDeleteResult.NOT_EXIST;
798 }
799 }
800
801 private class InternalDeviceListener implements DeviceListener {
802
803 @Override
804 public void event(DeviceEvent event) {
805 eventExecutor.execute(() -> {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000806 Device device = event.subject();
807 log.debug("Device event received: {}", event.type());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000808 switch (event.type()) {
809 case DEVICE_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000810 if (autoClearMacMapping) {
811 deleteMacMappings(device.id());
812 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000813 break;
814 case PORT_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000815 if (autoClearMacMapping) {
816 deleteMacMappings(device.id(), event.port().number());
817 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000818 break;
819 default:
820 log.debug("Unhandled device event for Mac Learner: {}", event.type());
821 }
822 });
823 }
824
825 @Override
826 public boolean isRelevant(DeviceEvent event) {
Andrea Campanellada3be772022-03-14 16:17:05 +0100827 boolean master = isLocalLeader(event.subject().id());
828 if (log.isDebugEnabled() && master) {
829 log.debug("Master for {}, handling event {}", event.subject().id(), event);
830 }
831 return master;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000832 }
833
834 }
835
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000836 private class InternalClusterListener implements ClusterEventListener {
837 @Override
838 public void event(ClusterEvent event) {
839 if (event.type() == ClusterEvent.Type.INSTANCE_READY) {
840 hasher.addServer(event.subject().id());
841 }
842 if (event.type() == ClusterEvent.Type.INSTANCE_DEACTIVATED) {
843 hasher.removeServer(event.subject().id());
844 }
845 }
846 }
847
Tunahan Sezen03e55272020-04-18 09:18:53 +0000848}