Publishing IPv6 NTP servers with DHCPv6

By on 7 Mar 2022

Category: Tech matters

Tags: , , ,

5 Comments

Blog home

Photo by Amador Loureiro on Unsplash.

Recently, I had an interesting request to publish Network Time Protocol (NTP) servers to client systems by using Dynamic Host Configuration Protocol (DHCP) in an IPv6-only network. This required configuring our FortiGate firewall to publish the information, a new experience for me. Hence this post, which is a small guide on the process, including how to walk through RFCs to get the relevant information.

A simple request

My journey started with a small question from a coworker: “Can you send our NTP servers with DHCP in the IPv6-only network?”. 

“Sure, I can configure this within minutes,” was my response. However, I soon realized that this was no quick config as there were missing options in the system configuration that the vendor hadn’t implemented (yet).

I will show how to manage this with FortiGate (FortiOS 6.0.14, there is no newer version available for this model at the time of writing this) operating as a DHCPv6 server.

Before I start, our DHCP6 server configuration looks like this:

config system dhcp6 server
    edit 641
        set rapid-commit enable
        set lease-time 3602
        set domain "example.com"
        set subnet 2001:db8:c:641::/64
        set interface "IPv6only-01"
        config ip-range
            edit 1
                set start-ip 2001:db8:c:641::6401
                set end-ip 2001:db8:c:641::64ff
            next
        end
        set dns-server1 2001:db8:c:a53::53
        set dns-server2 2001:db8:c:b53::53
    next
end

As you can see, the DHCPv6 server is configured to publish a domain name and two DNS servers for recursive name resolution. Further on, there’s an IPv6 range for stateful DHCPv6. But FortiOS didn’t allow me to configure the NTP server directly. Instead, it allows me to send three self-defined options in DHCPv6:

config system dhcp6 server
    edit < id >
        set option1 {string}   Option 1.
        set option2 {string}   Option 2.
        set option3 {string}   Option 3.
    next
end

Could this be the way to fulfil my coworker’s request, and the way to create the {string} to send an IPv6 address or hostname to the client system? Let’s look at the command line interface (CLI) of FortiGate:

config system dhcp6 server
edit < id >
(server) # set option1 ?
< option-code >  [< options >] options must be even number of hexadecimal characters(optional)

This information didn’t help me, so it looked like I had to start reading the RFCs.

Digging through the RFCs

RFC 3315 is mentioned in the list of FortiOS 6.0 supported RFCs, but there is no useful information about NTP in it. 

Instead, the information I needed was in the new RFC for DHCPv6, RFC 8415, where you can find in section 24 a list of DHCPv6 options and a link to the complete list of DHCPv6 parameters assigned by IANA. Here, there is an option with the name NTP Server, number 56 (decimal), but, in typical RFC style, the information about the order and possible values are in another RFC, RFC 5908

In RFC 5908 (page 4) you can find the required format of the option to send NTP-server information to the client system:

The format of the NTP Server Option is:
      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |      OPTION_NTP_SERVER        |          option-len           |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         suboption-1                           |
     :                                                               :
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         suboption-2                           |
     :                                                               :
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     :                                                               :
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                         suboption-n                           |
     :                                                               :
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
 
       option-code: OPTION_NTP_SERVER (56),
       option-len:  Total length of the included suboptions.

For the ones who never had the need to read something like this, here are a few hints that helped me:

  • The headlines of the ‘table’ are the bits, starting from 00 to 31 = 32 bits.
  • The field OPTION_NTP_SERVER is a 16-bit value and the field option-len is a 16-bit value, too.
  • The next fields — suboption-1, suboption-2 and suboption-n — have, at this point, no fixed length; but are at least 32-bits. This is because there is a pipe character on the left and right side, that indicates this line is required. The next line, which has a colon at both sides, tells us it has a variable length, 0 or more lines.

To find the next piece of the puzzle, I continued reading the next page in RFC 5908 (page 5). Here I find how to format the sub-options:

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |    NTP_SUBOPTION_SRV_ADDR     |        suboption-len = 16     |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |                                                               |
     |                                                               |
     |                   IPv6 address of NTP server                  |
     |                                                               |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+


       IPv6 address of the NTP server: An IPv6 address,
       suboption-code: NTP_SUBOPTION_SRV_ADDR (1),
       suboption-len:  16.

The format of the NTP Server Address Suboption is as follows:

  • The field NTP_SUBOPTION_SRV_ADDR is a 16-bit value and the field suboption-len is a 16-bit value, too.
  • The next field, IPv6 address of NTP server, has a fixed length of 128 bits (4 lines with 32 bits). I hope you already expected this length. 🙂

