blob: e1972c3bd24c652f962fc68776e6e9653a8a0745 [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 }
amit.ghoshab298902022-06-14 11:22:17 +0200375
376 o = Tools.isPropertyEnabled(properties, AUTO_CLEAR_MAC_MAPPING);
377 if (o != null) {
378 if (o != autoClearMacMapping) {
379 log.info("Changing autoClearMacMapping to: {} from {}", o, autoClearMacMapping);
380 autoClearMacMapping = o;
381 }
382 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000383 }
384
385 private Integer setMacMappingCacheDuration(Integer second) {
386 if (cacheDurationSec == second) {
387 log.info("Cache duration already: {}", second);
388 return second;
389 }
390 log.info("Changing cache duration to: {} second from {} second...", second, cacheDurationSec);
391 this.cacheDurationSec = second;
392 if (scheduledFuture != null) {
393 scheduledFuture.cancel(false);
394 }
395 createSchedulerForClearMacMappings();
396 return cacheDurationSec;
397 }
398
399 @Override
400 public void addPortToIgnore(DeviceId deviceId, PortNumber portNumber) {
401 log.info("Adding ignore port: {} {}", deviceId, portNumber);
402 Set<PortNumber> updatedPorts = Sets.newHashSet();
403 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
404 if (storedPorts == null || !storedPorts.value().contains(portNumber)) {
405 if (storedPorts != null) {
406 updatedPorts.addAll(storedPorts.value());
407 }
408 updatedPorts.add(portNumber);
409 ignoredPortsMap.put(deviceId, updatedPorts);
410 log.info("Port:{} of device: {} is added to ignoredPortsMap.", portNumber, deviceId);
411 deleteMacMappings(deviceId, portNumber);
412 } else {
413 log.warn("Port:{} of device: {} is already ignored.", portNumber, deviceId);
414 }
415 }
416
417 @Override
418 public void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber) {
419 log.info("Removing ignore port: {} {}", deviceId, portNumber);
420 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
421 if (storedPorts != null && storedPorts.value().contains(portNumber)) {
422 if (storedPorts.value().size() == 1) {
423 ignoredPortsMap.remove(deviceId);
424 } else {
425 Set<PortNumber> updatedPorts = Sets.newHashSet();
426 updatedPorts.addAll(storedPorts.value());
427 updatedPorts.remove(portNumber);
428 ignoredPortsMap.put(deviceId, updatedPorts);
429 }
430 log.info("Port:{} of device: {} is removed ignoredPortsMap.", portNumber, deviceId);
431 } else {
432 log.warn("Port:{} of device: {} is not found in ignoredPortsMap.", portNumber, deviceId);
433 }
434 }
435
436 @Override
437 public ImmutableMap<MacLearnerKey, MacAddress> getAllMappings() {
438 log.info("Getting all MAC Mappings");
439 Map<MacLearnerKey, MacAddress> immutableMap = Maps.newHashMap();
440 macAddressMap.entrySet().forEach(entry ->
441 immutableMap.put(entry.getKey(),
442 entry.getValue() != null ? entry.getValue().value().getMacAddress() : null));
443 return ImmutableMap.copyOf(immutableMap);
444 }
445
446 @Override
447 public Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
448 log.info("Getting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
449 Versioned<MacLearnerValue> value = macAddressMap.get(new MacLearnerKey(deviceId, portNumber, vlanId));
450 return value != null ? Optional.ofNullable(value.value().getMacAddress()) : Optional.empty();
451 }
452
453 @Override
454 public MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
455 log.info("Deleting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
456 MacLearnerKey key = new MacLearnerKey(deviceId, portNumber, vlanId);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000457 return removeFromMacAddressMap(key, true);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000458 }
459
460 @Override
461 public boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber) {
462 log.info("Deleting MAC mappings for: {} {}", deviceId, portNumber);
463 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
464 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId) &&
465 entry.getKey().getPortNumber().equals(portNumber))
466 .collect(Collectors.toSet());
467 if (entriesToDelete.isEmpty()) {
468 log.warn("MAC mapping not found for deviceId: {} and portNumber: {}", deviceId, portNumber);
469 return false;
470 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000471 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000472 return true;
473 }
474
475 @Override
476 public boolean deleteMacMappings(DeviceId deviceId) {
477 log.info("Deleting MAC mappings for: {}", deviceId);
478 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
479 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId))
480 .collect(Collectors.toSet());
481 if (entriesToDelete.isEmpty()) {
482 log.warn("MAC mapping not found for deviceId: {}", deviceId);
483 return false;
484 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000485 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000486 return true;
487 }
488
489 @Override
490 public ImmutableSet<DeviceId> getMappedDevices() {
491 Set<DeviceId> deviceIds = Sets.newHashSet();
492 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
493 deviceIds.add(entry.getKey().getDeviceId());
494 }
495 return ImmutableSet.copyOf(deviceIds);
496 }
497
498 @Override
499 public ImmutableSet<PortNumber> getMappedPorts() {
500 Set<PortNumber> portNumbers = Sets.newHashSet();
501 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
502 portNumbers.add(entry.getKey().getPortNumber());
503 }
504 return ImmutableSet.copyOf(portNumbers);
505 }
506
507 @Override
508 public ImmutableMap<DeviceId, Set<PortNumber>> getIgnoredPorts() {
509 log.info("Getting ignored ports");
510 Map<DeviceId, Set<PortNumber>> immutableMap = Maps.newHashMap();
511 ignoredPortsMap.forEach(entry -> immutableMap.put(entry.getKey(),
512 entry.getValue() != null ? entry.getValue().value() : Sets.newHashSet()));
513 return ImmutableMap.copyOf(immutableMap);
514 }
515
516 @Override
517 protected MacLearnerProviderService createProviderService(MacLearnerProvider provider) {
518 return new InternalMacLearnerProviderService(provider);
519 }
520
521 private static class InternalMacLearnerProviderService extends AbstractProviderService<MacLearnerProvider>
522 implements MacLearnerProviderService {
523
524 InternalMacLearnerProviderService(MacLearnerProvider provider) {
525 super(provider);
526 }
527 }
528
529 private void sendMacLearnerEvent(MacLearnerEvent.Type type, DeviceId deviceId,
530 PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
531 log.info("Sending MAC Learner Event: type: {} deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
532 type, deviceId, portNumber, vlanId.toShort(), macAddress);
533 DefaultMacLearner macLearner = new DefaultMacLearner(deviceId, portNumber, vlanId, macAddress);
534 MacLearnerEvent macLearnerEvent = new MacLearnerEvent(type, macLearner);
535 post(macLearnerEvent);
536 }
537
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000538 private boolean isOltDevice(Device device) {
539 return device.manufacturer().contains(OLT_MANUFACTURER_KEY);
540 }
541
Tunahan Sezen03e55272020-04-18 09:18:53 +0000542 private class MacLearnerPacketProcessor implements PacketProcessor {
543
544 @Override
545 public void process(PacketContext context) {
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200546 packetWorkers.submit(() -> processPacketInternal(context));
547 }
548
549 private void processPacketInternal(PacketContext context) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000550 // process the packet and get the payload
551 Ethernet packet = context.inPacket().parsed();
552
553 if (packet == null) {
554 log.warn("Packet is null");
555 return;
556 }
557
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000558 ConnectPoint cp = context.inPacket().receivedFrom();
559 DeviceId deviceId = cp.deviceId();
560 PortNumber sourcePort = cp.port();
561 MacAddress srcMac = packet.getSourceMAC();
562 MacAddress dstMac = packet.getDestinationMAC();
563
564 Device device = deviceService.getDevice(deviceId);
565 if (!isOltDevice(device)) { // not handle non OLT device packets
566 log.debug("Packet received from non-OLT device: {}. Returning.", deviceId);
567 return;
568 }
569
570 if (srcMac.isBroadcast() || srcMac.isMulticast()) {
571 log.debug("Broadcast or multicast packet received from: {}. Returning.", cp);
572 return;
573 }
574
575 // Ignore location probes
576 if (dstMac.isOnos() && !MacAddress.NONE.equals(dstMac)) {
577 log.debug("Location probe. cp: {}", cp);
578 return;
579 }
580
581 // If this arrived on control port, bail out.
582 if (cp.port().isLogical()) {
583 log.debug("Packet received from logical port: {}", cp);
584 return;
585 }
586
587 // If this is not an edge port, bail out.
588 Topology topology = topologyService.currentTopology();
589 if (topologyService.isInfrastructure(topology, cp)) {
590 log.debug("Packet received from non-edge port: {}", cp);
591 return;
592 }
593
594 VlanId vlan = VlanId.vlanId(packet.getVlanID());
595 VlanId outerVlan = VlanId.vlanId(packet.getQinQVID());
596 VlanId innerVlan = VlanId.NONE;
597 EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
598 // Set up values for double-tagged hosts
599 if (outerVlan.toShort() != Ethernet.VLAN_UNTAGGED) {
600 innerVlan = vlan;
601 vlan = outerVlan;
602 outerTpid = EthType.EtherType.lookup(packet.getQinQTPID()).ethType();
603 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000604
605 Versioned<Set<PortNumber>> ignoredPortsOfDevice = ignoredPortsMap.get(deviceId);
606 if (ignoredPortsOfDevice != null && ignoredPortsOfDevice.value().contains(sourcePort)) {
607 log.warn("Port Number: {} is in ignoredPortsMap. Returning", sourcePort);
608 return;
609 }
610
611 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
612 IPv4 ipv4Packet = (IPv4) packet.getPayload();
613
614 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
615 UDP udpPacket = (UDP) ipv4Packet.getPayload();
616 int udpSourcePort = udpPacket.getSourcePort();
617 if ((udpSourcePort == UDP.DHCP_CLIENT_PORT) || (udpSourcePort == UDP.DHCP_SERVER_PORT)) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000618 // Update host location
619 HostLocation hloc = new HostLocation(cp, System.currentTimeMillis());
620 HostLocation auxLocation = null;
621 Optional<Link> optLink = linkService.getDeviceLinks(deviceId).stream().findFirst();
622 if (optLink.isPresent()) {
623 Link link = optLink.get();
624 auxLocation = !link.src().deviceId().equals(deviceId) ?
625 new HostLocation(link.src(), System.currentTimeMillis()) :
626 new HostLocation(link.dst(), System.currentTimeMillis());
627 } else {
628 log.debug("Link not found for device {}", deviceId);
629 }
630 hostLocService.createOrUpdateHost(HostId.hostId(packet.getSourceMAC(), vlan),
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200631 packet.getSourceMAC(), packet.getDestinationMAC(),
632 vlan, innerVlan, outerTpid,
633 hloc, auxLocation, null);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000634 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
635 //This packet is dhcp.
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000636 processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlan);
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530637
638 if (enableDhcpForward) {
639 // Forward DHCP Packet to either uni or nni.
640 forwardDhcpPacket(packet, dhcpPayload, device, vlan);
641 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000642 }
643 }
644 }
645 }
646
Harsh Awasthi6bd73602022-01-31 18:40:25 +0530647 /**
648 * Returns the connectPoint which is the uplink port of the OLT.
649 */
650 private ConnectPoint getUplinkConnectPointOfOlt(DeviceId dId) {
651
652 Device device = deviceService.getDevice(dId);
653
654 if (device == null) {
655 log.warn("Could not find device for device ID {}", dId);
656 return null;
657 }
658
659 SubscriberAndDeviceInformation deviceInfo = subsService.get(device.serialNumber());
660 if (deviceInfo != null) {
661 log.debug("getUplinkConnectPointOfOlt DeviceId: {} devInfo: {}", dId, deviceInfo);
662 PortNumber pNum = PortNumber.portNumber(deviceInfo.uplinkPort());
663 Port port = deviceService.getPort(device.id(), pNum);
664 if (port != null) {
665 return new ConnectPoint(device.id(), pNum);
666 } else {
667 log.warn("Unable to find Port in deviceService for deice ID : {}, port : {}", dId, pNum);
668 }
669 } else {
670 log.warn("Unable to find Sadis entry for device ID : {}, device serial : {}",
671 dId, device.serialNumber());
672 }
673
674 return null;
675 }
676
677 /***
678 * Forwards the packet to uni port or nni port based on the DHCP source port.
679 * Client DHCP packets are transparently forwarded to the nni port.
680 * Server DHCP replies are forwared to the respective uni port based on the (mac,vlan) lookup
681 */
682 private void forwardDhcpPacket(Ethernet packet, DHCP dhcpPayload, Device device, VlanId vlan) {
683 UDP udpPacket = (UDP) dhcpPayload.getParent();
684 int udpSourcePort = udpPacket.getSourcePort();
685 MacAddress clientMacAddress = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
686
687 ConnectPoint destinationCp = null;
688
689 if (udpSourcePort == UDP.DHCP_CLIENT_PORT) {
690 destinationCp = getUplinkConnectPointOfOlt(device.id());
691 } else if (udpSourcePort == UDP.DHCP_SERVER_PORT) {
692 Host host = hostService.getHost(HostId.hostId(clientMacAddress, vlan));
693
694 ElementId elementId = host.location().elementId();
695 PortNumber portNumber = host.location().port();
696
697 destinationCp = new ConnectPoint(elementId, portNumber);
698 }
699
700 if (destinationCp == null) {
701 log.error("No connect point to send msg to DHCP message");
702 return;
703 }
704
705 if (log.isTraceEnabled()) {
706 VlanId printVlan = VlanId.NONE;
707
708 if (vlan != null) {
709 printVlan = vlan;
710 }
711
712 log.trace("Emitting : packet {}, with MAC {}, with VLAN {}, with connect point {}",
713 getDhcpPacketType(dhcpPayload), clientMacAddress, printVlan, destinationCp);
714 }
715
716 TrafficTreatment t = DefaultTrafficTreatment.builder()
717 .setOutput(destinationCp.port()).build();
718 OutboundPacket o = new DefaultOutboundPacket(destinationCp
719 .deviceId(), t, ByteBuffer.wrap(packet.serialize()));
720 packetService.emit(o);
721 }
722
Tunahan Sezen03e55272020-04-18 09:18:53 +0000723 //process the dhcp packet before forwarding
724 private void processDhcpPacket(PacketContext context, Ethernet packet,
725 DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
726 if (dhcpPayload == null) {
727 log.warn("DHCP payload is null");
728 return;
729 }
730
731 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
732
733 if (incomingPacketType == null) {
734 log.warn("Incoming packet type is null!");
735 return;
736 }
737
738 log.info("Received DHCP Packet of type {} from {}",
739 incomingPacketType, context.inPacket().receivedFrom());
740
741 if (incomingPacketType.equals(DHCP.MsgType.DHCPDISCOVER) ||
742 incomingPacketType.equals(DHCP.MsgType.DHCPREQUEST)) {
743 addToMacAddressMap(deviceId, sourcePort, vlanId, packet.getSourceMAC());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000744 } else if (incomingPacketType.equals(DHCP.MsgType.DHCPACK)) {
745 MacAddress hostMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
746 VlanId hostVlan = VlanId.vlanId(packet.getVlanID());
747 HostId hostId = HostId.hostId(hostMac, hostVlan);
748 hostLocService.updateHostIp(hostId, IpAddress.valueOf(dhcpPayload.getYourIPAddress()));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000749 }
750 }
751
752 // get type of the DHCP packet
753 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
754
755 for (DhcpOption option : dhcpPayload.getOptions()) {
756 if (option.getCode() == OptionCode_MessageType.getValue()) {
757 byte[] data = option.getData();
758 return DHCP.MsgType.getType(data[0]);
759 }
760 }
761 return null;
762 }
763
764 private void addToMacAddressMap(DeviceId deviceId, PortNumber portNumber,
765 VlanId vlanId, MacAddress macAddress) {
766 Versioned<MacLearnerValue> prevMacAddress =
767 macAddressMap.put(new MacLearnerKey(deviceId, portNumber, vlanId),
768 new MacLearnerValue(macAddress, new Date().getTime()));
769 if (prevMacAddress != null && !prevMacAddress.value().getMacAddress().equals(macAddress)) {
770 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
771 deviceId,
772 portNumber,
773 vlanId,
774 prevMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000775 }
776 if (prevMacAddress == null || !prevMacAddress.value().getMacAddress().equals(macAddress)) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000777 // Not sending event for already mapped
778 log.info("Mapped MAC: {} for port: {} of deviceId: {} and vlanId: {}",
779 macAddress, portNumber, deviceId, vlanId);
780 sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
781 }
782 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000783 }
784
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000785 private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey, boolean vanishHost) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000786 Versioned<MacLearnerValue> verMacAddress = macAddressMap.remove(macLearnerKey);
787 if (verMacAddress != null) {
788 log.info("Mapping removed. deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
789 macLearnerKey.getDeviceId(), macLearnerKey.getPortNumber(),
790 verMacAddress.value(), verMacAddress.value().getMacAddress());
791 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
792 macLearnerKey.getDeviceId(),
793 macLearnerKey.getPortNumber(),
794 macLearnerKey.getVlanId(),
795 verMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000796 if (vanishHost) {
797 hostLocService.vanishHost(verMacAddress.value().getMacAddress(), macLearnerKey.getVlanId());
798 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000799 return MacDeleteResult.SUCCESSFUL;
800 } else {
801 log.warn("MAC not removed, because mapping not found for deviceId: {} and portNumber: {} and vlanId: {}",
802 macLearnerKey.getDeviceId(),
803 macLearnerKey.getPortNumber(),
804 macLearnerKey.getVlanId());
805 return MacDeleteResult.NOT_EXIST;
806 }
807 }
808
809 private class InternalDeviceListener implements DeviceListener {
810
811 @Override
812 public void event(DeviceEvent event) {
813 eventExecutor.execute(() -> {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000814 Device device = event.subject();
815 log.debug("Device event received: {}", event.type());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000816 switch (event.type()) {
817 case DEVICE_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000818 if (autoClearMacMapping) {
819 deleteMacMappings(device.id());
820 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000821 break;
822 case PORT_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000823 if (autoClearMacMapping) {
824 deleteMacMappings(device.id(), event.port().number());
825 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000826 break;
827 default:
828 log.debug("Unhandled device event for Mac Learner: {}", event.type());
829 }
830 });
831 }
832
833 @Override
834 public boolean isRelevant(DeviceEvent event) {
Andrea Campanellada3be772022-03-14 16:17:05 +0100835 boolean master = isLocalLeader(event.subject().id());
836 if (log.isDebugEnabled() && master) {
837 log.debug("Master for {}, handling event {}", event.subject().id(), event);
838 }
839 return master;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000840 }
841
842 }
843
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000844 private class InternalClusterListener implements ClusterEventListener {
845 @Override
846 public void event(ClusterEvent event) {
847 if (event.type() == ClusterEvent.Type.INSTANCE_READY) {
848 hasher.addServer(event.subject().id());
849 }
850 if (event.type() == ClusterEvent.Type.INSTANCE_DEACTIVATED) {
851 hasher.removeServer(event.subject().id());
852 }
853 }
854 }
855
Tunahan Sezen03e55272020-04-18 09:18:53 +0000856}