20151122 - Leapseconds via DNS¶
As I had long expected, ITU ducked out of the leapsecond decision business and designated the question “for further study” - which is their phrase of art for “forget it”.
(Other examples of “for further study”: How to convert Voice and Telefax messages to X.400 emails. How to get the world to adopt X.400 emails. How to send TELEX across ISDN D-channels etc. etc.)
So we’re stuck with leap-seconds for another decade or more.
It would be possible for IERS to issue Bulletin C with longer lead-time than 4-6 months if they want to, but I have heard of no movement in that direction.
Even if they did, the german time-transmitter DCF-77 only announces leap-seconds one hour before it happens, so whatever we do, we’re stuck with the leap-second announcement problem.
Here is a work-around:
If you do a DNS lookup on the name ‘leapsecond.utcd.org’ you get the IPv4 number 244.34.36.97 back, and that encodes the content of Bulletin C 50
To decode it, you need to do a bit of math:
Querying currently published leapsecond announcement:
IP: 244.34.36.97 Error: 0 Year: 2015 Month 12 dTAI: 36 Delta: 0
That means:
Information is valid until end of UTC-month 12 of year 2015
After that month: UTC = TAI - 36 seconds
Until then: UTC = TAI - 36 seconds
Unless somebody more qualified, authoritative and trustworthy volunteers, I’m going to keep that service running and updated as long as I can.
Here is the reference implementation source code, which explains how it all works:
/*-
* Copyright (c) 2015 Poul-Henning Kamp <phk@FreeBSD.org>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
*
* Reference implementation of code to retrieve current leap-second
* announcement via DNS lookup.
*
* Specification:
* --------------
*
* The leap second information is encoded into a IPv4 adress as follows:
*
* 3 2 1 0
* 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |1 1 1 1| month | d | dTAI | CRC-8 |
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* 'month' Count of months since december 1971
* = (year - 1971) * 12 + month - 11
*
* 'dTAI' Number of seconds UTC is behind of TAI
UTC = TAI - dTAI
*
* 'd' what happens to dTAI at the end of the month indicated
* 0 -> nothing
* 1 -> subtract one from dTAI
* 2 -> add one to dTAI
* 3 -> Illegal
*
*
* Example:
* --------
*
* The IPv4 address "244.23.35.255" encodes Bulletin C 49
*
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
* |1 1 1 1|0 1 0 0 0 0 0 1 0 1 1|1 0|0 1 0 0 0 1 1|1 1 1 1 1 1 1 1|
* +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
*
* month = 0x20b = 523 = (2015 - 1971) * 12 + 6 - 11 -> June 2015
*
* d = 0x2 -> +1
*
* dTAI = 0x23 = 35 -> UTC = TAI - 35 sec
*
* CRC-8 = 0xff -> Calculated message {month d dTAI}. See below.
*
* Design notes:
* -------------
*
* The first four bits puts the resulting IPv4 address into the "class-E"
* space which is "reserved for future use", as a defense against lying
* DNS resolvers.
*
* At this point, late 2015, it does not look like class-E will ever be
* allocated for any use. Most network stacks treat them as martians
* (ie: patently invalid), and at current consumption rates, they would
* be gobbled up far faster than we could upgrade network stacks.
*
* Therefore no sane DNS resolver should ever return a class-E addres,
* unless somebody does really strange things with IPv4 numbers.
*
* A second layer of defense against lying DNS resolvers is the CRC8
* integrity check in the last octet.
*
* The field widths should be good until about year 2140.
*
* At this point in time the dTAI field is considered unsigned, but
* should strange and awe inspiring geophysical events unfold,
* spinning up the rotation of the planet, (while implausibly leaving
* this protocol still relevant) the field can be redefined as signed.
*
*
* Code notes:
* -----------
*
* The reference implementation below has been written for maximum
* portability an relies on text-processing rather than attempting
* to pick struct sockaddr apart to decode the IPv4 number.
*
*/
#include <assert.h>
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
/*
* MSB first CRC8 with polynomium (x^8 +x^5 +x^3 +x^2 +x +1)
*
* This is by a small margin the best CRC8 for the message length (28 bits)
*
* For much more about CRC's than you'd ever want to know:
*
* http://users.ece.cmu.edu/~koopman/crc/index.html
*
* PS: The CRC seed is not random.
*/
static int
crc8(uint32_t inp, int len)
{
uint32_t crc = 0x54a9abf8 ^ (inp << (32 - len));
int i;
for (i = 0; i < len; i++) {
if (crc & (1U << 31))
crc ^= (0x12fU << 23);
crc <<= 1;
}
return (crc >> 24);
}
/*
* Decode a numeric IPv4 string ("253.253.100.11").
*
* 'year' and 'month' is the announced horizon.
*
* 'dtai' is what you subtract from TAI to get UTC until that month ends.
*
* 'delta' is what you do to dtai at the end of that month
*/
static int
decode_leapsecond(const char *ip, int *year, int *month, int *dtai, int *delta)
{
int error;
unsigned o1, o2, o3, o4;
uint32_t u, d, mn, o;
/* Zero returns in case of error ------------------------------*/
if (year != NULL)
*year = 0;
if (month != NULL)
*month = 0;
if (dtai != NULL)
*dtai = 0;
if (delta != NULL)
*delta = 0;
/* Convert to 32 bit integer ----------------------------------*/
error = sscanf(ip, "%u.%u.%u.%u", &o1, &o2, &o3, &o4);
if (error != 4)
return (-1);
u = o1 << 24;
u |= o2 << 16;
u |= o3 << 8;
u |= o4;
/* Check & remove class E -------------------------------------*/
if ((u >> 28) != 0xf)
return (-1);
u &= (1 << 28) - 1;
/* Check & remove CRC8 ----------------------------------------*/
if (crc8(u, 28) != 0x80)
return (-2);
u >>= 8;
/* Split into fields ------------------------------------------*/
o = u & 0x7f;
u >>= 7;
d = u & 3;
u >>= 2;
mn = (u & 0x7ff) + 10;
/* Error checks -----------------------------------------------*/
if (d == 3)
return (-3);
/* Convert to return values -----------------------------------*/
if (year != NULL)
*year = 1971 + (mn / 12);
if (month != NULL)
*month = 1 + (mn % 12);
if (dtai != NULL)
*dtai = o;
if (delta != NULL) {
switch (d) {
case 0: *delta = 0; break;
case 1: *delta = -1; break;
case 2: *delta = +1; break;
}
}
return (0);
}
/*
* Query leapsecond.utcd.org for current leapsecond information
*/
static int
query_leapsecond(int *year, int *month, int *tai, int *delta, char **ip)
{
struct addrinfo hints, *res, *res0;
char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];
int error;
memset(&hints, 0, sizeof(hints));
hints.ai_family = PF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
error = getaddrinfo("leapsecond.utcd.org", NULL, &hints, &res0);
if (error != 0) {
fprintf(stderr, "Lookup error: %s\n", gai_strerror(error));
return (-10);
}
error = -11;
for (res = res0; res; res = res->ai_next) {
error = getnameinfo(res->ai_addr, res->ai_addrlen,
hbuf, sizeof(hbuf), sbuf, sizeof(sbuf),
NI_NUMERICHOST | NI_NUMERICSERV);
if (error != 0)
continue;
error = decode_leapsecond(hbuf, year, month, tai, delta);
if (error == 0) {
if (ip != NULL)
*ip = strdup(hbuf);
break;
}
}
return (error);
}
static struct test_vector {
const char *ip;
int error;
int year;
int month;
int tai;
int delta;
} test_vectors[] = {
{ "240.3.9.77", 0, 1971, 12, 9, +1 },
{ "240.15.10.108", 0, 1972, 6, 10, +1 },
{ "242.18.28.160", 0, 1993, 12, 28, 0 },
{ "255.76.200.237", 0, 2135, 1, 72, -1 },
{ "127.240.133.76", -1, 0, 0, 0, 0 },
{ "255.209.76.40", -2, 0, 0, 0, 0 },
{ "241.179.152.73", -3, 0, 0, 0, 0 },
{ NULL, 0, 0, 0, 0, 0 }
};
int
main(int argc, char **argv)
{
int error;
int year, month, tai, delta;
struct test_vector *tv;
char *ip;
(void)argc;
(void)argv;
printf("Checking test-vectors:\n\n");
for (tv = test_vectors; tv->ip != NULL; tv++) {
error = decode_leapsecond(tv->ip,
&year, &month, &tai, &delta);
printf(" IP: %-15s Error: %2d Year: %4d "
"Month %2d dTAI: %3d Delta: %2d\n",
tv->ip, error, year, month, tai, delta);
assert(error == tv->error);
assert(year == tv->year);
assert(month == tv->month);
assert(tai == tv->tai);
assert(delta == tv->delta);
}
printf("\nIf you see this, the tests ran OK\n");
printf("\n");
printf("Querying currently published leapsecond announcement:\n\n");
error = query_leapsecond(&year, &month, &tai, &delta, &ip);
if (error) {
printf("Failed with error %d\n", error);
return (0);
}
printf(" IP: %-15s Error: %2d Year: %4d "
"Month %2d dTAI: %3d Delta: %2d\n",
ip, error, year, month, tai, delta);
printf("\nThat means:\n\n");
printf(" Information is valid until end of UTC-month %d of year %d\n",
month, year);
printf(" After that month: UTC = TAI - %d seconds\n", tai + delta);
printf(" Until then: UTC = TAI - %d seconds\n", tai);
return (0);
}
If you run it, you should get this output:
Checking test-vectors:
IP: 240.3.9.77 Error: 0 Year: 1971 Month 12 dTAI: 9 Delta: 1
IP: 240.15.10.108 Error: 0 Year: 1972 Month 6 dTAI: 10 Delta: 1
IP: 242.18.28.160 Error: 0 Year: 1993 Month 12 dTAI: 28 Delta: 0
IP: 255.76.200.237 Error: 0 Year: 2135 Month 1 dTAI: 72 Delta: -1
IP: 127.240.133.76 Error: -1 Year: 0 Month 0 dTAI: 0 Delta: 0
IP: 255.209.76.40 Error: -2 Year: 0 Month 0 dTAI: 0 Delta: 0
IP: 241.179.152.73 Error: -3 Year: 0 Month 0 dTAI: 0 Delta: 0
If you see this, the tests ran OK
Querying currently published leapsecond announcement:
IP: 244.34.36.97 Error: 0 Year: 2015 Month 12 dTAI: 36 Delta: 0
That means:
Information is valid until end of UTC-month 12 of year 2015
After that month: UTC = TAI - 36 seconds
Until then: UTC = TAI - 36 seconds
phk