You have read the textbooks. You have memorized the OSI model. You know that Port 80 is HTTP and Port 443 is HTTPS. But if I handed you a raw raw network packet and asked you to write a program to decide its fate, could you do it?
Most aspiring cybersecurity professionals are stuck in “tutorial hell,” watching endless videos but never building anything substantial. They know the vocabulary of security, but not the physics of it.
Today, that changes.
This guide is not a quick script. This is a comprehensive, deep-dive engineering masterclass. We are going to build a Firewall Rule Simulator from scratch using Python. We will not be relying on pre-made firewall tools like iptables or ufw for the logic. We are going to write the logic ourselves.
What you will build:
- A Packet Class to mock real-world network traffic.
- A Rule Parser that understands IP ranges (CIDR), protocols, and ports.
- A Firewall Engine that makes millisecond decisions to
ALLOWorBLOCKtraffic. - A Simulation Loop to test your defense against thousands of randomized packets.
By the end of this guide, you will have a portfolio-ready project that demonstrates true understanding of network security, data structures, and Python programming.
Let’s build the digital shield.

The Theory – Thinking Like a Packet
Before we write a single line of code, we must understand the invisible war happening inside your network cables. A firewall is essentially a border patrol agent. It checks credentials (headers) against a list of laws (rules).
The Anatomy of a Network Packet
When data travels across the internet, it doesn’t teleport. It is chopped up into tiny chunks called packets. For our simulator, we care about the “headers”—the metadata that tells routers where the data is going.
We will focus on these four critical attributes:
- Source IP: Who sent this? (e.g.,
192.168.1.50) - Destination IP: Where is it going? (e.g.,
10.0.0.1) - Protocol: How should we talk? (TCP, UDP, ICMP)
- Port: Which door should we knock on? (80, 443, 22)
Stateless vs. Stateful Inspection
There are two main types of firewalls. We will start by building a Stateless firewall and discuss Stateful concepts later.
- Stateless (Packet Filtering): The firewall looks at each packet in isolation. It has no memory. If a packet says “I am a response to your request,” the stateless firewall says, “I don’t care, does my rule list say you are allowed?”
- Stateful: The firewall remembers connections. If you send a request to Google, the firewall remembers “User X is talking to Google.” When Google replies, the firewall checks its memory table and says, “Ah, this is the reply User X was waiting for. Come on in.”

Setting Up Your Environment
We need a professional-grade setup. We aren’t just scripting; we are engineering.
Prerequisites
- Python 3.8+: We need modern type hinting support.
- VS Code or PyCharm: Any good IDE.
- Terminal: PowerShell, Bash, or Zsh.
Project Structure
Create a new folder named py-firewall-sim. Inside, create the following structure:
py-firewall-sim/
│
├── main.py # The entry point of our simulation
├── firewall.py # The core logic engine
├── packet.py # Class definition for network packets
├── rules.conf # The file where we define our security policies
├── utils.py # Helper functions (IP parsing, etc.)
└── README.md # Documentation for your portfolio
The Code – Phase I: The Packet
We cannot filter what we cannot define. Our first task is to create a Python class that represents a network packet.
Create packet.py.
import random
import ipaddress
class Packet:
"""
Represents a simulated network packet with standard headers.
"""
def __init__(self, source_ip, dest_ip, protocol, port, payload=""):
self.source_ip = source_ip
self.dest_ip = dest_ip
self.protocol = protocol.upper()
self.port = int(port)
self.payload = payload
def __repr__(self):
return f"Packet(src={self.source_ip}, dst={self.dest_ip}, proto={self.protocol}, port={self.port})"
@staticmethod
def generate_random():
"""
Factory method to generate a random packet for testing purposes.
"""
protocols = ['TCP', 'UDP', 'ICMP']
# Generate random public IPs
src = f"{random.randint(1, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}.{random.randint(0, 255)}"
dst = f"192.168.1.{random.randint(1, 100)}" # Simulating traffic coming TO our internal network
proto = random.choice(protocols)
port = random.randint(1, 65535)
return Packet(src, dst, proto, port)
if __name__ == "__main__":
# Quick test
pkt = Packet.generate_random()
print(f"Generated Test Packet: {pkt}")
Deep Dive:
__repr__: This magic method defines how the object looks when printed. Crucial for debugging.ipaddressmodule: Python’s built-in library for handling IP math. We will use this heavily later.- Factory Pattern:
generate_random()allows us to easily spam our firewall with noise later to test performance.
The Code – Phase II: The Rules Engine
A firewall is useless without rules. We need a way to express things like “Block all traffic from China” or “Allow SSH only from the Admin PC.”
Understanding CIDR (Classless Inter-Domain Routing)
You will often see IPs written like 192.168.1.0/24. The /24 is the subnet mask. It tells the computer which part of the address is the “network” and which part is the “host.”
192.168.1.5matches192.168.1.0/24-> TRUE10.0.0.5matches192.168.1.0/24-> FALSE
We need to handle this math efficiently.

