32

我做了一个非常简单的端口扫描器,但是它运行得太慢了,所以我正在寻找一种让它扫描得更快的方法。这是我的代码:

public boolean portIsOpen(String ip, int port, int timeout) {
    try {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(ip, port), timeout);
        socket.close();
        return true;
    } catch (Exception ex) {
        return false;
    }
}

此代码测试特定端口是否在特定 IP 上打开。对于超时,我使用了最小值,200因为当我降低时它没有足够的时间来测试端口。

它工作得很好,但是从 0 扫描到 65535 需要太多时间。有没有其他方法可以在不到 5 分钟的时间内从 0 扫描到 65535?

4

7 回答 7

70

如果 65536 个端口中的每一个都需要 200 毫秒(在最坏的情况下,防火墙会阻止一切,从而使每个端口都达到超时),那么数学非常简单:您需要 13k 秒,或大约 3 小时一半。

您有 2 个(非排他性的)选项可以使其更快:

  • 减少超时
  • 并行化你的代码

由于操作是受 I/O 限制的(与 CPU 限制相反——也就是说,您花费时间等待 I/O,而不是等待完成一些巨大的计算),您可以使用很多很多线程。尝试从 20 开始。他们会在其中划分 3 个半小时,因此最长预期时间约为 10 分钟。请记住,这会给另一边带来压力,即,被扫描的主机会看到带有“不合理”或“奇怪”模式的巨大网络活动,从而使扫描非常容易被检测到。

最简单的方法(即,更改最少)是使用 ExecutorService 和 Future API:

public static Future<Boolean> portIsOpen(final ExecutorService es, final String ip, final int port, final int timeout) {
  return es.submit(new Callable<Boolean>() {
      @Override public Boolean call() {
        try {
          Socket socket = new Socket();
          socket.connect(new InetSocketAddress(ip, port), timeout);
          socket.close();
          return true;
        } catch (Exception ex) {
          return false;
        }
      }
   });
}

然后,您可以执行以下操作:

public static void main(final String... args) {
  final ExecutorService es = Executors.newFixedThreadPool(20);
  final String ip = "127.0.0.1";
  final int timeout = 200;
  final List<Future<Boolean>> futures = new ArrayList<>();
  for (int port = 1; port <= 65535; port++) {
    futures.add(portIsOpen(es, ip, port, timeout));
  }
  es.shutdown();
  int openPorts = 0;
  for (final Future<Boolean> f : futures) {
    if (f.get()) {
      openPorts++;
    }
  }
  System.out.println("There are " + openPorts + " open ports on host " + ip + " (probed with a timeout of " + timeout + "ms)");
}

如果您需要知道哪些端口是打开的(而不仅仅是多少,如上例所示),您需要将函数的返回类型更改为Future<SomethingElse>,其中SomethingElse将保存端口和扫描结果,一些像:

public final class ScanResult {
  private final int port;
  private final boolean isOpen;
  // constructor
  // getters
}

然后,在第一个片段中更改Boolean为,并返回or而不是仅仅orScanResultnew ScanResult(port, true)new ScanResult(port, false)truefalse

编辑:实际上,我只是注意到:在这种特殊情况下,您不需要 ScanResult 类来保存结果+端口,并且仍然知道哪个端口是打开的。由于您将期货添加到List,它是有序的,并且稍后,您按照添加它们的相同顺序处理它们,您可以有一个计数器,您可以在每次迭代时递增以了解您正在处理哪个端口. 但是,嘿,这只是为了完整和准确。永远不要尝试这样做,这太可怕了,我很惭愧我想到了这个......使用 ScanResult 对象更干净,代码更易于阅读和维护,并且允许您稍后,例如,使用 aCompletionService来改进扫描仪。

于 2012-07-18T17:34:51.690 回答
4

除了并行扫描之外,您还可以使用更高级的端口扫描技术,例如此处解释的那些(TCP SYN 和 TCP FIN 扫描):http: //nmap.org/nmap_doc.html。可以在此处找到实现的 VB 代码:http: //h.ackack.net/spoon-worlds-fastest-port-scanner.html

