Fastest way to scan ports with Java
Asked Answered
L

8

36

I made a very simple port scanner, but it runs too slow, so I'm looking for a way to make it scan faster. Here is my code:

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

This code tests if a specific port is open on a specific IP address. For timeout I used a minimum value of 200 because when I go lower it doesn't have enough time to test the port.

It works well, but it takes too much time to scan from 0 to 65535. Is there another way that could maybe scan from 0 to 65535 in less than 5 minutes?

Lailaibach answered 18/7, 2012 at 17:32 Comment(1)
You are leaking the socket in the failure case. You need to close it regardless of the success or failure of connect(). Regardless of all the other answers here, the fastest way is to use non-blocking connects with NIO and a Selector. Only one thread required, no Futures either.Northington
J
71

If you need 200 ms for each of the 65536 ports (in the worst case, a firewall is blocking everything, thus making you hit your timeout for every single port), the maths is pretty simple: you need 13k seconds, or about 3 hours and a half.

You have two (non-exclusive) options to make it faster:

  • reduce your timeout
  • parallelize your code

Since the operation is I/O bound (in contrast to CPU-bound—that is, you spend time waiting for I/O, and not for some huge calculation to complete), you can use many, many threads. Try starting with 20. They would divide the 3 hours and a half among them, so the maximum expected time is about 10 minutes. Just remember that this will put pressure on the other side, i.e., the scanned host will see huge network activity with "unreasonable" or "strange" patterns, making the scan extremely easy to detect.

The easiest way (ie, with minimal changes) is to use the ExecutorService and Future APIs:

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

Then, you can do something like:

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

If you need to know which ports are open (and not just how many, as in the above example), you'd need to change the return type of the function to Future<SomethingElse>, where SomethingElse would hold the port and the result of the scan, something like:

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

Then, change Boolean to ScanResult in the first snippet, and return new ScanResult(port, true) or new ScanResult(port, false) instead of just true or false

Actually, I noticed: in this particular case, you don't need the ScanResult class to hold result + port, and still know which port is open. Since you add the futures to a List, which is ordered, and, later on, you process them in the same order you added them, you could have a counter that you'd increment on each iteration to know which port you are dealing with. But, hey, this is just to be complete and precise. Don't ever try doing that, it is horrible, I'm mostly ashamed that I thought about this... Using the ScanResult object is much cleaner, the code is way easier to read and maintain, and allows you to, later, for example, use a CompletionService to improve the scanner.

Jegar answered 18/7, 2012 at 17:34 Comment(13)
I haven't used it but wouldn't a SocketFactory (docs.oracle.com/javase/7/docs/api/javax/net/SocketFactory.html) help too ?Bayne
I can't really see how using a SocketFactory relates to this problem. Would you care to explain what you have in mind?Jegar
I thought SocketFactory can be implemented to have something like a SocketPool in the backing, which can result in speed-up. Not sure if this works.. it is not a suggestion, just a query on your approach.Bayne
A "SocketPool", in this context, makes no sense at all. A TCP socket is the quadruple (src-ip, src-port, dest-ip, dest-port) which is immutable (ie, you cannot change ips or ports in the middle of a connection); since each port you test is a different dest-port, you see that you need a different TCP Socket for each port you test, therefore, no pooling can help.Jegar
that first code gives me many errors, cant even compile code, are you sure everything about it is right?Lailaibach
Fixed some typos, it should compile now. Sorry for that, I typed the code directly in SO without checking it. Anyway, if you can't easily spot and fix such trivial typos, there's a long, long way before you start playing with threads.Jegar
Any Idea what could be max size of thread pool? and is there any point making it around 10,000 if there is 65535 ports to scan?Lailaibach
If you grow it too much, you will start loosing performance. Among other reasons, some are: you will saturate your network connection, you will saturate your network card, you will saturate your CPU doing context switches. I wouldn't try more than maybe 100 threads, but that's only intuition. You really should test by measuring the time required and increasing the number of threads slowly.Jegar
but it takes way too long to scan 65535 ports even when I use like 1000 threadsLailaibach
@BrunoReis, Brilliant! Exactly what I looked! I am new to Java and this example I should say shows few interesting things that work's perfect after couple little fixes ;)Siemens
@BrunoReis This is good, but I have a question, how is it possible to print out like the services running on the port, or shall I put it as the name of the port like 21 is FTP and 80 is HTTP. If that port was open, then how should I print the corresponding name, will a switch be good to go?Ellata
using 100 threads instead of 20 dropped the time from 10 minutes to two minutes for me. Thank you.Owades
There are at least four options. The others are non-blocking and asynchronous I/O. Your code leaks the socket in the failure case.Northington
M
5

This code sample is inspired by 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;
    }

}
}
Mebane answered 20/5, 2015 at 13:51 Comment(0)
U
4

Apart from parallelizing the scan, you can use more advanced port scanning techniques like the ones (TCP SYN and TCP FIN scanning) explained here: http://nmap.org/nmap_doc.html. VB code of an implementation can be found here: http://h.ackack.net/spoon-worlds-fastest-port-scanner.html

In order to use these techniques, however, you need to use raw TCP/IP sockets. You should use RockSaw library for this.

Underskirt answered 18/7, 2012 at 17:49 Comment(1)
Please give me a complete snippet example. The spoon-worlds-fastest-port-scanner.html path didn't work. #66743094 am trying to implement the same idea as you but I have not been successfulMireille
J
1

If you decide to use the Nmap option and want to continue with Java, you should look at Nmap4j on SourceForge.net.

It's a simple API that allows you to integrate Nmap into a Java application.

Jurist answered 19/7, 2012 at 19:35 Comment(1)
old link is not valid anymore, please use this now: sourceforge.net/projects/nmap4j thanksPermeate
S
1

I wrote my own asynchronous port scanner Java service that can scan ports via TCP-SYN-Scan, like Nmap does. It also supports ICMP ping scans and can work with a very high throughput (depending on what the network can sustain):

invesdwin-webproxy

Internally, it uses a Java binding pcap and exposes its services via JMS/AMQP. Though you can also use it directly in your application if you don't mind it having root permissions.

Sining answered 9/6, 2017 at 19:17 Comment(0)
V
1

Nay, fastest way here is to use the dynamically created thread method

Executors.newCachedThreadPool();

This way, it uses threads until all of them are taken, then when all of them are taken and there is a new task it will open up a new thread and preform the new task on it.

Here's my code snippet (credits due to Jack and Bruno Reis).

I also added the function to search any IP address you type in for some added functionality and ease of use.

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

    }
}
Vivyan answered 10/9, 2019 at 18:2 Comment(0)
C
0

You can do a bulk port scan by doing the following using NIO 2 single-threaded. By following NIO 2 code with a single thread, I am able to scan all the hosts for a given port. Please try a reasonable timeout and make sure you have a large file descriptor for the process:

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

}
Confidant answered 7/12, 2021 at 6:59 Comment(0)
B
0

Inspired by you all, but just this code really worked!

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

             }
        });
    }
}

class 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 + "]";
    }

}
Benzaldehyde answered 10/10, 2022 at 13:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.