The Rule Class
Create a file named firewall.py. We will start by defining what a Rule looks like.
import ipaddress
class Rule:
def __init__(self, action, source_ip_cidr, port=None, protocol=None):
self.action = action.lower() # 'allow' or 'block'
self.source_network = ipaddress.ip_network(source_ip_cidr, strict=False)
self.port = int(port) if port and port != "*" else None
self.protocol = protocol.upper() if protocol and protocol != "*" else None
def matches(self, packet):
"""
Checks if a given packet matches this rule's criteria.
Returns True if match, False otherwise.
"""
# 1. Check IP Address (The most expensive operation usually)
packet_ip = ipaddress.ip_address(packet.source_ip)
if packet_ip not in self.source_network:
return False
# 2. Check Protocol (if rule specifies one)
if self.protocol and self.protocol != packet.protocol:
return False
# 3. Check Port (if rule specifies one)
if self.port and self.port != packet.port:
return False
return True
def __repr__(self):
return f"Rule({self.action.upper()}: IP={self.source_network}, Port={self.port}, Proto={self.protocol})"
Key Engineering Decision: We use ipaddress.ip_network. This handles the complex bitwise logic required to check if 192.168.1.55 falls inside 192.168.1.0/24. Never write your own regex for IP validation unless you want to suffer.
The Code – Phase III: The Firewall Class
Now we build the brain. The firewall needs to hold a list of rules and process packets against them sequentially.
The Logic Flow:
- Packet arrives.
- Firewall iterates through rules from Top to Bottom.
- If a rule matches, the action (
ALLOWorBLOCK) is taken immediately. - If NO rule matches, what happens? (The Implicit Deny).
Security Best Practice: Real firewalls default to DROP if no rule matches. This is called “Whitelisting.” You only allow what you explicitly trust.
Update firewall.py to include the Firewall class:
class Firewall:
def __init__(self, rules_file):
self.rules = []
self.load_rules(rules_file)
def load_rules(self, filepath):
print(f"Loading rules from {filepath}...")
with open(filepath, 'r') as f:
for line in f:
line = line.strip()
# Skip comments and empty lines
if not line or line.startswith('#'):
continue
# Expected format: ACTION SOURCE_IP PORT PROTOCOL
# Example: BLOCK 10.0.0.0/8 80 TCP
parts = line.split()
action = parts[0]
src_ip = parts[1]
port = parts[2] if len(parts) > 2 else "*"
proto = parts[3] if len(parts) > 3 else "*"
rule = Rule(action, src_ip, port, proto)
self.rules.append(rule)
print(f"Loaded: {rule}")
def inspect_packet(self, packet):
"""
The core decision engine.
"""
for rule in self.rules:
if rule.matches(packet):
return rule.action, rule # Return the decision and the rule responsible
return "block", "Default Policy" # Implicit Deny
Creating the Rule Set
Now we need a configuration file. This mimics how systems like iptables or Cisco IOS work.
Create rules.conf:
# Firewall Configuration File
# Format: ACTION SOURCE_IP PORT PROTOCOL
# 1. Allow local admin traffic on SSH (Port 22)
allow 192.168.1.0/24 22 TCP
# 2. Block known malicious subnet
block 203.0.113.0/24 * *
# 3. Allow HTTP traffic from anywhere
allow 0.0.0.0/0 80 TCP
# 4. Allow HTTPS traffic from anywhere
allow 0.0.0.0/0 443 TCP
# 5. Block specific suspicious UDP traffic
block 0.0.0.0/0 5000 UDP

