Building a Honeypot Defense System with Python and Scapy

Build a Python-based honeypot defense system with Scapy that detects port scanning, lures attackers using decoy ports, tracks reconnaissance activity, and automatically blocks malicious IPs.
  · 13 min read · Updated feb 2026 · Ethical Hacking · Packet Manipulation Using Scapy

Welcome! Meet our Python Code Assistant, your new coding buddy. Why wait? Start exploring now!

In this tutorial, we’ll be building a honeypot. A honeypot is a cybersecurity mechanism that acts as a decoy to lure attackers away from critical systems. It's a trap designed to detect, deflect, or study hacking attempts. We’ll be building a port-scanning variant.

Information gathering, particularly through port scanning, is often the first step in a cyber attack. Attackers scan networks looking for:

  • Open ports that might be vulnerable
  • Misconfigured services they can exploit
  • Entry points into your system

A honeypot defense system helps you:

  • Detect reconnaissance activities - Know when someone is probing your network
  • Identify attackers early - Catch them before they find real vulnerabilities
  • Automatically block threats - Stop malicious actors from accessing your services
  • Gather intelligence - Learn what attackers are looking for and their methods
  • Reduce attack surface - Hide real services from unauthorized users

Our Three-Tier Approach

This tutorial implements a sophisticated three-tier port security model:

  • Tier 1 - Public Ports: Services that should be accessible to everyone (like a web server on port 80)
  • Tier 2 - Honeypot Ports: Fake services that appear open to lure attackers. Anyone accessing these gets tracked and blocked after repeated attempts.
  • Tier 3 - Protected Ports: Real services that only appear accessible to trusted IP addresses. Unknown users can't even see these ports exist.

Prerequisites and Installation

System Requirements

For this demonstration, I'll make use of two machines:

  • Defender Machine (Ubuntu): The system you want to protect
  • Testing Machine (Kali Linux or any system with nmap): For testing the honeypot
  • Both machines should be on the same network so they’re reachable.

Installing Required Software

On Ubuntu (Defender Machine):

Install Scapy:

$ sudo pip3 install scapy

On Kali Linux (Testing Machine):

Kali usually comes with nmap pre-installed, but verify:

$ nmap --version

If not installed:

$ sudo apt update
$ sudo apt install nmap

Network Configuration

1. Find your Ubuntu IP address:

$ ip addr show

Or 

$ Ifconfig

Take note of your network interface (e.g., enp0s8, eth0) and IP address (e.g., 192.168.56.101). In my case, the interface I’ll be using is enp0s8 and my IP address is 192.168.56.101 as seen in the screenshot above.

You can get your Kali IP with the same commands.

2. Test connectivity:

From Kali, ping Ubuntu

$ ping 192.168.56.101

Walking through the Code

Open up a Python file, name it lock_port_scanner.py. The honeypot defense system is built with Python and Scapy. Let’s go over the code together.

We start by importing the necessary modules, specifying our interface (very important) and adding key variables like allowed IPs, decoy ports, max attempts and log file.

#!/usr/bin/env python3
"""Detects port scanners using decoy ports and blocks malicious IPs"""
from scapy.all import *
from datetime import datetime
import sys

# ==================== NETWORK INTERFACE ====================
conf.iface = "enp0s8"  # Specify your network interface
# ==================== CONFIGURATION ====================
DEFENDER_IP = "192.168.56.101"  # Change this to your Ubuntu IP
# Three-tier port system
PUBLIC_PORTS = [80]  # Open to everyone (realistic services)
HONEYPOT_PORTS = [8080, 8443, 3389, 3306]  # Decoy ports to trap attackers
PROTECTED_PORTS = [443, 53, 22, 5432]  # Hidden unless IP is allowed

ALLOWED_IPS = [
    "192.168.1.100",  # Add your Kali IP here
    "192.168.1.1",    # Add other trusted IPs
]
MAX_ATTEMPTS = 3  # Block after this many honeypot accesses (changeable)
LOG_FILE = "honeypot_logs.txt"

# ==================== GLOBALS ====================
blocked_ips = []
attempt_tracker = {}  # {IP: attempt_count}
total_scans = 0
total_blocks = 0

The defender_ip variable specifies which IP address to protect. Replace this with your actual Ubuntu IP.

conf.iface = "enp0s8"  # Network interface

The line above tells Scapy which network interface to listen on. Replace with your actual interface name from ip addr show.

PUBLIC_PORTS = [80]
HONEYPOT_PORTS = [8080, 8443, 3389, 22, 3306]
PROTECTED_PORTS = [443, 53, 5432]

These define the three-tier model:

  • PUBLIC_PORTS: Always accessible (like a public website)
  • HONEYPOT_PORTS: Trap ports that appear open to lure attackers
  • PROTECTED_PORTS: Only visible to trusted IPs

