The SNTP Client

Looking inside the SNTP.GetFrame Function.

The SNTP_Helper class function SNTP.GetFrame is used to assemble the correct NTP petition frame and there is another function to dis-semble the reply.

SNTP.GetFrame Function

The structure of the NTP frame is defined in NTP_PACKET. All the fields for both the version 3 and version 4 frames are defined.

    public class NTP_PACKET
    {
        public FLAGS flags = new FLAGS();
        public Byte stratum;                //	Stratum level of local
        public Byte poll;                   //	Poll interval
        public Byte precision;              //	Precision to nearest power of 2
        public UInt32 root_delay;          //	Root delay between local machine and server
        public UInt32 root_dispersion;     //	Root dispersion (maximum error)
        public UInt32 ref_identifier;      //	Reference clock identifier
        public UInt32 ref_ts_secs;         //	Reference time stamp
        public UInt32 ref_ts_frac;         //	Reference time stamp
        public UInt32 orig_ts_secs;        //	Originating time stamp
        public UInt32 orig_ts_frac;        //	Originating time stamp
        public UInt32 recv_ts_secs;        //	Time of request arrival at sender
        public UInt32 recv_ts_frac;        //	Time of request arrival at sender
        public UInt32 tx_ts_secs;          //	Time request left sender
        public UInt32 tx_ts_frac;          //	Time request left sender
        public UInt64 extension_field_1a = 0x000A001000000000;   //   Extension field 1 not used
        public UInt64 extension_field_1b = 0x0000000000000000;   //   Extension field 1 not used
        public UInt64 extension_field_2a = 0x000A001000000000;   //   Extension field 2 not used
        public UInt64 extension_field_2b = 0x0000000000000000;   //   Extension field 2 not used
        public UInt32 key_identifier;      //   Key identifier
        public UInt64 message_digest_1;    //   Message digest
        public UInt64 message_digest_2;    //   message digets
        public UInt32 recv_tx_secs;        //   Time of reply arrival at client. NOT part of the frame
        public UInt32 recv_tx_frac;        //   Time of reply arrival at client. NOT part of frame
    }

The frame has to be filled with the appropiate data. FOr bothe version 3 and version 4 the first 48 bytes are the same after that the authentication information must be placed. The extension information is of variable length with the minimum length of an extension being 16 bytes (128 bits). Two extensions of 16 bytes are incorporated. If extensions are used then authentication is obligatory. However authentication is highly dependent upon the NTP server.

    public class Ntp_Packet
    {
        private NTP_PACKET ntp_packet = new NTP_PACKET();
        private Byte[] key = null;
        private static Byte[,] key_list = new Byte[16,17];  //  A list of 16 keys, 16 bytes long terminated in #
        private static Boolean bKeyList = false;
        private UInt32 key_to_use = 0; 

        private Boolean endian = false;
         
        public Ntp_Packet ()
        {
            if (!bKeyList)
            {
                //  generate a random list of keys
                for (UInt32 i = 0; i < 16; i++)
                {
                    Byte[] new_key = CreateRandomKey(16);
                    SetKey(i, new_key);
                    key_list[1, 16] = (Byte)'#';
                }
                bKeyList = true;
            }
            ntp_packet.flags.get_set_flags = 0;
            ntp_packet.stratum = 0;
            ntp_packet.poll = 0;
            ntp_packet.precision = 0;
        }

        public Byte[] Get_Frame(DateTime tm_initial, Boolean bSntpV4, Boolean bAuthorisation)
        {
            //  assemble a frame to a byte array, depends upon version in use
            Int32 frame_size = 48;
            if (bSntpV4 && bAuthorisation) frame_size = 100;
            Byte[] frame = new Byte[frame_size];
            frame[0] = ntp_packet.flags.get_set_flags;
            frame[1] = ntp_packet.stratum;
            frame[2] = ntp_packet.poll;
            frame[3] = ntp_packet.precision;
            PlaceInFrame.GetBytes(ref frame, 4, ntp_packet.root_delay, endian);          // 4-7
            PlaceInFrame.GetBytes(ref frame, 8, ntp_packet.root_dispersion, endian);     // 8-11
            //  Place IPAddress as the reference, if required

            PlaceInFrame.GetBytes(ref frame, 12, ntp_packet.ref_identifier, endian);      // 12-15
            PlaceInFrame.GetBytes(ref frame, 16, ntp_packet.ref_ts_secs, endian);         // 16-19
            PlaceInFrame.GetBytes(ref frame, 20, ntp_packet.ref_ts_frac, endian);         // 20-23
            //  Place initial time as a time mark
            SNTP_TimeMark(ref ntp_packet.orig_ts_secs, ref ntp_packet.orig_ts_frac, tm_initial);
            PlaceInFrame.GetBytes(ref frame, 24, ntp_packet.orig_ts_secs, endian);        // 24-27
            PlaceInFrame.GetBytes(ref frame, 28, ntp_packet.orig_ts_frac, endian);        // 28-31
            PlaceInFrame.GetBytes(ref frame, 32, ntp_packet.recv_ts_secs, endian);        // 32-35
            PlaceInFrame.GetBytes(ref frame, 36, ntp_packet.recv_ts_frac, endian);        // 36-39
            PlaceInFrame.GetBytes(ref frame, 40, ntp_packet.tx_ts_secs, endian);          // 40-43
            PlaceInFrame.GetBytes(ref frame, 44, ntp_packet.tx_ts_frac, endian);          // 44-47
            if (bAuthorisation)
            {
                //  Exstension field have a 16 byte mininimum length up to.... However here we fix itat 16 bytes
                PlaceInFrame.GetBytes(ref frame, 48, ntp_packet.extension_field_1a, endian);   // 48-55
                PlaceInFrame.GetBytes(ref frame, 56, ntp_packet.extension_field_1b, endian);   // 56-63
                PlaceInFrame.GetBytes(ref frame, 64, ntp_packet.extension_field_2a, endian);   // 64-71
                PlaceInFrame.GetBytes(ref frame, 72, ntp_packet.extension_field_2a, endian);   // 72-79
                //  The message digest is a MD5 hash of all bytes form 0 to 79
                String sMessageToHash = ";
                for (Int32 i = 0; i < 90; i++) sMessageToHash += frame[i];
                //  key is the agreed private key
                key = GetKey(key_to_use);
                ntp_packet.key_identifier = key_to_use; //  This needs to be agreed with server
                PlaceInFrame.GetBytes(ref frame, 80, ntp_packet.key_identifier, endian);      // 80-83
                if (key != null)
                {
                    Byte[] sHashed = GetMD5HashData(sMessageToHash, ref key);
                    if (sHashed.Length >= 16)
                    {
                        ntp_packet.message_digest_1 = HashToUInt64(sHashed, 0, endian);
                        ntp_packet.message_digest_2 = HashToUInt64(sHashed, 0, endian);
                    }
                }
                PlaceInFrame.GetBytes(ref frame, 84, ntp_packet.message_digest_1, endian);    // 84-91
                PlaceInFrame.GetBytes(ref frame, 92, ntp_packet.message_digest_2, endian);    // 92-99
            }
            return frame;
        } 
    }

