Skip to content

Commit c7f84ba

Browse files
committed
added basic cli-logger, added logic to drop bogons-traffic on wan
1 parent 17491fb commit c7f84ba

File tree

9 files changed

+153
-46
lines changed

9 files changed

+153
-46
lines changed

scripts/cli.sh

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#!/bin/bash
2+
3+
set -eo pipefail
4+
5+
if [ -z "$1" ]
6+
then
7+
SRC='172.17.10.5'
8+
else
9+
SRC="$1"
10+
fi
11+
12+
if [ -z "$2" ]
13+
then
14+
DST='1.1.1.1'
15+
else
16+
DST="$2"
17+
fi
18+
19+
cd "$(dirname "$0")/.."
20+
21+
export DEBUG=1
22+
23+
python3 src/firewall_test/cli.py --firewall-system 'linux_netfilter' --file-interfaces 'testdata/plugin_translate_linux_interfaces.json' --file-routes 'testdata/plugin_translate_linux_routes.json' --file-route-rules 'testdata/plugin_translate_linux_route-rules.json' --src-ip "$SRC" --dst-ip "$DST"

src/firewall_test/cli.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from os import environ
12
from os import path as os_path
23
from sys import path as sys_path
34
from argparse import ArgumentParser
@@ -76,6 +77,7 @@ def main():
7677
l4_proto=args.proto_l4,
7778
)
7879

80+
print()
7981
loaded = load(
8082
system=args.firewall_system,
8183
file_interfaces=args.file_interfaces,
@@ -84,7 +86,11 @@ def main():
8486
)
8587
s = Simulator(**loaded)
8688
r = s.run(packet)
87-
print('\n', r.to_json())
89+
90+
if 'DEBUG' in environ:
91+
print('\n', r.to_json())
92+
93+
print()
8894

8995

9096
if __name__ == '__main__':
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
from ipaddress import ip_network
2+
3+
DEFAULT_ROUTE_IP4 = ip_network('0.0.0.0/0')
4+
DEFAULT_ROUTE_IP6 = ip_network('::/0')
5+
DEFAULT_ROUTES = [DEFAULT_ROUTE_IP4, DEFAULT_ROUTE_IP6]

src/firewall_test/plugins/translate/linux.py

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from ipaddress import ip_address, ip_network, IPv4Address
22
from json import loads as json_loads
33

4+
from plugins.translate.config import DEFAULT_ROUTE_IP4, DEFAULT_ROUTE_IP6
45
from plugins.translate.abstract import TranslatePluginStaticRoutes, TranslatePluginStaticRouteRules, \
56
StaticRoute, StaticRouteRule, TranslatePluginNetworkInterfaces, NetworkInterface
67

@@ -28,8 +29,8 @@ def _parse_rule(raw: dict) -> dict:
2829

2930
if src == 'all':
3031
r['src'] = [
31-
ip_network('0.0.0.0/0'),
32-
ip_network('::/0'),
32+
DEFAULT_ROUTE_IP4,
33+
DEFAULT_ROUTE_IP6,
3334
]
3435

3536
else:
@@ -63,13 +64,13 @@ def _parse_route(raw: dict) -> dict:
6364

6465
if raw.get('dst') == 'default':
6566
if r['gw'] is None:
66-
r['net'] = ip_network('0.0.0.0/0')
67+
r['net'] = DEFAULT_ROUTE_IP4
6768

6869
elif r['gw'].find(':') == -1:
69-
r['net'] = ip_network('0.0.0.0/0')
70+
r['net'] = DEFAULT_ROUTE_IP4
7071

7172
else:
72-
r['net'] = ip_network('::/0')
73+
r['net'] = DEFAULT_ROUTE_IP6
7374

7475
else:
7576
r['net'] = ip_network(raw['dst'])

src/firewall_test/simulator/02_routes_test.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ def test_router_dst_route():
2222

2323
packet = PacketIP(src='192.168.0.10', dst='1.1.1.1', l3_proto='ip4')
2424
r = router.get_route(packet)
25-
assert len(r) == 1
26-
r = r[0]
25+
assert r is not None
2726
assert r.net == ip_network('0.0.0.0/0')
2827
assert r.table == 'default'
2928

@@ -41,15 +40,13 @@ def test_router_src_route():
4140

4241
packet = PacketIP(src='192.168.0.10', dst='1.1.1.1', l3_proto='ip4')
4342
r = router.get_src_route(packet)
44-
assert len(r) == 1
45-
r = r[0]
43+
assert r is not None
4644
assert r.net == ip_network('0.0.0.0/0')
4745
assert r.table == 'default'
4846

4947
packet = PacketIP(src='10.255.255.20', dst='1.1.1.1', l3_proto='ip4')
5048
r = router.get_src_route(packet)
51-
assert len(r) == 2
52-
r = r[0]
49+
assert r is not None
5350
assert r.net == ip_network('10.255.255.0/24')
5451
assert r.table == 'default'
5552

