Introduced HostMoveTracker to suspend hosts that moves too frequently
Change-Id: I3037c626657790ce6817feddba7dbbfac203b188
diff --git a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java
index a88754f..6afb15c 100644
--- a/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java
+++ b/core/net/src/main/java/org/onosproject/net/OsgiPropertyConstants.java
@@ -62,6 +62,21 @@
public static final String HM_PROBE_RATE = "probeRate";
public static final long HM_PROBE_RATE_DEFAULT = 30000;
+ public static final String HM_HOST_MOVE_TRACKER_ENABLE = "hostMoveTrackerEnabled";
+ public static final boolean HM_HOST_MOVE_TRACKER_ENABLE_DEFAULT = false;
+
+ public static final String HM_HOST_MOVED_THRESHOLD_IN_MILLIS = "hostMoveThresholdInMillis";
+ public static final int HM_HOST_MOVED_THRESHOLD_IN_MILLIS_DEFAULT = 200000;
+
+ public static final String HM_HOST_MOVE_COUNTER = "hostMoveCounter";
+ public static final int HM_HOST_MOVE_COUNTER_DEFAULT = 3;
+
+ public static final String HM_OFFENDING_HOST_EXPIRY_IN_MINS = "offendingHostExpiryInMins";
+ public static final long HM_OFFENDING_HOST_EXPIRY_IN_MINS_DEFAULT = 1;
+
+ public static final String HM_OFFENDING_HOST_THREADS_POOL_SIZE = "offendingHostClearThreadPool";
+ public static final int HM_OFFENDING_HOST_THREADS_POOL_SIZE_DEFAULT = 10;
+
public static final String HM_GREEDY_LEARNING_IPV6 = "greedyLearningIpv6";
public static final boolean HM_GREEDY_LEARNING_IPV6_DEFAULT = false;
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 df25281..3bacd7f 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.onlab.packet.Ip6Address;
import org.onlab.packet.IpAddress;
import org.onlab.packet.MacAddress;
@@ -59,12 +60,19 @@
import org.slf4j.Logger;
import java.util.Dictionary;
+import java.util.Map;
import java.util.Optional;
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.net.OsgiPropertyConstants.HM_ALLOW_DUPLICATE_IPS;
import static org.onosproject.net.OsgiPropertyConstants.HM_ALLOW_DUPLICATE_IPS_DEFAULT;
import static org.onosproject.net.OsgiPropertyConstants.HM_GREEDY_LEARNING_IPV6;
@@ -73,6 +81,17 @@
import static org.onosproject.net.OsgiPropertyConstants.HM_MONITOR_HOSTS_DEFAULT;
import static org.onosproject.net.OsgiPropertyConstants.HM_PROBE_RATE;
import static org.onosproject.net.OsgiPropertyConstants.HM_PROBE_RATE_DEFAULT;
+import static org.onosproject.net.OsgiPropertyConstants.HM_HOST_MOVED_THRESHOLD_IN_MILLIS;
+import static org.onosproject.net.OsgiPropertyConstants.HM_HOST_MOVED_THRESHOLD_IN_MILLIS_DEFAULT;
+import static org.onosproject.net.OsgiPropertyConstants.HM_HOST_MOVE_COUNTER;
+import static org.onosproject.net.OsgiPropertyConstants.HM_HOST_MOVE_COUNTER_DEFAULT;
+import static org.onosproject.net.OsgiPropertyConstants.HM_HOST_MOVE_TRACKER_ENABLE;
+import static org.onosproject.net.OsgiPropertyConstants.HM_HOST_MOVE_TRACKER_ENABLE_DEFAULT;
+import static org.onosproject.net.OsgiPropertyConstants.HM_OFFENDING_HOST_EXPIRY_IN_MINS;
+import static org.onosproject.net.OsgiPropertyConstants.HM_OFFENDING_HOST_EXPIRY_IN_MINS_DEFAULT;
+import static org.onosproject.net.OsgiPropertyConstants.HM_OFFENDING_HOST_THREADS_POOL_SIZE;
+import static org.onosproject.net.OsgiPropertyConstants.HM_OFFENDING_HOST_THREADS_POOL_SIZE_DEFAULT;
+
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;
@@ -89,10 +108,17 @@
HostProviderRegistry.class
},
property = {
- HM_ALLOW_DUPLICATE_IPS + ":Boolean=" + HM_ALLOW_DUPLICATE_IPS_DEFAULT,
- HM_MONITOR_HOSTS + ":Boolean=" + HM_MONITOR_HOSTS_DEFAULT,
- HM_PROBE_RATE + ":Integer=" + HM_PROBE_RATE_DEFAULT,
- HM_GREEDY_LEARNING_IPV6 + ":Boolean=" + HM_GREEDY_LEARNING_IPV6_DEFAULT
+ HM_ALLOW_DUPLICATE_IPS + ":Boolean=" + HM_ALLOW_DUPLICATE_IPS_DEFAULT,
+ HM_MONITOR_HOSTS + ":Boolean=" + HM_MONITOR_HOSTS_DEFAULT,
+ HM_PROBE_RATE + ":Integer=" + HM_PROBE_RATE_DEFAULT,
+ HM_GREEDY_LEARNING_IPV6 + ":Boolean=" + HM_GREEDY_LEARNING_IPV6_DEFAULT,
+ HM_HOST_MOVE_TRACKER_ENABLE + ":Boolean=" + HM_HOST_MOVE_TRACKER_ENABLE_DEFAULT,
+ HM_HOST_MOVED_THRESHOLD_IN_MILLIS + ":Integer=" + HM_HOST_MOVED_THRESHOLD_IN_MILLIS_DEFAULT,
+ HM_HOST_MOVE_COUNTER + ":Integer=" + HM_HOST_MOVE_COUNTER_DEFAULT,
+ HM_OFFENDING_HOST_EXPIRY_IN_MINS + ":Long=" + HM_OFFENDING_HOST_EXPIRY_IN_MINS_DEFAULT,
+ HM_OFFENDING_HOST_THREADS_POOL_SIZE + ":Integer=" + HM_OFFENDING_HOST_THREADS_POOL_SIZE_DEFAULT
+
+
}
)
public class HostManager
@@ -140,8 +166,25 @@
/** Enable/Disable greedy learning of IPv6 link local address. */
private boolean greedyLearningIpv6 = HM_GREEDY_LEARNING_IPV6_DEFAULT;
+ /** Enable/Disable tracking of rogue host moves. */
+ private boolean hostMoveTrackerEnabled = HM_HOST_MOVE_TRACKER_ENABLE_DEFAULT;
+
+ /** Host move threshold in milli seconds. */
+ private int hostMoveThresholdInMillis = HM_HOST_MOVED_THRESHOLD_IN_MILLIS_DEFAULT;
+
+ /** If the host move happening within given threshold then increment the host move counter. */
+ private int hostMoveCounter = HM_HOST_MOVE_COUNTER_DEFAULT;
+
+ /** Max value of the counter after which the host will not be considered as offending host. */
+ private long offendingHostExpiryInMins = HM_OFFENDING_HOST_EXPIRY_IN_MINS_DEFAULT;
+
+ /** Default pool size of offending host clear executor thread. */
+ private int offendingHostClearThreadPool = HM_OFFENDING_HOST_THREADS_POOL_SIZE_DEFAULT;
+
private HostMonitor monitor;
private HostAnnotationOperator hostAnnotationOperator;
+ private ScheduledExecutorService offendingHostUnblockExecutor = null;
+ private Map<HostId, HostMoveTracker> hostMoveTracker = new ConcurrentHashMap<>();
@Activate
@@ -154,8 +197,8 @@
monitor = new HostMonitor(packetService, this, interfaceService, edgePortService);
monitor.setProbeRate(probeRate);
monitor.start();
- modified(context);
cfgService.registerProperties(getClass());
+ modified(context);
log.info("Started");
}
@@ -166,6 +209,9 @@
networkConfigService.removeListener(networkConfigListener);
cfgService.unregisterProperties(getClass(), false);
monitor.shutdown();
+ if (offendingHostUnblockExecutor != null) {
+ offendingHostUnblockExecutor.shutdown();
+ }
log.info("Stopped");
}
@@ -196,6 +242,10 @@
private void readComponentConfiguration(ComponentContext context) {
Dictionary<?, ?> properties = context.getProperties();
Boolean flag;
+ int newHostMoveThresholdInMillis;
+ int newHostMoveCounter;
+ int newOffendinghostPoolSize;
+ long newOffendingHostExpiryInMins;
flag = Tools.isPropertyEnabled(properties, HM_MONITOR_HOSTS);
if (flag == null) {
@@ -233,7 +283,71 @@
log.info("Configured. greedyLearningIpv6 {}",
greedyLearningIpv6 ? "enabled" : "disabled");
}
+ flag = Tools.isPropertyEnabled(properties, HM_HOST_MOVE_TRACKER_ENABLE);
+ 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, HM_HOST_MOVED_THRESHOLD_IN_MILLIS);
+ newHostMoveThresholdInMillis = isNullOrEmpty(s) ?
+ hostMoveThresholdInMillis : Integer.parseInt(s.trim());
+
+ s = get(properties, HM_HOST_MOVE_COUNTER);
+ newHostMoveCounter = isNullOrEmpty(s) ? hostMoveCounter : Integer.parseInt(s.trim());
+
+ s = get(properties, HM_OFFENDING_HOST_EXPIRY_IN_MINS);
+ newOffendingHostExpiryInMins = isNullOrEmpty(s) ?
+ offendingHostExpiryInMins : Integer.parseInt(s.trim());
+
+ s = get(properties, HM_OFFENDING_HOST_THREADS_POOL_SIZE);
+ newOffendinghostPoolSize = isNullOrEmpty(s) ?
+ offendingHostClearThreadPool : Integer.parseInt(s.trim());
+ } catch (NumberFormatException | ClassCastException e) {
+ newHostMoveThresholdInMillis = HM_HOST_MOVED_THRESHOLD_IN_MILLIS_DEFAULT;
+ newHostMoveCounter = HM_HOST_MOVE_COUNTER_DEFAULT;
+ newOffendingHostExpiryInMins = HM_OFFENDING_HOST_EXPIRY_IN_MINS_DEFAULT;
+ newOffendinghostPoolSize = HM_OFFENDING_HOST_THREADS_POOL_SIZE_DEFAULT;
+ }
+ 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);
}
/**
@@ -371,8 +485,16 @@
hostDescription = hostAnnotationOperator.combine(hostId, hostDescription, Optional.of(annoConfig));
}
- 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 -> {
@@ -424,7 +546,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);
}
});
@@ -509,8 +631,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();
+ }
+}