You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何可靠检测TCP SYN端口扫描无更多返回报文以重试获取缺失响应

问题核心原因

你遇到的永久阻塞本质是两个问题叠加:

  • SYN探测报文或对应的响应报文在网络中丢失,导致待检测的ports列表永远无法清空,recv_from的无限循环会一直等待新报文
  • 现有逻辑没有设置接收超时兜底,也没有和发送任务的完成状态做联动

具体修复方案

1. 新增状态跟踪与超时逻辑

  • 用trio的事件对象标记所有发送任务已完成
  • 所有发送任务结束后,启动最长等待计时器,超时后直接终止接收器,剩余未从ports中移除的就是无响应/丢包的端口
  • 支持对剩余端口做重试逻辑

2. 修改后代码

import trio
import socket

PORTS = 65535
TASKS = 15
# 自定义最大接收等待时间,单位秒,根据网络环境调整
MAX_RECV_WAIT = 10

async def recv_from(sock, target, cancel_event):
    while not cancel_event.is_set():
        try:
            # 给recvfrom加单次超时,避免永久阻塞
            with trio.move_on_after(1):
                data, addr = await sock.recvfrom(1024)
                if addr[0] == target:
                    yield data
        except:
            continue

async def receiver(remaining_ports: list) -> None:
    sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_TCP)
    cancel_event = trio.Event()
    async for packet in recv_from(sock, '192.168.1.1', cancel_event):
        if not remaining_ports:
            break
        src, dest, flags = unpack(packet)
        if dest == 6969 and src in remaining_ports:
            remaining_ports.remove(src)
            if flags == 20:
                print(f"port {src}: closed")
            elif flags == 18:
                print(f"port {src}: open")
    cancel_event.set()
    sock.close()

async def sender(idx: int, sender_done_event: trio.Event, ports_segment: list) -> None:
    sock = socket.socket(socket.AF_INET, socket.SOCK_RAW, socket.IPPROTO_RAW)
    sock.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)
    for port in ports_segment:
        tcp_packet = build_tcp_packet(port)
        ipv4_packet = build_ipv4_packet()
        await sock.sendto(ipv4_packet + tcp_packet, ('192.168.1.1', port))
    sock.close()
    # 标记当前发送任务完成
    sender_done_event.set()

async def scan_round(scan_ports: list) -> list:
    """单次扫描,返回未收到响应的端口列表"""
    remaining_ports = scan_ports.copy()
    async with trio.open_nursery() as nursery:
        sender_done_events = []
        # 拆分端口段给多个发送任务
        segment_size = len(scan_ports) // TASKS
        for i in range(TASKS):
            start = i * segment_size
            end = start + segment_size if i != TASKS-1 else len(scan_ports)
            seg = scan_ports[start:end]
            event = trio.Event()
            sender_done_events.append(event)
            nursery.start_soon(sender, i, event, seg)
            await trio.sleep(0.5)
        # 启动接收器
        nursery.start_soon(receiver, remaining_ports)
        # 等待所有发送任务完成
        for event in sender_done_events:
            await event.wait()
        # 所有报文发完后,再等待MAX_RECV_WAIT秒收响应
        await trio.sleep(MAX_RECV_WAIT)
        # 取消所有任务,结束本次扫描
        nursery.cancel_scope.cancel()
    return remaining_ports

async def main() -> None:
    # 初始全端口扫描
    remaining = list(range(PORTS))
    retry_times = 2 # 自定义重试次数
    for i in range(retry_times + 1):
        if not remaining:
            break
        print(f"第{i+1}次扫描,待扫描端口数:{len(remaining)}")
        remaining = await scan_round(remaining)
    if remaining:
        print(f"多次扫描后无响应端口:{remaining}")

if __name__ == '__main__':
    trio.run(main)

3. 额外优化点

  • 把ports.remove改为先判断端口是否在列表中再删除,避免重复收到响应时报错
  • 拆分单次扫描逻辑,可直接对无响应端口做多次重试
  • 发送速率可根据网络环境调整,避免短时间发送报文过多被防火墙拦截导致丢包

内容的提问来源于stack exchange,提问作者adder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.02 20:39:05