然而,为了使用这些技术,您需要使用原始 TCP/IP 套接字。为此,您应该使用RockSaw库。

于 2012-07-18T17:49:40.513 回答
4

代码示例的灵感来自“Bruno Reis”

class PortScanner {

public static void main(final String... args) throws InterruptedException, ExecutionException {
    final ExecutorService es = Executors.newFixedThreadPool(20);
    final String ip = "127.0.0.1";
    final int timeout = 200;
    final List<Future<ScanResult>> futures = new ArrayList<>();
    for (int port = 1; port <= 65535; port++) {
        // for (int port = 1; port <= 80; port++) {
        futures.add(portIsOpen(es, ip, port, timeout));
    }
    es.awaitTermination(200L, TimeUnit.MILLISECONDS);
    int openPorts = 0;
    for (final Future<ScanResult> f : futures) {
        if (f.get().isOpen()) {
            openPorts++;
            System.out.println(f.get().getPort());
        }
    }
    System.out.println("There are " + openPorts + " open ports on host " + ip + " (probed with a timeout of "
            + timeout + "ms)");
}

public static Future<ScanResult> portIsOpen(final ExecutorService es, final String ip, final int port,
        final int timeout) {
    return es.submit(new Callable<ScanResult>() {
        @Override
        public ScanResult call() {
            try {
                Socket socket = new Socket();
                socket.connect(new InetSocketAddress(ip, port), timeout);
                socket.close();
                return new ScanResult(port, true);
            } catch (Exception ex) {
                return new ScanResult(port, false);
            }
        }
    });
}

public static class ScanResult {
    private int port;

    private boolean isOpen;

    public ScanResult(int port, boolean isOpen) {
        super();
        this.port = port;
        this.isOpen = isOpen;
    }

    public int getPort() {
        return port;
    }

    public void setPort(int port) {
        this.port = port;
    }

    public boolean isOpen() {
        return isOpen;
    }

    public void setOpen(boolean isOpen) {
        this.isOpen = isOpen;
    }

}
}
于 2015-05-20T13:51:06.117 回答
2

如果您决定使用 Nmap 选项并希望继续使用 Java,您应该查看SourceForge.net 上的 Nmap4j

这是一个简单的 API,可让您将 Nmap 集成到 Java 应用程序中。

于 2012-07-19T19:35:48.907 回答
1

我编写了自己的异步 portscanner java 服务,它可以像 Nmap 一样通过 TCP-SYN-Scan 扫描端口。它还支持 IMCP ping 扫描,并且可以以非常高的吞吐量工作(取决于网络可以支持的内容):

https://github.com/subes/invesdwin-webproxy

在内部,它使用 java 绑定 pcap 并通过 JMS/AMQP 公开其服务。如果您不介意它具有 root 权限,您也可以直接在您的应用程序中使用它。

于 2017-06-09T19:17:56.703 回答
1

不,这里最快的方法是使用动态创建的线程方法

Executors.newCachedThreadPool();

这样,它使用线程直到所有线程都被占用,然后当所有线程都被占用并且有一个新任务时,它将打开一个新线程并在其上执行新任务。

这是我的代码片段(感谢 Jack 和 Bruno Reis)