src/firewall_test/simulator/12_main_test.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ def test_basic():
2323
assert r.packet.ni_out == 'wan'
2424
assert r.flow_type == FLOW_FORWARD
2525

26-
assert r.route_src[0].net == ip_network('172.17.0.0/16')
27-
assert r.route_src[0].ni == 'docker0'
28-
assert r.route_dst[0].net == ip_network('0.0.0.0/0')
29-
assert r.route_dst[0].ni == 'wan'
26+
assert r.route_src.net == ip_network('172.17.0.0/16')
27+
assert r.route_src.ni == 'docker0'
28+
assert r.route_dst.net == ip_network('0.0.0.0/0')
29+
assert r.route_dst.ni == 'wan'
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
from sys import stdout
2+
from os import environ
3+
4+
COLOR_OK = '\x1b[1;32m'
5+
COLOR_WARN = '\x1b[1;33m'
6+
COLOR_INFO = '\x1b[1;34m'
7+
COLOR_ERROR = '\x1b[1;31m'
8+
COLOR_DEBUG = '\x1b[35m'
9+
RESET_STYLE = '\x1b[0m'
10+
11+
12+
def _log(label: str, msg: str, color: str, symbol: str):
13+
stdout.write(
14+
color + symbol + ' ' + label.upper() + ': ' + msg + RESET_STYLE + '\n',
15+
)
16+
17+
18+
def log_debug(label: str, msg: str):
19+
if 'DEBUG' in environ:
20+
_log(label='DEBUG ' + label, msg=msg, color=COLOR_DEBUG, symbol='🛈')
21+
22+
23+
def log_ok(label: str, msg: str):
24+
_log(label=label, msg=msg, color=COLOR_OK, symbol=' ✓')
25+
26+
27+
def log_info(label: str, msg: str):
28+
_log(label=label, msg=msg, color=COLOR_INFO, symbol='🛈')
29+
30+
31+
def log_warn(label: str, msg: str):
32+
_log(label=label, msg=msg, color=COLOR_WARN, symbol='⚠')
33+
34+
35+
def log_error(label: str, msg: str):
36+
_log(label=label, msg=msg, color=COLOR_ERROR, symbol=' ✖')

src/firewall_test/simulator/main.py

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
from simulator.packet import PacketIP
55
from simulator.routes import Router
6+
from simulator.logger import log_info, log_error, log_ok
67

7-
from plugins.translate.abstract import NetworkInterface, StaticRoute, StaticRouteRule
88
from plugins.system.abstract import FirewallSystem
9+
from plugins.translate.config import DEFAULT_ROUTES
10+
from plugins.translate.abstract import NetworkInterface, StaticRoute, StaticRouteRule
911

1012
FLOW_INPUT = 'input'
1113
FLOW_OUTPUT = 'output'
@@ -29,37 +31,61 @@ def __init__(self, packet: PacketIP, simulator):
2931
self.local_src, packet.ni_in = self._is_ip_local(packet.src)
3032
self.local_dst, packet.ni_out = self._is_ip_local(packet.dst)
3133
self.flow_type = self._get_flow_type()
34+
# log_info('Firewall', f'Flow-type: {self.flow_type}')
3235

3336
self.route_src = self._s.router.get_src_route(self.packet)
3437
self._update_packet_ni_in()
38+
if packet.ni_in is not None:
39+
log_info('Firewall', f'Packet inbound-interface: {packet.ni_in}')
3540

3641
if self.route_src is None:
37-
raise ConnectionError('No Source-Route found')
42+
log_error('Router', 'No Source-Route found')
43+
return
3844

45+
# todo: prerouting firewall-filters
3946
# todo: DNAT
4047
self._dnat_done = True
4148
self.dnat = None
49+
if self.dnat is not None:
50+
log_info('Firewall', f'Performed DNAT: {self.dnat}')
4251

4352
self.local_dst, packet.ni_out = self._is_ip_local(packet.dst)
4453
self.flow_type = self._get_flow_type()
54+
4555
self.route_dst = self._s.router.get_route(self.packet)
4656
self._update_packet_ni_out()
57+
if packet.ni_out is not None:
58+
log_info('Firewall', f'Packet outbound-interface: {packet.ni_out}')
4759

4860
if self.route_dst is None:
49-
raise ConnectionError('No Destination-Route found')
61+
log_error('Router', 'No Destination-Route found')
62+
return
63+
64+
log_info('Firewall', f'Flow-type: {self.flow_type}')
65+
66+
if self._is_bogon_to_wan() and self._s.system.FIREWALL_WAN_DROP_BOGONS:
67+
log_error('Firewall', 'Dropping traffic to WAN targeting bogons')
68+
return
69+
70+
# todo: main firewall-filters
5071

5172
# todo: SNAT
5273
self.snat = None
74+
if self.snat is not None:
75+
log_info('Firewall', f'Performed SNAT: {self.snat}')
76+
77+
# todo: egress firewall-filters
78+
79+
log_ok('Firewall', 'Packet passed')
5380

