Task 2: Implement firewall rules
Details
We will use a fairly basic syntax, with no "stateful" rules. All rules
will be loaded from a text file named firewall_rules.txt
. The syntax
and semantics of the firewall rules are described in detail below. As a
preview and example, however, here is a rule that denies IP traffic
(with any transport protocol) with source address 10.0.0.1 and any
destination address:
# block any packets with source address 10.0.0.1
deny ip src 10.0.0.1 dst any
(Note that the lines beginning with # are just comments.)
The rules should be applied to packets just after they have been received at the firewall. The firewall should apply just to IPv4 packets (not to ARP, IPv6, or any other type of packet). In particular, non-IPv4 packets should all be permitted. As described in detail below, rules can either permit or deny packets. For packets that are permitted, they should simply be forwarded out the "other" interface by the firewall. For any denied packets, they should be dropped/ignored and no further processing should be done on them.
Again, the syntax and meaning of the firewall rules is described below in detail.
Firewall rule specification and syntax
The firewall rules to be loaded by the router must be included in a text
file named firewall_rules.txt
. The allowable syntax for rules is as
follows:
[permit|deny] ip src [srcnet|any] dst [dstnet|any]
[permit|deny| icmp src [srcnet|any] dst [dstnet|any]
[permit|deny] [udp|tcp] src [srcnet|any] srcport [portno|any] dst [dstnet|any] dstport [portno|any]
Note that items in square braces indicate items to be made concrete in a specific rule. For example, a valid rule is:
permit ip src 10.0.0.1 dst any
which would allow any IP packets (any protocol) with source address 10.0.0.1 and any destination address.
Note that the srcnet
or dstnet
values may either be an exact IP
address, or an IP prefix indicating a subnet of addresses. Also,
portno
should be specified as a single integer between 0 and 65535.
any
, somewhat obviously, should match anything.
Here is another example:
deny tcp src 1.2.3.0/24 srcport any dst any dstport 80
This rule blocks any TCP traffic with source address in the range 1.2.3.0-255, with any source TCP port, any destination IP address, and a destination port of 80.
It is straightforward to access TCP and UDP port numbers using the Switchyard packet library. See the Switchyard documentation for details and examples.
You may also find the IPv4Network
class useful (it is built in to the
ipaddress module in Python 3.4). You can instantiate an IPv4Network
object by passing in a network address (with prefix) as a string. On
that object, you can get the prefix length as an integer, convert the
address to an integer (to be able to bitwise operations), and other
useful operations. See the standard Python documentation for full
details on the ipaddress
module.
>>> from ipaddress import IPv4Network, IPv4Address
>>> net1 = IPv4Network('149.43.80.0/22')
>>> net2 = IPv4Network('149.43.0.0/16')
>>> net3 = IPv4Network('149.43.80.25', strict=False)
>>> # for above, if you don't have a prefix at the end of an address
>>> # you'll get an exception unless you say strict=False
>>> # w/o a prefix length, it assumes 32 bit prefix
>>> net1.prefixlen
22
>>> net2.prefixlen
16
>>> net3.prefixlen
32
>>> net1.network_address
IPv4Address('149.43.80.0')
>>> int(net1.network_address)
2502643712
>>> net2.network_address
IPv4Address('149.43.0.0')
>>> int(net2.network_address)
2502623232
>>> int(net2.network_address) & int(net3.network_address) == int(net2.network_address)
True
>>> # of course, the above should be true because we're basically checking
>>> # whether 149.43.80.25 is contained within the network 149.43.0.0
>>> # by doing the bitwise & (AND) operation
>>>
Blank lines are allowed in the firewall_rules.txt
file, and any line
on which the first non-whitespace character is # should be ignored.
Thus, you should allow Python-like comments, but you do not need to
handle the situation where a comment and a rule appear on the same line
--- comments will always appear separately.
Rule order matters! Packets should be checked against the rules in the same order as the rules are listed in firewall_rules.txt. When a matching rule is found, the action (permit/deny) according to the rule should apply, and no more rules should be checked. If no rules match, then the default action should be to permit the packet. Note that in the example rule set below, the last rule explicitly drops all packets but your firewall should handle any reasonable rule set.
Rate limits (details can be found in Task 3) can be applied to any "permit" rule. To specify a rate limit, the syntax is "ratelimit [bytessec]", included at the end of a rule. The rate limit accounting should apply to the entire packet except the Ethernet header (i.e., the packet size used for rate limit accounting should just include the IP header and beyond).
Impairment (details can be found in Task 4) can be applied to any "permit" rule (although rate limits and impairment cannot be applied to the same rule). The only additional syntax is the inclusion of the keyword impair at the end of the rule.
The project folder includes a firewall_rules.txt
file. I'd recommend
reading through this file to get familiar with the types of rules
included in order to get a sense for how your firewall should behave.
Coding
Your task is to implement the logic described above. The start file is named lab_7/firewall.py
.
✅ In the report, show how you implement the firewall rules.