Simple Network Time Protocol

Jump to: navigation, search

Table of Contents >> Application Reference


The Simple Network Time Protocol (SNTP) is a subset of NTP that avoids the complex clock mitigation algorithms by using a single clock source only. Treck provides an SNTPv4 client implementation that can communicate with an existing SNTPv4 or NTPv4 server in unicast or broadcast mode. A unicast client actively polls the server for time updates and the server responds accordingly. A broadcast client passively listens to a specific server that periodically sends time updates to all SNTP/NTP clients on a network.

Treck implementation notes:

  • Supports SNTPv4 described in RFC 5905 and RFC 4330.
  • Implements the SNTP client side only; there is no ability to serve downstream clients.
  • Supports unicast and broadcast servers only; multicast and symmetric associations are not supported.
  • Uses UDP as the message transport mechanism.
  • Supports blocking and non-blocking API calls as would typically be used in multithreaded and single-threaded applications, respectively.
  • The server can be SNTPv4 or NTPv4.
  • Protocol versions prior to v4 are not supported.
  • The Treck SNTP client does not include any security features at this time (e.g. Autokey).

Implementation changes for version 6.0.1.77:

  • The precision counter/timer is used for computing times. Previously, it was only used for interval timing. The imprecise tfKernelGetSystemTime() is now used only for initialization.
  • Time corrections are automatically applied to the association at every time update (usually, every minute or two). Previously, the user's application was responsible for adjusting the time. The user can disable the automatic update with the new TM_SNTP_OPT_USER_ADJUST option.
  • The internal time can be retrieved and changed with the new TM_SNTP_OPT_CURRENT_TIME and TM_SNTP_OPT_ADJUST_TIME options.

Code Configuration

If you have purchased the Treck SNTP client option, the code will be enabled by default. To remove the code from your build and reduce the Treck code size, uncomment the following compile time macro in trsystem.h.

#define TM_DISABLE_SNTP

If your kernel or hardware has a high frequency counter/timer, the Treck SNTP client can use it for accurately measuring round trip polling time. Read kernel timekeeping and uncomment the following compile time macro in your trsystem.h if you wish to use the feature.

#define TM_USE_KERNEL_PRECISE_TIME

The User Interface

Typically, you would start your SNTP client and allow it to run in the background to provide periodic time updates. Time updates and other events are signaled via a callback function that you must provide when you create an association (see the Notification of Time Updates and Other Events section below).

The API allows you to create more than one association, which is required if you want to receive time updates from more than one server or you want both unicast and broadcast associations. This may also be useful at startup or in a fail-over scenario when you don't know which servers are available or suitable—you can close the unresponsive associations at any time. With multiple associations, you can easily share the same callback function and maintain your own association specific data structure by using tfSntpUserSetOption() and tfSntpUserGetOption() with the TM_SNTP_OPT_USER_POINTER option.

The logical sequence of operations is as follows.

  1. Create an SNTP association with a specific server (the client is inactive, initially).
  2. Configure your SNTP association.
  3. Start/run you SNTP client.
  4. Recieve periodic time updates and adjust your system time.
  5. Close the association.

The following functions provide the Treck SNTPv4 client API.

tfSntpUserCreate()
Create (but do not start) an association with a remote SNTPv4 or NTPv4 server. Specify blocking or non-blocking mode and unicast or broadcast mode. Use the descriptor handle that is returned to manage the association with the following functions. The association becomes active with your first call to tfSntpUserExecute().
tfSntpUserBind()
Bind the association to a specific local port and address. This is required for broadcast associations and is optional for unicast associations.
tfSntpUserSetOption()
Change a configuration or runtime option (see the Runtime Options section below).
tfSntpUserGetOption()
Get a runtime value (see the Runtime Options section below).
tfSntpUserSetSockOpt()
Call setsockopt() on the underlying UDP socket. If, for example, you have multiple interfaces and need to restrict SNTP communications to one interface, you could call this function with the SO_BINDTODEVICE option.
tfSntpUserExecute()
Start and run the SNTP client. Almost all events are serviced and signaled to the user by this function.
Blocking mode
Call this function once from a dedicated task. It will block when the SNTP association is idle. This function returns only when the association is closed or an error occurs that prevents further execution.
Non-blocking mode
Call this function periodically to service SNTP events. It returns whenever the association is idle. Check the return code for possible errors.
tfSntpUserRefresh()
Manually poll the server for an immediate time update. This is for unicast associations only. Note: if you need a fast time update when starting, you should set the TM_SNTP_OPT_MINFIRST and TM_SNTP_OPT_MAXFIRST options via tfSntpUserSetOption() before calling tfSntpUserExecute().
Blocking mode
Call this function from any task. It returns TM_EINPROGRESS to indicate that a signal was sent to the task running tfSntpUserExecute() to do the work.
Non-blocking mode
This function includes a call to tfSntpUserExecute() to send the request. You get the time update via your callback function when it arrives from the server.
tfSntpUserClose()
Close the association.
Blocking mode
Call this function from any task. It returns TM_EINPROGRESS to indicate that a signal was sent to the task running tfSntpUserExecute() to close the association and return from that function.
Non-blocking mode
This function includes a call to tfSntpUserExecute() to close the association.

