• Main Page
  • Related Pages
  • Classes
  • Files
  • File List

PortScanner.java

Go to the documentation of this file.
00001 
00002 import java.io.FileOutputStream;
00003 import java.io.IOException;
00004 import java.io.PrintStream;
00005 import java.net.InetAddress;
00006 import java.net.InetSocketAddress;
00007 import java.net.UnknownHostException;
00008 import java.text.SimpleDateFormat;
00009 import java.util.Calendar;
00010 import java.util.concurrent.ExecutorService;
00011 import java.util.concurrent.Executors;
00012 
00013 /**
00014  *  Encapsulates port scanning application.
00015  *  
00016  *  The instance maintains pool of end-points (to be scanned)
00017  *  starts worker threads that scan end-points from the pool
00018  *  and displays progress to the user.
00019  *  
00020  *  The static main() entry point facility  parses command
00021  *  line arguments and starts the single instance of the class.
00022  *  
00023  *  @author Mikica B Kocic
00024  */
00025 public class PortScanner extends Thread implements PortConnect.Context
00026 {
00027     //////////////////////////////////////////////////////////// CONSTANTS ///////////////
00028     
00029     /** Connection timeout constant
00030      */
00031     private final static int connectionTimeout = 0;
00032     
00033     /** Number of maximum allowed worker threads
00034      */
00035     private final static int  maxThreadCount = 1000;
00036     
00037     /** Maximum allowed number of end-points to scan
00038      */
00039     private final static int maxEndpointsToScan = 1024 * 1024;
00040     
00041     /** Should application report failed end-points or not.
00042      *  Disabled by default: Who is interested in failed ports at all?
00043      */
00044     private final static boolean reportFailedEndpoints = false;
00045 
00046     //////////////////////////////////////////////////////////// MEMBERS /////////////////
00047     
00048     /** Instance of the lookup database for TCP and UDP service names
00049      */
00050     private ServiceNames verboseServices;
00051 
00052     /** Output stream where the results are written
00053      */
00054     private PrintStream out;
00055 
00056     //////////////////////////////////////////////////////////////////////////////////////
00057     /*  The pool of end-points (end-point == IP address + TCP port)
00058      *  from which the 'next end-point to scan' is retrieved.
00059      */
00060     
00061     /** True if pool is depleted
00062      */
00063     private boolean endpointDepleted;
00064     
00065     /** The current IP address
00066      */
00067     private RawIpAddress currentAddr;
00068     
00069     /** The current TCP port
00070      */
00071     private int currentPort; 
00072     
00073     /** The first IP address in the pool
00074      */
00075     private RawIpAddress firstAddr;
00076     
00077     /** The last IP address in the pool
00078      */
00079     private RawIpAddress lastAddr;
00080     
00081     /** The first TCP port in the pool
00082      */
00083     private int firstPort;
00084     
00085     /** The last TCP port in the pool
00086      */
00087     private int lastPort;
00088     
00089     //////////////////////////////////////////////////////////////////////////////////////
00090     /*  Statistics
00091      */
00092     
00093     /** Number of active working threads
00094      */
00095     private int workerThreadCount = 0; 
00096     
00097     /** Number of scanned end-points
00098      */
00099     private int scannedEndpointCount = 0; 
00100     
00101     /** Number of connected end-points
00102      */
00103     private int okEndpointCount  = 0;
00104     
00105     /** Number of failed to connect end-points
00106      */
00107     private int failedEndpointCount = 0;
00108     
00109     //////////////////////////////////////////////////////////// METHODS /////////////////
00110     
00111     /**
00112      *  Creates instance of the PortScanner worker thread.
00113      *  
00114      *  @param firstAddr    the first raw IP address in range
00115      *  @param lastAddr     the last raw IP address in range
00116      *  @param firstPort    the first port in range
00117      *  @param lastPort     the last port in range
00118      *  @param out          output stream for results
00119      */
00120     public PortScanner( 
00121             RawIpAddress firstAddr, RawIpAddress lastAddr, 
00122             int firstPort, int lastPort,
00123             PrintStream out )
00124     {
00125         this.firstAddr = firstAddr;
00126         this.lastAddr  = lastAddr;
00127         this.firstPort = firstPort;
00128         this.lastPort  = lastPort;
00129         this.out       = out;
00130 
00131         this.endpointDepleted = false;
00132 
00133         /* Set currentAddr <= copy of the firstAddr
00134          */
00135         this.currentAddr = new RawIpAddress( firstAddr );
00136 
00137         /* Set currentPort <= firstPort
00138          */
00139         this.currentPort = firstPort;
00140     }
00141 
00142     /**
00143      *  Gets next socket address (end-point) from the pool.
00144      *  
00145      *  The next end-point is retrieved by first scanning all addresses in range
00146      *  keeping the TCP port constant. This means that worker threads do not flood 
00147      *  the specific IP address with different simultaneous connections on different 
00148      *  TCP ports all at once.
00149      *  
00150      *  The next algorithm clarifies previous explanation:
00151      * 
00152      *   for( all ports in range ) { 
00153      *      for ( all addresses in range ) {
00154      *          end-point := address + port;
00155      *          perform scan on end-point;
00156      *      }
00157      *   }
00158      *   
00159      */
00160     @Override
00161     public synchronized InetSocketAddress getNextSocketAddress ()
00162     {
00163         InetSocketAddress endpoint = null;
00164 
00165         while( ! endpointDepleted && endpoint == null ) 
00166         {
00167             /* Create end-point from the current IP address and the current TCP port
00168              */
00169             try {
00170                 InetAddress addr = currentAddr.getInetAddress ();
00171                 endpoint = new InetSocketAddress( addr, currentPort );
00172             } catch( Exception e ) {
00173                 /* this should not happen; however, skip this address */
00174                 e.printStackTrace ();
00175             }
00176 
00177             /* Advance to the next end-point 
00178              */
00179             currentAddr.increase ();
00180 
00181             if ( currentAddr.compare( lastAddr ) > 0 ) 
00182             {
00183                 currentAddr.set( firstAddr ); // set currentAddr := firstAddr
00184 
00185                 if ( ++currentPort > lastPort ) {
00186                     currentPort = firstPort; // start the loop all over again
00187                     endpointDepleted = true; // suppress further 'gets'
00188                 }
00189             }
00190         }
00191 
00192         return endpoint;
00193     }
00194 
00195     /**
00196      *  Reports information about end-point. 
00197      *  (Call-back from the scanning worker thread.)
00198      */
00199     @Override
00200     public synchronized void onPortConnected( long threadId, InetSocketAddress addr,
00201             boolean ok, String error, int timeMillis )
00202     {
00203         ++scannedEndpointCount;
00204         
00205         if ( ok ) {
00206             ++okEndpointCount;
00207         } else {
00208             ++failedEndpointCount;
00209         }
00210 
00211         if ( ok || ( ! ok && reportFailedEndpoints ) ) 
00212         {
00213             String serviceName = verboseServices.lookup( "tcp", addr.getPort () );
00214     
00215             if ( serviceName != null ) {
00216                 out.println( now () 
00217                         + error + ": " + addr.getAddress() + ":" + addr.getPort ()
00218                         + " (" + serviceName + ")"
00219                         + ", Thread " + threadId + ", Elapsed " + timeMillis + " ms" );
00220             } else {
00221                 out.println( now () 
00222                         + error + ": " + addr.getAddress() + ":" + addr.getPort ()
00223                         + ", Thread " + threadId + ", Elapsed " + timeMillis + " ms" );
00224             }
00225 
00226             out.flush ();
00227         }
00228     }
00229 
00230     /**
00231      *  Call-back from the worker thread reporting that it is alive
00232      */
00233     @Override
00234     public synchronized void workerThreadSignIn( long threadId )
00235     {
00236         ++workerThreadCount;
00237     }
00238     
00239     /**
00240      *  Call-back from the worker thread reporting that it is zombie (dead waiting)
00241      */
00242     public synchronized void workerThreadSignOut( long threadId )
00243     {
00244         --workerThreadCount;
00245     }
00246     
00247     /**
00248      *  Gets current time stamp (millis resolution)
00249      *  
00250      *  @return time in ISO format 
00251      */
00252     private static String now ()
00253     {
00254         Calendar cal = Calendar.getInstance ();
00255         SimpleDateFormat sdf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss.SSS " );
00256         return sdf.format( cal.getTime() );
00257     }
00258     
00259     /**
00260      *  Starts working threads and monitors end-point scanning progress.
00261      */
00262     @Override
00263     public void run()
00264     {
00265         long startTime = System.nanoTime ();
00266 
00267         out.println( now () + "Started...\n" );
00268 
00269         /* Load service names
00270          */
00271         verboseServices = new ServiceNames( "services.txt" );
00272 
00273         /* Calculate how long it will take to perform the scan
00274          * (if all ports throw timeouts)
00275          */
00276         int addrSpanSize = (int)( lastAddr.subtract( firstAddr ) + 1 );
00277         int portSpanSize = lastPort - firstPort + 1;
00278         int endpointCount = addrSpanSize * portSpanSize;
00279 
00280         System.out.println( "\nScanning " 
00281                 + addrSpanSize + " hosts * " 
00282                 + portSpanSize + " ports/host = total "
00283                 + endpointCount + " end-points..."
00284                 );
00285         
00286         /* Ensure that we do not scan impossible range of end-points
00287          */
00288         if ( endpointCount >= maxEndpointsToScan ) {
00289             String error = "Error: Cowardly refusing to scan more than " 
00290                 + maxEndpointsToScan + " end-points!\n\n"
00291                 + "If you really wanted to scan " + endpointCount + " end-points, "
00292                 + "please start multiple instances of the port scanner "
00293                 + "(either sequantially or in parallel).\n";
00294             
00295             out.println( now () + error ); 
00296             out.println( now () + "Completed." );
00297             
00298             System.err.println( "\n" + error ); 
00299             System.out.println( "Completed." );
00300             
00301             out.flush ();
00302             out.close ();
00303             
00304             return;
00305         }
00306         
00307         /* Start port scanner working threads (see PortConnect.run()) in the pool.
00308          * 
00309          */
00310         ExecutorService threadPool = Executors.newFixedThreadPool( maxThreadCount );
00311         for ( int i = 0; i < maxThreadCount; ++ i )
00312         {
00313             threadPool.execute( new PortConnect( this, connectionTimeout ) );
00314         }
00315         
00316         /* Wait all threads to complete. While waiting, display the progress.
00317          */
00318         while( true )
00319         {
00320             try {
00321                 Thread.sleep( 1000 );
00322             } catch( InterruptedException e ) {
00323                 break;
00324             }
00325 
00326             synchronized( this )
00327             {
00328                 /* Display progress...
00329                  */
00330                 int elapsed = (int) ( ( System.nanoTime () - startTime ) / 1000000000l );
00331                 
00332                 System.out.print(
00333                         "\rElapsed " + elapsed + " sec; "
00334                         + "Scanned " + scannedEndpointCount
00335                         + " of " + endpointCount
00336                         + " end-points (" + okEndpointCount
00337                         + " alive + " + failedEndpointCount
00338                         + " dead); " + workerThreadCount 
00339                         + " threads active                 "
00340                         );
00341                 System.out.flush ();
00342                 
00343                 /* Done if no more working threads.
00344                  */
00345                 if ( workerThreadCount <= 0) {
00346                     break;
00347                 }
00348             }
00349         }
00350 
00351         /* Be nice and clean-up...
00352          */
00353         out.println( "\n" + now() + "Completed." );
00354 
00355         out.flush ();
00356         out.close ();
00357         
00358         threadPool.shutdownNow ();
00359         
00360         System.out.println( "\nCompleted." );
00361     }
00362 
00363     /**
00364      *  Parses arguments then starts port scanner worker thread.
00365      *  
00366      *  @param args
00367      */
00368     public static void main( String[] args )
00369     {
00370         if ( args.length < 5 ) {
00371             System.err.println( 
00372                     "\nUsage: java -jar portScan.jar "
00373                     + "startAddr stopAddr startPort stopPort resultFile\n"
00374                     );
00375             System.exit( 0 );
00376         }
00377 
00378         String firstHostname = args[0];
00379         String lastHostname  = args[1];
00380         
00381         //////////////////////////////////////////////////////////////////////////////////
00382         
00383         /* Get the first raw IP address in range to scan
00384          */
00385         RawIpAddress firstAddr = null;
00386         try {
00387             firstAddr = new RawIpAddress( firstHostname );
00388         }  catch( UnknownHostException e ) {
00389             System.err.println( 
00390                     "\nError: Failed to parse startAddr " + firstHostname + "\n"
00391                     + e.toString () );
00392             System.exit( 0 );
00393         }
00394 
00395         //////////////////////////////////////////////////////////////////////////////////
00396         
00397         /* Get the last raw IP address in range to scan
00398          */
00399         RawIpAddress lastAddr = null;
00400         try {
00401             lastAddr = new RawIpAddress( lastHostname );
00402         } catch( UnknownHostException e ) {
00403             System.err.println( 
00404                     "\nError: Failed to parse stopAddr " + lastHostname + "\n"
00405                     + e.toString () );
00406             System.exit( 0 );
00407         }
00408 
00409         //////////////////////////////////////////////////////////////////////////////////
00410         
00411         /* Ensure not to mix IPv4 and IPv6 addresses
00412          */
00413         if ( ! firstAddr.isSameVersion( lastAddr ) ) {
00414             System.err.println( 
00415                     "\nError: The first and last IP addresses must be of the same IP version\n"
00416                     );
00417             System.exit( 0 );
00418         }
00419 
00420         //////////////////////////////////////////////////////////////////////////////////
00421         
00422         /* Ensure that the first is less then or equal the last IP address
00423          */
00424         long spanSize = lastAddr.subtract( firstAddr ) + 1; 
00425         if ( spanSize <= 0 ) {
00426             System.out.println( 
00427                     "\nWarning: The first IP address is greater than the last "
00428                     + "IP address.\nSwapping IP addresses...\n"
00429                     );
00430             RawIpAddress temp = firstAddr; firstAddr = lastAddr; lastAddr = temp;
00431         }
00432 
00433         //////////////////////////////////////////////////////////////////////////////////
00434         
00435         /* Parse the first port in range
00436          */
00437         int firstPort = -1;
00438         try {
00439             firstPort = Integer.parseInt( args[2] );
00440         } catch( NumberFormatException e ) {
00441             /* will fail later */
00442         }
00443         if ( firstPort < 0 ) {
00444             System.err.println( 
00445                     "\nError: The startPort must be integer between 0 and 65535\n"
00446                     );
00447             System.exit( 0 );
00448         }
00449         
00450         //////////////////////////////////////////////////////////////////////////////////
00451         
00452         /* Parse the last port in range
00453          */
00454         int lastPort  = 80;
00455         try {
00456             lastPort = Integer.parseInt( args[3] );
00457         } catch( NumberFormatException e ) {
00458             /* will fail later */
00459         }
00460         if ( lastPort < 0 ) {
00461             System.err.println( 
00462                     "\nError: The stopPort must be integer between 0 and 65535\n"
00463                     );
00464             System.exit( 0 );
00465         }
00466 
00467         //////////////////////////////////////////////////////////////////////////////////
00468         
00469         /* Ensure that the first port is less or equal than the last port
00470          */
00471         if ( firstPort > lastPort ) {
00472             System.out.println( 
00473                     "\nWarning: The first TCP port is greater than the last "
00474                     + "TCP port.\nSwapping TCP ports...\n"
00475                     );
00476             
00477             int temp = firstPort; firstPort = lastPort; lastPort = temp;
00478         }
00479         
00480         //////////////////////////////////////////////////////////////////////////////////
00481         
00482         /* Open the results file
00483          */
00484         String result = args[4];
00485 
00486         PrintStream out = null;
00487         try {
00488             FileOutputStream file = new FileOutputStream( result );
00489             out = new PrintStream( file );
00490         } catch( IOException e ) {
00491             System.err.println( 
00492                     "\nError: Failed to open file '" + result + "' for writing\n"
00493                     + e.toString () );
00494             System.exit( 0 );
00495         }
00496 
00497         //////////////////////////////////////////////////////////////////////////////////
00498         
00499         /* We have all parameters valid now.
00500          * Create the instance of the port scanner and start its main thread...
00501          */
00502         PortScanner ps = new PortScanner( firstAddr, lastAddr, firstPort, lastPort, out );
00503         ps.start ();
00504     }
00505 }
00506 
00507 /*! 
00508  *  \mainpage Port Scanner
00509  *
00510  *  \section s_intro Introduction
00511  *  
00512  *  The package implements solution to \ref p_task as a part of 
00513  *  the <a href="http://dsv.su.se/utbildning/distans/ip1" target="_blank"><b>SU/IP1 
00514  *  course</b></a>.
00515  *  
00516  *  \section s_desc Description
00517  *  
00518  *  The application scans a range of TCP ports for services on the hosts belonging 
00519  *  to a specified range  of IP addresses (IPv4 or IPv6).
00520  *  
00521  *  The application main entry point PortScanner.main() parses command
00522  *  line arguments and starts the single instance of the PortScanner class.
00523  *  
00524  *  The instance of PortScanner class maintains pool of end-points (= IP
00525  *  address + TCP port) to be scanned. It also starts worker threads (that should
00526  *  scan end-points from the pool) and displays progress to the user.
00527  *  
00528  *  The PortConnect class encapsulates port scanning worker thread. 
00529  *  The thread retrieves end-points from the owner's pool and reports (with a call-back) 
00530  *  what was found on the particular end-point.
00531  *  
00532  *  TCP port names are retrieved using instances of the ServiceNames class.
00533  *  
00534  *  The RawIpAddress class encapsulates arithmetics for raw IPv4/v6 addresses.
00535  *  
00536  *  \section s_usage Usage
00537  *  \verbatim
00538     $ java -jar portScan.jar
00539     
00540     Usage: java -jar portScan.jar startAddr stopAddr startPort stopPort resultFile \endverbatim
00541  *  
00542  *   - \a startAddr - the first IPv4/v6 IP address (or host name) in range
00543  *   - \a stopAddr  - the last IPv4/v6 IP address (or host name) in range
00544  *   - \a startPort - the first TCP port in range
00545  *   - \a stopPort  - the last TCP port in range
00546  *   - \a resultFile - the filename where results are written
00547  *  
00548  *  The application will ensure that \a startAddr is <= \a stopAddr and that
00549  *  \a startPort <= \a stopPort (i.e. it will swap addresses and/or ports).
00550  *  
00551  *  Note, however, that \a startAddr and \a stopAddr must belong the same IP version:
00552  *  
00553     \verbatim
00554     $ java -jar portScan.jar  2001:0db8:0000:0000:0000:0000:1428:0700 192.168.5.10 1 10 r.txt
00555     
00556     Error: The first and last IP addresses must be of the same IP version \endverbatim
00557  *
00558  *  \section s_jar Executable
00559  *  
00560  *  The jar file of the package can be found <a href="../portScan.jar"><b>here</b></a>.
00561  *  
00562  *  \section s_src Source Files
00563  *  
00564  *   - \ref PortScanner.java
00565  *   - \ref PortConnect.java
00566  *   - \ref RawIpAddress.java
00567  *   - \ref ServiceNames.java
00568  *
00569  *  \section s_examples Examples
00570  *  \verbatim
00571     
00572     $ java -jar portScan.jar  10.0.1.255 10.0.1.1  1024 1  results.txt
00573     
00574     Warning: The first IP address is greater than the last IP address.
00575     Swapping IP addresses...
00576     
00577     Warning: The first TCP port is greater than the last TCP port.
00578     Swapping TCP ports...
00579     
00580     Scanning 255 hosts * 1024 ports/host = total 261120 end-points...
00581     ...
00582     
00583     $ java -jar portScan.jar  10.0.1.27 10.0.1.27  1  1024 results.txt
00584     
00585     Scanning 1 hosts * 1024 ports/host = total 1024 end-points...
00586     Elapsed 21 sec; Scanned 1024 of 1024 end-points (16 alive + 1008 dead); 0 threads active
00587     Completed.   \endverbatim
00588     
00589  *  \subsection s_results The sample results:
00590  *  
00591  *  \verbatim
00592     2010-11-22 15:13:15.265 Started...
00593     
00594     2010-11-22 15:13:15.484 Connected: /10.0.1.27:25 (smtp), Thread 60, Elapsed 6 ms
00595     2010-11-22 15:13:15.500 Connected: /10.0.1.27:21 (ftp), Thread 44, Elapsed 24 ms
00596     2010-11-22 15:13:15.531 Connected: /10.0.1.27:110 (pop3), Thread 220, Elapsed 0 ms
00597     2010-11-22 15:13:15.546 Connected: /10.0.1.27:119 (nntp), Thread 250, Elapsed 1 ms
00598     2010-11-22 15:13:15.578 Connected: /10.0.1.27:143 (imap), Thread 294, Elapsed 25 ms
00599     2010-11-22 15:13:15.578 Connected: /10.0.1.27:22 (ssh), Thread 56, Elapsed 105 ms
00600     2010-11-22 15:13:15.578 Connected: /10.0.1.27:53 (domain), Thread 114, Elapsed 80 ms
00601     2010-11-22 15:13:15.734 Connected: /10.0.1.27:465 (smtps), Thread 912, Elapsed 5 ms
00602     2010-11-22 15:13:15.781 Connected: /10.0.1.27:563 (nntps), Thread 1118, Elapsed 1 ms
00603     2010-11-22 15:13:15.781 Connected: /10.0.1.27:587 (submission), Thread 1168, Elapsed 0 ms
00604     2010-11-22 15:13:15.921 Connected: /10.0.1.27:80 (http), Thread 170, Elapsed 398 ms
00605     2010-11-22 15:13:16.062 Connected: /10.0.1.27:995 (pop3s), Thread 1978, Elapsed 1 ms
00606     2010-11-22 15:13:16.062 Connected: /10.0.1.27:993 (imaps), Thread 1972, Elapsed 5 ms
00607     2010-11-22 15:13:19.062 Connected: /10.0.1.27:111 (sunrpc), Thread 230, Elapsed 3499 ms
00608     2010-11-22 15:13:19.109 Connected: /10.0.1.27:139 (netbios-ssn), Thread 278, Elapsed 3532 ms
00609     2010-11-22 15:13:19.687 Connected: /10.0.1.27:443 (https), Thread 880, Elapsed 3949 ms
00610     
00611     2010-11-22 15:13:37.062 Completed.   \endverbatim
00612  *  
00613  */
00614 /*! \page p_task IP1-9.5.1 Uppgift
00615  *  
00616  *  Skapa ett fristående program som vid kommando-promten startas med 
00617  *  
00618  *  \verbatim
00619     java PortScanner <host-start> <host-stop> <port-start> <port-stop> <result file> \endverbatim
00620  *    
00621  *  som scannar av alla (stream sockets) IP-adresser i intervallet 
00622  *  <code>&lt;host-start&gt;</code> till <code>&lt;host-stop&gt;</code> 
00623  *  för alla portar i intervallet <code>&lt;port-start&gt;</code> 
00624  *  till <code>&lt;port-stop&gt;</code> och skriver resultatet till 
00625  *  filen <code>&lt;result file&gt;</code>.
00626  *  
00627  *  Frivillig utvidgning är att till varje port som är en standarport för någon 
00628  *  tjänst även ta med tjänstens namn i filen <code>&lt;result file&gt;</code>.
00629  * 
00630  *  Observera! En del datoransvariga anser att portskanning är dataintrång och anmäler
00631  *  sådana till den ISP varifrån man kört skanningen.  
00632  *
00633  */

Generated on Thu Dec 16 2010 12:29:37 for Port Scanner by  doxygen 1.7.2