Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

switch per per-IP to per-UUID rate limiting #146

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions broker/l2tp_broker.cfg.example
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,11 @@ tunnel_id_base=100
; Reject connections if there are less than N seconds since the last connection.
; Can be less than a second (e.g., 0.1).
connection_rate_limit=0.2
; Reject connection if an IP address connects more than COUNT times in TIME seconds. Also runs
; Reject connection if a UUID connects more than COUNT times in TIME seconds. Also runs
; "broker.connection-rate-limit" hook (e.g. to block client via iptables).
; Disabled when at least one value is 0 (the default).
;connection_rate_limit_per_ip_count=20
;connection_rate_limit_per_ip_time=60
;connection_rate_limit_per_uuid_count=20
;connection_rate_limit_per_uuid_time=60
; Set PMTU to a fixed value. Use 0 for automatic PMTU discovery. A non-0 value also disables
; PMTU discovery on the client side, by having the server not respond to client-side PMTU
; discovery probes.
Expand Down Expand Up @@ -52,5 +52,5 @@ session.pre-down=
session.down=
; Called after the tunnel MTU gets changed because of PMTU discovery
session.mtu-changed=
; Called when the tunnel connection rate per ip limit is exceeded
; Called when the tunnel connection rate per UUID limit is exceeded
broker.connection-rate-limit=
32 changes: 16 additions & 16 deletions broker/src/tunneldigger_broker/broker.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def __init__(
max_tunnels,
tunnel_id_base,
connection_rate_limit,
connection_rate_limit_per_ip_count,
connection_rate_limit_per_ip_time,
connection_rate_limit_per_uuid_count,
connection_rate_limit_per_uuid_time,
pmtu_fixed,
log_ip_addresses,
):
Expand All @@ -40,10 +40,10 @@ def __init__(
self.tunnel_ids = set(range(tunnel_id_base, tunnel_id_base + max_tunnels))
self.tunnels = {}
self.last_tunnel_created = None
self.last_tunnel_created_per_ip = {} # Key: IP address Value: deque collection with timestamps
self.last_tunnel_created_per_uuid = {} # Key: UUID; Value: deque collection with timestamps
self.connection_rate_limit = connection_rate_limit
self.connection_rate_limit_per_ip_count = connection_rate_limit_per_ip_count
self.connection_rate_limit_per_ip_time = connection_rate_limit_per_ip_time
self.connection_rate_limit_per_uuid_count = connection_rate_limit_per_uuid_count
self.connection_rate_limit_per_uuid_time = connection_rate_limit_per_uuid_time
self.pmtu_fixed = pmtu_fixed
self.require_unique_session_id = False
self.log_ip_addresses = log_ip_addresses
Expand Down Expand Up @@ -85,24 +85,24 @@ def create_tunnel(self, broker, address, uuid, remote_tunnel_id, client_features

# Rate limit creation of new tunnels to at most one every 10 seconds to prevent the
# broker from being overwhelmed with creating tunnels, especially on embedded devices.
# We do this before the per-IP rate limiting so that these failed attempts do not count towards the latter.
# We do this before the per-UUID rate limiting so that these failed attempts do not count towards the latter.
if self.last_tunnel_created is not None and now - self.last_tunnel_created < self.connection_rate_limit:
logger.info("Rejecting tunnel %s due to global rate limiting: last tunnel was created too recently" % tunnel_str)
return False

# Rate limit creation of new tunnels with the same IP address.
# Rate limit creation of new tunnels with the same UUID.
# Runs broker.connection-rate-limit hook if threshold exceeded:
if self.connection_rate_limit_per_ip_count > 0 and self.connection_rate_limit_per_ip_time > 0:
if address[0] not in self.last_tunnel_created_per_ip:
# Create deque with max size if IP address does not exist in dict
self.last_tunnel_created_per_ip[address[0]] = deque([], self.connection_rate_limit_per_ip_count)
tunnelCollection = self.last_tunnel_created_per_ip[address[0]]
if len(tunnelCollection) >= self.connection_rate_limit_per_ip_count:
# We have "count" many connection attempts registered from this IP.
if self.connection_rate_limit_per_uuid_count > 0 and self.connection_rate_limit_per_uuid_time > 0:
if uuid not in self.last_tunnel_created_per_uuid:
# Create deque with max size if UUID does not exist in dict
self.last_tunnel_created_per_uuid[uuid] = deque([], self.connection_rate_limit_per_uuid_count)
tunnelCollection = self.last_tunnel_created_per_uuid[uuid]
if len(tunnelCollection) >= self.connection_rate_limit_per_uuid_count:
# We have "count" many connection attempts registered from this UUID.
# Check if they are all within "time".
delta = now - tunnelCollection[0] # Delta of oldest timestamp in collection and now
if delta <= self.connection_rate_limit_per_ip_time:
logger.info("Rejecting tunnel {0} due to per-IP rate limiting: {1} attempts in {2} seconds".format(
if delta <= self.connection_rate_limit_per_uuid_time:
logger.info("Rejecting tunnel {0} due to per-UUID rate limiting: {1} attempts in {2} seconds".format(
tunnel_str,
len(tunnelCollection),
int(delta),
Expand Down
4 changes: 2 additions & 2 deletions broker/src/tunneldigger_broker/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,8 @@
max_tunnels=config.getint('broker', 'max_tunnels'),
tunnel_id_base=config.getint('broker', 'tunnel_id_base'),
connection_rate_limit=config.getfloat('broker', 'connection_rate_limit'),
connection_rate_limit_per_ip_count=config.getint('broker', 'connection_rate_limit_per_ip_count', fallback=0),
connection_rate_limit_per_ip_time=config.getfloat('broker', 'connection_rate_limit_per_ip_time', fallback=0),
connection_rate_limit_per_uuid_count=config.getint('broker', 'connection_rate_limit_per_uuid_count', fallback=0),
connection_rate_limit_per_uuid_time=config.getfloat('broker', 'connection_rate_limit_per_uuid_time', fallback=0),
pmtu_fixed=config.getint('broker', 'pmtu'),
log_ip_addresses=config.getboolean('log', 'log_ip_addresses'),
)
Expand Down
4 changes: 2 additions & 2 deletions docs/server.rst
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,8 @@ There are currently four different hooks, namely:
* ``session.mtu-changed`` is called after the broker's path MTU discovery determines that the tunnel's MTU has changed
and should be adjusted. (Example is found under ``scripts/mtu_changed.sh``.)

* ``broker.connection-rate-limit`` is called when a IP address tries to connect ``connection_rate_limit_per_ip_count``
times within ``connection_rate_limit_per_ip_time`` seconds. (Example is found under ``scripts/broker.connection-rate-limit.sh``.)
* ``broker.connection-rate-limit`` is called when a UUID address tries to connect ``connection_rate_limit_per_uuid_count``
times within ``connection_rate_limit_per_uuid_time`` seconds. (Example is found under ``scripts/broker.connection-rate-limit.sh``.)

Please look at all the example hook scripts carefully and try to understand
them before use. They should be considered configuration and some things in
Expand Down