Time/Date Conversion

Treck also has the following user interface for time/date conversion:

tfGetUtcTime()
Convert from seconds since January 1, 1970 to the ttTimeCal calendar time/date structure (see below).

The ttTimeCal structure:

#include <trsocket.h>
 
/*
 * Time/date structure.
 */
typedef struct tsTimeCal
{
    int tmcMsec;    /* milliseconds after the second - [0,999] */
    int tmcSec;     /* seconds after the minute - [0,59] */
    int tmcMin;     /* minutes after the hour - [0,59] */
    int tmcHour;    /* hours since midnight - [0,23] */
    int tmcMday;    /* day of the month - [1,31] */
    int tmcMon;     /* months since January - [0,11] */
    int tmcYear;    /* years since 1900 */
    int tmcWday;    /* days since Sunday - [0,6] */
    int tmcYday;    /* days since January 1 - [0,365] */
} ttTimeCal;
typedef ttTimeCal TM_FAR * ttTimeCalPtr;


Notification of Time Updates and Other Events

An SNTP association is started with your first call to tfSntpUserExecute() and persists until tfSntpUserClose() is called or an error prevents further execution. While the association is active, periodic requests will be sent (for unicast mode) and time updates will be received. The processing of these and other events occurs within the tfSntpUserExecute() call, which then relays the results to you.

Treck notifies you of various events by calling the function pointer that you provide when you call tfSntpUserCreate(). There are three different contexts from which your notification function may be called: your receive task (for socket events), your timer task (for poll interval expiry), and your tfSntpUserExecute() call (for everything else). The first two will occur only in non-blocking mode—their significance is described in the table below.

Your callback function must have prototype ttUserSntpCBFuncPtr, as shown below. Parameter paramPtr type and content depends on the event and, for some events, may be NULL.

void MySntpNotifyFunc(
    ttSntpHandle        sntpHandle,
    const void TM_FAR * paramPtr,
    int                 eventId)
{
    /*
     * Check the eventId and do something with paramPtr.
     */
}

The events your callback function could receive are as follows.