OK, let’s try to understand the information we have up to this point. We have to set the option number for the NTP server, which is 56 (dec) and 0x38 (hex). The NTP_SUBOPTION_SRV_ADDR has a sub-option code of 1 (0x0001 as 16-bit hex value) and a suboption-length of 16 octets (if it is easier for you: 16 bytes). Then we have to put in the IPv6 (unicast-) address of the server. We take 2001:0db8:2800:0000:0000:0000:0ac3:0123 as an example. In the end, we calculate the complete length of the option and put the value in the field option-len:

  • 2 octets for the NTP suboption field
  • 2 octets for the NTP suboption length
  • 16 octets for the NTP IPv6 server address

In summary, 20 octets (0x14 in hex) is the calculated value for the field option-len. If we put in all the data, the fields look like this:

      0                   1                   2                   3
      0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     | OPTION_NTP_SERVER = 0x0038    |      option-len = 0x0014      |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     |SUBOPTION_NTP_SRV_ADDR = 0x0001| suboption-len = 16dec = 0x0010|
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     | 2001:0db8:                                                    |
     | 2800:0000:                                                    |
     | 0000:0000:        IPv6 address of NTP server                  |
     | 0ac3:0123                                                     |
     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

But in FortiOS this was different than what I expected — we don’t have to calculate and put in the option-length here! We just need the option code (in decimal = 56) and the option value in hexadecimal characters as in the following line:

(server) # set option1 56 '0001001020010db828000000000000000ac30123'
       NTP_SUBOPTION_SRV_ADDR |   |
                 suboption-lenght | IPv6 unicast address of NTP server

Note, it took me a few hours with Wireshark to realize this.

Publishing FQDNs

My coworker was quite happy about my phone call with the good news. But, is publishing an IPv6 unicast address a good solution for the future? I decided it wasn’t, and instead, I wanted to publish a fully qualified domain name (FQDN).

Given that I now understood the format of the NTP-server option, I had a look at how to publish the name of the NTP server. For this, I had to read section 4.3 of RFC 5908. The format of the NTP Server FQDN Suboption is:

    0                   1                   2                   3
    0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |    NTP_SUBOPTION_SRV_FQDN     |         suboption-len         |
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
   |                                                               |
   |                      FQDN of NTP server                       |
   :                                                               :
   +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
     suboption-code: NTP_SUBOPTION_SRV_FQDN (3),
     suboption-len: Length of the included FQDN field,
     FQDN: Fully-Qualified Domain Name of the NTP server or SNTP server.
           This field MUST be encoded as described in [RFC3315],
           Section 8.  Internationalized domain names are not allowed
           in this field.

Let’s try to send the hostname 3.de.pool.ntp.org to the client systems.

The FQDN must be encoded as described in section 8 of RFC 3315. The few lines there tell us to read section 3.1 of RFC 1035 but we MUST NOT use the compressed form as described in section 4.1.4 of RFC 1035. And we MUST NOT use an internationalized domain name (IDN)!

This is how I encoded the hostname 3.de.pool.ntp.org based on section 3.1 in RFC 1035. First of all, I had to specify the length of the next part of the hostname (also called ‘label’):

  • One character that results in value 1 (0x01) followed by the ascii-code of the character 3: 0x33
  • Repeat this for the next label: Length of 0x02 followed by 0x6465 for de
  • For the next label: Length (4 octets) and the word pool results in data 0x04706f6f6c
  • And so on: Length (3 octets) and data are 0x036e7470 for ntp
  • One time, similar again: Length (3 octets) and data are 0x036f7267 for org
  • And now, very important: Notification for end of hostname: 0x00

I repeated the whole hexadecimal characters (separated with space characters for a better understanding) as follows:

01 33 02 6465 04 706f6f6c 03 6e7470 03 6f7267 00
    3  .  d e  .  p o o l  .  n t p  .  o r g

This is the FQDN for the NTP suboption. Now I had to calculate the length of the suboption: 19 octets, 0x13 in hex. My line for configuring the hostname 3.de.pool.ntp.org in FortiOS looked like this:

(server) # set option2 56 '00030013013302646504706f6f6c036e7470036f726700'
        suboption type = FQDN |   |
suboption length = 19dec = 0x0013 | FQDN as described a few lines above

Do you want to publish two FQDNs? For example 2.de.pool.ntp.org and 3.de.pool.ntp.org? No problem, just do it:

(server) # set option2 56 '00030026013202646504706f6f6c036e7470036f726700013302646504706f6f6c036e7470036f726700'

But this wasn’t a good solution for my coworker. He was not able to configure his client with the information I sent by DHCPv6. (Please don’t ask me for details!). 

