Skip to content

Commit

Permalink
Add traffic direction differentiation
Browse files Browse the repository at this point in the history
  • Loading branch information
bitbrute committed Sep 17, 2019
1 parent 4d7c98b commit e8307d9
Show file tree
Hide file tree
Showing 3 changed files with 202 additions and 84 deletions.
54 changes: 36 additions & 18 deletions evillimiter/menus/main_menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from evillimiter.console.io import IO
from evillimiter.console.banner import get_main_banner
from evillimiter.networking.host import Host
from evillimiter.networking.limiter import Limiter
from evillimiter.networking.limiter import Limiter, Direction, NetRate
from evillimiter.networking.spoof import ARPSpoofer
from evillimiter.networking.scan import HostScanner

Expand All @@ -26,9 +26,13 @@ def __init__(self, version, interface, gateway_ip, gateway_mac, netmask):
limit_parser = self.parser.add_subparser('limit', self._limit_handler)
limit_parser.add_parameter('id')
limit_parser.add_parameter('rate')
limit_parser.add_flag('--upload', 'upload')
limit_parser.add_flag('--download', 'download')

block_parser = self.parser.add_subparser('block', self._block_handler)
block_parser.add_parameter('id')
block_parser.add_flag('--upload', 'upload')
block_parser.add_flag('--download', 'download')

free_parser = self.parser.add_subparser('free', self._free_handler)
free_parser.add_parameter('id')
Expand Down Expand Up @@ -139,34 +143,36 @@ def _limit_handler(self, args):
Limits bandwith of host to specified rate
"""
hosts = self._get_hosts_by_ids(args.id)
rate = args.rate
rate = NetRate(args.rate)
direction = self._parse_direction_args(args)

if not rate.is_valid():
IO.error('limit rate is invalid.')
return

if hosts is not None and len(hosts) > 0:
for host in hosts:
if not host.spoofed:
self.arp_spoofer.add(host)

if netutils.validate_netrate_string(rate):
self.limiter.limit(host, rate)
else:
IO.error('limit rate is invalid.')
return
self.limiter.limit(host, direction, rate)

IO.ok('{}{}{} limited{} to {}.'.format(IO.Fore.LIGHTYELLOW_EX, host.ip, IO.Fore.LIGHTRED_EX, IO.Style.RESET_ALL, rate))
IO.ok('{}{}{r} {} {}limited{r} to {}.'.format(IO.Fore.LIGHTYELLOW_EX, host.ip, Direction.pretty_direction(direction), IO.Fore.LIGHTRED_EX, rate, r=IO.Style.RESET_ALL))

def _block_handler(self, args):
"""
Handles 'block' command-line argument
Blocks internet communication for host
"""
hosts = self._get_hosts_by_ids(args.id)
direction = self._parse_direction_args(args)

if hosts is not None and len(hosts) > 0:
for host in hosts:
if not host.spoofed:
self.arp_spoofer.add(host)

self.limiter.block(host)
IO.ok('{}{}{} blocked{}.'.format(IO.Fore.LIGHTYELLOW_EX, host.ip, IO.Fore.RED, IO.Style.RESET_ALL))
self.limiter.block(host, direction)
IO.ok('{}{}{r} {} {}blocked{r}.'.format(IO.Fore.LIGHTYELLOW_EX, host.ip, Direction.pretty_direction(direction), IO.Fore.RED, r=IO.Style.RESET_ALL))

def _free_handler(self, args):
"""
Expand Down Expand Up @@ -228,7 +234,7 @@ def _help_handler(self, args):
Handles 'help' command-line argument
Prints help message including commands and usage
"""
spaces = ' ' * 30
spaces = ' ' * 33

IO.print(
"""
Expand All @@ -242,13 +248,13 @@ def _help_handler(self, args):
{s}contains host information, including IDs.
{y}limit [ID1,ID2,...] [rate]{r}{}limits bandwith of host(s) (uload/dload).
{b}{s}e.g.: limit 4 100kbit
{s} limit 2,3,4 1gbit
{s} limit all 200kbit{r}
{y} (--upload) (--download){r}{}{b}e.g.: limit 4 100kbit
{s} limit 2,3,4 1gbit --download
{s} limit all 200kbit --upload{r}
{y}block [ID1,ID2,...]{r}{}blocks internet access of host(s).
{b}{s}e.g.: block 3,2
{s} block all{r}
{y} (--upload) (--download){r}{}{b}e.g.: block 3,2
{s} block all --upload{r}
{y}free [ID1,ID2,...]{r}{}unlimits/unblocks host(s).
{b}{s}e.g.: free 3
Expand All @@ -266,7 +272,9 @@ def _help_handler(self, args):
spaces[len('scan (--range [IP range])'):],
spaces[len('hosts'):],
spaces[len('limit [ID1,ID2,...] [rate]'):],
spaces[len(' (--upload) (--download)'):],
spaces[len('block [ID1,ID2,...]'):],
spaces[len(' (--upload) (--download)'):],
spaces[len('free [ID1,ID2,...]'):],
spaces[len('add [IP] (--mac [MAC])'):],
spaces[len('clear'):],
Expand Down Expand Up @@ -318,11 +326,21 @@ def _get_hosts_by_ids(self, ids_string):