SNTP Client Callback Events
Event Name Context Parameter Type Description
TM_SNTP_EVENT_SLEW tfSntpUserExecute struct timeval TM_FAR * (offset from local clock) A time update message was received from the server. The local clock offset was below the step threshold, set with option TM_SNTP_OPT_STEP_TIME, default 0.2s. The parameter contains the relative offset from the local clock—a signed value to be added to the local clock, e.g. clock += (double)(long)tvPtr->tv_sec + ((double)tvPtr->tv_usec * 1e-6). Application note: you may wish to set your step threshold to your maximum clock error tolerance such that you can ignore all slew events.
TM_SNTP_EVENT_STEP tfSntpUserExecute struct timeval TM_FAR * (offset from Jan 1, 1970) A time update message was received from the server. The local clock offset was between the step threshold and the panic threshold, set with options TM_SNTP_OPT_STEP_TIME and TM_SNTP_OPT_PANIC_TIME, default 0.2s and 1000s, respectively. The parameter contains the absolute offset from January 1, 1970—the clock value to be set, e.g. clock = (double)(unsigned long)tvPtr->tv_sec + ((double)tvPtr->tv_usec * 1e-6).
TM_SNTP_EVENT_PANIC tfSntpUserExecute struct timeval TM_FAR * (offset from Jan 1, 1970) A time update message was received from the server. The local clock offset was above the panic threshold, set with option TM_SNTP_OPT_PANIC_TIME, default 1000s. The parameter contains the absolute offset from January 1, 1970—the clock value to be set, e.g. clock = (double)(unsigned long)tvPtr->tv_sec + ((double)tvPtr->tv_usec * 1e-6). On a typical O/S, a panic event would cause the SNTP client to terminate with an error. Treck SNTP takes no action on a panic event and leaves the decision on what to do up to the application. If an embedded system starts with no knowledge of the current time, you will likely get this event on the first update.
TM_SNTP_EVENT_KISS tfSntpUserExecute char TM_FAR * or ttUser32Bit TM_FAR * A kiss-o-death status message was received from the server with a kiss code in the 32-bit Reference Id field of the message. The value should be nul terminated ASCII characters but there is no guarantee. The parameter paramPtr points to the string/value. A full list of kiss codes can be found in RFC 5905, section 7.4. A few important codes can be found in the table below. The RFC dictates that recipients of kiss codes MUST inspect them and take appropriate action (see the Receiving a Kiss-o-Death message section). Treck SNTP takes no action upon receiving a kiss-o-death message and leaves the decision on what to do up to the application.
TM_SNTP_EVENT_ERROR tfSntpUserExecute int TM_FAR * (Blocking mode only) An error occurred that is not severe enough to cause an exit from tfSntpUserExecute(). The parameter *paramPtr is the Treck error code. The decision on what action to take is left up to the application. (Note: for non-blocking mode, errors are returned from tfSntpUserExecute().)
TM_SNTP_EVENT_SOCKET Receive Task, Socket Callback NULL (Non-blocking mode only) A socket event occurred, e.g. message received. In non-blocking mode, the application calls tfSntpUserExecute() periodically to perform any outstanding work, in which case, you may ignore this event. If you need faster response to events, use this event to know exactly when you need to call tfSntpUserExecute(). However, do not call tfSntpUserExecute() from this context, as it will impact all sockets. Set a flag to tell your main task or event loop to call tfSntpUserExecute().
TM_SNTP_EVENT_TIMER Timer Task, Timer Callback NULL (Non-blocking mode only) A timer event occurred, e.g. poll interval expired. In non-blocking mode, the application calls tfSntpUserExecute() periodically to perform any outstanding work, in which case, you may ignore this event. If you need faster response to events, use this event to know exactly when you need to call tfSntpUserExecute(). However, do not call tfSntpUserExecute() from this context, as it will impact all timers. Set a flag to tell your main task or event loop to call tfSntpUserExecute().

Treck defines the timeval structure as follows, unless you have defined TM_BSD_TYPES_CLASH in your trsystem.h.

#include <trsocket.h>
 
#ifndef TM_BSD_TYPES_CLASH
/* This structure is usually defined by the compiler or kernel in time.h */
struct timeval
{
    long tv_sec;    /* Number of seconds */
    long tv_usec;   /* Number of microseconds */
};
#endif /* TM_BSD_TYPES_CLASH */


Receiving a Kiss-o-Death message

For the TM_SNTP_EVENT_KISS event mentioned in the table above, RFC 5905 has the following to say. Note: Treck SNTP takes no action despite the requirements outlined in the RFC—the choice of what action to take upon receiving a TM_SNTP_EVENT_KISS event is left up to the application.

  • Kiss codes are encoded in four-character ASCII strings that are left justified and zero filled.
  • Recipients of kiss codes MUST inspect them and, in the following cases, take the specified action.
  • For kiss codes DENY and RSTR, the client MUST demobilize any associations to that server and stop sending packets to that server.
  • For kiss code RATE, the client MUST immediately reduce its polling interval to that server and continue to reduce it each time it receives a RATE kiss code.
  • Kiss codes beginning with the ASCII character X are for unregistered experimentation and development and MUST be ignored if not recognized.
  • Other than the above conditions, Kiss-o-Death packets have no protocol significance and are discarded after inspection.
SNTP Kiss-o-Death message Kiss Codes
Kiss Code Meaning
ACST The association belongs to a unicast server.
BCST The association belongs to a broadcast server.
DENY Access denied by remote server.
RSTR Access denied due to local policy.
INIT The association has not yet synchronized for the first time.
RATE Rate exceeded. The server has temporarily denied access because the client exceeded the rate threshold.
STEP A step change in system time has occurred, but the association has not yet resynchronized.

