apermon
is still WIP. Todos:
- Foolproof configuration validation.
- More filter terms?
- L3: TTL, Frag bit, DSCP, ECN, etc?
- L4: TCP flags?
apermon
is a sflow monitoring program. It can be used for various purposes - but its main goal is to allow setting some rules to filter traffics and triggers some external scripts when the set thresholds are reached.
First, clone the source and compile it. You will need some build tools, flex
, and bison
to compile this project. On a debian-based system:
# apt install build-essential flex bison
Then, clone the project and build it:
$ git clone https://github.com/apernet/apermon
$ cd apermon
$ make
And now you can run it:
$ ./apermon
usage: ./apermon <config-file-path>
Configuration for a minimal DDoS protection setup may look like this:
options {
listen 0.0.0.0 6343 sflow v5;
min-ban-time 300;
}
agents {
my-switch {
addresses [ 10.0.0.254 ];
}
}
prefixes {
my-network {
192.0.2.0/24;
198.18.0.0/15;
}
whitelist {
192.0.2.0/28;
}
}
actions {
blackhole {
script "/opt/apermon/add-and-withdraw-blackhole.sh" {
events [ ban unban ];
}
}
notify {
script "/opt/apermon/scripts/mailgun.sh" {
events [ ban unban ];
env {
API_KEY = "api:key-...";
DOMAIN = "noreply.example.com";
FROM = "AperMon <apermon@noreply.example.com>";
TO = "noc@example.com";
SUBJECT = "[apermon] $TRIGGER: $TYPE $TARGET";
}
}
script "/opt/apermon/scripts/telegram.sh" {
events [ ban unban ];
env {
BOT_TOKEN = "...";
CHAT_ID = "...";
}
}
}
}
triggers {
protect-my-network {
networks [ my-network ];
directions [ ingress egress ];
aggregate-type host;
thresholds {
bps 2.5g;
pps 1m;
}
filter {
not {
source whitelist;
destination whitelist;
}
}
actions [ blackhole notify ];
}
}
Let's break it down:
options
- global options. Syntax:
options {
listen <host> <port> <protocol> <protocol-args>;
min-ban-time <time-in-second>;
burst-period <time-in-second>;
status-file "<file-path>" dump-interval <time-in-second>;
}
Notes:
- You may have more than one
listen
s. - Currently, the only supported protocol is
sflow
, and the only sflow arg isv5
, which specifies sFlow version 5. min-ban-time
sets how long a host or network should be kept "banned" after it stops triggering thresholds.0
to disable banning.burst-period
sets how long hosts and networks are allowed to burst beyond set thresholds without getting banned.0
to disable bursting.status-file
dumps bps/pps of each host/net of each trigger to given file every<time-in-second>
seconds. Status file are just CSV, but can be viewed in a human-friendly way using theutils/status-viewer
script.
agents
- defines agent(s) to listen samples from. Syntax:
agents {
agent-name-1 {
addresses [ address1 address2 ... ];
sample-rate-cap <number>;
}
agent-name-2 {
addresses [ address1 address2 ... ];
}
...
}
Notes:
- For sFlow,
addresses
specify the agent address field in the sFlow header. sample-rate-cap
can be optionally configured for agents to cap sample rate at a max value to smooth out agents with erroneous high sample rate spikes that create abnormal traffic spikes.
prefixes
- defines prefix list(s). Used in various places. Syntax:
prefixes {
prefix-list-1 {
192.0.2.0/24;
198.18.0.0/15;
2001:db8::/32;
...
}
prefix-list-2 {
...
}
...
}
actions
- define actions. Later, we will define triggers
, which, when triggered, will run an action. actions
section defines what action(s) can be performed.
actions {
action-1 {
script "/path/to/script/1.sh" {
events [ ban unban ];
env {
ENV_1 = value_1;
ENV_2 = "value 2";
}
}
script "/path/to/script/2.sh" {
events [ ban ];
}
...
}
action-2 {
...
}
...
}
Notes:
- Possible event types are:
ban
: run the script when an IP address or network should be "banned." (i.e., trigger fired)unban
: run the script when an IP address or network should be "unbanned."
- For
ban
events, the following environment variables are passed to the script (values are just example):TRIGGER=protect-my-network
: name of the trigger.TYPE=ban
: type of event. Alwaysban
forban
event.AF=1
: address family.1
- IPv4,2
- IPv6.AGGREGATOR=host
: aggergator type. See below.TARGET=192.0.2.1
: host/prefix/network to be banned.PREFIX=192.0.2.0/24
: prefix containing the address. if net aggregator, this will be a list of prefixes.NET=my-network
: name of the network.IN_PPS=114514
: inbound pps to the host/network.OUT_PPS=1919810
: outbound pps from the host/network.IN_BPS=171771000
: inbound bps to the host/network.OUT_BPS=2879715000
: outbound bps from the host/network.FLOWS=...
: csv of recent (upto 100) flow records from/to this host/network. Columns:af
: address family.1
- IPv4,2
- IPv6.in_ifindex
: input interface index.out_ifindex
: out interface index.src
: source IP / IPv6 address.dst
: dst IP / IPv6 address.proto
: IP protocol / IPv6 next header.sport
: layer 4 src port. (not valid if frag = 2)dport
: layer 4 dst port. (not valid if frag = 2)bytes
: number of bytes.packets
: number of packets.frag
:0
- not fragmented,1
- first fragment,2
- non-first fragment
- For
unban
events, the following environment variables are passed to the script (values are just example):TRIGGER=protect-my-network
: name of the trigger.TYPE=unban
: type of event. Alwaysunban
forunban
event.AF=1
: address family.1
- IPv4,2
- IPv6.AGGREGATOR=host
: aggergator type. See below.FIRST_TRIGGERED=1660166143
: timestamp of initial trigger.LAST_TRIGGERED=1660169743
: timestamp of last trigger.TARGET=192.0.2.1
: host/prefix/network to be unbanned.PREFIX=192.0.2.0/24
: prefix containing the address. if net aggregator, this will be a list of prefixes.NET=my-network
: name of the network.PEAK_IN_PPS=114514
: peak inbound pps to the host/network.PEAK_OUT_PPS=1919810
: peak outbound pps from the host/network.PEAK_IN_BPS=171771000
: peak inbound bps to the host/network.PEAK_OUT_BPS=2879715000
: peak outbound bps from the host/network.
- To pass custom environment variables, use
env
.
triggers
- defines triggers. i.e., when to do what. Syntax:
triggers {
trigger-name-1 {
min-ban-time <time-in-second>;
burst-period <time-in-second>;
networks [ <prefix-name-1> <prefix-name-2> ... ];
directions [ ingress egress ];
aggregate-type <host|prefix|net>;
thresholds {
bps <number>[k|m|g];
pps <number>[k|m|g];
}
filter {
...
}
actions [ <action-name-1> <action-name-2> ... ];
}
}
Notes:
min-ban-time
andburst-period
override the global value if set.networks
should be the name of a prefix list defined inprefixes
.- Possible values of
directions
are:ingress
: to the network(s) and/or host(s) defined innetworks
.egress
: from the network(s) and/or host(s) defined innetworks
.
- Possible values of
aggregate-type
are:host
: aggregate traffic by hosts (i.e.,/32
or/128
forinet
andinet6
)prefix
: aggregate traffic by prefixes. (prefixes of networks defined above innetworks
)net
: aggregate traffic by nets (networks defined above innetworks
)
- Possible values of
thresholds
are:bps <number>[k|m|g];
: trigger if aggregated traffic greater than given bps.pps <number>[k|m|g];
: trigger if aggregated traffic greater than given pps.
- If both
pps
andbps
are set forthresholds
, the trigger will fire if either threshold is reached. - Possible
filter
terms:and { ... }
: logical and. All sub-term(s) must be true for it to be true.or { ... }
: logical or. Ture if any sub-term is true.not { ... }
: logical not. False if any sub-term is true.source <prefix-list-name>;
: source inet/inet6 address.destination <prefix-list-name>;
: destination inet/inet6 address.in-interface <interface-name>;
: input interface.out-interface <interface-name>;
: output interface.protocol <udp|tcp|number>;
: inet protocol number / inet6 next-header number.source-port <number>;
: layer 4 source port.destination-port <number>;
: layer 4 destination port.is-fragment;
: IP fragments (i.e.,frag-off
!= 0 or mf bit set)
- The three logical operators (
and
,or
, andnot
) may be nested to build a more complex filter. If the root term underfilter {}
is not one of the logical operators,and
is assumed. actions
should be a list of names of actions defined inactions
.
interfaces
- while not used above, you may also define named interfaces:
interfaces {
# interface name
ix-lacp {
# interface ifindexes - format "<agent_name>.<ifindex>"
ifindexes [ my-switch.100 my-switch.101 ];
}
cheap-transit {
ifindexes [ my-switch.102 ];
}
costly-95th-transit {
ifindexes [ my-switch.103 ];
}
}
This will allow you to use them in a filter:
triggers {
save-money {
min-ban-time 3600;
networks [ customer-a-that-pays-way-too-less customer-b-that-pays-way-too-less ];
# meaning all hosts on the two networks above are added together and becomes two "hosts," one for each net.
aggregate-type net;
directions [ ingress egress ];
filter {
or {
in-interface costly-95th-transit;
out-interface costly-95th-transit;
}
}
thresholds {
bps 2.5g;
}
actions [ witdraw-prefix-from-costly-transit ];
}
}
See apermon.conf.example
for a more complete example.