#!/usr/bin/env python3
# For authorized testing in lab/CTF environments only
"""IPv6 vulnerability assessment agent using scapy for NDP/RA analysis."""

import argparse
import json
import logging
import sys
from datetime import datetime
from typing import List

try:
    from scapy.all import (
        sniff, sendp, sr, get_if_hwaddr, get_if_addr6, conf,
        Ether, IPv6, ICMPv6ND_RA, ICMPv6ND_NA, ICMPv6ND_NS,
        ICMPv6NDOptSrcLLAddr, ICMPv6NDOptPrefixInfo, ICMPv6NDOptRDNSS,
        ICMPv6NDOptDstLLAddr,
    )
    from scapy.layers.inet6 import ICMPv6EchoRequest
except ImportError:
    sys.exit("scapy is required: pip install scapy")

logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
logger = logging.getLogger(__name__)


def discover_ipv6_hosts(interface: str, timeout: int = 5) -> List[dict]:
    """Discover IPv6 hosts by sending ICMPv6 Echo to all-nodes multicast."""
    target = "ff02::1"
    pkt = IPv6(dst=target) / ICMPv6EchoRequest()
    logger.info("Sending ICMPv6 Echo to all-nodes multicast on %s", interface)

    replies = []
    try:
        ans, _ = sr(
            pkt, iface=interface, timeout=timeout, verbose=0, multi=True
        )
        for sent, recv in ans:
            src = recv[IPv6].src
            replies.append({"ipv6_address": src, "mac": recv.src if hasattr(recv, "src") else ""})
    except Exception as exc:
        logger.error("Discovery failed: %s", exc)

    logger.info("Discovered %d IPv6 hosts", len(replies))
    return replies


def capture_router_advertisements(interface: str, timeout: int = 10) -> List[dict]:
    """Capture and analyze Router Advertisement packets on the network."""
    logger.info("Capturing Router Advertisements on %s for %ds", interface, timeout)
    ras = []

    def process_ra(pkt):
        if pkt.haslayer(ICMPv6ND_RA):
            ra_info = {
                "src_ip": pkt[IPv6].src,
                "src_mac": pkt.src if hasattr(pkt, "src") else "",
                "router_lifetime": pkt[ICMPv6ND_RA].routerlifetime,
                "managed_flag": bool(pkt[ICMPv6ND_RA].M),
                "other_flag": bool(pkt[ICMPv6ND_RA].O),
                "prefixes": [],
                "dns_servers": [],
            }
            if pkt.haslayer(ICMPv6NDOptPrefixInfo):
                layer = pkt[ICMPv6NDOptPrefixInfo]
                ra_info["prefixes"].append({
                    "prefix": layer.prefix,
                    "prefix_len": layer.prefixlen,
                    "valid_lifetime": layer.validlifetime,
                })
            if pkt.haslayer(ICMPv6NDOptRDNSS):
                ra_info["dns_servers"] = pkt[ICMPv6NDOptRDNSS].dns
            ras.append(ra_info)
            logger.info("RA from %s (lifetime=%d)", ra_info["src_ip"], ra_info["router_lifetime"])

    sniff(iface=interface, filter="icmp6", prn=process_ra, timeout=timeout, store=0)
    return ras


def detect_rogue_ra(ras: List[dict], known_routers: List[str]) -> List[dict]:
    """Identify rogue Router Advertisements from unknown sources."""
    rogues = []
    for ra in ras:
        if ra["src_ip"] not in known_routers:
            rogues.append({
                "alert": "ROGUE_ROUTER_ADVERTISEMENT",
                "src_ip": ra["src_ip"],
                "src_mac": ra["src_mac"],
                "router_lifetime": ra["router_lifetime"],
                "prefixes": ra["prefixes"],
            })
            logger.warning("ROGUE RA detected from %s", ra["src_ip"])
    return rogues


def check_ipv6_firewall() -> dict:
    """Check if ip6tables rules are configured (Linux only)."""
    import subprocess
    result = {"ip6tables_present": False, "rules_count": 0, "rules": []}
    try:
        output = subprocess.run(
            ["ip6tables", "-L", "-n", "--line-numbers"],
            capture_output=True, text=True, timeout=5,
        )
        lines = [l.strip() for l in output.stdout.strip().split("\n") if l.strip()]
        result["ip6tables_present"] = True
        result["rules_count"] = len([l for l in lines if l and not l.startswith("Chain") and not l.startswith("num")])
        result["rules"] = lines[:20]
    except (FileNotFoundError, subprocess.TimeoutExpired):
        logger.warning("ip6tables not available")
    return result


def check_tunnel_protocols(interface: str, timeout: int = 5) -> dict:
    """Check for IPv6 tunneling protocols (Teredo, 6to4, ISATAP)."""
    tunnels = {"teredo": False, "six_to_four": False, "isatap": False}

    def detect_tunnel(pkt):
        if pkt.haslayer("UDP") and (pkt["UDP"].sport == 3544 or pkt["UDP"].dport == 3544):
            tunnels["teredo"] = True
        if pkt.haslayer("IP") and pkt["IP"].proto == 41:
            tunnels["six_to_four"] = True

    try:
        sniff(iface=interface, prn=detect_tunnel, timeout=timeout, store=0)
    except Exception as exc:
        logger.warning("Tunnel detection failed: %s", exc)

    return tunnels


def generate_assessment(interface: str, known_routers: List[str]) -> dict:
    """Run complete IPv6 security assessment."""
    hosts = discover_ipv6_hosts(interface, timeout=5)
    ras = capture_router_advertisements(interface, timeout=10)
    rogues = detect_rogue_ra(ras, known_routers)
    firewall = check_ipv6_firewall()
    tunnels = check_tunnel_protocols(interface, timeout=5)

    findings = []
    if rogues:
        findings.append(f"CRITICAL: {len(rogues)} rogue Router Advertisements detected")
    if firewall["rules_count"] == 0:
        findings.append("HIGH: No ip6tables rules configured")
    if tunnels["teredo"]:
        findings.append("MEDIUM: Teredo tunnel traffic detected")

    return {
        "assessment_date": datetime.utcnow().isoformat(),
        "interface": interface,
        "ipv6_hosts_discovered": len(hosts),
        "hosts": hosts,
        "router_advertisements": ras,
        "rogue_ras": rogues,
        "firewall_status": firewall,
        "tunnel_protocols": tunnels,
        "risk_findings": findings,
    }


def main():
    parser = argparse.ArgumentParser(description="IPv6 Vulnerability Assessment Agent")
    parser.add_argument("--interface", default="eth0", help="Network interface")
    parser.add_argument("--known-routers", nargs="*", default=[], help="Known legitimate router IPv6 addresses")
    parser.add_argument("--output", default="ipv6_assessment.json")
    args = parser.parse_args()

    report = generate_assessment(args.interface, args.known_routers)
    with open(args.output, "w") as f:
        json.dump(report, f, indent=2, default=str)
    logger.info("Report saved to %s", args.output)
    print(json.dumps(report, indent=2, default=str))


if __name__ == "__main__":
    main()