The Simulation Loop (Putting it Together)
We need a main.py to act as the world around our firewall. It will generate traffic and feed it to the engine.
# main.py
import time
from packet import Packet
from firewall import Firewall
def start_simulation(firewall, num_packets=20):
print(f"\n--- STARTING SIMULATION ({num_packets} Packets) ---")
print(f"{'RESULT':<10} | {'SOURCE IP':<15} | {'DST PORT':<8} | {'PROTO':<6} | {'RULE MATCHED'}")
print("-" * 80)
allowed_count = 0
blocked_count = 0
for _ in range(num_packets):
# 1. Create a random packet
pkt = Packet.generate_random()
# 2. Ask firewall for a decision
decision, rule = firewall.inspect_packet(pkt)
# 3. Log the result
color = "\033[92m" if decision == "allow" else "\033[91m" # Green for allow, Red for block
reset = "\033[0m"
print(f"{color}{decision.upper():<10}{reset} | {pkt.source_ip:<15} | {str(pkt.port):<8} | {pkt.protocol:<6} | {rule}")
if decision == "allow":
allowed_count += 1
else:
blocked_count += 1
time.sleep(0.1) # Add delay for dramatic effect
print("-" * 80)
print(f"Simulation Complete.")
print(f"Allowed: {allowed_count}")
print(f"Blocked: {blocked_count}")
if __name__ == "__main__":
fw = Firewall("rules.conf")
start_simulation(fw, num_packets=50)
Running the Simulator
Open your terminal and run: python main.py
You will see a Matrix-style stream of packets being analyzed in real-time. Some will be green (Allowed), and some will be red (Blocked).
Advanced Concepts & “Viral” Features
If you want this project to guarantee you a job, you don’t stop at the basics. Here is how we elevate this project to Senior Engineer level.
Optimization: Order of Operations
In our current code, if we have 10,000 rules, every packet might check 10,000 comparisons. This is O(N). Pro Tip: Move your most common rules to the TOP of rules.conf. If 90% of your traffic is HTTP, put the “Allow Port 80” rule first. The firewall will match it instantly and skip the other 9,999 rules.
Logging and Analytics
Real firewalls don’t just print to the screen; they log to files for forensic analysis (SIEM). Modify the inspect_packet method to append results to a firewall_log.csv file. You can then use Pandas or Excel to create a pie chart showing “Top Blocked IPs” or “Most Attacked Ports.”
Implementing “Stateful” Logic
To make this stateful, you would add a connection_table (a Python Dictionary) to the Firewall class.
- Logic: When a TCP packet with the
SYNflag is allowed, record the(SourceIP, DestIP, Port)tuple in the dictionary. - Logic: When the return packet comes back, check the dictionary. If the tuple exists (reversed), allow it automatically without checking the rule list.

Troubleshooting Guide
Even the best code breaks. Here are common issues you might face building this.
Issue 1: ValueError: '...' does not appear to be an IPv4 or IPv6 network
- Cause: Your
rules.confmight have a typo in the IP address, or you forgot the CIDR suffix (e.g., using192.168.1.1instead of192.168.1.1/32). - Fix: Ensure strict formatting in your config file.
Issue 2: All packets are being blocked.
- Cause: The “Implicit Deny” is catching everything.
- Fix: Check if your generated random IPs actually match the specific ranges in your
rules.conf. Try adding a “Allow All” rule (allow 0.0.0.0/0 * *) at the top to verify logic is working, then remove it.
Conclusion: Next Steps for Your Career
Congratulations. You haven’t just written code; you’ve built a security appliance simulation.
How to leverage this for your career:
- GitHub: Push this code. Ensure your
README.mdexplains how you optimized the rule matching. - LinkedIn: Post a video of the terminal output. Tag it #CyberSecurity #Python.
- Resume: Add this under “Projects.” Bullet point: “Designed a custom packet filtering engine in Python capable of processing ACLs and validating CIDR ranges.”
The difference between a script kiddie and a security engineer is understanding how the tools work under the hood. You now possess that understanding.
Ready for the next challenge? Try implementing a DDoS Simulator that floods your firewall class and measures how many packets per second it can handle before crashing.
Happy Coding and Stay Secure.