我还添加了搜索您输入的任何 IP 地址的功能,以获得一些附加功能和易用性。

    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Scanner;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    import java.util.concurrent.TimeUnit;

    class PortScanner {

    public static void main(final String... args) throws InterruptedException, ExecutionException 
    {
        final ExecutorService es = Executors.newCachedThreadPool();
        System.out.print("Please input the ip address you would like to scan for open ports: ");
        Scanner inputScanner = new Scanner(System.in);
        final String ip = inputScanner.nextLine();
        final int timeout = 200;
        final List<Future<ScanResult>> futures = new ArrayList<>();
        for (int port = 1; port <= 65535; port++) {
            // for (int port = 1; port <= 80; port++) {
            futures.add(portIsOpen(es, ip, port, timeout));
        }
        es.awaitTermination(200L, TimeUnit.MILLISECONDS);
        int openPorts = 0;
        for (final Future<ScanResult> f : futures) {
            if (f.get().isOpen()) {
                openPorts++;
                System.out.println(f.get().getPort());
            }
        }
        System.out.println("There are " + openPorts + " open ports on host " + ip + " (probed with a timeout of "
        + timeout + "ms)");
 es.shutdown();
    }



    public static Future<ScanResult> portIsOpen(final ExecutorService es, final String ip, final int port,
    final int timeout) 
    {
        return es.submit(new Callable<ScanResult>() {
            @Override
            public ScanResult call() {
                try {
                    Socket socket = new Socket();
                    socket.connect(new InetSocketAddress(ip, port), timeout);
                    socket.close();
                    return new ScanResult(port, true);
                } catch (Exception ex) {
                    return new ScanResult(port, false);
                }
            }
        });
    }

    public static class ScanResult {
private int port;

private boolean isOpen;

public ScanResult(int port, boolean isOpen) {
    super();
    this.port = port;
    this.isOpen = isOpen;
}

public int getPort() {
    return port;
}

public void setPort(int port) {
    this.port = port;
}

public boolean isOpen() {
    return isOpen;
}

public void setOpen(boolean isOpen) {
    this.isOpen = isOpen;
}

    }
    }
于 2019-09-10T18:02:03.243 回答
0

我可能迟到了,但您可以通过使用 NIO2 单线程执行以下操作来进行批量端口扫描。通过使用单线程跟踪 NIO2 代码,我能够扫描所有主机以查找给定端口。请尝试合理的超时时间,并确保您有较大的 File Discriptor 用于处理

public static List<HostTarget> getRechabilityStatus(String...hosts,final int port, final int bulkDevicesPingTimeoutinMS) throws Exception {

    List<AsynchronousSocketChannel> channels = new ArrayList<>(hosts.length);
    try {
        List<CompletableFuture<HostTarget>> all = new ArrayList<>(hosts.length);

        List<HostTarget> allHosts = new ArrayList(hosts.length);
        for (String host : hosts) {
            InetSocketAddress address = new InetSocketAddress(host, port);
            HostTarget target = new HostTarget();
            target.setIpAddress(host);
            allHosts.add(target);
            AsynchronousSocketChannel client = AsynchronousSocketChannel.open();
            channels.add(client);
            final CompletableFuture<HostTarget> targetFuture = new CompletableFuture<>();
            all.add(targetFuture);
            client.connect(address, target, new CompletionHandler<Void, HostTarget>() {
                @Override
                public void completed(Void result, HostTarget attachment) {
                    attachment.setIsReachable(true);
                    targetFuture.complete(attachment);
                }

                @Override
                public void failed(Throwable exc, HostTarget attachment) {
                    attachment.setIsReachable(false);
                    attachment.errrorMessage = exc.getMessage();
                    targetFuture.complete(attachment);
                }
            });
        }

        try {
            if(bulkDevicesPingTimeoutinMS > 0) {
                CompletableFuture.allOf(all.toArray(new CompletableFuture[]{})).get(bulkDevicesPingTimeoutinMS, TimeUnit.MILLISECONDS);
            }else{
                // wait for all future to be complete 1000 scan is taking 7 seconds.
                CompletableFuture.allOf(all.toArray(new CompletableFuture[]{})).join();
            }
        } catch (Exception timeoutException) {
            // ignore
        }
        return allHosts;
    }finally {
        for(AsynchronousSocketChannel channel : channels){
            try{
                channel.close();
            }catch (Exception e){
                if(LOGGER.isDebugEnabled()) {
                    LOGGER.error("Erorr while closing socket",e);
                }
            }

        }
    }
static class HostTarget {

String ipAddress;
Boolean isReachable;
 public String getIpAddress() {
    return ipAddress;
}
public Boolean getIsReachable() {
    return isReachable;
}

public void setIpAddress(String ipAddress) {
    this.ipAddress = ipAddress;
}
public void setIsReachable(Boolean isReachable) {
    this.isReachable = isReachable;
}

}
于 2021-12-07T06:59:56.160 回答