return hosts

def _parse_direction_args(self, args):
direction = Direction.NONE

if args.upload:
direction |= Direction.OUTGOING
if args.download:
direction |= Direction.INCOMING

return Direction.BOTH if direction == Direction.NONE else direction

def _free_host(self, host):
"""
Stops ARP spoofing and unlimits host
"""
if host.spoofed:
self.arp_spoofer.remove(host)
self.limiter.unlimit(host)
self.limiter.unlimit(host, Direction.BOTH)
IO.ok('{}{}{} freed.'.format(IO.Fore.LIGHTYELLOW_EX, host.ip, IO.Style.RESET_ALL))
214 changes: 166 additions & 48 deletions evillimiter/networking/limiter.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,70 +4,99 @@


class Limiter(object):
class HostLimitIDs(object):
def __init__(self, upload_id, download_id):
self.upload_id = upload_id
self.download_id = download_id

def __init__(self, interface):
self.interface = interface
self.host_ids_dict = {}

# maps an ID to each host to destinguish between the forwarded packets
self.host_id_map = {}

def limit(self, host: Host, rate):
def limit(self, host, direction, rate):
"""
Limits the uload/dload traffic of a host
to a specified rate
"""
id_ = self._create_id()

if host in self.host_id_map:
id_ = self.host_id_map[host]
self.unlimit(host)

# add a class to the root qdisc with specified rate
shell.execute_suppressed('{} class add dev {} parent 1:0 classid 1:{} htb rate {r} ceil {r}'.format(BIN_TC, self.interface, id_, r=rate))
# add a fw filter that filters packets marked with the corresponding ID
shell.execute_suppressed('{} filter add dev {} parent 1:0 protocol ip prio {id} handle {id} fw flowid 1:{id}'.format(BIN_TC, self.interface, id=id_))
host_ids = self._new_host_limit_ids(host, direction)