What about SNTP?

Next, my coworker asked me to send the Simple Network Time Protocol (SNTP) address.

I found the next interesting RFC with the name ‘Simple Network Time Protocol (SNTP) Configuration Option for DHCPv6’: RFC 4075. Looking on page 2:

   The format of the Simple Network Time Protocol servers option is as
   shown below:
       0                   1                   2                   3
       0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |      OPTION_SNTP_SERVERS       |        option-len            |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                                                               |
      |                  SNTP server (IPv6 address)                   |
      |                                                               |
      |                                                               |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      |                                                               |
      |                  SNTP server (IPv6 address)                   |
      |                                                               |
      |                                                               |
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      :                              ...                              :
      +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      option-code: OPTION_SNTP_SERVERS (31)
      option-len:  Length of the 'SNTP server' fields, in octets;
                   it must be a multiple of 16
      SNTP server: IPv6 address of SNTP server

SNTP is option code 31. FortiOS is calculating the option length itself; we just need the IPv6 unicast (only unicast?) address of the SNTP server:

(server) # set option3 31 '20010db828000000000000000ac30123'

Do you want to publish two SNTP servers? No problem, do it like this:

(server) # set option3 31 '20010db828000000000000000ac2012320010db828000000000000000ac30123'

FortiOS and RouterOS configuration

Today, the DHCP6 server configuration of FortiGate looks like this:

config system dhcp6 server
    edit 641
        set rapid-commit enable
        set lease-time 3602
        set domain "example.com"
        set subnet 2001:db8:c:641::/64
        set interface "IPv6only-01"
        set option1 56 '0001001020010db828000000000000000ac30123'
        set option2 56 '00030013013302646504706f6f6c036e7470036f726700'
        set option3 21 '20010db828000000000000000ac30123'
        config ip-range
            edit 1
                set start-ip 2001:db8:c:641::6401
                set end-ip 2001:db8:c:641::64ff
            next
        end
        set dns-server1 2001:db8:c:a53::53
        set dns-server2 2001:db8:c:b53::53
    next
end

FortiOS in 6.0.14 allows only three custom values. This is a feature that I’d like to request to Fortinet — please allow more (maybe 9?) custom options here. 🙂

In the same way, it’s possible to publish other information with DHCPv6, if the operating systems of the server allow the administrator to publish custom values in DHCPv6. In some operating systems, at the time of writing these lines, it’s the only way to send even basic information to a client system. For example, if you have to do a similar configuration in MikroTik RouterOS (tested with 6.48.5), it looks like this:

/ipv6 dhcp-server option
add code=23 name=DNSa value="'2001:db8:c:a53::53'"
add code=23 name=DNSb value="'2001:db8:c:b53::53'"
add code=24 name=DNSsearch value="0x07'example'0x03'com'0x00"
add code=31 name=SNTPboth value="'2001:db8:2800::ac2:123''2001:db8:2800::ac3:123'"
add code=56 name=NTPaddr 0001001020010db828000000000000000ac30123
/ipv6 dhcp-server
add dhcp-option= DNSa,DNSb,DNSsearch,SNTPboth,NTPaddr interface=eth3 lease-time=29m name=IPv6test

In my experience, RouterOS is sending only requested options, other than FortiOS. In RouterOS, we don’t have to calculate the length of the DHCPv6 option, too.

Please always keep in mind — if you don’t send at least the other-flag in RA packets, most client systems don’t try to get DHCPv6 information from the network.

Let me know how your experience goes.

Adapted from original post which appeared on Weberblog.net.

Ulrich Hauser is a Network Engineer at the University of Konstanz.

Rate this article

The views expressed by the authors of this blog are their own and do not necessarily reflect the views of APNIC. Please note a Code of Conduct applies to this blog.

5 Comments

  1. Marco Davids

    Interesting read! One question though; I may have misunderstood, but isn’t 2.pool.ntp.org the only pool-instance with AAAA-records? Wouldn’t it be better to configure that one in an IPv6-only environment, rather than 3.pool.ntp.org ?

    Reply
  2. Ulrich Hauser

    Hi Marco,
    Thank you for your question.
    I had to replace my internal server names with public once and I took an example with different labels for Germany.
    Please configure the ntp servers of your organization or the servers of your provider.
    Sorry for the missing note about that.

    Reply
  3. carina.xiao

    How can we make DHCPv6 client to send request for NTP servers ? I tried to add this line in dhclient.conf, also request dhcp6.ntp-servers, but it didn’t work.

    Reply
  4. Chad Tomaszewski

    Thank you so much for this! You have saved me HOURS of time trying to deploy this on our Fortigate firewalls! I again thank so you much

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *

Top