5481
def dump(self) -> dict:
5582
return {
5683
'packet': self.packet.dump(),
57-
'ipp': self._ipp,
5884
'src_is_local': self.local_src,
5985
'dst_is_local': self.local_dst,
6086
'flow_type': self.flow_type,
61-
'route_src': [route.dump() for route in self.route_src],
62-
'route_dst': [route.dump() for route in self.route_dst],
87+
'route_src': self.route_src.dump() if self.route_src is not None else None,
88+
'route_dst': self.route_dst.dump() if self.route_dst is not None else None,
6389
'dnat': self.dnat,
6490
'snat': self.snat,
6591
}
@@ -91,19 +117,26 @@ def _update_packet_ni_in(self):
91117
if self.packet.ni_in is not None:
92118
return
93119

94-
if len(self.route_src) == 0:
120+
if self.route_src is None:
95121
return
96122

97-
self.packet.ni_in = self.route_src[0].ni
123+
self.packet.ni_in = self.route_src.ni
98124

99125
def _update_packet_ni_out(self) -> (str, None):
100126
if self.packet.ni_out is not None:
101127
return
102128

103-
if len(self.route_dst) == 0:
129+
if self.route_dst is None:
104130
return
105131

106-
self.packet.ni_out = self.route_dst[0].ni
132+
self.packet.ni_out = self.route_dst.ni
133+
134+
def _is_bogon_to_wan(self) -> bool:
135+
if self.route_dst.net in DEFAULT_ROUTES and \
136+
not self.packet.dst.is_global:
137+
return True
138+
139+
return False
107140

108141

109142
class Simulator:

src/firewall_test/simulator/routes.py

Lines changed: 26 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
1+
from os import environ
2+
13
from plugins.system.abstract import FirewallSystem
24
from plugins.translate.abstract import StaticRouteRule, StaticRoute
5+
36
from simulator.packet import PacketIP
7+
from simulator.logger import log_debug
48

59

610
class Router:
@@ -70,18 +74,18 @@ def _get_matching_rules(self, packet: PacketIP) -> list[StaticRouteRule]:
7074
def _get_matching_routes(self, packet: PacketIP, matching_rules: list[StaticRouteRule], src_route: bool = False) -> list[StaticRoute]:
7175
# todo: ignore routes of interfaces that are down
7276

77+
route_for = packet.dst
78+
if src_route:
79+
route_for = packet.src
80+
7381
routes = []
7482
if len(matching_rules) > 0:
7583
for rule in matching_rules:
7684
for route in self.rule_route_mapping[rule]:
77-
if packet.dst in route.net:
85+
if route_for in route.net:
7886
routes.append(route)
7987

8088
else:
81-
route_for = packet.dst
82-
if src_route:
83-
route_for = packet.src
84-
8589
for route in self.routes:
8690
if route_for in route.net:
8791
routes.append(route)
@@ -123,23 +127,25 @@ def _sort_routes(self, matching_routes: list[StaticRoute]) -> list[StaticRoute]:
123127

124128
return sorted_routes
125129

126-
def get_route(self, packet: PacketIP) -> list[StaticRoute]:
127-
matching_rules = self._get_matching_rules(packet)
128-
matching_routes = self._get_matching_routes(packet, matching_rules)
130+
def get_route(self, packet: PacketIP) -> (StaticRoute, None):
131+
rules = self._get_matching_rules(packet)
132+
routes = self._get_matching_routes(packet, rules)
133+
routes = self._sort_routes(routes)
134+
if 'DEBUG' in environ:
135+
log_debug('Router', f'Packet {packet.dump()} | Destination routes: {[r.dump() for r in routes]}')
129136

130-
print(f"ROUTE RULES MATCHED: {matching_rules}")
131-
print(f"ROUTES MATCHED: {matching_routes}")
137+
if len(routes) == 0:
138+
return None
132139

133-
sorted_routes = self._sort_routes(matching_routes)
140+
return routes[0]
134141

135-
print(f"ROUTES SORTED: {sorted_routes}")
136-
return sorted_routes
137-
138-
def get_src_route(self, packet: PacketIP) -> list[StaticRoute]:
139-
matching_routes = self._get_matching_routes(packet, [], src_route=True)
140-
print(f"ROUTES MATCHED: {matching_routes}")
142+
def get_src_route(self, packet: PacketIP) -> (StaticRoute, None):
143+
routes = self._get_matching_routes(packet, [], src_route=True)
144+
routes = self._sort_routes(routes)
145+
if 'DEBUG' in environ:
146+
log_debug('Router', f'Packet {packet.dump()} | Source routes: {[r.dump() for r in routes]}')
141147

142-
sorted_routes = self._sort_routes(matching_routes)
148+
if len(routes) == 0:
149+
return None
143150

144-
print(f"ROUTES SORTED: {sorted_routes}")
145-
return sorted_routes
151+
return routes[0]

0 commit comments

Comments
 (0)