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

Traceroute.java

Go to the documentation of this file.
00001 
00002 import java.io.IOException;
00003 import java.net.Inet4Address;
00004 import java.net.InetAddress;
00005 import java.net.MalformedURLException;
00006 import java.net.URL;
00007 import java.net.UnknownHostException;
00008 import java.util.Arrays;
00009 
00010 import jpcap.JpcapCaptor;
00011 import jpcap.JpcapSender;
00012 import jpcap.NetworkInterface;
00013 import jpcap.NetworkInterfaceAddress;
00014 import jpcap.packet.EthernetPacket;
00015 import jpcap.packet.ICMPPacket;
00016 import jpcap.packet.IPPacket;
00017 import jpcap.packet.Packet;
00018 import jpcap.packet.TCPPacket;
00019 
00020 /**
00021  *  Encapsulates ICMP interface that can be used to ping or trace route remote
00022  *  hosts. 
00023  *  
00024  *  @author Mikica B Kocic
00025  *
00026  */
00027 public class Traceroute implements Runnable
00028 {
00029     /**
00030      *  Provides message presentation context to instance of <code>Traceroute</code>.
00031      */
00032     public interface Context
00033     {
00034         /**
00035          *  Logs new line
00036          */
00037         public abstract void logNewLine ();
00038         
00039         /**
00040          *  Logs message, with optional timestamp
00041          *  
00042          *  @param str        message that will be logged
00043          *  @param timestamp  indicates whether to put timestamp in front
00044          */
00045         public abstract void logMessage( String str, boolean timestamp );
00046         
00047         /**
00048          *  Changed status
00049          */
00050         public abstract void onTracerouteCompleted ();
00051     }
00052 
00053     //////////////////////////////////////////////////////////////////////////////////////
00054 
00055     /**
00056      *  Instance of the thread that sends ICMP packets
00057      */
00058     private Thread tracingThread;
00059     
00060     /**
00061      *  Indicates if thread is (or should be) running
00062      */
00063     private volatile boolean running = false;
00064     
00065     /**
00066      *  Indicates that thread has been completed
00067      */
00068     private volatile boolean completed;
00069     
00070     /**
00071      *  Jpcap <code>NetworkInterface</code> device count. 
00072      */
00073     private int deviceCount = 0;
00074     
00075     /**
00076      *  Instance of the Jpcap capturing class
00077      */
00078     private JpcapCaptor captor = null;
00079     
00080     /**
00081      *  Instance of the Jpcap network interface used for sending and 
00082      *  receiving ICMP packets
00083      */
00084     private NetworkInterface device = null;
00085     
00086     /**
00087      *  Local IP address
00088      */
00089     private InetAddress localIP = null;
00090     
00091     /**
00092      *  Indicates whether to resolve addresses to names or not. By default
00093      *  disabled, because resolving will slow down trace route presentation.
00094      */
00095     private boolean resolveNames = false;
00096     
00097     /**
00098      *  Host name or IP address to ping
00099      */
00100     private String hostName;
00101     
00102     /**
00103      *  Initial TTL (time to live or hop count). When set to 0, thread will do
00104      *  trace route. When set to e.g. 64, thread will do ping.
00105      */
00106     private int initialTTL;
00107     
00108     /**
00109      *  Presentation context for messages
00110      */
00111     private Context context;
00112 
00113     //////////////////////////////////////////////////////////////////////////////////////
00114 
00115     /**
00116      *  Creates new instance of <code>Traceroute</code>
00117      *  
00118      *  @param context   where to log messages
00119      */
00120     public Traceroute( Context context )
00121     {
00122         this.context       = context;
00123         this.running       = false;
00124         this.completed     = true;
00125         this.tracingThread = null;
00126         
00127         deviceCount = JpcapCaptor.getDeviceList ().length;
00128     }
00129 
00130     /**
00131      *  Prints line to log area prefixed with timestamp 
00132      */
00133     private void println( String str )
00134     {
00135         context.logMessage( str, /*timestamp*/ true );
00136         context.logNewLine ();
00137     }
00138 
00139     /**
00140      *  Advances to new line in log area 
00141      */
00142     private void println ()
00143     {
00144         context.logNewLine ();
00145     }
00146     
00147     /**
00148      *  Prints characters to log area optionally prefixed with timestamp
00149      *  
00150      *  @param str         message to log
00151      *  @param timestamp   whether to prefix message with timestamp or not
00152      */
00153     private void print( String str, boolean timestamp )
00154     {
00155         context.logMessage( str,timestamp );
00156     }
00157     
00158     /**
00159      *  Starts thread that will trace route to given host. The instance is 
00160      *  locked in the mean time, so other trace routes could not start in parallel.
00161      *  To start trace-route <code>initialTTL</code> must be set to 0. To start
00162      *  ping instead, set <code>initialTTL</code> to 64.
00163      *  
00164      *  @param deviceNo    network interface on which to start pinging
00165      *  @param hostName    target host address or host name
00166      *  @param initialTTL  initial hop limit (or time-to-live)
00167      */
00168     public void startPinging( int deviceNo, String hostName, int initialTTL )
00169     {
00170         synchronized( this )
00171         {
00172             if ( ! completed ) {  // Allows only one thread per instance
00173                 return;
00174             }
00175     
00176             /* Set thread parameters
00177              */
00178             openDeviceOnInterface( deviceNo );
00179             this.hostName   = hostName;
00180             this.initialTTL = initialTTL;
00181 
00182             /* Enable thread
00183              */
00184             running   = true;
00185             completed = false;
00186 
00187             /* Start thread
00188              */
00189             tracingThread = new Thread( this );
00190             tracingThread.start ();
00191         }
00192     }
00193 
00194     /**
00195      *  Stops on-going trace route or ping
00196      */
00197     public void stopTrace ()
00198     {
00199         synchronized( this )
00200         {
00201             running = false; // signal thread to exit
00202             this.notifyAll (); // interrupt any sleep
00203         }
00204 
00205         print( " <interrupt> ", /*timestamp*/ false );
00206     }
00207 
00208     /**
00209      *  Returns if IDLE, i.e. if tracing thread does not exist or previous thread has
00210      *  been completed.
00211      */
00212     public boolean isIdle ()
00213     {
00214         return completed;
00215     }
00216 
00217     /**
00218      *  Dumps details about particular Jpcap network interface into log area
00219      *  
00220      *  @param title  title line
00221      *  @param ni     network interface to show 
00222      */
00223     public void dumpInterfaceInfo( String title, NetworkInterface ni )
00224     {
00225         println( title );
00226         println( "    Desc: " + ni.description );
00227         println( "    Name: " + ni.name );
00228         for( NetworkInterfaceAddress na : ni.addresses )
00229         {
00230             println( "    Addr: " + na.address );
00231         }
00232     }
00233     
00234     /**
00235      *  Gets array of interface descriptions (suitable for the JComboBox)
00236      *  
00237      *  @return array of strings with descriptions
00238      */
00239     public String[] getInterfaceList ()
00240     {
00241         this.deviceCount = JpcapCaptor.getDeviceList ().length;
00242         String[] devList = new String[ this.deviceCount ];
00243         
00244         int ni_index = 0;
00245         for( NetworkInterface ni : JpcapCaptor.getDeviceList () )
00246         {
00247             String ourDescription = ni.description;
00248             for( NetworkInterfaceAddress addr : ni.addresses )
00249             {
00250                 if( addr.address instanceof Inet4Address ) {
00251                     ourDescription = addr.address.toString () + " -- " + ni.description;
00252                     break;
00253                 }
00254             }
00255 
00256             devList[ ni_index ] = " iface" + ni_index + " -- " + ourDescription;
00257 
00258             dumpInterfaceInfo( "Interface " + (ni_index++), ni );
00259         }
00260         
00261         return devList;
00262     }
00263     
00264     /*
00265      *  Open Jpcap device to send/receive on particular network interface
00266      *  
00267      *  @param deviceNo  device index (e.g., 0, 1..)
00268      */
00269     private void openDeviceOnInterface( int deviceNo )
00270     {
00271         // Open specified device from the list
00272         //
00273         device = JpcapCaptor.getDeviceList()[ deviceNo ];
00274         localIP = null;
00275         captor = null;
00276         
00277         try
00278         {
00279             captor = JpcapCaptor.openDevice( device, 
00280                     /*MTU*/ 2000, /*promiscuous*/ false, /*timeout*/ 1 );
00281             
00282             // captor.setNonBlockingMode( true );
00283             // captor.setPacketReadTimeout( 1000 );
00284 
00285             for( NetworkInterfaceAddress addr : device.addresses )
00286             {
00287                 if( addr.address instanceof Inet4Address ) {
00288                     localIP = addr.address;
00289                     break;
00290                 }
00291             }
00292         }
00293         catch ( IOException e )
00294         {
00295             device  = null;
00296             localIP = null;
00297             captor  = null;
00298         }
00299     }
00300 
00301     /**
00302      *  Interruptible sleep (replacement for <code>Thread.sleep</code>).
00303      *  
00304      *  @param millis - the length of time to sleep in milliseconds. 
00305      */
00306     private void interruptibleSleep( int millis )
00307     {
00308         synchronized( this )
00309         {
00310             try {
00311                 this.wait( millis );
00312             }
00313             catch( InterruptedException ie ) {
00314                 running = false; // kills the thread
00315             }
00316         }
00317     }
00318     
00319     /**
00320      *  Obtains MAC address of the default gateway for captor interface.
00321      *  
00322      *  @return MAC address as byte array
00323      */
00324     private byte[] obtainDefaultGatewayMac( String httpHostToCheck )
00325     {
00326         print( "Obtaining default gateway MAC address... ", true );
00327         
00328         byte[] gatewayMAC = null;
00329         
00330         if ( captor != null ) try
00331         {
00332             InetAddress hostAddr = InetAddress.getByName( httpHostToCheck );
00333             captor.setFilter( "tcp and dst host " + hostAddr.getHostAddress(), true );
00334             
00335             int timeoutTimer = 0;
00336             new URL("http://" + httpHostToCheck ).openStream().close();
00337             
00338             while( running )
00339             {
00340                 Packet ping = captor.getPacket ();
00341                 
00342                 if( ping == null )
00343                 {
00344                     if ( timeoutTimer < 20 ) { // max 2 sec
00345                         interruptibleSleep( 100  /*millis*/ );
00346                         ++timeoutTimer;
00347                         continue;
00348                     }
00349                     /* else: Timeout exceeded
00350                      */
00351                     print( "<timeout>", /*timestamp*/ false );
00352                     println( "ERROR: Cannot obtain MAC address for default gateway." );
00353                     println( "Maybe there is no default gateway on selected interface?" );
00354                     return gatewayMAC;
00355                 }
00356                 
00357                 byte[] destinationMAC = ((EthernetPacket)ping.datalink).dst_mac; 
00358                 
00359                 if( ! Arrays.equals( destinationMAC, device.mac_address ) ) {
00360                     gatewayMAC = destinationMAC;
00361                     break;
00362                 }
00363 
00364                 timeoutTimer = 0; // restart timer
00365                 new URL("http://" + httpHostToCheck ).openStream().close();
00366             }
00367         }
00368         catch( MalformedURLException e )
00369         {
00370             println( "Invalid URL: " + e.toString () );
00371         }
00372         catch( UnknownHostException e )
00373         {
00374             println( "Unknown host: " + httpHostToCheck );
00375         }
00376         catch( IOException e )
00377         {
00378             println( "ERROR: " + e.toString () );
00379         }
00380         
00381         print( " OK.", /*timestamp*/ false );
00382         println ();
00383         return gatewayMAC;
00384     }
00385 
00386     /**
00387      *  Scan TCP ports 
00388      *
00389      *  @throws UnknownHostException 
00390      */
00391     public boolean tcpPortScan ( int localPort, String remoteHost, int rp1, int rp2 )
00392         throws UnknownHostException, IOException
00393     {
00394         println( "-------------------------------------------------" );
00395         print( "Looking up " + hostName + "...", /*timestamp*/ true );
00396         
00397         InetAddress remoteIP = InetAddress.getByName( remoteHost );
00398         
00399         print( "  " + remoteIP.getHostAddress (), /*timestamp*/ false );
00400         println ();
00401 
00402         byte[] defaultGatewayMAC = obtainDefaultGatewayMac( "dsv.su.se" );
00403         if ( defaultGatewayMAC == null )
00404         {
00405             running = false;
00406             completed = true;
00407             context.onTracerouteCompleted ();
00408             return running;
00409         }
00410 
00411         TCPPacket tcp = new TCPPacket(
00412                 localPort,  // int src_port 
00413                 0,          // int dst_port 
00414                 701,        // long sequence 
00415                 0,          // long ack_num 
00416                 false,      // boolean urg 
00417                 false,      // boolean ack 
00418                 false,      // boolean psh 
00419                 false,      // boolean rst 
00420                 true,       // boolean syn 
00421                 false,      // boolean fin 
00422                 true,       // boolean rsv1 
00423                 true,       // boolean rsv2 
00424                 10,         // int window
00425                 10          // int urgent
00426                 );
00427         
00428         tcp.setIPv4Parameter(
00429                 0,          // int priority - Priority
00430                 false,      // boolean: IP flag bit: Delay
00431                 false,      // boolean: IP flag bit: Through
00432                 false,      // boolean: IP flag bit: Reliability
00433                 0,          // int: Type of Service (TOS)
00434                 false,      // boolean: Fragmentation Reservation flag
00435                 false,      // boolean: Don't fragment flag
00436                 false,      // boolean: More fragment flag
00437                 0,          // int: Offset
00438                 (int)(Math.random () * 65000), // int: Identifier
00439                 100,        // int: Time To Live
00440                 IPPacket.IPPROTO_TCP, // Protocol 
00441                 localIP,    // Source IP address
00442                 remoteIP    // Destination IP address
00443                 );
00444         
00445         tcp.data=("").getBytes();
00446         
00447         EthernetPacket ether = new EthernetPacket ();
00448         ether.frametype = EthernetPacket.ETHERTYPE_IP;
00449         ether.src_mac   = device.mac_address;
00450         ether.dst_mac   = defaultGatewayMAC;
00451         tcp.datalink    = ether;
00452 
00453         /* Send TCP packets...
00454          */
00455         JpcapSender sender = captor.getJpcapSenderInstance ();
00456         captor.setFilter( "tcp and dst port " + localPort 
00457                           + " and dst host " + localIP.getHostAddress(), 
00458                           true );
00459         
00460         for ( int remotePort = rp1; remotePort <= rp2; ++remotePort ) {
00461             tcp.src_port = localPort;
00462             tcp.dst_port = remotePort;
00463             sender.sendPacket( tcp );
00464             //println( "SENT: " + tcp );
00465         }
00466 
00467         while( running )
00468         {
00469             TCPPacket p = (TCPPacket)captor.getPacket ();
00470 
00471             if( p == null ) // TIMEOUT
00472             {
00473                 interruptibleSleep( 100 );
00474                 continue;
00475             }
00476 
00477             /* We are here because we got some ICMP packet... resolve name first.
00478              */
00479             String hopID = p.src_ip.getHostAddress ();
00480             if ( resolveNames ) {
00481                 p.src_ip.getHostName ();
00482                 hopID = p.src_ip.toString ();
00483             }
00484 
00485             // println( "---------------------------------------------------------" );
00486             // println( "RECEIVED: " + p.toString () );
00487             
00488             if ( ! p.rst ) {
00489                 if ( p.ack_num == 702 ) {
00490                     println( "---- " + hopID + " : " + p.src_port );
00491                 }
00492                 tcp.dst_port = p.src_port;
00493                 tcp.fin = p.ack_num == 702 ? true : false;
00494                 tcp.ack = true;
00495                 tcp.rst = false;
00496                 tcp.syn = false;
00497                 tcp.sequence = p.ack_num;
00498                 tcp.ack_num = p.sequence + 1;
00499                 sender.sendPacket( tcp );
00500                 //println( "SENT: " + tcp );
00501             }
00502             // running = false;
00503         }
00504         
00505         return running;
00506     }
00507     
00508     /**
00509      *  Traces route to given host. The instance is locked during in the mean time
00510      *  so other trace routes could not start (completed == false suppresses other 
00511      *  threads).
00512      */
00513     @Override
00514     public void run ()
00515     {
00516         /* Release instance to other threads
00517          */
00518         if ( ! running ) {
00519             completed = true;
00520             context.onTracerouteCompleted ();
00521             return;
00522         }
00523 
00524         /* Make sure that capturing device is configured
00525          */
00526         if ( captor == null ) {
00527             println( "Capturing device is not configured..." );
00528             running   = false;
00529             completed = true;
00530             context.onTracerouteCompleted ();
00531             return;
00532         }
00533 
00534         /* Starts sending ICMP packets and tracing route...
00535          */
00536         try
00537         {
00538 /*            
00539             if ( ! tcpPortScan( 15000, hostName, 1, 1000 ) ) {
00540                 completed = true;
00541                 context.onTracerouteCompleted ();
00542                 return;
00543             }
00544 */
00545             println( "-------------------------------------------------" );
00546             print( "Looking up " + hostName + "...", /*timestamp*/ true );
00547             
00548             InetAddress remoteIP = InetAddress.getByName( hostName );
00549             
00550             print( "  " + remoteIP.getHostAddress (), /*timestamp*/ false );
00551             println ();
00552 
00553             byte[] defaultGatewayMAC = obtainDefaultGatewayMac( "dsv.su.se" );
00554             if ( defaultGatewayMAC == null )
00555             {
00556                 running = false;
00557                 completed = true;
00558                 context.onTracerouteCompleted ();
00559                 return;
00560             }
00561   
00562             if ( initialTTL == 0 ) {
00563                 println( "Tracing route to " + remoteIP + "..." );
00564             } else {
00565                 println( "Pinging host " + remoteIP + "..." );
00566             }
00567             
00568             /* Create ICMP packet
00569              */
00570             ICMPPacket icmp = new ICMPPacket ();
00571 
00572             icmp.type       = ICMPPacket.ICMP_ECHO;
00573             icmp.seq        = 100;
00574             icmp.id         = 0;
00575             icmp.data       = "data".getBytes ();
00576             
00577             icmp.setIPv4Parameter(
00578                     0,          // int priority - Priority
00579                     false,      // boolean: IP flag bit: Delay
00580                     false,      // boolean: IP flag bit: Through
00581                     false,      // boolean: IP flag bit: Reliability
00582                     0,          // int: Type of Service (TOS)
00583                     false,      // boolean: Fragmentation Reservation flag
00584                     false,      // boolean: Don't fragment flag
00585                     false,      // boolean: More fragment flag
00586                     0,          // int: Offset
00587                     0,          // int: Identifier
00588                     0,          // int: Time To Live
00589                     IPPacket.IPPROTO_ICMP, // Protocol 
00590                     localIP,    // Source IP address
00591                     remoteIP    // Destination IP address
00592                     );
00593 
00594             EthernetPacket ether = new EthernetPacket ();
00595             ether.frametype = EthernetPacket.ETHERTYPE_IP;
00596             ether.src_mac   = device.mac_address;
00597             ether.dst_mac   = defaultGatewayMAC;
00598             icmp.datalink   = ether;
00599 
00600             /* Send ICMP packets...
00601              */
00602             JpcapSender sender = captor.getJpcapSenderInstance ();
00603             captor.setFilter( "icmp and dst host " + localIP.getHostAddress(), true );
00604             
00605             icmp.hop_limit  = (short)initialTTL;
00606             print( icmp.hop_limit + ": ", /*timestamp*/ true );
00607             int timeoutTimer = 0;
00608             int timeoutCounter = 0;
00609             long tStart = System.nanoTime ();
00610             sender.sendPacket( icmp );
00611             
00612             while( running )
00613             {
00614                 ICMPPacket p = (ICMPPacket)captor.getPacket ();
00615                 int tDelay = (int)( ( System.nanoTime () - tStart ) / 1000000l );
00616 
00617                 if( p == null ) // TIMEOUT
00618                 {
00619                     /* Continue waiting until ~2 sec elapses
00620                      */
00621                     if ( timeoutTimer < 30 ) 
00622                     {
00623                         interruptibleSleep( timeoutTimer < 10 ? 1 : 100 );
00624                         ++timeoutTimer;
00625                         if ( timeoutTimer >= 10 ) {
00626                             print( ".", /*timestamp*/ false );
00627                         }
00628                         continue;
00629                     }
00630 
00631                     /* Increase timeout counter and either retry or advance Hop limit
00632                      */
00633                     ++timeoutCounter;
00634                     print( " Timeout #" + timeoutCounter, /*timestamp*/ false );
00635                     
00636                     if ( timeoutCounter < 3 ) // Retry send to the same Hop
00637                     {
00638                         print( icmp.hop_limit + ": ", /*timestamp*/ true );
00639                         tStart = System.nanoTime ();
00640                         timeoutTimer = 0;
00641                         sender.sendPacket( icmp );
00642                     }
00643                     else // Advance Hop limit and send to next hop
00644                     {
00645                         ++icmp.hop_limit;
00646                         print( icmp.hop_limit + ": ", /*timestamp*/ true );
00647                         timeoutTimer = 0;
00648                         timeoutCounter = 0;
00649                         tStart = System.nanoTime ();
00650                         sender.sendPacket( icmp );
00651                     }
00652                     continue;
00653                 }
00654                 
00655                 /* We are here because we got some ICMP packet... resolve name first.
00656                  */
00657                 String hopID = p.src_ip.getHostAddress ();
00658                 if ( resolveNames ) {
00659                     p.src_ip.getHostName ();
00660                     hopID = p.src_ip.toString ();
00661                 }
00662 
00663                 /* Now, in case if we received 'time exceeded' packet we should advance
00664                  * to the next Hop limit. Otherwise if host is either unreachable or 
00665                  * we got echo reply, we should quit.
00666                  */
00667                 if( p.type == ICMPPacket.ICMP_TIMXCEED ) // Time exceeded
00668                 {
00669                     print( hopID + ", " + tDelay + " ms", /*ts*/ false  );
00670                     ++icmp.hop_limit;
00671                     print( icmp.hop_limit + ": ", /*timestamp*/ true );
00672                     timeoutTimer = 0;
00673                     timeoutCounter = 0;
00674                     tStart = System.nanoTime ();
00675                     sender.sendPacket( icmp );
00676                 }
00677                 else if( p.type == ICMPPacket.ICMP_UNREACH ) // Host unreachable
00678                 {
00679                     print( hopID + " unreachable", /*ts*/ false  );
00680                     running = false;
00681                 }
00682                 else if( p.type == ICMPPacket.ICMP_ECHOREPLY ) // Echo reply (pong)
00683                 {
00684                     print( hopID + ", " + tDelay + " ms", /*ts*/ false );
00685                     if ( initialTTL != 0 ) {
00686                         println( hopID + " is alive." );
00687                     }
00688                     running = false;
00689                 }
00690             }
00691         }
00692         catch( UnknownHostException e )
00693         {
00694             println( "Unknown host: " + hostName );
00695             completed = true;
00696             context.onTracerouteCompleted ();
00697             return;
00698         }
00699         catch( IOException e )
00700         {
00701             println( "ERROR: " + e.toString () );
00702             completed = true;
00703             context.onTracerouteCompleted ();
00704             return;
00705         }
00706 
00707         /* Release instance to other threads
00708          */
00709         println( initialTTL == 0 ? "Traceroute completed." : "Ping completed." );
00710         completed = true;
00711         context.onTracerouteCompleted ();
00712      }
00713 }

Generated on Thu Dec 16 2010 12:29:23 for Traceroute and Ping by  doxygen 1.7.2