blob: d628f8027d0e160888752f47437c584644236986 [file] [log] [blame]
/*
* Copyright 2019-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.packet.packetfilter;
import org.onosproject.net.packet.PacketContext;
import org.onosproject.net.packet.PacketInClassifier;
import org.onosproject.net.packet.PacketInFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import static org.onlab.util.Tools.groupedThreads;
/**
* Default implementation of a packet-in filter.
*/
public class DefaultPacketInFilter implements PacketInFilter {
/**
* Tracks the count of specific packet types (eg ARP, ND, DHCP etc)
* to be limited in the packet queue. This count always reflects the
* number of packets in the queue at any point in time
*/
private AtomicInteger currentCounter = new AtomicInteger(0);
/**
* Tracks the number of continuous windows where the drop packet happens.
*/
private AtomicInteger windowBlockCounter = new AtomicInteger(0);
/**
* Tracks the counter of the packet which are dropped.
*/
private AtomicInteger overFlowCounter = new AtomicInteger(0);
private final Logger log = LoggerFactory.getLogger(getClass());
/**
* Max Allowed packet rate beyond which the packet will be dropped
* within given window size.
*/
private int pps = 100;
/**
* Window size which will be used for number of packets acceptable
* based on the accepted pps.
*/
private int winSize = 500;
/**
* Guard time in seconds which will be enabled if there are continuous
* windows crossing winThres where the packet rate crosses the acceptable
* packet count calculated based on accepted pps.
* Guard time should be always greater then the the window size.
*/
private int guardTime = 10;
/**
* Threshold of continuous windows where the packet drop happens post which
* the guardTime will be triggered and no future packet processing happens
* till the expiry of this guard time.
*/
private int winThres = 10;
private int maxCounter;
private ScheduledExecutorService timerExecutor;
private ScheduledExecutorService windowUnblockExecutor;
private boolean windowBlocked;
private boolean packetProcessingBlocked;
/**
* Name of the counter.
*/
private String counterName;
/**
* PacketInclassifier associated with this filter object.
*/
private final PacketInClassifier classifier;
/**
* Only one filter object per packet type to be associated.
* Multiple filter types will result in undefined behavior.
* @param pps Rate at which the packet is accepted in packets per second
* @param winSize Size of window in milli seconds within which
* the packet rate will be analyzed
* @param guardTime Time duration in seconds for which the packet processing
* will be on hold if there is a continuous window where
* cross of the rate happens and that window count crosses
* winThres
* @param winThres Continuous window threshold after which gaurdTime will be
* activated
* @param counterName Name of the counter
* @param classifier Packet classification
*/
public DefaultPacketInFilter(int pps, int winSize, int guardTime, int winThres,
String counterName, PacketInClassifier classifier) {
this.pps = pps;
this.winSize = winSize;
this.guardTime = guardTime;
this.winThres = winThres;
this.counterName = counterName;
this.classifier = classifier;
this.maxCounter = (pps * winSize) / 1000;
timerExecutor = Executors.newScheduledThreadPool(1,
groupedThreads("packet/packetfilter",
"packet-filter-timer-%d", log));
windowUnblockExecutor = Executors.newScheduledThreadPool(1,
groupedThreads("packet/packetfilter",
"packet-filter-unblocker-%d", log));
timerExecutor.scheduleAtFixedRate(new ClearWindowBlock(),
0,
winSize,
TimeUnit.MILLISECONDS);
windowBlocked = false;
packetProcessingBlocked = false;
}
@Override
public FilterAction preProcess(PacketContext packet) {
maxCounter = (pps * winSize) / 1000;
// If pps is set then min value for maxCounter is 1
if (maxCounter == 0 && pps != 0) {
log.trace("{}: maxCounter set to 1 as was coming as 0", counterName);
maxCounter = 1;
}
if (!classifier.match(packet)) {
return FilterAction.FILTER_INVALID;
}
if (pps == 0 && maxCounter == 0) {
log.trace("{}: Filter is disabled", counterName);
return FilterAction.FILTER_DISABLED;
}
log.trace("{}: Preprocess called", counterName);
// Packet block checking should be done before windowBlocked checking
// otherwise there will be windows with packets while packet processing is suspended
// and that may break the existing check logic
if (packetProcessingBlocked) {
log.trace("{}: Packet processing is blocked for sometime", counterName);
return FilterAction.PACKET_BLOCKED;
}
if (windowBlocked) {
log.trace("{}: Packet processing is blocked for the window number: {}",
counterName, windowBlockCounter.get());
return FilterAction.WINDOW_BLOCKED;
}
if (currentCounter.getAndIncrement() < maxCounter) {
log.trace("{}: Packet is picked for processing with currentCounter: {} and maxCounter: {}",
counterName, currentCounter.get(), maxCounter);
return FilterAction.PACKET_ALLOW;
}
//Need to decrement the currentCounter and increment overFlowCounter
//Need to block the window and increment the window block counter
windowBlocked = true;
//TODO: Review this and the complete state machine
// If windowBlock crosses threshold then block packet processing for guard time
if (windowBlockCounter.incrementAndGet() > winThres) {
log.trace("{}: Packet processing blocked as current window crossed threshold " +
"currentWindowNumber: {} maxWindowNumber: {}",
counterName, windowBlockCounter.get(), winThres);
packetProcessingBlocked = true;
windowUnblockExecutor.schedule(new ClearPacketProcessingBlock(),
guardTime,
TimeUnit.SECONDS);
} else {
log.trace("{}: WindowBlockCounter: {} winThres: {}", counterName, windowBlockCounter.get(),
winThres);
}
//MT: Temp change in logic to branch the code - Rolled back
currentCounter.decrementAndGet();
if (overFlowCounter.incrementAndGet() < 0) {
overFlowCounter.set(0);
}
log.trace("{}: Overflow counter is: {}", counterName, overFlowCounter.get());
return FilterAction.PACKET_DENY;
}
@Override
public String name() {
return counterName;
}
@Override
public int pendingPackets() {
return currentCounter.get();
}
@Override
public int droppedPackets() {
return overFlowCounter.get();
}
@Override
public void setPps(int pps) {
this.pps = pps;
}
@Override
public void setWinSize(int winSize) {
this.winSize = winSize;
}
@Override
public void setGuardTime(int guardTime) {
this.guardTime = guardTime;
}
@Override
public void setWinThres(int winThres) {
this.winThres = winThres;
}
@Override
public void stop() {
timerExecutor.shutdown();
windowUnblockExecutor.shutdown();
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
DefaultPacketInFilter that = (DefaultPacketInFilter) o;
return pps == that.pps &&
winSize == that.winSize &&
guardTime == that.guardTime &&
winThres == that.winThres &&
counterName.equals(that.counterName) &&
classifier.equals(that.classifier);
}
@Override
public int hashCode() {
return Objects.hash(pps, winSize, guardTime, winThres, counterName, classifier);
}
private final class ClearWindowBlock implements Runnable {
@Override
public void run() {
// If window is not already blocked and there is at least one packet processed
// in that window then reset the window block counter:
if (!windowBlocked) {
log.trace("{}: WindowBlockCounter is reset as there was no blocking in current " +
"window with current windowBlockCounter: {}", counterName, windowBlockCounter.get());
windowBlockCounter.set(0);
}
if (currentCounter.get() == 0) {
//No packet processed in current window so do not change anything in the current state
log.trace("{}: No packets in the current window so not doing anything in ClearWindowBlock",
counterName);
return;
}
//Reset the counter and unblock the window
log.trace("{}: Current counter and windowBlocked is reset in ClearWindowBlock", counterName);
currentCounter.set(0);
windowBlocked = false;
}
}
private final class ClearPacketProcessingBlock implements Runnable {
@Override
public void run() {
//Reset the counter and unblock the window and packet processing
//CurrentCounter and windowBlocked counter setting is not required here
//Still setting to be on safer side
log.trace("{}: All blocks cleared in ClearPacketProcessingBlock", counterName);
currentCounter.set(0);
windowBlocked = false;
packetProcessingBlocked = false;
windowBlockCounter.set(0);
}
}
}