For the other variables too - like allowed_ips and max_attempts and log_file, feel free to adjust them to fit your scenario.

The honeypot_logs.txt will be saved in your current directory.

Next, we create helper functions - functions for different use cases.

def log_message(message, color_code=None):
    """Print and save log messages with timestamps"""
    timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
    log_entry = f"[{timestamp}] {message}"
    # Color output for terminal
    if color_code:
        print(f"\033[{color_code}m{log_entry}\033[0m")
    else:
        print(log_entry)
    # Save to file
    with open(LOG_FILE, "a") as f:
        f.write(log_entry + "\n")

The log_messages function writes messages to both the console and log file with timestamps and color coding.

Parameters:

  • message: The text to log
  • color_code: Optional ANSI color code for terminal display
def is_allowed_ip(ip):
    """Check if IP is in the allowlist"""
    return ip in ALLOWED_IPS

is_allowed_ip(ip)

Checks if an IP address is in the trusted whitelist.

Parameters:

  • ip: The IP address to check

Returns: True if the IP is allowed, False otherwise

def track_attempt(ip):
    """Track honeypot access attempts and return current count"""
    if ip not in attempt_tracker:
        attempt_tracker[ip] = 0
    attempt_tracker[ip] += 1
    return attempt_tracker[ip]

The track_attempt function increments and returns the number of times an IP has accessed honeypot ports.

Parameters:

  • ip: The IP address to track

Returns: Current attempt count for that IP

def block_ip(ip):
    """Add IP to blocklist"""
    global total_blocks
    if ip not in blocked_ips:
        blocked_ips.append(ip)
        total_blocks += 1
        log_message(f"[!] IP BLOCKED: {ip}", "91")  # Red

The block_ip(ip) function adds an IP to the blocklist and logs the action.

Parameters:

  • ip: The IP address to block

It enforces security by preventing further access from malicious IPs.

def create_response(packet, flags):
    """Create a TCP response packet"""
    if packet.haslayer(IP):
        response = (
            Ether(src=packet[Ether].dst, dst=packet[Ether].src) /
            IP(src=packet[IP].dst, dst=packet[IP].src) /
            TCP(
                sport=packet[TCP].dport,
                dport=packet[TCP].sport,
                flags=flags,
                seq=0,
                ack=packet[TCP].seq + 1
            )
        )
    else:  # IPv6
        response = (
            Ether(src=packet[Ether].dst, dst=packet[Ether].src) /
            IPv6(src=packet[IPv6].dst, dst=packet[IPv6].src) /
            TCP(
                sport=packet[TCP].dport,
                dport=packet[TCP].sport,
                flags=flags,
                seq=0,
                ack=packet[TCP].seq + 1
            )
        )
    return response

The create_response function crafts a TCP response packet with specified flags.

Parameters:

  • packet: The original incoming packet
  • flags: TCP flags to set (e.g., "SA" for SYN-ACK, "RA" for RST-ACK)

Returns: A crafted response packet

  • Reverses source and destination addresses
  • Supports both IPv4 and IPv6
  • Sets proper TCP sequence and acknowledgment numbers

It allows the script to control what the attacker sees - making ports appear open or closed.

def handle_packet(packet):
    """Process incoming TCP packets with three-tier security"""
    global total_scans
    # Only process SYN packets (connection attempts)
    if packet[TCP].flags != "S":
        return
    # Extract source IP and destination port
    if packet.haslayer(IP):
        source_ip = packet[IP].src
    else:
        source_ip = packet[IPv6].src
   
    dest_port = packet[TCP].dport
    total_scans += 1
    # ===== CHECK IF IP IS BLOCKED FIRST =====
    if source_ip in blocked_ips:
        # Drop packet silently - no response to show as "filtered" in nmap
        log_message(f"[-] Blocked IP {source_ip} denied access to port {dest_port}", "90")
        return  # Don't send any response - this makes it appear "filtered"
    # ===== PUBLIC PORTS (open to everyone) =====
    if dest_port in PUBLIC_PORTS:
        # Let the real service handle it - no response needed from script
        log_message(f"[+] Public port {dest_port} accessed by {source_ip}", "94")  # Blue
        return
    # ===== HONEYPOT PORTS (trap for attackers) =====
    if dest_port in HONEYPOT_PORTS:
        # Always respond with SYN-ACK to appear "open"
        response = create_response(packet, "SA")
        sendp(response, verbose=False)
        # Check if IP is allowed
        if is_allowed_ip(source_ip):
            log_message(
                f"[+] HONEYPOT ACCESS from {source_ip}:{dest_port}\n"
                f"[!]    Status: TRUSTED IP (allowed)",
                "92"  # Green
            )
        else:
            # Track attempts for unknown IPs
            attempts = track_attempt(source_ip)
            log_message(
                f"[!] HONEYPOT ACCESS from {source_ip}:{dest_port}\n"
                f"[-]    Status: UNKNOWN IP - POTENTIAL ATTACKER\n"
                f"[!]    Strike {attempts}/{MAX_ATTEMPTS}",
                "93"  # Yellow
            )
            # Block after max attempts
            if attempts >= MAX_ATTEMPTS:
                block_ip(source_ip)
        return
    # ===== PROTECTED PORTS (only allowed IPs) =====
    if dest_port in PROTECTED_PORTS:
        if is_allowed_ip(source_ip):
            # Respond with SYN-ACK for allowed IPs
            response = create_response(packet, "SA")
            sendp(response, verbose=False)
            log_message(f"[!] Protected port {dest_port} accessed by TRUSTED IP {source_ip}", "92")
        else:
            # Drop packet silently for unknown IPs (appears filtered)
            log_message(f"[!] Protected port {dest_port} hidden from {source_ip}", "93")
        return
    # ===== OTHER PORTS (default behavior - drop silently) =====
    # Unknown ports are silently dropped (appear filtered)

