A Server-Based Date/Time Synchronizer for TCP/IP
Ed Schaefer
Having a consistent system date/time on each node across
a UNIX TCP/IP-based
network is often vitally important. A solution is to
designate one
node as the server (where the date and time are accurately
kept) and
have every other client node obtain the date/time from
this server.
While some UNIX vendors provide facilities to perform
this function,
others do not. This article presents a C utility (setime.c,
Listing 1), developed under HP-UX, which obtains the
date/time from
the server and sets the system date/time on the client.
The pseudo-code for the utility is:
1. Setup the datagram socket.
2. Get the hostname from the command line.
3. Get the optional time range parameter from
the command line.
4. Send the daytime datagram to the server.
5. Set the timeout alarm.
6. Receive the daytime string back from
the server.
7. Time out if a response doesn't arrive in 20
seconds.
8. Parse the returned date/time string.
9. Setup the time tm structure.
10. If the new time is within range parameter,
set the system date/time.
The Datagram Socket
To share data across a network, you must create a connection
or socket
from the client to the server. In the internet domain,
the two general
types of sockets are virtual circuits and the datagram.
Since a datagram sends and receives individual packets
of data, it's
not guaranteed to deliver. The datagram service my C
utility uses
is the daytime service, which is supported by most BSD
and
System 5.3 and greater versions of UNIX. To verify if
your variant
of UNIX supports the daytime datagram, check the /etc/services
file for the following line:
daytime 13/udp
which identifies the datagram by name on port 13. The
server, via the daytime datagram, returns the following
string:
Fri Dec 16 04:21:39 1994
Armed with this time information, the client can perform
a time change.
The Network Connection
The first order of business is to establish the network
connection
using the socket() function call. The two main arguments
to
socket() are:
AF_INET -- which establishes the internet
domain.
SOCK_DGRAM -- which identifies a datagram
socket.
Once the socket call is completed, I use the getservbyname()
function call to get the port number of the daytime
datagram.
The two arguments to this function are:
SERVICE -- which points to the daytime port.
udp -- which identifies the service as a
datagram.
The return value of getservbyname() is a pointer to
the server
entry structure, servent.
I then call gethostbyname() to retrieve the server name
from
the command line and set the pointer to the host entry
structure,
hostent. The server name must be a valid entry in the
/etc/hosts
file.
Before sending the datagram, I build the address of
the server on
the client using the sockaddr_in structure. Building
the address
entails setting the internet domain and the daytime
service port,
then using bcopy() to copy the address of the client
to the
structure.
Send and Receive the Datagram
The sendto() function call sends the service to the
server.
The sockaddr_in structure previously defined is the
major
parameter.
The recvfrom() function call returns the current system
date/time
from the server. The required date/time string will
be returned in
the character string, buf.
The alarm() function call terminates the utility if
recvfrom()
doesn't return in the allotted time (20 seconds). If
a network failure
occurs, alarm() needs only to call the timedout()
function and terminate. As this utility is coded, once
alarm()
is tripped, the contents of the stack are lost and there
is no option
but to terminate. (For more information on saving the
stack and other
network programming issues, refer to Using C on the
UNIX System, by
David A. Curry.)
Finally, strtok() parses the system date/time returned
string
from the server, yielding the year, month, day, hour,
minute, and
second.
UNIX and Time
In his book, The Standard C Library, P. J. Plauger warns
that
if a time stamp is critical, you should check the vendor's
C implementation
closely. Although it is wise to heed this warning, almost
all UNIX
systems track time as the number of seconds since midnight
of January
1, 1970 (GMT) the ubiquitous EPOCH.
The time() function call returns the number of seconds
since
the epoch, in the form of a value of type time_t. In
every
implementation of UNIX I have seen, time_t is TYPEDEFed
as a long integer in the time.h header file.
To break the number of seconds since the epoch into
a more readable
format, the C compiler provides a time tm strucuture:
struct tm {
int tm_sec; /* seconds 0 to 59 */
int tm_min; /* minutes 0 to 59 */
int tm_hour; /* hours 0 to 23 */
int tm_mday; /* days 1 to 31 */
int tm_mon; /* months since january 0 to 11 */
int tm_year; /* years since 1900 */
int tm_wday; /* days since sunday 0 to 6 */
int tm_yday; /* days since january 1, 0 to 365 */
int tm_isdst; /* daylight saving time flag */
};
Ultimately, to change the client system date/time, it's
necessary to build a tm structure with the parsed data
obtained
from the client machine.
Checking and Setting the System Date/Time
The second optional command-line argument to setime.c
is the
time range parameter, with 24 hours being the default.
If the time
change between the client and server is more than the
range parameter,
the change is disallowed.
I build the client tm structure from the parsed data,
then
change the tm structure to the number of seconds from
the
epoch. I use the mktime() to obtain the number of seconds
since the epoch for this structure.
In order to check for a valid change, I find the difference
in seconds
between the client and server times. For future portability
reasons,
I chose not to perform arithmetic on the data types,
even though time_t
is a long integer. The compiler provides the difftime()
function, which returns the number of seconds between
two time_t
types to a double data type.
Finally, if the system date/time change is within the
range parameter,
I call stime() to change the system date/time. The parameter
to this function is the address of the server time variable
previously
created by mktime(). The only obvious error checking
performed
is for a user other than root changing the system date/time.
Conclusion
If your variant of UNIX is BSD or System 5.3 or greater,
the utility
presented here should change the system date/time as
expected. To
determine whether it will work for you, consider three
questions:
Does your TCP/IP-based network support the daytime
service?
Does your UNIX version support time since the epoch
in the described way?
Does your version of UNIX allow changing time with the
stime() function call?
If the answer to all three is "yes," setime
should
work for you.
References
Curry, David A. Using C on the UNIX System.
Sebastopol, CA: O'Reilly & Associates, 1989.
Plauger, P. J. The Standard C Library. Englewood
Cliffs, NJ: Prentice-Hall, 1992.
About the Author
Ed Schaefer is an Informix software developer and UNIX
system
administrator at jeTECH Data Systems of Moorpark, CA,
where he develops
Time and Attendance Software. He has been involved with
UNIX and Informix
since 1987 and was previously head of software development
at Marie
Callendar Pie Shops of Orange, CA.
|