/***************************************************************************** * Copyright (c) 2007, 2008 g-Eclipse Consortium * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Initial development of the original code was made for the * g-Eclipse project founded by European Union * project number: FP6-IST-034327 http://www.geclipse.eu/ * * Contributors: * Ariel Garcia - initial API and implementation *****************************************************************************/ package eu.geclipse.core.util; import java.lang.Math; import java.net.DatagramSocket; import java.net.DatagramPacket; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.text.DateFormat; import java.util.Arrays; import java.util.Date; import org.eclipse.core.runtime.IProgressMonitor; import eu.geclipse.core.ICoreProblems; import eu.geclipse.core.ICoreSolutions; import eu.geclipse.core.Messages; import eu.geclipse.core.internal.Activator; import eu.geclipse.core.reporting.IProblem; import eu.geclipse.core.reporting.ISolution; import eu.geclipse.core.reporting.ProblemException; import eu.geclipse.core.reporting.ReportingPlugin; /** * A time checker using the TIME protocol (RFC 868), * also known as rdate protocol. * * @author agarcia */ public class TimeChecker { // Public rdate servers static final String[] SERVERS = { "time.fu-berlin.de", //$NON-NLS-1$ "time.mtu.edu", //$NON-NLS-1$ "ntp0.csx.cam.ac.uk" //$NON-NLS-1$ }; // Port private static final int RDATE_PORT = 37; // Seconds from midnight Jan. 1st 1900 - 1970 private static final long EPOCH_DIFFERENCE = 2208988800L; // Connection timeout in milliseconds private static final int TIMEOUT = 5000; // Maximum allowed system-time offset in seconds private static final int MAX_SYSTIME_OFFSET = 90; // Maximum expected time difference between servers in seconds private static final int MAX_SERVER_SPREAD = 10; // The system time at the moment of querying the rdate servers private long sysTime; // The reference time, mean of the values returned by the servers private long referenceTime; /** * The constructor. Each instance stores the system and reference * time at the time of calling <code>{@link #checkSysTime}</code>. */ public TimeChecker() { this.sysTime = 0; this.referenceTime = 0; } /** * Gets the system clock status. The method <code>{@link #checkSysTime}</code> * must be called first to initialize the TimeChecker instance. * * @return <code>true</code> if the system time is OK, false otherwise */ public boolean getTimeCheckStatus() { boolean result = false; if( ( this.sysTime != 0 ) && ( Math.abs( this.sysTime - this.referenceTime ) <= MAX_SYSTIME_OFFSET ) ) { result = true; } return result; } /** * Gets the time from some rdate time servers and compares it * to the system time. In the worst case, this method can take * up to TIMEOUT * SERVERS.length seconds (currently 15s) to * return. DNS timeouts would add to this delay, but they should * be a pathological case only. * The method <code>{@link #getTimeCheckStatus}</code> must be * called afterwards to get the result of the system time check. * * @param monitor a progress monitor used to monitor the * time-check operation * @throws GridException if no servers could be contacted or they yielded * inconsistent results * @throws InterruptedException if the operation was canceled by the user */ public void checkSysTime( final IProgressMonitor monitor ) throws ProblemException, InterruptedException { long[] time = new long[ SERVERS.length ]; long mean = 0; long disp = 0; ProblemException problemExc = null; // Time from 1970 in seconds... this.sysTime = System.currentTimeMillis() / 1000; // Network queries might take some time, use a progress monitor monitor.beginTask( Messages.getString( "TimeChecker.quering_time_servers" ), //$NON-NLS-1$ SERVERS.length ); // Loop over all servers for ( int i = 0; i < SERVERS.length; ++i ) { monitor.subTask( Messages.getString( "TimeChecker.contacting" ) //$NON-NLS-1$ + " " + SERVERS[ i ] + "..." ); //$NON-NLS-1$ //$NON-NLS-2$ try { time[ i ] = queryTime( SERVERS[ i ] ); } catch ( ProblemException pe ) { // We don't care about individual servers failing time[ i ] = 0; // Keep the last exception, we propagate it to the user if this check as a whole fails problemExc = pe; } monitor.worked( 1 ); if ( monitor.isCanceled() ) { throw new InterruptedException(); } } Arrays.sort( time ); // The potentially time consuming operations are finished now monitor.done(); // Loop over all results and compare them short i_min = 0; for ( int i = 0; i < SERVERS.length; ++i ) { // Search the first non-null result, time[] is sorted if ( time[ i ] == 0 ) { i_min ++; } else { mean += time[ i ]; } } // No servers could be contacted if ( i_min == SERVERS.length ) { IProblem problem; problem = ReportingPlugin.getReportingService() .getProblem( ICoreProblems.SYS_SYSTEM_TIME_CHECK_FAILED, null, problemExc, Activator.PLUGIN_ID ); problem.addReason( Messages.getString( "TimeChecker.no_servers_reachable" ) ); //$NON-NLS-1$ ISolution solution; solution = ReportingPlugin.getReportingService() .getSolution( ICoreSolutions.NET_CHECK_INTERNET_CONNECTION, null ); problem.addSolution( solution ); solution = ReportingPlugin.getReportingService() .getSolution( ICoreSolutions.NET_CHECK_FIREWALL, null ); problem.addSolution( solution ); throw new ProblemException( problem ); } mean = mean / ( SERVERS.length - i_min ); this.referenceTime = mean; // The servers don't agree among themselves disp = time[ SERVERS.length -1 ] - time[ i_min ]; if ( disp >= MAX_SERVER_SPREAD ) { IProblem problem = ReportingPlugin.getReportingService() .getProblem( ICoreProblems.SYS_SYSTEM_TIME_CHECK_FAILED, null, problemExc, Activator.PLUGIN_ID ); problem.addReason( Messages.getString( "TimeChecker.inconsistent_servers" ) ); //$NON-NLS-1$ ISolution solution = ReportingPlugin.getReportingService() .getSolution( ICoreSolutions.NET_CONTACT_SERVER_ADMIN, null ); problem.addSolution( solution ); throw new ProblemException( problem ); } } /** * This method contacts an rdate server and returns the time * it reported. It <b>blocks</b> until it gets the answer back or * a timeout (TIMEOUT seconds) happens. * * @param serverHostname the rdate server to contact * @return the time in seconds since 1970 * @throws GridException if the server could not be contacted */ protected static long queryTime( final String serverHostname ) throws ProblemException { long time = 0; InetAddress addr; // Resolve the server's IP address try { addr = InetAddress.getByName( serverHostname ); } catch ( UnknownHostException uhe ) { throw new ProblemException( ICoreProblems.NET_UNKNOWN_HOST, uhe, Activator.PLUGIN_ID ); } /* * The rdate "protocol" is trivial: establish a connection * to the server on UDP/37 and send an empty datagram, the * server answers back with the time in seconds since 1900. */ DatagramSocket s; try { s = new DatagramSocket(); s.setSoTimeout( TIMEOUT ); } catch ( SocketException se ) { throw new ProblemException( ICoreProblems.NET_BIND_FAILED, se, Activator.PLUGIN_ID ); } // Empty datagram to be sent to the rdate server DatagramPacket p; p = new DatagramPacket( new byte[0], 0, addr, RDATE_PORT ); // Contact the server try { s.send( p ); } catch ( Exception exc ) { throw new ProblemException( ICoreProblems.NET_CONNECTION_FAILED, exc, Activator.PLUGIN_ID ); } // Datagram to collect the server's answer, 4 bytes byte[] b = new byte[4]; p = new DatagramPacket( b, b.length); // We wait for the server's response try { s.receive( p ); } catch ( Exception exc ) { throw new ProblemException( ICoreProblems.NET_CONNECTION_TIMEOUT, exc, Activator.PLUGIN_ID ); } // Read value from the buffer time = readLongFromNetwork( b ); // The system time is measured starting from 1970... return time - EPOCH_DIFFERENCE; } /** * Reads an array of bytes (unsigned!) into a long. Network order is * big endian (MSB first), which is also Java's default for a byte array. * * @param array the array to read * @return the long value contained in the array */ private static long readLongFromNetwork( final byte[] array ) { long unsigned = 0; long val = 0; for ( int i = 0; i < array.length; ++i ) { // The first shift is a no-op val <<= 8; // This casts the byte to long, avoiding sign issues unsigned = 0xFFL & array[ i ]; val |= unsigned; } return val; } /** * Gets the system date/time. The method <code>{@link #checkSysTime}</code> * must be called first to initialize the TimeChecker instance. * * @return a string representation of the system date/time */ public String getSystemDate() { // Date needs the time in ms Date date = new Date( this.sysTime * 1000 ); DateFormat df = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.FULL); return df.format( date ); } /** * Gets the reference date/time. The method <code>{@link #checkSysTime}</code> * must be called first to initialize the TimeChecker instance. * * @return a string representation of the reference date/time */ public String getReferenceDate() { // Date needs the time in ms Date date = new Date( this.referenceTime * 1000 ); DateFormat df = DateFormat.getDateTimeInstance( DateFormat.MEDIUM, DateFormat.FULL); return df.format( date ); } /** * Gets the system clock offset. The method <code>{@link #checkSysTime}</code> * must be called first to initialize the TimeChecker instance. * * @return the system clock offset in seconds */ public long getOffset() { return ( Math.abs( this.sysTime - this.referenceTime ) ); } /** * Gets the tolerance for the system clock offset. * * @return the maximum allowed system clock offset */ public int getTolerance() { return MAX_SYSTIME_OFFSET; } }