使用Java扫描端口的最快方法

36

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

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的扫描呢?

8个回答

72
如果每个65536个端口需要200毫秒(在最坏的情况下,防火墙阻止所有连接,因此每个端口都会超时),那么数学计算就很简单:您需要13,000秒,大约3个半小时。

您有两个(非排他性)选项可以使它更快:

  • 减少超时时间
  • 并行化代码

由于该操作是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更改为ScanResult,并返回new ScanResult(port, true)new ScanResult(port, false)而不是仅返回truefalse。编辑:实际上,我刚刚注意到:在这种特定情况下,您不需要使用ScanResult类来保存结果+端口,并且仍然知道哪个端口是开放的。由于您将未来添加到 List 中,它是有序的,并且稍后,您会按照添加顺序进行处理,因此可以有一个计数器,您将在每次迭代时递增,以了解正在处理哪个端口。但是,嘿,这只是为了完整和精确。永远不要尝试这样做,这太可怕了,我大多数时间都感到惭愧... 使用ScanResult对象要干净得多,代码易于阅读和维护,并允许您稍后例如使用CompletionService来改进扫描器。

我认为可以实现SocketFactory来创建类似于SocketPool的后备机制,这可能会加速。不确定是否可行...这不是建议,只是对您方法的疑问。 - WinOrWin
4
在这个上下文中,“SocketPool”毫无意义。TCP套接字是不可变的四元组(src-ip, src-port, dest-ip, dest-port),也就是说,你不能在连接过程中更改ip或端口;由于每个你测试的端口都是一个不同的目标端口,你会发现你需要为每个测试的端口使用不同的TCP套接字,因此,池化是没有任何帮助的。 - Bruno Reis
修正了一些错别字,现在应该可以编译了。很抱歉,我直接在 SO 上输入代码而没有检查它。无论如何,如果你不能轻松地发现和修复这样微不足道的错误,那么在开始使用线程之前,还有很长很长的路要走。 - Bruno Reis
即使我使用1000个线程,扫描65535个端口也需要太长时间。 - Rohit Malish
@BrunoReis,太棒了!正是我所需要的!我是Java的新手,这个例子展示了一些有趣的东西,在进行了几个小修复后完美地运行。 - Al Po
显示剩余7条评论

5

代码示例灵感来源于"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;
    }

}
}

4

请给我一个完整的代码片段示例。spoon-worlds-fastest-port-scanner.html路径无效。https://stackoverflow.com/questions/66742594/how-to-implement-tcp-syn-scanning-with-java-code-fastest-way-to-scan-ports-with。我正在尝试实现与您相同的想法,但我没有成功。 - Touya Akira

1

如果您决定使用Nmap选项并希望继续使用Java,则应查看{{link1:SourceForge.net上的Nmap4j}}。

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


1
旧链接已经失效,请使用这个新链接:https://sourceforge.net/projects/nmap4j/ 谢谢。 - user3340335

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

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

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


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;
}

    }
    }

0

受到你们的启发,但只有这个代码真正起作用了!

类 PortScaner

import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
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;

public class PortScaner {

    public static void main(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++) 
            futures.add(portIsOpen(es, ip, port, timeout));
        
        
        es.shutdown();
        int openPorts = 0;

        for (final Future<ScanResult> f : futures)
            if (f.get().isOpen()) {
                openPorts++;
                System.out.println(f.get());
            }
        
        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);
                }
              
             }
        });
    }
}

类别 ScanResult

public final class ScanResult {
    
    private final int port;
    
    private final boolean isOpen;


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


    /**
     * @return the port
     */
    public int getPort() {
        return port;
    }

    /**
     * @return the isOpen
     */
    public boolean isOpen() {
        return isOpen;
    }


    @Override
    public String toString() {
        return "ScanResult [port=" + port + ", isOpen=" + isOpen + "]";
    }
    
}

0

可能我来晚了,但你可以使用NIO2单线程执行以下操作来进行批量端口扫描。通过使用单线程的NIO2代码,我能够扫描所有给定端口的主机。请尝试合理的超时设置,并确保您有大型文件描述符以进行处理。

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;
}

}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接