diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java index 6bc92c98fae1e9866c99606d352f6115d1b0c618..55195c66ede181e378eed869bc1dcb37a92dff96 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java @@ -106,6 +106,14 @@ public interface IOFSwitch { */ public void setChannel(Channel channel); + /** + * Called when OFMessage enters pipeline. Returning true cause the message + * to be dropped. + * @param ofm + * @return + */ + public boolean inputThrottled(OFMessage ofm); + /** * Write OFMessage to the output stream, subject to switch rate limiting. * The message will be handed to the floodlightProvider for possible filtering diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java index 465df255d6014fa12eebc41cd325e8c530e93865..8b201b325c55fce36f800697a2739a324b28e9b7 100644 --- a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java +++ b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java @@ -20,6 +20,7 @@ package net.floodlightcontroller.core; import java.io.IOException; import java.net.SocketAddress; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -41,7 +42,11 @@ import net.floodlightcontroller.core.internal.Controller; import net.floodlightcontroller.core.internal.OFFeaturesReplyFuture; import net.floodlightcontroller.core.internal.OFStatisticsFuture; import net.floodlightcontroller.core.web.serializers.DPIDSerializer; +import net.floodlightcontroller.devicemanager.SwitchPort; +import net.floodlightcontroller.packet.Ethernet; +import net.floodlightcontroller.routing.ForwardingBase; import net.floodlightcontroller.threadpool.IThreadPoolService; +import net.floodlightcontroller.util.MACAddress; import net.floodlightcontroller.util.TimedCache; import org.codehaus.jackson.annotate.JsonIgnore; @@ -54,6 +59,7 @@ import org.openflow.protocol.OFFeaturesReply; import org.openflow.protocol.OFFlowMod; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFPhysicalPort.OFPortConfig; import org.openflow.protocol.OFPhysicalPort.OFPortState; @@ -113,6 +119,20 @@ public abstract class OFSwitchBase implements IOFSwitch { // Private members for throttling private boolean writeThrottleEnabled = false; + protected boolean packetInThrottleEnabled = false; // used by test + private int packetInRateThresholdHigh = Integer.MAX_VALUE; + private int packetInRateThresholdLow = 1; + private int packetInRatePerMacThreshold = Integer.MAX_VALUE; + private int packetInRatePerPortThreshold = Integer.MAX_VALUE; + private long messageCount = 0; + private long messageCountUniqueOFMatch = 0; + private long lastMessageTime; + private int currentRate = 0; + private TimedCache<OFMatch> ofMatchCache; + private TimedCache<Long> macCache; + private TimedCache<Long> macBlockedCache; + private TimedCache<Short> portCache; + private TimedCache<Short> portBlockedCache; protected final static ThreadLocal<Map<IOFSwitch,List<OFMessage>>> local_msg_buffer = new ThreadLocal<Map<IOFSwitch,List<OFMessage>>>() { @@ -141,6 +161,7 @@ public abstract class OFSwitchBase implements IOFSwitch { this.timedCache = new TimedCache<Long>(100, 5*1000 ); // 5 seconds interval this.listenerLock = new ReentrantReadWriteLock(); this.portBroadcastCacheHitMap = new ConcurrentHashMap<Short, AtomicLong>(); + this.lastMessageTime = System.currentTimeMillis(); // Defaults properties for an ideal switch this.setAttribute(PROP_FASTWILDCARDS, OFMatch.OFPFW_ALL); @@ -748,4 +769,177 @@ public abstract class OFSwitchBase implements IOFSwitch { public void setFloodlightProvider(Controller controller) { floodlightProvider = controller; } + + + /** + * For switch drivers to set thresholds, all rates in per second + * @param pktInHigh - above this start throttling + * @param pktInLow - below this stop throttling + * @param pktInPerMac - block host if unique pktIn rate reaches this + * @param pktInPerPort - block port if unique pktIn rate reaches this + */ + @JsonIgnore + protected void setInputThrottleThresholds(int pktInHigh, int pktInLow, + int pktInPerMac, int pktInPerPort) { + packetInRateThresholdHigh = pktInHigh; + packetInRateThresholdLow = pktInLow; + packetInRatePerMacThreshold = pktInPerMac; + packetInRatePerPortThreshold = pktInPerPort; + } + + /** + * Determine if this message should be dropped. + * + * We compute the current rate by taking a timestamp every 100 messages. + * Could change to a more complex scheme if more accuracy is needed. + * + * Enable throttling if the rate goes above packetInRateThresholdHigh + * Disable throttling when the rate drops below packetInRateThresholdLow + * + * While throttling is enabled, we do the following: + * - Remove duplicate packetIn's mapped to the same OFMatch + * - After filtering, if packetIn rate per host (mac) is above + * packetInRatePerMacThreshold, push a flow mod to block mac on port + * - After filtering, if packetIn rate per port is above + * packetInRatePerPortThreshold, push a flow mod to block port + * - Allow blocking flow mods have a hard timeout and expires automatically + * + * TODO: keep a history of all events related in input throttling + * + * @param ofm + * @return + */ + @Override + public boolean inputThrottled(OFMessage ofm) { + if (ofm.getType() != OFType.PACKET_IN) { + return false; + } + // Compute current packet in rate + messageCount++; + if (messageCount % 100 == 0) { + long now = System.currentTimeMillis(); + if (now != lastMessageTime) { + currentRate = (int) (100000 / (now - lastMessageTime)); + lastMessageTime = now; + } else { + currentRate = Integer.MAX_VALUE; + } + } + if (!packetInThrottleEnabled) { + if (currentRate <= packetInRateThresholdHigh) { + return false; // most common case + } + enablePacketInThrottle(); + } else if (currentRate < packetInRateThresholdLow) { + disablePacketInThrottle(); + return false; + } + + // Now we are in the slow path where we need to do filtering + // First filter based on OFMatch + OFPacketIn pin = (OFPacketIn)ofm; + OFMatch match = new OFMatch(); + match.loadFromPacket(pin.getPacketData(), pin.getInPort()); + if (ofMatchCache.update(match)) { + // TODO keep stats for dropped packets + return true; + } + + // We have packet in with a distinct flow, check per mac rate + messageCountUniqueOFMatch++; + if ((messageCountUniqueOFMatch % packetInRatePerMacThreshold) == 1) { + checkPerSourceMacRate(pin); + } + + // Check per port rate + if ((messageCountUniqueOFMatch % packetInRatePerPortThreshold) == 1) { + checkPerPortRate(pin); + } + return false; + } + + /** + * We rely on the fact that packet in processing is single threaded + * per switch, so no locking is necessary. + */ + private void disablePacketInThrottle() { + ofMatchCache = null; + macCache = null; + macBlockedCache = null; + portCache = null; + portBlockedCache = null; + packetInThrottleEnabled = false; + log.info("Packet in rate is {}, disable throttling on {}", + currentRate, this); + } + + private void enablePacketInThrottle() { + ofMatchCache = new TimedCache<OFMatch>(2048, 5000); // 5 second interval + macCache = new TimedCache<Long>(64, 1000 ); // remember last second + macBlockedCache = new TimedCache<Long>(256, 5000 ); // 5 second interval + portCache = new TimedCache<Short>(16, 1000 ); // rememeber last second + portBlockedCache = new TimedCache<Short>(64, 5000 ); // 5 second interval + packetInThrottleEnabled = true; + messageCountUniqueOFMatch = 0; + log.info("Packet in rate is {}, enable throttling on {}", + currentRate, this); + } + + /** + * Check if we have sampled this mac in the last second. + * Since we check every packetInRatePerMacThreshold packets, + * the presence of the mac in the macCache means the rate is + * above the threshold in a statistical sense. + * + * Take care not to block topology probing packets. Also don't + * push blocking flow mod if we have already done so within the + * last 5 seconds. + * + * @param pin + * @return + */ + private void checkPerSourceMacRate(OFPacketIn pin) { + byte[] data = pin.getPacketData(); + byte[] mac = Arrays.copyOfRange(data, 6, 12); + MACAddress srcMac = MACAddress.valueOf(mac); + short ethType = (short) (((data[12] & 0xff) << 8) + (data[13] & 0xff)); + if (ethType != Ethernet.TYPE_LLDP && ethType != Ethernet.TYPE_BSN && + macCache.update(srcMac.toLong())) { + // Check if we already pushed a flow in the last 5 seconds + if (macBlockedCache.update(srcMac.toLong())) { + return; + } + // write out drop flow per srcMac + int port = pin.getInPort(); + SwitchPort swPort = new SwitchPort(getId(), port); + ForwardingBase.blockHost(floodlightProvider, + swPort, srcMac.toLong(), (short) 5, 0); + log.info("Excessive packet in from {} on {}, block host for 5 sec", + srcMac.toString(), swPort); + } + } + + /** + * Works in a similar way as checkPerSourceMacRate(). + * + * TODO Don't block ports with links? + * + * @param pin + * @return + */ + private void checkPerPortRate(OFPacketIn pin) { + Short port = pin.getInPort(); + if (portCache.update(port)) { + // Check if we already pushed a flow in the last 5 seconds + if (portBlockedCache.update(port)) { + return; + } + // write out drop flow per port + SwitchPort swPort = new SwitchPort(getId(), port); + ForwardingBase.blockHost(floodlightProvider, + swPort, -1L, (short) 5, 0); + log.info("Excessive packet in from {}, block port for 5 sec", + swPort); + } + } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index 3c3892b506ea107e8c10e8d460d4671397afe43a..3b90773ae7b261a1de21d04a8da2f3252fb2a4d2 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -613,6 +613,10 @@ public class Controller implements IFloodlightProviderService, } for (OFMessage ofm : msglist) { + // Per-switch input throttling + if (sw != null && sw.inputThrottled(ofm)) { + continue; + } try { if (overload_drop && !loadlevel.equals(LoadMonitor.LoadLevel.OK)) { diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java index 9be2079cf1aa2cf268cc14c6b5fa790439a7b3d8..11478f46bbdce6d2709d252a3a2c9cdef613cb84 100644 --- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java +++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java @@ -554,7 +554,7 @@ public abstract class ForwardingBase if (sw == null) return false; int inputPort = sw_tup.getPort(); log.debug("blockHost sw={} port={} mac={}", - new Object[] { sw, sw_tup.getPort(), new Long(host_mac) }); + new Object[] { sw, sw_tup.getPort(), Long.valueOf(host_mac) }); // Create flow-mod based on packet-in and src-switch OFFlowMod fm = @@ -563,10 +563,14 @@ public abstract class ForwardingBase OFMatch match = new OFMatch(); List<OFAction> actions = new ArrayList<OFAction>(); // Set no action to // drop - match.setDataLayerSource(Ethernet.toByteArray(host_mac)) - .setInputPort((short)inputPort) - .setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_DL_SRC - & ~OFMatch.OFPFW_IN_PORT); + match.setInputPort((short)inputPort); + if (host_mac != -1L) { + match.setDataLayerSource(Ethernet.toByteArray(host_mac)) + .setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_DL_SRC + & ~OFMatch.OFPFW_IN_PORT); + } else { + match.setWildcards(OFMatch.OFPFW_ALL & ~OFMatch.OFPFW_IN_PORT); + } fm.setCookie(cookie) .setHardTimeout(hardTimeout) .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) diff --git a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java index 29191365cddad3344d7390f4d363cfcd0eaf8820..650ead70dd09fd875317f073139849c11762b039 100644 --- a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java +++ b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java @@ -433,4 +433,10 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { return false; } + @Override + public boolean inputThrottled(OFMessage ofm) { + // TODO Auto-generated method stub + return false; + } + }