/************************************************************************** * * $Id: ntptest.c 1.17 2021/11/19 14:33:54 martin.burnicki REL_M $ * * Copyright (c) Meinberg Funkuhren, Bad Pyrmont, Germany * * Description: * Main module for ntptest program. Sends one or several client * request packets to an NTP server, receives the replies and * prints NTP statistics. * * ----------------------------------------------------------------------- * $Log: ntptest.c $ * Revision 1.17 2021/11/19 14:33:54 martin.burnicki * If built from a git repo, use the version from git preferably. * Fixed a potential infinite loop with getopt on systems * where char is unsigned. * Revision 1.16 2020/04/01 12:50:08 martin * Use exit codes defined in mbgerror.h. * Revision 1.15 2019/09/04 07:42:17 martin * Return specific exit codes in case of error, and * list the valid exit codes in the usage information. * Revision 1.14 2018/07/31 16:14:14 martin * Changed version number to 1.10. * Support symmetric MD5 keys. * Configurable number for request packet mode. * Print request packet even if no response was received. * Support build on FreeBSD. * Revision 1.13 2018/03/29 12:18:05 martin * Don't print min/max results after single query. * Changed version number to 1.9. * Revision 1.12 2018/03/09 09:59:29 martin * Updated version number to 1.8 and copyright year to 2018. * Revision 1.11 2017/09/06 14:26:59 martin * Support build under Windows. * Revision 1.10 2017/09/06 14:14:40Z martin * Changed version code to 1.7, and copyright year to 2017. * Revision 1.9 2017/01/05 14:08:58 martin * Changed version code to 1.6. * Conditionally display dispersion and leap smear offset. * Revision 1.8 2016/12/01 17:09:20 martin * New version number 1.5. * Distinguish flags which increase verbosity or print packets. * Revision 1.7 2016/11/21 17:32:54 martin * New parameter -P to set client poll value sent to server. * Moved some Windows-specific code elsewhere. * Revision 1.6 2016/08/05 12:42:48 martin * Support build build under Windows. * Use precise time API under Windows, if supported. * Make stdout unbuffered under POSIX, so output is shown * immediately even if piped e.g. via the tee command. * Use safe string functions. * Use compatible Meinberg error codes. * Revision 1.5 2015/06/26 14:11:56Z martin * Don't exit on initial timeout in continuous mode. * Revision 1.4 2015/05/11 11:12:51 martin * Support continuous monitoring (-Z), e.g. for leap second tests. * Revision 1.3 2015/02/27 13:10:31 martin * Fixed a bug where the xmt/rcv packets were interchanged when printed. * Revision 1.2 2014/09/16 15:54:09 martin * Account for updated library files. * Revision 1.1 2014/04/01 13:02:21 martin * Initial revision. * **************************************************************************/ #include #if defined( _USE_MBG_NTP_AUTH ) #include #endif #include #include #if defined( MBG_TGT_WIN32 ) #include #include #endif #if defined( MBG_TGT_POSIX ) #include #include #include #include #endif #include #include #include #include #include #include static const char program_name[] = "ntptest"; // If built from a git repo, the version can be // predefined by the Makefile. #if !defined( NTPTEST_VERSION ) #define NTPTEST_VERSION 1.12 #endif static const char program_version[] = "v" STRINGIFY( NTPTEST_VERSION ); static const char program_copyright[] = "(c) Meinberg 2014-2021"; static const char program_contact[] = "contact: "; static int exit_code = MBG_NTP_EXIT_SUCCESS; #if defined( MBG_TGT_WIN32 ) #define __const__ const #define clock_gettime mbg_clock_gettime #endif #define RCV_TIMEOUT 400 // [msec] #if defined( MBG_TGT_WIN32 ) #define SLEEP_INTV_SEC_SCALE 1000 // [msec/sec] #define SLEEP_INTV_USEC_SCALE 1000 // [usec/msec] #else #define SLEEP_INTV_SEC_SCALE 1 // [sec/sec] #define SLEEP_INTV_USEC_SCALE 1 // [usec/usec] #endif #define MIN_SLEEP_INTV ( 1 * SLEEP_INTV_SEC_SCALE ) #define DFLT_SHORT_SLEEP_INTV ( 250000L / SLEEP_INTV_USEC_SCALE ) // parameters which can to some extent be modified via command // line options to control the program's behaviour static int run_continuously; // -c, -Z static int sleep_intv = MIN_SLEEP_INTV; // -s, (-u, -Z also on non-POSIX), sleep interval between requests #if defined( MBG_TGT_POSIX ) static int usleep_intv; // -u, -Z (POSIX only), short sleep interval between requests #endif static int prot_version; // -V, protocol version to be used static int req_mode = MODE_CLIENT; // -M, NTP packet mode sent in requests, "client" by default static unsigned long rcv_timeout; // -T, [msec] receive timeout after request sent static int print_packets; // print full packet details static int verbose; // -v print more verbose output static int compensate_initial_offset; // -Z, compensate initial time offset to server #if defined( MBG_TGT_WIN32 ) static bool force_legacy_gstaft; #endif static const char *tgt_host; // target hostname or IP address static struct sockaddr_in tgt_addr; // target address static int must_print_usage; static NTP_PACKET default_ntp_req_packet; // template of request packet static QUERY_STATS glb_query_stats; #if defined( _USE_MBG_NTP_AUTH ) static NTP_KEY_ID md5_key_id; static char md5_key[128]; #endif /*HDR*/ void mbglog( int priority, const char *fmt, ... ) { char s[300]; va_list ap; va_start( ap, fmt); vsnprintf( s, sizeof( s ), fmt, ap ); va_end(ap); fprintf( stderr, "%s\n", s ); } // mbglog // // send query packet(s) to a host, wait for a response // and evaluate the response packet // static /*HDR*/ void do_ntp_queries( void ) { fd_set read_set; struct timeval tv_timeout; NTP_PACKET_INFO req_info = { 0 }; // will be set up from template NTP_PACKET_INFO resp_info = { 0 }; NTP_RESULTS rslt = { { 0 } }; #if defined( _USE_MBG_NTP_AUTH ) KEY_CACHE key_cache = { 0 }; #endif MBG_SOCK_FD sock_fd; int rc; struct sockaddr_in si_src; socklen_t slen_src; sock_fd = socket( AF_INET, SOCK_DGRAM, IPPROTO_UDP ); // create socket on which to send if ( sock_fd == MBG_INVALID_SOCK_FD ) { mbglog( LOG_ERR, "Failed to open UDP socket in %s: %s", __func__, mbg_strerror( mbg_get_last_socket_error( NULL ) ) ); exit( MBG_NTP_EXIT_FAIL_SOCKET ); } req_info.packet = default_ntp_req_packet; for (;;) { struct timespec tsr; // Get time stamp when request packet was sent. clock_gettime( CLOCK_REALTIME, &req_info.tsr ); // Convert to NTP format and save in request packet. timespec_to_ntp_tstamp( &req_info.packet.base.transmit_time, &req_info.tsr ); ntoh_ntp_time( &req_info.packet.base.transmit_time ); req_info.len = NTP_BASE_PACKET_SIZE; #if defined( _USE_MBG_NTP_AUTH ) if ( md5_key_id ) { int auth_code = mbg_ntp_auth_encrypt( &key_cache, &req_info, md5_key_id ); if ( auth_code != MBG_NTP_AUTH_OK ) { exit_code = MBG_NTP_EXIT_FAIL_ENCRYPT; mbglog( LOG_WARNING, "Failed to encrypt request packet with key %i", md5_key_id ); } } #endif // Send request packet. if ( sendto( sock_fd, (void *) &req_info.packet, req_info.len, 0, (__const__ struct sockaddr *) &tgt_addr, sizeof( tgt_addr ) ) == MBG_SOCKET_ERR_RETVAL ) { mbglog( LOG_ERR, "Failed to send UDP packet in %s: %s", __func__, mbg_strerror( mbg_get_last_socket_error( NULL ) ) ); exit( MBG_NTP_EXIT_FAIL_SEND ); } glb_query_stats.requests++; FD_ZERO( &read_set ); FD_SET( sock_fd, &read_set ); tv_timeout.tv_sec = 0; tv_timeout.tv_usec = rcv_timeout * 1000; rc = select( sock_fd + 1, &read_set, NULL, NULL, &tv_timeout ); // Take a receive time stamp here, but discard it // in case of an error. clock_gettime( CLOCK_REALTIME, &tsr ); if ( rc == MBG_SOCKET_ERR_RETVAL ) // select() failed { mbglog( LOG_ERR, "Select failed in %s: %s", __func__, mbg_strerror( mbg_get_last_socket_error( NULL ) ) ); break; } if ( rc == 0 ) // Timeout. { // Eventually print at least the request packet. if ( print_packets ) { // Adjust the endianess of the request packet first. ntoh_ntp_packet( &req_info ); print_ntp_results( NULL, &req_info, NULL, NULL, 1, verbose ); } mbglog( LOG_ERR, "Select timed out, received no response." ); goto cont; } // Read response packet. slen_src = sizeof( si_src ); resp_info.len = recvfrom( sock_fd, (void *) &resp_info.packet, sizeof( resp_info.packet ), 0, (struct sockaddr *) &si_src, &slen_src ); if ( resp_info.len == MBG_SOCKET_ERR_RETVAL ) { mbglog( LOG_ERR, "Failed to receive from UDP socket: %s", mbg_strerror( mbg_get_last_socket_error( NULL ) ) ); exit( MBG_NTP_EXIT_FAIL_RECEIVE ); } // Use the receive time stamp taken earlier. resp_info.tsr = tsr; // Authentication must be checked *before* byte order is adjusted #if defined( _USE_MBG_NTP_AUTH ) rslt.auth_code = mbg_ntp_auth_decrypt( &key_cache, &resp_info ); if ( rslt.auth_code == MBG_NTP_AUTH_NACK ) exit_code = MBG_NTP_EXIT_FAIL_CRYPT_NACK; else if ( rslt.auth_code == MBG_NTP_AUTH_FAIL ) exit_code = MBG_NTP_EXIT_FAIL_DECRYPT; #else rslt.auth_code = MBG_NTP_AUTH_NONE; #endif eval_ntp_packet( &rslt, &resp_info, &glb_query_stats ); // Adjust byte order of the request packet. ntoh_ntp_packet( &req_info ); if ( print_packets ) print_ntp_results( &rslt, &req_info, &resp_info, run_continuously ? &glb_query_stats : NULL, 1, verbose ); else { NTP_PACKET *p = &resp_info.packet; if ( compensate_initial_offset ) { static double initial_delta_t; static int has_been_called; if ( !has_been_called ) { // Compute and save the initial delta t. // The system time when the packet has been received is // available in tv_rcv and t_rcv. initial_delta_t = delta_tstamps( &p->base.transmit_time, &rslt.t_rcv ); printf( "Initial time offset %.9f will be compensated\n", initial_delta_t ); has_been_called = 1; } rslt.ntp_offs -= initial_delta_t; } print_ntp_time( "", &p->base.transmit_time, NULL, 1 ); printf( ", st %u, leap %u, offs[ms]: %+.3f", p->base.flags.stratum, p->base.flags.leap_ind, rslt.ntp_offs * 1000 ); if ( verbose ) { double d = 0.0; printf( ", spoll: %u", p->base.flags.poll ); if ( verbose > 1 ) printf( ", disp[s/s]: %.6f", _u_fp_to_d( p->base.root_dispersion ) ); if ( leap_smearing_active( &p->base.refid, &d ) || ( verbose > 2 ) ) printf( ", smear[s]: %.6f", d ); } } cont: printf( "\n" ); fflush( NULL ); if ( !run_continuously ) break; #if defined( MBG_TGT_WIN32 ) Sleep( sleep_intv ); #else if ( usleep_intv ) usleep( usleep_intv ); else sleep( sleep_intv ); #endif } #if defined( MBG_TGT_WIN32 ) closesocket( sock_fd ); #else close( sock_fd ); #endif } // do_ntp_queries static /*HDR*/ void usage( void ) { const char *exit_code_str[N_MBG_NTP_EXIT_CODES] = MBG_NTP_EXIT_CODE_STRS; int i; printf( "Usage: %s [[OPTION]...] \n", program_name ); printf( "\n" ); printf( "Send NTP client requests to an NTP server with IP address or\n" "hostname , show the sent and received packets,\n" "and evaluate the results.\n" ); printf( "\n" ); printf( "[OPTION] can be one of the following:\n" ); printf( " -c run continuously, print summary output unless verbose\n" ); printf( " -s n n seconds delay between queries, default: %i s\n", sleep_intv ); printf( " -v verbose, print full packets even in continuous mode\n" ); printf( " -M mode send NTP request packets with specified mode\n" ); printf( " -V n NTP protocol version, %i..%i, default: %i\n", MIN_REQ_NTP_VERSION, MAX_REQ_NTP_VERSION, DEFAULT_REQ_NTP_VERSION ); printf( " -P n NTP client poll value sent to server, default: %i\n", DEFAULT_CLIENT_POLL_VALUE ); printf( " -T n packet receive timeout [ms], default: %u\n", RCV_TIMEOUT ); printf( " -Z send 4 req/s to server, print time, offset changes and status summary\n" ); printf( " e.g. for leap second tests\n" ); #if defined( _USE_MBG_NTP_AUTH ) printf( " -m key_id use MD5 trusted key key_id for NTP authentication\n" ); printf( " -k key the alphanumeric MD5 key for NTP authentication\n" ); #endif #if defined( MBG_TGT_WIN32 ) printf( " -L use legacy system time function even if precise time is supported\n" ); #endif printf( " -?, -h print this usage information\n" "\n" ); printf( "Program exit codes:\n" ); for ( i = 0; i < N_MBG_EXIT_CODES; i++ ) printf( " %i: %s\n", i, exit_code_str[i] ); printf( "\n" ); } // usage static /*HDR*/ void check_options( int argc, char *argv[] ) { int c; #if defined( MBG_TGT_WIN32 ) #define WIN32_OPTS "L" #else #define WIN32_OPTS #endif while ( ( c = getopt( argc, argv, "cs:k:m:vM:P:T:V:Zh?" WIN32_OPTS ) ) != -1 ) { switch ( c ) { case 'c': run_continuously = 1; break; case 's': run_continuously = 1; sleep_intv = strtol( optarg, NULL, 10 ) * SLEEP_INTV_SEC_SCALE; if ( sleep_intv < MIN_SLEEP_INTV ) sleep_intv = MIN_SLEEP_INTV; break; #if defined( _USE_MBG_NTP_AUTH ) case 'k': strncpy( md5_key, optarg, sizeof( md5_key ) - 1 ); break; #endif #if defined( _USE_MBG_NTP_AUTH ) case 'm': md5_key_id = strtol( optarg, NULL, 10 ); break; #endif case 'v': verbose++; break; case 'M': req_mode = strtol( optarg, NULL, 10 ); break; case 'P': client_poll_value = (uint8_t) strtol( optarg, NULL, 10 ); break; case 'T': rcv_timeout = strtoul( optarg, NULL, 10 ); break; case 'V': prot_version = strtol( optarg, NULL, 10 ); break; case 'Z': // quick settings for leap second test run_continuously = 1; #if defined( MBG_TGT_WIN32 ) sleep_intv = DFLT_SHORT_SLEEP_INTV; // 4 req/s #else usleep_intv = DFLT_SHORT_SLEEP_INTV; // 4 req/s #endif print_packets = 0; compensate_initial_offset = 1; break; #if defined( MBG_TGT_WIN32 ) case 'L': force_legacy_gstaft = true; break; #endif case 'h': case '?': default: must_print_usage = 1; } } } // check_options int main( int argc, char *argv[] ) { struct hostent *p_hostent; fprintf( stderr, "\n%s %s, %s, %s\n\n", program_name, program_version, program_copyright, program_contact ); #if 0 && defined( _MSC_VER ) printf( "built with _MSC_VER %i, _MSC_FULL_VER %i\n\n", _MSC_VER, _MSC_FULL_VER ); #endif check_options( argc, argv ); if ( !run_continuously ) print_packets = 1; if ( rcv_timeout == 0 ) rcv_timeout = RCV_TIMEOUT; prot_version = check_bounds( prot_version, MIN_REQ_NTP_VERSION, MAX_REQ_NTP_VERSION, DEFAULT_REQ_NTP_VERSION ); if ( ( argc - optind ) > 0 ) // a host name has been specified tgt_host = argv[optind]; else must_print_usage = 1; // required in client mode if ( must_print_usage ) { usage(); exit( MBG_NTP_EXIT_FAIL_USAGE ); } #if defined( MBG_TGT_WIN32 ) { WSADATA wsaData = { 0 }; WSAStartup( MAKEWORD( 2, 2 ), &wsaData ); check_precise_time_api(); } #endif #if defined( MBG_TGT_POSIX ) // Make stdout unbuffered, so output is also shown immediately // if piped via the tee command. setvbuf( stdout, NULL, _IOLBF, 0 ); #endif if ( tgt_host ) { // Construct name, with no wildcards, of the socket to send to. // getnostbyname() returns a structure including the network address // of the specified host. p_hostent = gethostbyname( tgt_host ); if ( p_hostent == NULL ) { int rc = mbg_get_gethostbyname_error( NULL ); mbglog( LOG_ERR, "Unknown host %s: %s", tgt_host, mbg_strerror( rc ) ); exit( MBG_NTP_EXIT_FAIL_DNS ); } // set up host info for sending memset( &tgt_addr, 0, sizeof( tgt_addr ) ); memcpy( &tgt_addr.sin_addr, p_hostent->h_addr, p_hostent->h_length ); tgt_addr.sin_family = AF_INET; tgt_addr.sin_port = htons( NTP_PORT ); } reset_query_stats( &glb_query_stats ); // setup packet templates init_ntp_req_packet( &default_ntp_req_packet, req_mode, prot_version ); #if defined( _USE_MBG_NTP_AUTH ) { // Setup authentication support int rc = mbg_ntp_auth_init(); if ( mbg_rc_is_error( rc ) ) { exit_code = MBG_NTP_EXIT_FAIL_GENERIC; goto out; } if ( md5_key_id ) // A key ID has been specified on the command line { // If also a key (secret) has been specified on the command line, // add the key plus ID to the known keys. if ( strlen( md5_key ) ) mbg_ntp_auth_add_key( md5_key_id, "MD5", md5_key ); // Make the key with the specified ID a "trusted" key. mbg_ntp_auth_trust_key( md5_key_id, 1 ); } #if DEBUG_MBG_NTP_AUTH if ( md5_key_id ) mbg_ntp_auth_test( md5_key_id ); #endif } #endif // defined( _USE_MBG_NTP_AUTH ) printf( "Host %s%s\n", tgt_host, run_continuously ? ", running continuously" : "" ); do_ntp_queries(); out: return exit_code; }