Introduced HostMoveTracker to suspend hosts that moves too frequently
Change-Id: I29b233bb2185a712eec122b142fc0111f98e8f09
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
index 6cb1cb6..feb4c1d 100644
--- a/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostManager.java
@@ -15,6 +15,7 @@
*/
package org.onosproject.net.host.impl;
+import com.google.common.collect.Sets;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
@@ -56,14 +57,22 @@
import org.onosproject.net.packet.PacketService;
import org.onosproject.net.provider.AbstractProviderService;
import org.osgi.service.component.ComponentContext;
+
import org.slf4j.Logger;
import java.util.Dictionary;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
+import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onlab.packet.IPv6.getLinkLocalAddress;
+import static org.onlab.util.Tools.get;
import static org.onosproject.security.AppGuard.checkPermission;
import static org.onosproject.security.AppPermission.Type.HOST_EVENT;
import static org.onosproject.security.AppPermission.Type.HOST_READ;
@@ -123,7 +132,38 @@
label = "Enable/Disable greedy learning of IPv6 link local address")
private boolean greedyLearningIpv6 = false;
+ @Property(name = "hostMoveTrackerEnabled", boolValue = false,
+ label = "Enable tracking of rogue host moves")
+ private boolean hostMoveTrackerEnabled = false;
+
+ private static final int HOST_MOVED_THRESHOLD_IN_MILLIS = 200000;
+ // If the host move happening within given threshold then increment the host move counter
+ private static final int HOST_MOVE_COUNTER = 3;
+ // Max value of the counter after which the host will not be considered as offending host
+ private static final long OFFENDING_HOST_EXPIRY_IN_MINS = 1;
+ // Default pool size of offending host clear executor thread
+ private static final int DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE = 10;
+ private Map<HostId, HostMoveTracker> hostMoveTracker = new ConcurrentHashMap<>();
+
+ @Property(name = "hostMoveThresholdInMillis", intValue = HOST_MOVED_THRESHOLD_IN_MILLIS,
+ label = "Host move threshold in milliseconds")
+ private int hostMoveThresholdInMillis = HOST_MOVED_THRESHOLD_IN_MILLIS;
+
+ @Property(name = "hostMoveCounter", intValue = HOST_MOVE_COUNTER,
+ label = "Host move counter before host is marked as rogue host")
+ private int hostMoveCounter = HOST_MOVE_COUNTER;
+
+ @Property(name = "offendingHostExpiryInMins", longValue = OFFENDING_HOST_EXPIRY_IN_MINS,
+ label = "Expiry time after which the host is cleared from being rogue host")
+ private long offendingHostExpiryInMins = OFFENDING_HOST_EXPIRY_IN_MINS;
+
+ @Property(name = "offendingHostClearThreadPool", intValue = DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE,
+ label = "Thread pool capacity")
+ protected int offendingHostClearThreadPool = DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE;
+
private HostMonitor monitor;
+ private ScheduledExecutorService offendingHostUnblockExecutor = null;
+
@Activate
@@ -135,8 +175,8 @@
monitor = new HostMonitor(packetService, this, interfaceService, edgePortService);
monitor.setProbeRate(probeRate);
monitor.start();
- modified(context);
cfgService.registerProperties(getClass());
+ modified(context);
log.info("Started");
}
@@ -147,6 +187,9 @@
networkConfigService.removeListener(networkConfigListener);
cfgService.unregisterProperties(getClass(), false);
monitor.shutdown();
+ if (offendingHostUnblockExecutor != null) {
+ offendingHostUnblockExecutor.shutdown();
+ }
log.info("Stopped");
}
@@ -177,15 +220,19 @@
private void readComponentConfiguration(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
Boolean flag;
+ int newHostMoveThresholdInMillis;
+ int newHostMoveCounter;
+ int newOffendinghostPoolSize;
+ long newOffendingHostExpiryInMins;
flag = Tools.isPropertyEnabled(properties, "monitorHosts");
if (flag == null) {
log.info("monitorHosts is not enabled " +
- "using current value of {}", monitorHosts);
+ "using current value of {}", monitorHosts);
} else {
monitorHosts = flag;
log.info("Configured. monitorHosts {}",
- monitorHosts ? "enabled" : "disabled");
+ monitorHosts ? "enabled" : "disabled");
}
Long longValue = Tools.getLongProperty(properties, "probeRate");
@@ -202,19 +249,85 @@
} else {
allowDuplicateIps = flag;
log.info("Removal of duplicate ip address is {}",
- allowDuplicateIps ? "disabled" : "enabled");
+ allowDuplicateIps ? "disabled" : "enabled");
}
flag = Tools.isPropertyEnabled(properties, "greedyLearningIpv6");
if (flag == null) {
log.info("greedy learning is not enabled " +
- "using current value of {}", greedyLearningIpv6);
+ "using current value of {}", greedyLearningIpv6);
} else {
greedyLearningIpv6 = flag;
log.info("Configured. greedyLearningIpv6 {}",
- greedyLearningIpv6 ? "enabled" : "disabled");
+ greedyLearningIpv6 ? "enabled" : "disabled");
}
+ flag = Tools.isPropertyEnabled(properties, "hostMoveTrackerEnabled");
+ if (flag == null) {
+ log.info("Host move tracker is not configured " +
+ "using current value of {}", hostMoveTrackerEnabled);
+ } else {
+ hostMoveTrackerEnabled = flag;
+ log.info("Configured. hostMoveTrackerEnabled {}",
+ hostMoveTrackerEnabled ? "enabled" : "disabled");
+
+ //On enable cfg ,sets default configuration vales added , else use the default values
+ properties = context.getProperties();
+ try {
+ String s = get(properties, "hostMoveThresholdInMillis");
+ newHostMoveThresholdInMillis = isNullOrEmpty(s) ?
+ hostMoveThresholdInMillis : Integer.parseInt(s.trim());
+
+ s = get(properties, "hostMoveCounter");
+ newHostMoveCounter = isNullOrEmpty(s) ? hostMoveCounter : Integer.parseInt(s.trim());
+
+ s = get(properties, "offendingHostExpiryInMins");
+ newOffendingHostExpiryInMins = isNullOrEmpty(s) ?
+ offendingHostExpiryInMins : Integer.parseInt(s.trim());
+
+ s = get(properties, "offendingHostClearThreadPool");
+ newOffendinghostPoolSize = isNullOrEmpty(s) ?
+ offendingHostClearThreadPool : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ newHostMoveThresholdInMillis = HOST_MOVED_THRESHOLD_IN_MILLIS;
+ newHostMoveCounter = HOST_MOVE_COUNTER;
+ newOffendingHostExpiryInMins = OFFENDING_HOST_EXPIRY_IN_MINS;
+ newOffendinghostPoolSize = DEFAULT_OFFENDING_HOST_THREADS_POOL_SIZE;
+ }
+ if (newHostMoveThresholdInMillis != hostMoveThresholdInMillis) {
+ hostMoveThresholdInMillis = newHostMoveThresholdInMillis;
+ }
+ if (newHostMoveCounter != hostMoveCounter) {
+ hostMoveCounter = newHostMoveCounter;
+ }
+ if (newOffendingHostExpiryInMins != offendingHostExpiryInMins) {
+ offendingHostExpiryInMins = newOffendingHostExpiryInMins;
+ }
+ if (hostMoveTrackerEnabled && offendingHostUnblockExecutor == null) {
+ setupThreadPool();
+ } else if (newOffendinghostPoolSize != offendingHostClearThreadPool
+ && offendingHostUnblockExecutor != null) {
+ offendingHostClearThreadPool = newOffendinghostPoolSize;
+ offendingHostUnblockExecutor.shutdown();
+ offendingHostUnblockExecutor = null;
+ setupThreadPool();
+ } else if (!hostMoveTrackerEnabled && offendingHostUnblockExecutor != null) {
+ offendingHostUnblockExecutor.shutdown();
+ offendingHostUnblockExecutor = null;
+ }
+ if (newOffendinghostPoolSize != offendingHostClearThreadPool) {
+ offendingHostClearThreadPool = newOffendinghostPoolSize;
+ }
+
+ log.debug("modified hostMoveThresholdInMillis: {}, hostMoveCounter: {}, " +
+ "offendingHostExpiryInMins: {} ", hostMoveThresholdInMillis,
+ hostMoveCounter, offendingHostExpiryInMins);
+ }
+
+ }
+
+ private synchronized void setupThreadPool() {
+ offendingHostUnblockExecutor = Executors.newScheduledThreadPool(offendingHostClearThreadPool);
}
/**
@@ -338,8 +451,17 @@
if (!allowDuplicateIps) {
removeDuplicates(hostId, hostDescription);
}
- store.createOrUpdateHost(provider().id(), hostId,
- hostDescription, replaceIps);
+
+ if (!hostMoveTrackerEnabled) {
+ store.createOrUpdateHost(provider().id(), hostId,
+ hostDescription, replaceIps);
+ } else if (!shouldBlock(hostId, hostDescription.locations())) {
+ log.debug("Host move is allowed for host with Id: {} ", hostId);
+ store.createOrUpdateHost(provider().id(), hostId,
+ hostDescription, replaceIps);
+ } else {
+ log.info("Host move is NOT allowed for host with Id: {} , removing from host store ", hostId);
+ }
if (monitorHosts) {
hostDescription.ipAddress().forEach(ip -> {
@@ -391,7 +513,7 @@
allHosts.forEach(eachHost -> {
if (!(eachHost.id().equals(hostId))) {
log.info("Duplicate ip {} found on host {} and {}", ip,
- hostId.toString(), eachHost.id().toString());
+ hostId.toString(), eachHost.id().toString());
store.removeIp(eachHost.id(), ip);
}
});
@@ -476,8 +598,83 @@
Host host = store.getHost(hostId);
return host == null || !host.configured() || host.providerId().equals(provider().id());
}
+
+
+ /**
+ * Deny host move if happening within the threshold time,
+ * track moved host to identify offending hosts.
+ *
+ * @param hostId host identifier
+ * @param locations host locations
+ */
+ private boolean shouldBlock(HostId hostId, Set<HostLocation> locations) {
+ Host host = store.getHost(hostId);
+ // If host is not present in host store means host added for hte first time.
+ if (host != null) {
+ if (host.suspended()) {
+ // Checks host is marked as offending in other onos cluster instance/local instance
+ log.debug("Host id {} is moving frequently hence host moving " +
+ "processing is ignored", hostId);
+ return true;
+ }
+ } else {
+ //host added for the first time.
+ return false;
+ }
+ HostMoveTracker hostMove = hostMoveTracker.computeIfAbsent(hostId, id -> new HostMoveTracker(locations));
+ if (Sets.difference(hostMove.getLocations(), locations).isEmpty() &&
+ Sets.difference(locations, hostMove.getLocations()).isEmpty()) {
+ log.debug("Not hostmove scenario: Host id: {}, Old Host Location: {}, New host Location: {}",
+ hostId, hostMove.getLocations(), locations);
+ return false; // It is not a host move scenario
+ } else if (hostMove.getCounter() >= hostMoveCounter && System.currentTimeMillis() - hostMove.getTimeStamp()
+ < hostMoveThresholdInMillis) {
+ //Check host move is crossed the threshold, then to mark as offending Host
+ log.debug("Host id {} is identified as offending host and entry is added in cache", hostId);
+ hostMove.resetHostMoveTracker(locations);
+ store.suspend(hostId);
+ //Set host suspended flag to false after given offendingHostExpiryInMins
+ offendingHostUnblockExecutor.schedule(new UnblockOffendingHost(hostId),
+ offendingHostExpiryInMins,
+ TimeUnit.MINUTES);
+ return true;
+ } else if (System.currentTimeMillis() - hostMove.getTimeStamp()
+ < hostMoveThresholdInMillis) {
+ //Increment the host move count as hostmove occured within the hostMoveThresholdInMillis time
+ hostMove.updateHostMoveTracker(locations);
+ log.debug("Updated the tracker with the host move registered for host: {}", hostId);
+ } else if (System.currentTimeMillis() - hostMove.getTimeStamp()
+ > hostMoveThresholdInMillis) {
+ //Hostmove is happened after hostMoveThresholdInMillis time so remove from host tracker.
+ hostMove.resetHostMoveTracker(locations);
+ store.unsuspend(hostId);
+ log.debug("Reset the tracker with the host move registered for host: {}", hostId);
+ }
+ return false;
+ }
+
+ // Set host suspended flag to false after given offendingHostExpiryInMins.
+ private final class UnblockOffendingHost implements Runnable {
+ private HostId hostId;
+
+ UnblockOffendingHost(HostId hostId) {
+ this.hostId = hostId;
+ }
+
+ @Override
+ public void run() {
+ // Set the host suspended flag to false
+ try {
+ store.unsuspend(hostId);
+ log.debug("Host {}: Marked host as unsuspended", hostId);
+ } catch (Exception ex) {
+ log.debug("Host {}: not present in host list", hostId);
+ }
+ }
+ }
}
+
// Store delegate to re-post events emitted from the store.
private class InternalStoreDelegate implements HostStoreDelegate {
@Override
diff --git a/core/net/src/main/java/org/onosproject/net/host/impl/HostMoveTracker.java b/core/net/src/main/java/org/onosproject/net/host/impl/HostMoveTracker.java
new file mode 100644
index 0000000..05a8f77
--- /dev/null
+++ b/core/net/src/main/java/org/onosproject/net/host/impl/HostMoveTracker.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.net.host.impl;
+
+import org.onosproject.net.HostLocation;
+
+import java.util.Objects;
+import java.util.Set;
+
+import static com.google.common.base.MoreObjects.toStringHelper;
+
+/**
+ * Used for tracking of the host move.
+ */
+public class HostMoveTracker {
+ private Integer counter;
+ private Long timeStamp;
+ private Set<HostLocation> locations;
+
+ /**
+ * Initialize the instance of HostMoveTracker.
+ *
+ * @param locations List of locations where host is present
+ */
+ public HostMoveTracker(Set<HostLocation> locations) {
+ counter = 1;
+ timeStamp = System.currentTimeMillis();
+ this.locations = locations;
+ }
+
+ /**
+ * Updates locations in HostMoveTracker.
+ *
+ * @param locations List of locations where host is present
+ */
+ public void updateHostMoveTracker(Set<HostLocation> locations) {
+ counter += 1;
+ timeStamp = System.currentTimeMillis();
+ this.locations = locations;
+ }
+
+ /**
+ * Reset hostmove count,timestamp and updated locations.
+ *
+ * @param locations List of locations where host is present
+ */
+ public void resetHostMoveTracker(Set<HostLocation> locations) {
+ counter = 0;
+ timeStamp = System.currentTimeMillis();
+ this.locations = locations;
+ }
+
+ /**
+ * Reset hostmove count and timestamp.
+ */
+ public void resetHostMoveTracker() {
+ counter = 0;
+ timeStamp = System.currentTimeMillis();
+ }
+
+ public Long getTimeStamp() {
+ return timeStamp;
+ }
+
+ public Integer getCounter() {
+ return counter;
+ }
+
+
+ public Set<HostLocation> getLocations() {
+ return locations;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ HostMoveTracker that = (HostMoveTracker) o;
+ return Objects.equals(locations, that.locations) &&
+ Objects.equals(counter, that.counter);
+ }
+
+ @Override
+ public int hashCode() {
+
+ return Objects.hash(locations, counter);
+ }
+
+ @Override
+ public String toString() {
+ return toStringHelper(this)
+ .add("counter", getCounter())
+ .add("timeStamp", getTimeStamp())
+ .add("locations", getLocations())
+ .toString();
+ }
+}