mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 06:38:20 +00:00
340 lines
8.8 KiB
Text
340 lines
8.8 KiB
Text
# $Id:$
|
|
#
|
|
# drop.bro implements a drop/restore policy termed "catch-and-release"
|
|
# whereby the first time an address is dropped, it is restored a while after
|
|
# the last connection attempt seen. If a connection attempt is subsequently
|
|
# seen, however, then the system is blocked again, and for a longer time.
|
|
#
|
|
# This policy has significant benefits when using Bro to update router
|
|
# ACLs for which:
|
|
# - The router has a limited number of ACLs slots.
|
|
# - You care about possible reuse of IP addresses by now-benign hosts,
|
|
# so don't want blocks to last forever.
|
|
#
|
|
# Original code by Jim Mellander, LBNL.
|
|
# Updated by Brian Tierney, LBNL and by Robin Sommer, ICSI.
|
|
|
|
@load site
|
|
|
|
module Drop;
|
|
|
|
export {
|
|
redef enum Notice += {
|
|
# Connectivity with given address has been dropped.
|
|
AddressDropped,
|
|
|
|
# A request to drop connectivity has been ignored.
|
|
AddressDropIgnored,
|
|
|
|
# Connectivity with given address has been restored.
|
|
AddressRestored,
|
|
|
|
AddressAlreadyDropped, # host is already dropped
|
|
|
|
# Previously dropped host connects again.
|
|
AddressSeenAgain,
|
|
|
|
# Previous offenders re-dropped or re-restored.
|
|
RepeatAddressDropped,
|
|
RepeatAddressRestored,
|
|
};
|
|
|
|
# True if we have the capability to drop hosts at all.
|
|
const can_drop_connectivity = F &redef;
|
|
|
|
# True if we never want to drop local addresses.
|
|
const dont_drop_locals = T &redef;
|
|
|
|
# True if we should use the catch-and-release scheme. If not then
|
|
# we simply drop addresses via the drop_connectivity_script and
|
|
# never restore them (they must be restored out-of-band).
|
|
const use_catch_release = F &redef;
|
|
|
|
# Catch-and-release parameters.
|
|
|
|
# Interval to wait for release following inactivity after
|
|
# first offense.
|
|
global drop_time = 5 min &redef;
|
|
|
|
# For repeat offenders: if the total time a host has already been
|
|
# dropped reaches persistent_offender_time, we drop the host for
|
|
# long_drop_time. Setting persistent_offender_time to zero disables
|
|
# this functionality.
|
|
const persistent_offender_time = 2 hr &redef;
|
|
global long_drop_time = 12 hr &redef;
|
|
|
|
# Scripts to perform the actual dropping/restore. They get the
|
|
# IP address as their first argument.
|
|
const drop_connectivity_script = "drop-connectivity" &redef;
|
|
const restore_connectivity_script = "restore-connectivity" &redef;
|
|
|
|
const root_servers = {
|
|
a.root-servers.net, b.root-servers.net, c.root-servers.net,
|
|
d.root-servers.net, e.root-servers.net, f.root-servers.net,
|
|
g.root-servers.net, h.root-servers.net, i.root-servers.net,
|
|
j.root-servers.net, k.root-servers.net, l.root-servers.net,
|
|
m.root-servers.net,
|
|
} &redef;
|
|
|
|
const gtld_servers = {
|
|
a.gtld-servers.net, b.gtld-servers.net, c.gtld-servers.net,
|
|
d.gtld-servers.net, e.gtld-servers.net, f.gtld-servers.net,
|
|
g.gtld-servers.net, h.gtld-servers.net, i.gtld-servers.net,
|
|
j.gtld-servers.net, k.gtld-servers.net, l.gtld-servers.net,
|
|
m.gtld-servers.net,
|
|
} &redef;
|
|
|
|
const never_shut_down = {
|
|
root_servers, gtld_servers,
|
|
} &redef;
|
|
|
|
const never_drop_nets: set[subnet] &redef;
|
|
|
|
# Drop the connectivity for the address. "msg" gives a reason.
|
|
# It returns a copy of the NOTICE generated for the drop, which
|
|
# gives more information about the kind of dropping performed.
|
|
# If the notice type is NoticeNone, the drop was not successful
|
|
# (e.g., because this Bro instance is not configured to do drops.)
|
|
global drop_address: function(a: addr, msg: string) : notice_info;
|
|
|
|
# The following events are used to communicate information about the
|
|
# drops, in particular for C&R in the cluster setting.
|
|
|
|
# Address has been dropped.
|
|
global address_dropped: event(a: addr);
|
|
|
|
# Raised when an IP is restored.
|
|
global address_restored: event(a: addr);
|
|
|
|
# Raised when an that was dropped in the past is no
|
|
# longer monitored specifically for new connections.
|
|
global address_cleared: event(a: addr);
|
|
|
|
const debugging = F &redef;
|
|
global debug_log: function(msg: string);
|
|
}
|
|
|
|
type drop_rec: record {
|
|
tot_drop_count: count &default=0;
|
|
tot_restore_count: count &default=0;
|
|
actual_restore_count: count &default=0;
|
|
tot_drop_time: interval &default=0secs;
|
|
last_timeout: interval &default=0secs;
|
|
};
|
|
|
|
global clear_host: function(t: table[addr] of drop_rec, a: addr): interval;
|
|
|
|
global drop_info: table[addr] of drop_rec
|
|
&read_expire = 1 days &expire_func=clear_host &persistent;
|
|
|
|
global last_notice: notice_info;
|
|
|
|
function do_notice(n: notice_info)
|
|
{
|
|
last_notice = n;
|
|
NOTICE(n);
|
|
}
|
|
|
|
function dont_drop(a: addr) : bool
|
|
{
|
|
return ! can_drop_connectivity || a in never_shut_down ||
|
|
a in never_drop_nets || (dont_drop_locals && is_local_addr(a));
|
|
}
|
|
|
|
function is_dropped(a: addr) : bool
|
|
{
|
|
if ( a !in drop_info )
|
|
return F;
|
|
|
|
local di = drop_info[a];
|
|
|
|
if ( di$tot_drop_count < di$tot_restore_count )
|
|
{ # This shouldn't happen.
|
|
# FIXME: We need an assert().
|
|
print "run-time error: more restores than drops!";
|
|
return F;
|
|
}
|
|
|
|
return di$tot_drop_count > di$tot_restore_count;
|
|
}
|
|
|
|
global debug_log_file: file;
|
|
|
|
function debug_log(msg: string)
|
|
{
|
|
if ( ! debugging )
|
|
return;
|
|
|
|
print debug_log_file,
|
|
fmt("%.6f [%s] %s", network_time(), peer_description, msg);
|
|
}
|
|
|
|
event bro_init()
|
|
{
|
|
if ( debugging )
|
|
{
|
|
debug_log_file =
|
|
open_log_file(fmt("drop-debug.%s", peer_description));
|
|
set_buf(debug_log_file, F);
|
|
}
|
|
}
|
|
|
|
function do_direct_drop(a: addr, msg: string)
|
|
{
|
|
if ( msg != "" )
|
|
msg = fmt(" (%s)", msg);
|
|
|
|
if ( a !in drop_info )
|
|
{
|
|
local tmp: drop_rec;
|
|
drop_info[a] = tmp;
|
|
}
|
|
|
|
local di = drop_info[a];
|
|
|
|
if ( is_dropped(a) )
|
|
# Already dropped. Nothing to do.
|
|
do_notice([$note=Drop::AddressAlreadyDropped, $src=a,
|
|
$msg=fmt("%s%s", a, msg)]);
|
|
else
|
|
{
|
|
system(fmt("%s %s", Drop::drop_connectivity_script, a));
|
|
|
|
debug_log(fmt("sending drop for %s", a));
|
|
event Drop::address_dropped(a);
|
|
|
|
if ( di$tot_drop_count == 0 )
|
|
do_notice([$note=Drop::AddressDropped, $src=a,
|
|
$msg=fmt("%s%s", a, msg)]);
|
|
else
|
|
{
|
|
local s = fmt("(%d times)", di$tot_drop_count + 1);
|
|
do_notice([$note=Drop::RepeatAddressDropped,
|
|
$src=a, $n=di$tot_drop_count+1,
|
|
$msg=fmt("%s%s %s", a, msg, s), $sub=s]);
|
|
}
|
|
}
|
|
|
|
++di$tot_drop_count;
|
|
debug_log(fmt("dropped %s: tot_drop_count=%d tot_restore_count=%d",
|
|
a, di$tot_drop_count, di$tot_restore_count));
|
|
}
|
|
|
|
# Restore a previously dropped address.
|
|
global do_restore: function(a: addr, force: bool);
|
|
|
|
event restore_dropped_address(a: addr)
|
|
{
|
|
do_restore(a, F);
|
|
}
|
|
|
|
function do_catch_release_drop(a: addr, msg: string)
|
|
{
|
|
do_direct_drop(a, msg);
|
|
|
|
local di = drop_info[a];
|
|
|
|
local t = (persistent_offender_time != 0 sec &&
|
|
di$tot_drop_time >= persistent_offender_time) ?
|
|
long_drop_time : drop_time;
|
|
|
|
di$tot_drop_time += t;
|
|
di$last_timeout = t;
|
|
|
|
schedule t { restore_dropped_address(a) };
|
|
}
|
|
|
|
function do_restore(a: addr, force: bool)
|
|
{
|
|
if ( a !in drop_info )
|
|
return;
|
|
|
|
local di = drop_info[a];
|
|
++drop_info[a]$tot_restore_count;
|
|
debug_log(fmt("restored %s: tot_drop_count=%d tot_restore_count=%d force=%s", a, drop_info[a]$tot_drop_count, drop_info[a]$tot_restore_count, force));
|
|
|
|
if ( di$tot_drop_count == di$tot_restore_count || force )
|
|
{
|
|
++di$actual_restore_count;
|
|
system(fmt("%s %s", Drop::restore_connectivity_script, a));
|
|
|
|
debug_log(fmt("sending restored for %s", a));
|
|
event Drop::address_restored(a);
|
|
|
|
local t = di$last_timeout;
|
|
|
|
if ( di$actual_restore_count == 1 )
|
|
{
|
|
local s1 = fmt("(timeout %.1f)", t);
|
|
do_notice([$note=Drop::AddressRestored, $src=a,
|
|
$msg=fmt("%s %s", a, s1), $sub=s1]);
|
|
}
|
|
|
|
else
|
|
{
|
|
local s2 = fmt("(%d times, timeout %.1f)",
|
|
di$actual_restore_count, t);
|
|
do_notice([$note=Drop::RepeatAddressRestored, $src=a,
|
|
$n=di$tot_restore_count,
|
|
$msg=fmt("%s %s", a, s2), $sub=s2]);
|
|
}
|
|
}
|
|
}
|
|
|
|
function clear_host(t: table[addr] of drop_rec, a: addr): interval
|
|
{
|
|
if ( is_dropped(a) )
|
|
# Restore address.
|
|
do_restore(a, T);
|
|
|
|
debug_log(fmt("sending cleared for %s", a));
|
|
event Drop::address_cleared(a);
|
|
|
|
return 0 secs;
|
|
}
|
|
|
|
# Returns true if drop was successful (or IP was already dropped).
|
|
function drop_address(a: addr, msg: string) : notice_info
|
|
{
|
|
debug_log(fmt("drop_address(%s, %s)", a, msg));
|
|
|
|
last_notice = [$note=NoticeNone];
|
|
|
|
if ( dont_drop(a) )
|
|
do_notice([$note=AddressDropIgnored, $src=a,
|
|
$msg=fmt("ignoring request to drop %s (%s)", a, msg)]);
|
|
else if ( use_catch_release )
|
|
do_catch_release_drop(a, msg);
|
|
else
|
|
do_direct_drop(a, msg);
|
|
|
|
if ( last_notice$note == NoticeNone )
|
|
print "run-time error: drop_address did not raise a NOTICE";
|
|
|
|
return last_notice;
|
|
}
|
|
|
|
event new_connection(c: connection)
|
|
{
|
|
if ( ! can_drop_connectivity )
|
|
return;
|
|
|
|
# With Catch & Release, 1 connection from a previously dropped system
|
|
# triggers an immediate redrop.
|
|
if ( ! use_catch_release )
|
|
return;
|
|
|
|
local a = c$id$orig_h;
|
|
|
|
if ( a !in drop_info )
|
|
# Never dropped.
|
|
return;
|
|
|
|
local di = drop_info[a];
|
|
if ( is_dropped(a) )
|
|
# Still dropped.
|
|
return;
|
|
|
|
NOTICE([$note=AddressSeenAgain, $src=a,
|
|
$msg=fmt("%s seen again after release", a)]);
|
|
}
|