The handle_packet is the core function that processes every incoming TCP packet and implements the three-tier security logic.

Flow of execution:

Step 1: Filter packets

if packet[TCP].flags != "S":
    return

Only processes SYN packets (new connection attempts) ignores ongoing connections.

Step 2: Extract information

source_ip = packet[IP].src
dest_port = packet[TCP].dport

Identifies who is connecting and which port they're targeting.

Step 3: Check if IP is blocked

if source_ip in blocked_ips:
    log_message(f"Blocked IP {source_ip} denied access")
    return  # Drop packet silently

If the IP is already blocked, drop the packet without responding. This makes all ports appear "filtered" to the attacker.

Step 4: Handle public ports

if dest_port in PUBLIC_PORTS:
    log_message(f"Public port {dest_port} accessed")
    return

Public ports are left alone - the real service handles them. Just logs the access.

Step 5: Handle honeypot ports

if dest_port in HONEYPOT_PORTS:
    response = create_response(packet, "SA")
    sendp(response, verbose=False)

Always responds with SYN-ACK to make the port appear "open" regardless of whether a service is actually running.

Then checks if the IP is trusted:

  • Trusted IP: Logs as allowed access
  • Unknown IP: Increments strike counter and blocks after MAX_ATTEMPTS

Step 6: Handle protected ports

if dest_port in PROTECTED_PORTS:
    if is_allowed_ip(source_ip):
        response = create_response(packet, "SA")
        sendp(response, verbose=False)
    else:
        # Drop silently
  • Trusted IPs: Port appears open
  • Unknown IPs: Port appears filtered (invisible)

This function is the brain of the system - making real-time decisions about how to respond to each connection attempt.

def print_banner():
    """Display startup information"""
    print("\n" + "="*60)
    print("[+]  HONEYPOT DEFENSE SYSTEM ACTIVE")
    print("="*60)
    print(f"Defending IP: {DEFENDER_IP}")
    print(f"Public Ports (open to all): {PUBLIC_PORTS}")
    print(f"Honeypot Ports (trap): {HONEYPOT_PORTS}")
    print(f"Protected Ports (allowed IPs only): {PROTECTED_PORTS}")
    print(f"Allowed IPs: {ALLOWED_IPS}")
    print(f"Block Threshold: {MAX_ATTEMPTS} attempts")
    print(f"Log File: {LOG_FILE}")
    print("="*60)
    print("Monitoring traffic... Press Ctrl+C to stop\n")

print_banner() displays the system configuration when the script starts.

def print_summary():
    """Display statistics on exit"""
    print("\n" + "="*60)
    print("[+] SESSION SUMMARY")
    print("="*60)
    print(f"Total scans detected: {total_scans}")
    print(f"IPs blocked: {total_blocks}")
    print(f"Current blocklist: {blocked_ips if blocked_ips else 'None'}")
    print("="*60 + "\n")

print_summary() shows statistics when you stop the script (Ctrl+C).

Information displayed:

  • Total scans detected
  • Number of IPs blocked
  • Current blocklist

Provides insight into how much attack activity occurred during the session.

def main():
    """Main execution"""
    print_banner()
    # Create BPF filter
    packet_filter = f"dst host {DEFENDER_IP} and tcp"
    try:
        # Start sniffing
        sniff(filter=packet_filter, prn=handle_packet, store=False)
    except KeyboardInterrupt:
        print("\n\n[!] Stopping honeypot defense...")
        print_summary()
        sys.exit(0)

