#!/usr/bin/env python3
''' 
    Detect simple port scans (TCP SYN / UDP probes) by tracking
    how many unique destination ports a source IP hits within
    a sliding time window.

	Author: Tim Pierson, Dartmouth CS60, Fall 2026
		Adapted from ChatGPT solution

	run: python3 port_scan_detector2.py
'''

from scapy.all import sniff, IP, TCP, UDP
import time
import sys
from collections import defaultdict, deque

# ---- Configuration ----
TIME_WINDOW = 5.0         # seconds to keep recent packets
PORT_THRESHOLD = 20       # number of unique destination ports to trigger alert
MIN_PACKETS = 20          # minimum total packets before alert
IFACE = None              # network interface, or None for default
FILTER = "tcp or udp"     # BPF filter for scapy sniff
# ------------------------

# src_ip -> deque of (timestamp, dest_port)
packet_history = defaultdict(deque)
last_alert_time = {}

def packet_handler(pkt):
    global packet_history, last_alert_time
    if IP not in pkt:
        return

    src = pkt[IP].src
    dport = None

    # Only track TCP SYNs and UDP packets
    if TCP in pkt:
        tcp = pkt[TCP]
        if tcp.flags & 0x02 and not (tcp.flags & 0x10):  # SYN and not ACK
            dport = tcp.dport
    elif UDP in pkt:
        dport = pkt[UDP].dport

    if dport is None:
        return

    # Get history for this source address
    now = time.time()
    dq = packet_history[src]
    dq.append((now, dport))

    # Remove old entries outside time window
    cutoff = now - TIME_WINDOW
    while dq and dq[0][0] < cutoff:
        dq.popleft()

    # Count distinct ports and total packets
    ports = {p for _, p in dq}
    unique_ports = len(ports)
    total_packets = len(dq)

    # Check if it looks like a scan
    if unique_ports >= PORT_THRESHOLD and total_packets >= MIN_PACKETS:
        last = last_alert_time.get(src, 0)
        if now - last > TIME_WINDOW:  # avoid spamming
            last_alert_time[src] = now
            print("=" * 50)
            print(f"[ALERT] Possible port scan from {src}")
            print(f"  Unique ports: {unique_ports}")
            print(f"  Packets seen in {TIME_WINDOW}s: {total_packets}")
            print(f"  Time: {time.strftime('%Y-%m-%d %H:%M:%S')}")
            print("=" * 50)

if __name__ == "__main__":
    #check parameters
    if len(sys.argv) != 2:
        print(f"Usage: python3 {sys.argv[0]} <interface>")
        exit()
    IFACE = sys.argv[1]
    
    print("Starting simple port scan detector")
    print(f"Window={TIME_WINDOW}s, Threshold={PORT_THRESHOLD}, MinPackets={MIN_PACKETS}")
    print("Press Ctrl+C to stop.")
    sniff(prn=packet_handler, store=False, filter=FILTER, iface=IFACE)



