Build Your Own Firewall: The Ultimate Python Rule Simulator Guide

The CyberSec Guru

Updated on:

Build a Firewall in Python

If you like this post, then please share it:

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Why your support matters: Zero paywalls: Keep the main content 100% free for learners worldwide, Writeup Access: Get complete in-depth writeup with scripts access within 12 hours of machine drop.

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

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:

  1. A Packet Class to mock real-world network traffic.
  2. A Rule Parser that understands IP ranges (CIDR), protocols, and ports.
  3. A Firewall Engine that makes millisecond decisions to ALLOW or BLOCK traffic.
  4. 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.

Build a Firewall
Build a Firewall

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:

  1. Source IP: Who sent this? (e.g., 192.168.1.50)
  2. Destination IP: Where is it going? (e.g., 10.0.0.1)
  3. Protocol: How should we talk? (TCP, UDP, ICMP)
  4. 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.”
Stateless vs Stateful Firewall
Stateless vs Stateful Firewall

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.
  • ipaddress module: 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.5 matches 192.168.1.0/24 -> TRUE
  • 10.0.0.5 matches 192.168.1.0/24 -> FALSE

We need to handle this math efficiently.

CIDR Notation Explained
CIDR Notation Explained

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:

  1. Packet arrives.
  2. Firewall iterates through rules from Top to Bottom.
  3. If a rule matches, the action (ALLOW or BLOCK) is taken immediately.
  4. 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
Rules.conf File
Rules.conf File

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 SYN flag 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.
Stateful Firewall Logic
Stateful Firewall Logic

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.conf might have a typo in the IP address, or you forgot the CIDR suffix (e.g., using 192.168.1.1 instead of 192.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:

  1. GitHub: Push this code. Ensure your README.md explains how you optimized the rule matching.
  2. LinkedIn: Post a video of the terminal output. Tag it #CyberSecurity #Python.
  3. 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.

Buy me A Coffee!

Support The CyberSec Guru’s Mission

🔐 Fuel the cybersecurity crusade by buying me a coffee! Your contribution powers free tutorials, hands-on labs, and security resources.

Why your support matters:
  • Writeup Access: Get complete writeup access within 12 hours
  • Zero paywalls: Keep the main content 100% free for learners worldwide

Perks for one-time supporters:
☕️ $5: Shoutout in Buy Me a Coffee
🛡️ $8: Fast-track Access to Live Webinars
💻 $10: Vote on future tutorial topics + exclusive AMA access

“Your coffee keeps the servers running and the knowledge flowing in our fight against cybercrime.”☕ Support My Work

Buy Me a Coffee Button

If you like this post, then please share it:

Projects

Discover more from The CyberSec Guru

Subscribe to get the latest posts sent to your email!

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Discover more from The CyberSec Guru

Subscribe now to keep reading and get access to the full archive.

Continue reading