if __name__ == "__main__":
    # Check for root privileges
    if os.geteuid() != 0:
        print("[!] This script requires root privileges. Run with: sudo python3 honeypot_defender.py")
        sys.exit(1)
    main()

main() is the entry point that starts the packet capture.

Catches Ctrl+C gracefully and displays the summary.

Important: Kernel RST Prevention

Before running the script, you must disable the kernel's automatic RST (reset) packets:

$ sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -j DROP

Why this is necessary: By default, when someone tries to connect to a closed port, the Linux kernel automatically sends a RST packet saying "port closed." This would interfere with our honeypot's ability to control responses. By dropping RST packets, our script has full control over what the attacker sees.

To remove this rule later:

$ sudo iptables -D OUTPUT -p tcp --tcp-flags RST RST -j DROP

Running the Honeypot Defense System

Please make sure to run the script as root.

On Ubuntu:

$ sudo python3 block_port_scan.py

You should see:

Testing Scenarios

Scenario 1: Legitimate User (Trusted IP)

I will try to scan for open ports as an allowed user. Remember to add the IP address to the code.

 

On kali:

$ sudo nmap -sS -sV 192.168.56.101 -T4

Result:

 

On Ubuntu:

As Expected:

  • All ports appear open (public, honeypot, and protected)
  • Activity logged as "TRUSTED IP"
  • No blocking occurs

Scenario 2: Attacker (Unknown IP)

On Kali:

$ sudo nmap -sS -sV 192.168.56.101 -T4

Result:

On Ubuntu:

We can see that in the first scan, we got the three non-existent (honeypots) opened. And from the ubuntu terminal, we can see that after strike three, you get blocked. 

Now, trying another scan (after getting blocked)

$ sudo nmap -sS -sV 192.168.56.101 -p 8080,8443,3389 -Pn

Result:

As Expected:

First scan:

  • Public ports: Open
  • Honeypot ports: Open (trap working!)
  • Protected ports: Filtered (hidden)
  • Log shows "Strike 1/3"

Second scan:

  • Same visibility
  • Log shows "Strike 2/3"

Third scan:

  • Log shows "Strike 3/3" and "IP BLOCKED"

Fourth scan (after blocking):

  • All ports: Filtered
  • Every attempt logged as "Blocked IP denied access"

Customization Options

Change Block Threshold

Want to be more lenient or stricter?

MAX_ATTEMPTS = 5  # Block after 5 attempts instead of 3

Add More Honeypot Ports

Common ports attackers scan:

HONEYPOT_PORTS = [
    22,    # SSH
    23,    # Telnet
    3306,  # MySQL
    3389,  # RDP
    5432,  # PostgreSQL
    6379,  # Redis
    8080,  # HTTP Proxy
    8443,  # HTTPS Alt
]

Add More Protected Ports

Ports you may want to hide from attackers:

PROTECTED_PORTS = [
    443,   # HTTPS
    53,    # DNS
    22,    # SSH (if you want to hide it)
    3306,  # Database
]

Whitelist Multiple IPs

ALLOWED_IPS = [
    "192.168.56.1",   # Admin workstation
    "10.0.0.50",      # Monitoring server
    "172.16.0.100",   # Backup system
]

Final Notes

Security Considerations

  1. This is a detection and deterrent system - It identifies and blocks attackers but doesn't replace a proper firewall.
  2. Run with caution - The script requires root privileges to capture and send packets. Only run on systems you control.
  3. False positives - Legitimate security scanners or network diagnostics might trigger the honeypot. Whitelist known IPs.
  4. Log rotation - For long-term deployment, implement log rotation to prevent the log file from growing too large.
  5. Persistent blocking - Currently, blocks are only maintained while the script runs. For permanent blocking, integrate with iptables.

Best Practices

  1. Monitor regularly - Check logs daily for suspicious activity
  2. Update whitelists - Keep ALLOWED_IPS current
  3. Test thoroughly - Verify legitimate users can access services
  4. Combine with other security - Use alongside firewalls, IDS/IPS, and proper system hardening
  5. Keep it running - Consider setting up as a system service for automatic startup

Integration Ideas

This honeypot can be extended to:

  • Send email/SMS alerts when attackers are detected
  • Automatically add blocked IPs to iptables for permanent blocking
  • Export logs to SIEM systems for centralized monitoring
  • Create a web dashboard showing real-time attack activity
  • Integrate with threat intelligence feeds to identify known malicious IPs

I hope you enjoyed this one. See you in the next one! For now, check out similar tutorials

Liked what you read? You'll love what you can learn from our AI-powered Code Explainer. Check it out!

View Full Code Transform My Code
Sharing is caring!



Read Also



Comment panel

    Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!