blob: 437cd3c4bc75356c90c924fef5ebab470b4d800b [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;
32import org.onosproject.cluster.NodeId;
Tunahan Sezen03e55272020-04-18 09:18:53 +000033import org.onosproject.core.ApplicationId;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000034import org.onosproject.net.ConnectPoint;
35import org.onosproject.net.Device;
36import org.onosproject.net.HostId;
37import org.onosproject.net.HostLocation;
38import org.onosproject.net.Link;
Tunahan Sezen03e55272020-04-18 09:18:53 +000039import org.onosproject.net.device.DeviceEvent;
40import org.onosproject.net.device.DeviceListener;
41import org.onosproject.net.device.DeviceService;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000042import org.onosproject.net.link.LinkService;
43import org.onosproject.net.topology.Topology;
44import org.onosproject.net.topology.TopologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000045import org.onosproject.store.service.ConsistentMap;
46import org.onosproject.store.service.StorageService;
47import org.onosproject.store.service.Versioned;
48import org.opencord.maclearner.api.DefaultMacLearner;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000049import org.opencord.maclearner.api.MacLearnerHostLocationService;
Tunahan Sezen03e55272020-04-18 09:18:53 +000050import org.opencord.maclearner.api.MacDeleteResult;
51import org.opencord.maclearner.api.MacLearnerEvent;
52import org.opencord.maclearner.api.MacLearnerKey;
53import org.opencord.maclearner.api.MacLearnerListener;
54import org.opencord.maclearner.api.MacLearnerProvider;
55import org.opencord.maclearner.api.MacLearnerProviderService;
56import org.opencord.maclearner.api.MacLearnerService;
57import org.opencord.maclearner.api.MacLearnerValue;
58import org.osgi.service.component.ComponentContext;
59import org.osgi.service.component.annotations.Activate;
60import org.osgi.service.component.annotations.Component;
61import org.osgi.service.component.annotations.Deactivate;
62import org.osgi.service.component.annotations.Modified;
63import org.osgi.service.component.annotations.Reference;
64import org.onlab.packet.DHCP;
65import org.onlab.packet.Ethernet;
66import org.onlab.packet.IPv4;
67import org.onlab.packet.MacAddress;
68import org.onlab.packet.UDP;
69import org.onlab.packet.dhcp.DhcpOption;
70import org.onlab.util.KryoNamespace;
71import org.onosproject.core.CoreService;
72import org.onosproject.net.DeviceId;
73import org.onosproject.net.PortNumber;
74import org.onosproject.net.packet.PacketContext;
75import org.onosproject.net.packet.PacketProcessor;
76import org.onosproject.net.packet.PacketService;
77import org.onosproject.net.provider.AbstractListenerProviderRegistry;
78import org.onosproject.net.provider.AbstractProviderService;
79import org.onosproject.store.LogicalTimestamp;
80import org.onosproject.store.serializers.KryoNamespaces;
81import org.onosproject.store.service.Serializer;
82import org.onosproject.store.service.WallClockTimestamp;
83import org.slf4j.Logger;
84import org.slf4j.LoggerFactory;
85
86import java.net.URI;
87import java.util.Date;
88import java.util.Dictionary;
Tunahan Sezen1f65c902020-09-08 13:10:16 +000089import java.util.List;
Tunahan Sezen03e55272020-04-18 09:18:53 +000090import java.util.Map;
91import java.util.Optional;
92import java.util.Properties;
93import java.util.Set;
94import java.util.concurrent.ExecutorService;
95import java.util.concurrent.Executors;
96import java.util.concurrent.ScheduledExecutorService;
97import java.util.concurrent.ScheduledFuture;
98import java.util.concurrent.TimeUnit;
99import java.util.stream.Collectors;
100
101import static com.google.common.base.Strings.isNullOrEmpty;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000102import static java.util.stream.Collectors.toList;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000103import static org.onlab.packet.DHCP.DHCPOptionCode.OptionCode_MessageType;
104import static org.onlab.util.Tools.groupedThreads;
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000105import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING;
106import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000107import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION_DEFAULT;
108import static org.opencord.maclearner.app.impl.OsgiPropertyConstants.CACHE_DURATION;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000109import static org.osgi.service.component.annotations.ReferenceCardinality.MANDATORY;
110
111/**
112 * Mac Learner Service implementation.
113 */
114@Component(immediate = true,
115 property = {
116 CACHE_DURATION + ":Integer=" + CACHE_DURATION_DEFAULT,
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000117 AUTO_CLEAR_MAC_MAPPING + ":Boolean=" + AUTO_CLEAR_MAC_MAPPING_DEFAULT
Tunahan Sezen03e55272020-04-18 09:18:53 +0000118 },
119 service = MacLearnerService.class
120)
121public class MacLearnerManager
122 extends AbstractListenerProviderRegistry<MacLearnerEvent, MacLearnerListener,
123 MacLearnerProvider, MacLearnerProviderService>
124 implements MacLearnerService {
125
126 private static final String MAC_LEARNER_APP = "org.opencord.maclearner";
127 private static final String MAC_LEARNER = "maclearner";
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000128 private static final String OLT_MANUFACTURER_KEY = "VOLTHA";
Tunahan Sezen03e55272020-04-18 09:18:53 +0000129 private ApplicationId appId;
130
131 private final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
132 private ScheduledFuture scheduledFuture;
133
134 private final Logger log = LoggerFactory.getLogger(getClass());
135
136 @Reference(cardinality = MANDATORY)
137 protected CoreService coreService;
138
139 @Reference(cardinality = MANDATORY)
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000140 protected ClusterService clusterService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000141
142 @Reference(cardinality = MANDATORY)
143 protected DeviceService deviceService;
144
145 @Reference(cardinality = MANDATORY)
146 protected PacketService packetService;
147
148 @Reference(cardinality = MANDATORY)
149 protected StorageService storageService;
150
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000151 @Reference(cardinality = MANDATORY)
152 protected TopologyService topologyService;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000153
154 @Reference(cardinality = MANDATORY)
155 protected ComponentConfigService componentConfigService;
156
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000157 @Reference(cardinality = MANDATORY)
158 protected MacLearnerHostLocationService hostLocService;
159
160 @Reference(cardinality = MANDATORY)
161 protected LinkService linkService;
162
163 private final MacLearnerPacketProcessor macLearnerPacketProcessor =
Tunahan Sezen03e55272020-04-18 09:18:53 +0000164 new MacLearnerPacketProcessor();
165
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000166 private final DeviceListener deviceListener = new InternalDeviceListener();
167 private final ClusterEventListener clusterListener = new InternalClusterListener();
168
169 private ConsistentHasher hasher;
170 public static final int HASH_WEIGHT = 10;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000171
172 /**
173 * Minimum duration of mapping, mapping can be exist until 2*cacheDuration because of cleanerTimer fixed rate.
174 */
175 protected int cacheDurationSec = CACHE_DURATION_DEFAULT;
176
177 /**
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000178 * Removes mappings from MAC Address Map for removed events.
Tunahan Sezen03e55272020-04-18 09:18:53 +0000179 */
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000180 protected boolean autoClearMacMapping = AUTO_CLEAR_MAC_MAPPING_DEFAULT;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000181
182 private ConsistentMap<DeviceId, Set<PortNumber>> ignoredPortsMap;
183 private ConsistentMap<MacLearnerKey, MacLearnerValue> macAddressMap;
184
185 protected ExecutorService eventExecutor;
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200186 // Packet workers - 0 will leverage available processors
187 private static final int DEFAULT_THREADS = 0;
188 private PredictableExecutor packetWorkers;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000189
190 @Activate
191 public void activate() {
192 eventExecutor = Executors.newFixedThreadPool(5, groupedThreads("onos/maclearner",
193 "events-%d", log));
194 appId = coreService.registerApplication(MAC_LEARNER_APP);
195 componentConfigService.registerProperties(getClass());
196 eventDispatcher.addSink(MacLearnerEvent.class, listenerRegistry);
197 macAddressMap = storageService.<MacLearnerKey, MacLearnerValue>consistentMapBuilder()
198 .withName(MAC_LEARNER)
199 .withSerializer(createSerializer())
200 .withApplicationId(appId)
201 .build();
202 ignoredPortsMap = storageService
203 .<DeviceId, Set<PortNumber>>consistentMapBuilder()
204 .withName("maclearner-ignored")
205 .withSerializer(createSerializer())
206 .withApplicationId(appId)
207 .build();
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200208 packetWorkers = new PredictableExecutor(DEFAULT_THREADS,
209 groupedThreads("onos/mac-learner-host-loc-provider",
210 "packet-worker-%d", log));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000211 //mac learner must process the packet before director processors
212 packetService.addProcessor(macLearnerPacketProcessor,
213 PacketProcessor.advisor(2));
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000214 List<NodeId> readyNodes = clusterService.getNodes().stream()
215 .filter(c -> clusterService.getState(c.id()) == ControllerNode.State.READY)
216 .map(ControllerNode::id)
217 .collect(toList());
218 hasher = new ConsistentHasher(readyNodes, HASH_WEIGHT);
219 clusterService.addListener(clusterListener);
220 deviceService.addListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000221 createSchedulerForClearMacMappings();
222 log.info("{} is started.", getClass().getSimpleName());
223 }
224
225 private Serializer createSerializer() {
226 return Serializer.using(KryoNamespace.newBuilder()
227 .register(KryoNamespace.newBuilder().build(MAC_LEARNER))
228 // not so robust way to avoid collision with other
229 // user supplied registrations
230 .nextId(KryoNamespaces.BEGIN_USER_CUSTOM_ID + 100)
231 .register(KryoNamespaces.BASIC)
232 .register(LogicalTimestamp.class)
233 .register(WallClockTimestamp.class)
234 .register(MacLearnerKey.class)
235 .register(MacLearnerValue.class)
236 .register(DeviceId.class)
237 .register(URI.class)
238 .register(PortNumber.class)
239 .register(VlanId.class)
240 .register(MacAddress.class)
241 .build(MAC_LEARNER + "-ecmap"));
242 }
243
244 private void createSchedulerForClearMacMappings() {
245 scheduledFuture = scheduledExecutorService.scheduleAtFixedRate(this::clearExpiredMacMappings,
246 0,
247 cacheDurationSec,
248 TimeUnit.SECONDS);
249 }
250
251 private void clearExpiredMacMappings() {
252 Date curDate = new Date();
253 for (Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>> entry : macAddressMap.entrySet()) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000254 if (!isDeviceMine(entry.getKey().getDeviceId())) {
255 continue;
Tunahan Sezen03e55272020-04-18 09:18:53 +0000256 }
257 if (curDate.getTime() - entry.getValue().value().getTimestamp() > cacheDurationSec * 1000) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000258 removeFromMacAddressMap(entry.getKey(), false);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000259 }
260 }
261 }
262
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000263 /**
264 * Determines if this instance should handle this device based on
265 * consistent hashing.
266 *
267 * @param id device ID
268 * @return true if this instance should handle the device, otherwise false
269 */
270 private boolean isDeviceMine(DeviceId id) {
271 NodeId nodeId = hasher.hash(id.toString());
272 if (log.isDebugEnabled()) {
273 log.debug("Node that will handle {} is {}", id, nodeId);
274 }
275 return nodeId.equals(clusterService.getLocalNode().id());
276 }
277
Tunahan Sezen03e55272020-04-18 09:18:53 +0000278 @Deactivate
279 public void deactivate() {
280 if (scheduledFuture != null) {
281 scheduledFuture.cancel(true);
282 }
283 packetService.removeProcessor(macLearnerPacketProcessor);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000284 clusterService.removeListener(clusterListener);
285 deviceService.removeListener(deviceListener);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000286 eventDispatcher.removeSink(MacLearnerEvent.class);
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200287 packetWorkers.shutdown();
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000288 if (eventExecutor != null) {
289 eventExecutor.shutdown();
290 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000291 componentConfigService.unregisterProperties(getClass(), false);
292 log.info("{} is stopped.", getClass().getSimpleName());
293 }
294
295 @Modified
296 public void modified(ComponentContext context) {
297 Dictionary<?, ?> properties = context != null ? context.getProperties() : new Properties();
298
299 String cacheDuration = Tools.get(properties, CACHE_DURATION);
300 if (!isNullOrEmpty(cacheDuration)) {
301 int cacheDur = Integer.parseInt(cacheDuration.trim());
302 if (cacheDurationSec != cacheDur) {
303 setMacMappingCacheDuration(cacheDur);
304 }
305 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000306 }
307
308 private Integer setMacMappingCacheDuration(Integer second) {
309 if (cacheDurationSec == second) {
310 log.info("Cache duration already: {}", second);
311 return second;
312 }
313 log.info("Changing cache duration to: {} second from {} second...", second, cacheDurationSec);
314 this.cacheDurationSec = second;
315 if (scheduledFuture != null) {
316 scheduledFuture.cancel(false);
317 }
318 createSchedulerForClearMacMappings();
319 return cacheDurationSec;
320 }
321
322 @Override
323 public void addPortToIgnore(DeviceId deviceId, PortNumber portNumber) {
324 log.info("Adding ignore port: {} {}", deviceId, portNumber);
325 Set<PortNumber> updatedPorts = Sets.newHashSet();
326 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
327 if (storedPorts == null || !storedPorts.value().contains(portNumber)) {
328 if (storedPorts != null) {
329 updatedPorts.addAll(storedPorts.value());
330 }
331 updatedPorts.add(portNumber);
332 ignoredPortsMap.put(deviceId, updatedPorts);
333 log.info("Port:{} of device: {} is added to ignoredPortsMap.", portNumber, deviceId);
334 deleteMacMappings(deviceId, portNumber);
335 } else {
336 log.warn("Port:{} of device: {} is already ignored.", portNumber, deviceId);
337 }
338 }
339
340 @Override
341 public void removeFromIgnoredPorts(DeviceId deviceId, PortNumber portNumber) {
342 log.info("Removing ignore port: {} {}", deviceId, portNumber);
343 Versioned<Set<PortNumber>> storedPorts = ignoredPortsMap.get(deviceId);
344 if (storedPorts != null && storedPorts.value().contains(portNumber)) {
345 if (storedPorts.value().size() == 1) {
346 ignoredPortsMap.remove(deviceId);
347 } else {
348 Set<PortNumber> updatedPorts = Sets.newHashSet();
349 updatedPorts.addAll(storedPorts.value());
350 updatedPorts.remove(portNumber);
351 ignoredPortsMap.put(deviceId, updatedPorts);
352 }
353 log.info("Port:{} of device: {} is removed ignoredPortsMap.", portNumber, deviceId);
354 } else {
355 log.warn("Port:{} of device: {} is not found in ignoredPortsMap.", portNumber, deviceId);
356 }
357 }
358
359 @Override
360 public ImmutableMap<MacLearnerKey, MacAddress> getAllMappings() {
361 log.info("Getting all MAC Mappings");
362 Map<MacLearnerKey, MacAddress> immutableMap = Maps.newHashMap();
363 macAddressMap.entrySet().forEach(entry ->
364 immutableMap.put(entry.getKey(),
365 entry.getValue() != null ? entry.getValue().value().getMacAddress() : null));
366 return ImmutableMap.copyOf(immutableMap);
367 }
368
369 @Override
370 public Optional<MacAddress> getMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
371 log.info("Getting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
372 Versioned<MacLearnerValue> value = macAddressMap.get(new MacLearnerKey(deviceId, portNumber, vlanId));
373 return value != null ? Optional.ofNullable(value.value().getMacAddress()) : Optional.empty();
374 }
375
376 @Override
377 public MacDeleteResult deleteMacMapping(DeviceId deviceId, PortNumber portNumber, VlanId vlanId) {
378 log.info("Deleting MAC mapping for: {} {} {}", deviceId, portNumber, vlanId);
379 MacLearnerKey key = new MacLearnerKey(deviceId, portNumber, vlanId);
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000380 return removeFromMacAddressMap(key, true);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000381 }
382
383 @Override
384 public boolean deleteMacMappings(DeviceId deviceId, PortNumber portNumber) {
385 log.info("Deleting MAC mappings for: {} {}", deviceId, portNumber);
386 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
387 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId) &&
388 entry.getKey().getPortNumber().equals(portNumber))
389 .collect(Collectors.toSet());
390 if (entriesToDelete.isEmpty()) {
391 log.warn("MAC mapping not found for deviceId: {} and portNumber: {}", deviceId, portNumber);
392 return false;
393 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000394 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000395 return true;
396 }
397
398 @Override
399 public boolean deleteMacMappings(DeviceId deviceId) {
400 log.info("Deleting MAC mappings for: {}", deviceId);
401 Set<Map.Entry<MacLearnerKey, Versioned<MacLearnerValue>>> entriesToDelete = macAddressMap.entrySet().stream()
402 .filter(entry -> entry.getKey().getDeviceId().equals(deviceId))
403 .collect(Collectors.toSet());
404 if (entriesToDelete.isEmpty()) {
405 log.warn("MAC mapping not found for deviceId: {}", deviceId);
406 return false;
407 }
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000408 entriesToDelete.forEach(e -> removeFromMacAddressMap(e.getKey(), true));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000409 return true;
410 }
411
412 @Override
413 public ImmutableSet<DeviceId> getMappedDevices() {
414 Set<DeviceId> deviceIds = Sets.newHashSet();
415 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
416 deviceIds.add(entry.getKey().getDeviceId());
417 }
418 return ImmutableSet.copyOf(deviceIds);
419 }
420
421 @Override
422 public ImmutableSet<PortNumber> getMappedPorts() {
423 Set<PortNumber> portNumbers = Sets.newHashSet();
424 for (Map.Entry<MacLearnerKey, MacAddress> entry : getAllMappings().entrySet()) {
425 portNumbers.add(entry.getKey().getPortNumber());
426 }
427 return ImmutableSet.copyOf(portNumbers);
428 }
429
430 @Override
431 public ImmutableMap<DeviceId, Set<PortNumber>> getIgnoredPorts() {
432 log.info("Getting ignored ports");
433 Map<DeviceId, Set<PortNumber>> immutableMap = Maps.newHashMap();
434 ignoredPortsMap.forEach(entry -> immutableMap.put(entry.getKey(),
435 entry.getValue() != null ? entry.getValue().value() : Sets.newHashSet()));
436 return ImmutableMap.copyOf(immutableMap);
437 }
438
439 @Override
440 protected MacLearnerProviderService createProviderService(MacLearnerProvider provider) {
441 return new InternalMacLearnerProviderService(provider);
442 }
443
444 private static class InternalMacLearnerProviderService extends AbstractProviderService<MacLearnerProvider>
445 implements MacLearnerProviderService {
446
447 InternalMacLearnerProviderService(MacLearnerProvider provider) {
448 super(provider);
449 }
450 }
451
452 private void sendMacLearnerEvent(MacLearnerEvent.Type type, DeviceId deviceId,
453 PortNumber portNumber, VlanId vlanId, MacAddress macAddress) {
454 log.info("Sending MAC Learner Event: type: {} deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
455 type, deviceId, portNumber, vlanId.toShort(), macAddress);
456 DefaultMacLearner macLearner = new DefaultMacLearner(deviceId, portNumber, vlanId, macAddress);
457 MacLearnerEvent macLearnerEvent = new MacLearnerEvent(type, macLearner);
458 post(macLearnerEvent);
459 }
460
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000461 private boolean isOltDevice(Device device) {
462 return device.manufacturer().contains(OLT_MANUFACTURER_KEY);
463 }
464
Tunahan Sezen03e55272020-04-18 09:18:53 +0000465 private class MacLearnerPacketProcessor implements PacketProcessor {
466
467 @Override
468 public void process(PacketContext context) {
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200469 packetWorkers.submit(() -> processPacketInternal(context));
470 }
471
472 private void processPacketInternal(PacketContext context) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000473 // process the packet and get the payload
474 Ethernet packet = context.inPacket().parsed();
475
476 if (packet == null) {
477 log.warn("Packet is null");
478 return;
479 }
480
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000481 ConnectPoint cp = context.inPacket().receivedFrom();
482 DeviceId deviceId = cp.deviceId();
483 PortNumber sourcePort = cp.port();
484 MacAddress srcMac = packet.getSourceMAC();
485 MacAddress dstMac = packet.getDestinationMAC();
486
487 Device device = deviceService.getDevice(deviceId);
488 if (!isOltDevice(device)) { // not handle non OLT device packets
489 log.debug("Packet received from non-OLT device: {}. Returning.", deviceId);
490 return;
491 }
492
493 if (srcMac.isBroadcast() || srcMac.isMulticast()) {
494 log.debug("Broadcast or multicast packet received from: {}. Returning.", cp);
495 return;
496 }
497
498 // Ignore location probes
499 if (dstMac.isOnos() && !MacAddress.NONE.equals(dstMac)) {
500 log.debug("Location probe. cp: {}", cp);
501 return;
502 }
503
504 // If this arrived on control port, bail out.
505 if (cp.port().isLogical()) {
506 log.debug("Packet received from logical port: {}", cp);
507 return;
508 }
509
510 // If this is not an edge port, bail out.
511 Topology topology = topologyService.currentTopology();
512 if (topologyService.isInfrastructure(topology, cp)) {
513 log.debug("Packet received from non-edge port: {}", cp);
514 return;
515 }
516
517 VlanId vlan = VlanId.vlanId(packet.getVlanID());
518 VlanId outerVlan = VlanId.vlanId(packet.getQinQVID());
519 VlanId innerVlan = VlanId.NONE;
520 EthType outerTpid = EthType.EtherType.UNKNOWN.ethType();
521 // Set up values for double-tagged hosts
522 if (outerVlan.toShort() != Ethernet.VLAN_UNTAGGED) {
523 innerVlan = vlan;
524 vlan = outerVlan;
525 outerTpid = EthType.EtherType.lookup(packet.getQinQTPID()).ethType();
526 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000527
528 Versioned<Set<PortNumber>> ignoredPortsOfDevice = ignoredPortsMap.get(deviceId);
529 if (ignoredPortsOfDevice != null && ignoredPortsOfDevice.value().contains(sourcePort)) {
530 log.warn("Port Number: {} is in ignoredPortsMap. Returning", sourcePort);
531 return;
532 }
533
534 if (packet.getEtherType() == Ethernet.TYPE_IPV4) {
535 IPv4 ipv4Packet = (IPv4) packet.getPayload();
536
537 if (ipv4Packet.getProtocol() == IPv4.PROTOCOL_UDP) {
538 UDP udpPacket = (UDP) ipv4Packet.getPayload();
539 int udpSourcePort = udpPacket.getSourcePort();
540 if ((udpSourcePort == UDP.DHCP_CLIENT_PORT) || (udpSourcePort == UDP.DHCP_SERVER_PORT)) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000541 // Update host location
542 HostLocation hloc = new HostLocation(cp, System.currentTimeMillis());
543 HostLocation auxLocation = null;
544 Optional<Link> optLink = linkService.getDeviceLinks(deviceId).stream().findFirst();
545 if (optLink.isPresent()) {
546 Link link = optLink.get();
547 auxLocation = !link.src().deviceId().equals(deviceId) ?
548 new HostLocation(link.src(), System.currentTimeMillis()) :
549 new HostLocation(link.dst(), System.currentTimeMillis());
550 } else {
551 log.debug("Link not found for device {}", deviceId);
552 }
553 hostLocService.createOrUpdateHost(HostId.hostId(packet.getSourceMAC(), vlan),
Andrea Campanellabccc74e2020-09-30 14:05:24 +0200554 packet.getSourceMAC(), packet.getDestinationMAC(),
555 vlan, innerVlan, outerTpid,
556 hloc, auxLocation, null);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000557 DHCP dhcpPayload = (DHCP) udpPacket.getPayload();
558 //This packet is dhcp.
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000559 processDhcpPacket(context, packet, dhcpPayload, sourcePort, deviceId, vlan);
Tunahan Sezen03e55272020-04-18 09:18:53 +0000560 }
561 }
562 }
563 }
564
565 //process the dhcp packet before forwarding
566 private void processDhcpPacket(PacketContext context, Ethernet packet,
567 DHCP dhcpPayload, PortNumber sourcePort, DeviceId deviceId, VlanId vlanId) {
568 if (dhcpPayload == null) {
569 log.warn("DHCP payload is null");
570 return;
571 }
572
573 DHCP.MsgType incomingPacketType = getDhcpPacketType(dhcpPayload);
574
575 if (incomingPacketType == null) {
576 log.warn("Incoming packet type is null!");
577 return;
578 }
579
580 log.info("Received DHCP Packet of type {} from {}",
581 incomingPacketType, context.inPacket().receivedFrom());
582
583 if (incomingPacketType.equals(DHCP.MsgType.DHCPDISCOVER) ||
584 incomingPacketType.equals(DHCP.MsgType.DHCPREQUEST)) {
585 addToMacAddressMap(deviceId, sourcePort, vlanId, packet.getSourceMAC());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000586 } else if (incomingPacketType.equals(DHCP.MsgType.DHCPACK)) {
587 MacAddress hostMac = MacAddress.valueOf(dhcpPayload.getClientHardwareAddress());
588 VlanId hostVlan = VlanId.vlanId(packet.getVlanID());
589 HostId hostId = HostId.hostId(hostMac, hostVlan);
590 hostLocService.updateHostIp(hostId, IpAddress.valueOf(dhcpPayload.getYourIPAddress()));
Tunahan Sezen03e55272020-04-18 09:18:53 +0000591 }
592 }
593
594 // get type of the DHCP packet
595 private DHCP.MsgType getDhcpPacketType(DHCP dhcpPayload) {
596
597 for (DhcpOption option : dhcpPayload.getOptions()) {
598 if (option.getCode() == OptionCode_MessageType.getValue()) {
599 byte[] data = option.getData();
600 return DHCP.MsgType.getType(data[0]);
601 }
602 }
603 return null;
604 }
605
606 private void addToMacAddressMap(DeviceId deviceId, PortNumber portNumber,
607 VlanId vlanId, MacAddress macAddress) {
608 Versioned<MacLearnerValue> prevMacAddress =
609 macAddressMap.put(new MacLearnerKey(deviceId, portNumber, vlanId),
610 new MacLearnerValue(macAddress, new Date().getTime()));
611 if (prevMacAddress != null && !prevMacAddress.value().getMacAddress().equals(macAddress)) {
612 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
613 deviceId,
614 portNumber,
615 vlanId,
616 prevMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000617 }
618 if (prevMacAddress == null || !prevMacAddress.value().getMacAddress().equals(macAddress)) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000619 // Not sending event for already mapped
620 log.info("Mapped MAC: {} for port: {} of deviceId: {} and vlanId: {}",
621 macAddress, portNumber, deviceId, vlanId);
622 sendMacLearnerEvent(MacLearnerEvent.Type.ADDED, deviceId, portNumber, vlanId, macAddress);
623 }
624 }
625
626 }
627
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000628 private MacDeleteResult removeFromMacAddressMap(MacLearnerKey macLearnerKey, boolean vanishHost) {
Tunahan Sezen03e55272020-04-18 09:18:53 +0000629 Versioned<MacLearnerValue> verMacAddress = macAddressMap.remove(macLearnerKey);
630 if (verMacAddress != null) {
631 log.info("Mapping removed. deviceId: {} portNumber: {} vlanId: {} macAddress: {}",
632 macLearnerKey.getDeviceId(), macLearnerKey.getPortNumber(),
633 verMacAddress.value(), verMacAddress.value().getMacAddress());
634 sendMacLearnerEvent(MacLearnerEvent.Type.REMOVED,
635 macLearnerKey.getDeviceId(),
636 macLearnerKey.getPortNumber(),
637 macLearnerKey.getVlanId(),
638 verMacAddress.value().getMacAddress());
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000639 if (vanishHost) {
640 hostLocService.vanishHost(verMacAddress.value().getMacAddress(), macLearnerKey.getVlanId());
641 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000642 return MacDeleteResult.SUCCESSFUL;
643 } else {
644 log.warn("MAC not removed, because mapping not found for deviceId: {} and portNumber: {} and vlanId: {}",
645 macLearnerKey.getDeviceId(),
646 macLearnerKey.getPortNumber(),
647 macLearnerKey.getVlanId());
648 return MacDeleteResult.NOT_EXIST;
649 }
650 }
651
652 private class InternalDeviceListener implements DeviceListener {
653
654 @Override
655 public void event(DeviceEvent event) {
656 eventExecutor.execute(() -> {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000657 Device device = event.subject();
658 log.debug("Device event received: {}", event.type());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000659 switch (event.type()) {
660 case DEVICE_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000661 if (autoClearMacMapping) {
662 deleteMacMappings(device.id());
663 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000664 break;
665 case PORT_REMOVED:
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000666 if (autoClearMacMapping) {
667 deleteMacMappings(device.id(), event.port().number());
668 }
Tunahan Sezen03e55272020-04-18 09:18:53 +0000669 break;
670 default:
671 log.debug("Unhandled device event for Mac Learner: {}", event.type());
672 }
673 });
674 }
675
676 @Override
677 public boolean isRelevant(DeviceEvent event) {
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000678 return isDeviceMine(event.subject().id());
Tunahan Sezen03e55272020-04-18 09:18:53 +0000679 }
680
681 }
682
Tunahan Sezen1f65c902020-09-08 13:10:16 +0000683 private class InternalClusterListener implements ClusterEventListener {
684 @Override
685 public void event(ClusterEvent event) {
686 if (event.type() == ClusterEvent.Type.INSTANCE_READY) {
687 hasher.addServer(event.subject().id());
688 }
689 if (event.type() == ClusterEvent.Type.INSTANCE_DEACTIVATED) {
690 hasher.removeServer(event.subject().id());
691 }
692 }
693 }
694
Tunahan Sezen03e55272020-04-18 09:18:53 +0000695}