Agreement with the NTP is required with the NTP server provider regarding the type of authentication to be used and the keys to be used. In the case of symmetrical MD5 based authentication a private key is agreed before hand, this is identified by the Key ID that is sent in the frame. A 128 bit hash code is generated using the key in the manner required by the NTP server provider. This is placed at the end of the frame.

Authentication using encryption via public key is similar, agreement is required and the SSL function library installed.

There are several helper functions required to place the information in the correct array places of the frame. Likewise a decoder function is required, this is just the reverse of the frame assembler!

A typical set of methods for the SNTP_PACKET would be:

        public Byte FLAGS_LEAPINDICATOR { get { return ntp_packet.flags.LEAPINDICATOR; } set { ntp_packet.flags.LEAPINDICATOR = value; } }
        public Byte FLAGS_VERSIONNUMBER {  get { return ntp_packet.flags.VERSIONNUMBER; } set { ntp_packet.flags.VERSIONNUMBER = value; } }
        public Byte FLAGS_MODE {  get { return ntp_packet.flags.MODE; } set { ntp_packet.flags.MODE = value; } }
        public Byte STRATUM { get { return ntp_packet.stratum; } set { ntp_packet.stratum = value; } }
        public Byte POLL { get { return ntp_packet.poll; } set { ntp_packet.poll = value; } }
        public Byte PRECISION { get { return ntp_packet.precision; } set { ntp_packet.precision = value; } }
        public UInt32 ROOT_DELAY { get { return ntp_packet.root_delay; } set { ntp_packet.root_delay = value; } }
        public UInt32 ROOT_DISPERSION { get { return ntp_packet.root_dispersion; } set { ntp_packet.root_dispersion = value; } }
        public UInt32 REF_IDENTIFIER { get { return ntp_packet.ref_identifier; } set { ntp_packet.ref_identifier = value; } }
        public UInt32 REF_TS_SECS { get { return ntp_packet.ref_ts_secs; } set { ntp_packet.ref_ts_secs = value; } }
        public UInt32 REF_TS_FRAC { get { return ntp_packet.ref_ts_frac; } set { ntp_packet.ref_ts_frac = value; } }
        public UInt32 ORIG_TS_SECS { get { return ntp_packet.orig_ts_secs; } set { ntp_packet.orig_ts_secs = value; } }
        public UInt32 ORIG_TS_FRAC { get { return ntp_packet.orig_ts_frac; } set { ntp_packet.orig_ts_frac = value; } }
        public UInt32 RECV_TS_SECS { get { return ntp_packet.recv_ts_secs; } set { ntp_packet.recv_ts_secs = value; } }
        public UInt32 RECV_TS_FRAC { get { return ntp_packet.recv_ts_frac; } set { ntp_packet.recv_ts_frac = value; } }
        public UInt32 TX_TS_SECS { get { return ntp_packet.tx_ts_secs; } set { ntp_packet.tx_ts_secs = value; } }
        public UInt32 TX_TS_FRAC { get { return ntp_packet.tx_ts_frac; } set { ntp_packet.tx_ts_frac = value; } }
        public UInt64 EXTENSION_FIELD_1a { get { return ntp_packet.extension_field_1a; } set { ntp_packet.extension_field_1a = value; } }
        public UInt64 EXTENSION_FIELD_1b { get { return ntp_packet.extension_field_1b; } set { ntp_packet.extension_field_1b = value; } }
        public UInt64 EXTENSION_FIELD_2a { get { return ntp_packet.extension_field_2a; } set { ntp_packet.extension_field_2a = value; } }
        public UInt64 EXTENSION_FIELD_2b { get { return ntp_packet.extension_field_2b; } set { ntp_packet.extension_field_2b = value; } }
        public UInt64 MESSAGE_DIGEST_1 { get { return ntp_packet.message_digest_1; } set { ntp_packet.message_digest_1 = value; } }
        public UInt64 MESSAGE_DIGEST_2 { get { return ntp_packet.message_digest_2; } set { ntp_packet.message_digest_2 = value; } }
        public UInt32 RECV_TX_SECS { get { return ntp_packet.recv_tx_secs; } set { ntp_packet.recv_tx_secs = value; } }
        public UInt32 RECV_TX_FRAC { get { return ntp_packet.recv_tx_frac; } set { ntp_packet.recv_tx_frac = value; } }