if (direction & Direction.OUTGOING) == Direction.OUTGOING:
# add a class to the root qdisc with specified rate
shell.execute_suppressed('{} class add dev {} parent 1:0 classid 1:{} htb rate {r} burst {b}'.format(BIN_TC, self.interface, host_ids.upload_id, r=rate, b=rate * 1.1))
# add a fw filter that filters packets marked with the corresponding ID
shell.execute_suppressed('{} filter add dev {} parent 1:0 protocol ip prio {id} handle {id} fw flowid 1:{id}'.format(BIN_TC, self.interface, id=host_ids.upload_id))
# marks outgoing packets
shell.execute_suppressed('{} -t mangle -A POSTROUTING -s {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, host_ids.upload_id))
if (direction & Direction.INCOMING) == Direction.INCOMING:
# add a class to the root qdisc with specified rate
shell.execute_suppressed('{} class add dev {} parent 1:0 classid 1:{} htb rate {r} burst {b}'.format(BIN_TC, self.interface, host_ids.download_id, r=rate, b=rate * 1.1))
# add a fw filter that filters packets marked with the corresponding ID
shell.execute_suppressed('{} filter add dev {} parent 1:0 protocol ip prio {id} handle {id} fw flowid 1:{id}'.format(BIN_TC, self.interface, id=host_ids.download_id))
# marks incoming packets
shell.execute_suppressed('{} -t mangle -A PREROUTING -d {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, host_ids.download_id))

# marks outgoing packets
shell.execute_suppressed('{} -t mangle -A POSTROUTING -s {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, id_))
# marks incoming packets
shell.execute_suppressed('{} -t mangle -A PREROUTING -d {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, id_))

self.host_id_map[host] = id_
host.limited = True
self.host_ids_dict[host] = host_ids

def block(self, host):
id_ = self._create_id()

if host in self.host_id_map:
id_ = self.host_id_map[host]
self.unlimit(host)
def block(self, host, direction):
host_ids = self._new_host_limit_ids(host, direction)

# drops forwarded packets with matching source
shell.execute_suppressed('{} -t filter -A FORWARD -s {} -j DROP'.format(BIN_IPTABLES, host.ip))
# drops forwarded packets with matching destination
shell.execute_suppressed('{} -t filter -A FORWARD -d {} -j DROP'.format(BIN_IPTABLES, host.ip))
if (direction & Direction.OUTGOING) == Direction.OUTGOING:
# drops forwarded packets with matching source
shell.execute_suppressed('{} -t filter -A FORWARD -s {} -j DROP'.format(BIN_IPTABLES, host.ip))
if (direction & Direction.INCOMING) == Direction.INCOMING:
# drops forwarded packets with matching destination
shell.execute_suppressed('{} -t filter -A FORWARD -d {} -j DROP'.format(BIN_IPTABLES, host.ip))

self.host_id_map[host] = id_
host.blocked = True
self.host_ids_dict[host] = host_ids

def unlimit(self, host):
id_ = self.host_id_map[host]
self._delete_tc_class(id_)
self._delete_iptables_entries(host, id_)
def unlimit(self, host, direction):
host_ids = self.host_ids_dict[host]

del self.host_id_map[host]
if (direction & Direction.OUTGOING) == Direction.OUTGOING:
self._delete_tc_class(host_ids.upload_id)
self._delete_iptables_entries(host, direction, host_ids.upload_id)
if (direction & Direction.INCOMING) == Direction.INCOMING:
self._delete_tc_class(host_ids.download_id)
self._delete_iptables_entries(host, direction, host_ids.download_id)

del self.host_ids_dict[host]
host.limited = False
host.blocked = False

def _create_id(self):
def _new_host_limit_ids(self, host, direction):
"""
Get limit information for corresponding host
If not present, create new
"""
Returns a unique ID that is
host_ids = None

if host in self.host_ids_dict:
host_ids = self.host_ids_dict[host]
self.unlimit(host, direction)

return Limiter.HostLimitIDs(*self._create_ids()) if host_ids is None else host_ids

def _create_ids(self):
"""
Returns unique IDs that are
currently not in use
"""
id_ = 1
while True:
if id_ not in self.host_id_map.values():
return id_
id_ += 1
def generate_id(*exc):
"""
Generates a unique, unused ID
exc: IDs that will not be used (exceptions)
"""
id_ = 1
while True:
if (id_ not in exc) and (id_ not in (x for y in self.host_ids_dict.values() for x in [y.upload_id, y.download_id])):
return id_
else:
id_ += 1

id1 = generate_id()
return (id1, generate_id(id1))

def _delete_tc_class(self, id_):
"""
Expand All @@ -77,11 +106,100 @@ def _delete_tc_class(self, id_):
shell.execute_suppressed('{} filter del dev {} parent 1:0 prio {}'.format(BIN_TC, self.interface, id_))
shell.execute_suppressed('{} class del dev {} parent 1:0 classid 1:{}'.format(BIN_TC, self.interface, id_))

def _delete_iptables_entries(self, host: Host, id_):
def _delete_iptables_entries(self, host, direction, id_):
"""
Deletes iptables rules for a given ID (host)
"""
shell.execute_suppressed('{} -t mangle -D POSTROUTING -s {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, id_))
shell.execute_suppressed('{} -t mangle -D PREROUTING -d {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, id_))
shell.execute_suppressed('{} -t filter -D FORWARD -s {} -j DROP'.format(BIN_IPTABLES, host.ip))
shell.execute_suppressed('{} -t filter -D FORWARD -d {} -j DROP'.format(BIN_IPTABLES, host.ip))
if (direction & Direction.OUTGOING) == Direction.OUTGOING:
shell.execute_suppressed('{} -t mangle -D POSTROUTING -s {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, id_))
shell.execute_suppressed('{} -t filter -D FORWARD -s {} -j DROP'.format(BIN_IPTABLES, host.ip))
if (direction & Direction.INCOMING) == Direction.INCOMING:
shell.execute_suppressed('{} -t mangle -D PREROUTING -d {} -j MARK --set-mark {}'.format(BIN_IPTABLES, host.ip, id_))
shell.execute_suppressed('{} -t filter -D FORWARD -d {} -j DROP'.format(BIN_IPTABLES, host.ip))


class Direction:
NONE = 0
OUTGOING = 1
INCOMING = 2
BOTH = 3

def pretty_direction(direction):
if direction == Direction.OUTGOING:
return 'upload'
elif direction == Direction.INCOMING:
return 'download'
elif direction == Direction.BOTH:
return 'upload / download'
else:
return '-'


class NetRate(object):
def __init__(self, rate_string):
self.rate_string = rate_string

def __repr__(self):
return self.rate_string

def __str__(self):
return self.rate_string

def __mul__(self, other):
return NetRate._restring(int(self.bit_value() * other))

def is_valid(self):
try:
self.bit_value()
except Exception:
return False
return True

def bit_value(self):
number = 0 # rate number
offset = 0 # string offset

for c in self.rate_string:
if c.isdigit():
number = number * 10 + int(c)
offset += 1
else:
break

unit = self.rate_string[offset:].lower()

if unit == 'bit':
return number
elif unit == 'kbit':
return number * 1000
elif unit == 'mbit':
return number * 1000 ** 2
elif unit == 'gbit':
return number * 1000 ** 3
else:
raise Exception('Invalid net rate')

def _restring(bit_value):
"""
Convert bit value back to net rate string
"""
counter = 0
while True:
if bit_value >= 1000:
bit_value /= 1000
counter += 1
else:
unit = ''
if counter == 0:
unit = 'bit'
elif counter == 1:
unit = 'kbit'
elif counter == 2:
unit = 'mbit'
elif counter == 3:
unit = 'gbit'

return '{}{}'.format(int(bit_value), unit)

if counter > 3:
raise Exception('Net rate limit exceeded')
Loading

0 comments on commit e8307d9

Please sign in to comment.