Runtime Options

There are a number of configuration options have been derived from the RFCs and are defined as macros in trsntp.c. Some of these options can be modified at runtime via the tfSntpUserSetOption() call. Some of values can be retrieved at runtime via the tfSntpUserGetOption() call.

The supported options and their default macro definitions are listed below. Option values that you set may be converted and stored in an alternate form. For this reason many options are write-only and only supported by tfSntpUserSetOption(). Some are status values that cannot be set and are only supported by tfSntpUserGetOption().

SNTP Client Configuration and Runtime Values
Option Name Set/Get Parameter Type Macro Name Default Value Description
TM_SNTP_OPT_STEP_TIME Set struct timeval TM_SNTP_STEP_TIME 0.2s Sets the step time threshold. If a time update is received and the local clock offset is below this threshold, your notification callback function gets a TM_SNTP_EVENT_SLEW event. If the local clock offset is above the threshold, a TM_SNTP_EVENT_STEP or TM_SNTP_EVENT_PANIC event is signaled.
TM_SNTP_OPT_PANIC_TIME Set struct timeval TM_SNTP_PANIC_TIME 1000s Sets the panic time threshold. If a time update is received and the local clock offset above this threshold, your notification callback function gets a TM_SNTP_EVENT_PANIC event. If the local clock offset is below the threshold, a TM_SNTP_EVENT_STEP or TM_SNTP_EVENT_SLEW event is signaled.
TM_SNTP_OPT_BURST_TIME Set struct timeval TM_SNTP_BURST_TIME 2s (Unicast mode only) Sets the interval between burst requests. When an NTP client communicates with multiple servers, it usually sends a burst of requests to each server initially to quickly determine which servers will yield the most accurate time measurement. SNTP clients communicate with one server only, so burst requests may not be very useful. However, if your connection with the server is lossy, a burst of requests may mitigate the loss and help get you synchronized sooner.
TM_SNTP_OPT_BURST_COUNT Set or Get int TM_SNTP_BURST_COUNT 3 (Unicast mode only) On write, sets the number of burst packets to send. On read, gets the number of burst packets left to send. Set a zero value to skip the burst phase and go straight to TM_SNTP_MINPOLL interval between requests.
TM_SNTP_OPT_UNREACH_COUNT Set or Get int TM_SNTP_UNREACH 12 (Unicast mode only) On write, sets the number of packets to send to an unreachable server before exponentially backing off on sending requests. On read, gets the current unreachable counter value. A server is considered unreachable after sending 8 consecutive unanswered requests.
TM_SNTP_OPT_CLK_TOLERANCE Set struct timeval TM_SNTP_PHI 15×10−6 Sets the rate, φ, that the local clock drifts in seconds per second. The default value of 15×10−6 is equivalent to 1.3s per day. This value is important for NTP servers but not very useful for SNTP clients.
TM_SNTP_OPT_CLK_PRECISION Set struct timeval TM_SNTP_PRECISION 3.8µs Sets the local clock precision, ρ. The value ρ is the local clock resolution or the time it takes to read the local clock, whichever is greater. This value is important for NTP servers but not very useful for SNTP clients.
TM_SNTP_OPT_BROADCAST_DELAY Set struct timeval TM_SNTP_BCAST_DELAY 0.004s (Broadcast mode only) Sets the estimated time it takes for a packet to travel from the broadcast server to us. This value is important for NTP servers but not very useful for SNTP clients.
TM_SNTP_OPT_MINPOLL Set struct timeval TM_SNTP_MINPOLL 64s (Unicast mode only) Sets the minimum interval between time update requests, excluding burst send phase. This interval is used when the server is reachable, i.e. has responded at least once in the last 8 attempts.
TM_SNTP_OPT_MAXPOLL Set struct timeval TM_SNTP_MAXPOLL 17min (Unicast mode only) Sets the maximum interval between time update requests. This interval is used when the server has been unreachable for some time.
TM_SNTP_OPT_MINFIRST Set struct timeval TM_SNTP_MINFIRST 20s (Unicast mode only) Sets the minimum time to delay before Treck SNTP sends it's first time update request. If there are many SNTP clients on the same network, it is useful to have a random delay when powering up to avoid overwhelming the server. This value sets the lower bound on the random delay.
TM_SNTP_OPT_MAXFIRST Set struct timeval TM_SNTP_MAXFIRST 90s (Unicast mode only) Sets the maximum time to delay before Treck SNTP sends it's first time update request. If there are many SNTP clients on the same network, it is useful to have a random delay when powering up to avoid overwhelming the server. This value sets the upper bound on the random delay.
TM_SNTP_OPT_MINDISP Set struct timeval TM_SNTP_MINDISP 0.005s Sets a minimum bound on the dispersion, ε, calculation. The dispersion is the worst case error in a time measurement. In some circumstances, ε can become unreasonably small and even negative, affecting other calculations. This value is important for NTP servers but not very useful for SNTP clients.
TM_SNTP_OPT_MAXDISP Set struct timeval TM_SNTP_MAXDISP 16s Sets the maximum allowable dispersion. If the root dispersion calculated from the received message ((RootDelay / 2) + RootDisp) exceeds this value, the received time update is ignored as having too much inherent error.
TM_SNTP_OPT_MAXDIST Set struct timeval TM_SNTP_MAXDIST 1s Sets the maximum allowable distance (maximum of all errors: dispersion, delay, etc.). Currently, this value is not used as it is primarily intended for NTP servers.
TM_SNTP_OPT_REACH_REGISTER Get int (Unicast only) Get the current reach register value. The reach register holds an 8-bit value that is shifted left by 1 bit for each request sent and the least significant bit is set for each valid response received. When the reach register is zero, the server is considered unreachable.
TM_SNTP_OPT_CURRENT_POLL Get struct timeval (Unicast only) Get the current poll interval.
TM_SNTP_OPT_CLOCK_OFFSET Get struct timeval Get the most recently computed clock offset (computed at the last update).
TM_SNTP_OPT_TOTAL_DELAY Get struct timeval Get the most recently computed delay (the root delay from the last message and the delay from the server to us).
TM_SNTP_OPT_TOTAL_DISP Get struct timeval Get the most recently computed dispersion (the root dispersion from the last message and our dispersion).
TM_SNTP_OPT_REFERENCE_ID Get long Get the most recently received reference identifier (typically the IP address of the root).
TM_SNTP_OPT_LEAP_INDICATOR Get int Get the most recently received Leap Indicator (LI field, informs of leap second adjustment at midnight):
0: no change
1: last minute of the day has 61 seconds
2: last minute of the day has 59 seconds
3: unknown (clock unsynchronized)
TM_SNTP_OPT_STRATUM Get int Get the most recently received server stratum:
0: unspecified or invalid
1: primary server (e.g., equipped with a GPS receiver)
2-15: secondary server (via NTP)
16: unsynchronized
TM_SNTP_OPT_USER_POINTER Set or Get void * Set your own data pointer in the association object so that you can retrieve it, for example, from within your event notification function. This may be useful if you are using the same code for multiple SNTP associations. Important: Do not specify a pointer to a stack variable when you set the data pointer—specify the data pointer directly (static or dynamic memory). When retrieving, specify a pointer to a variable (possibly on the stack) to receive the value you set earlier.
TM_SNTP_OPT_CURRENT_TIME Set or Get struct timeval Get or set the time kept internally for the association. This is only meaningful if you have not disabled automatic time correction (option TM_SNTP_OPT_USER_ADJUST).
TM_SNTP_OPT_ADJUST_TIME Set struct timeval Add (subtract) to (from) the time kept internally for the association. This is only meaningful if you have not disabled automatic time correction (option TM_SNTP_OPT_USER_ADJUST).
TM_SNTP_OPT_USER_ADJUST Set or Get int 0 (false) User adjusts the precise counter/timer. The default behavior assumes that the counter/timer input used by this implementation is not affected by time corrections made by the user's application. This might not be the case for legacy applications, especially if you do not use the TM_USE_KERNEL_PRECISE_TIME option.

If you are upgrading from a version that did not support this option, please check your configuration and your SNTP callback function to make sure the counter/timer is not corrected both internally and externally. If you enable TM_USE_KERNEL_PRECISE_TIME, user-maintained function tfKernelGetPreciseTime() provides counter/timer input. Otherwise, Treck's sub-second timer is used (see tfTimerUpdate).

Function Calls

 


Table of Contents >> Application Reference