From fce6117ad983a9c53087860058769dc65fb09d73 Mon Sep 17 00:00:00 2001 From: Michael Brown Date: Mon, 13 Jun 2016 15:55:49 +0100 Subject: [PATCH] [ntp] Add simple NTP client Signed-off-by: Michael Brown --- src/include/ipxe/errfile.h | 1 + src/include/ipxe/ntp.h | 109 +++++++++++++++ src/net/udp/ntp.c | 275 +++++++++++++++++++++++++++++++++++++ 3 files changed, 385 insertions(+) create mode 100644 src/include/ipxe/ntp.h create mode 100644 src/net/udp/ntp.c diff --git a/src/include/ipxe/errfile.h b/src/include/ipxe/errfile.h index 4dae35b4c..cbab452bb 100644 --- a/src/include/ipxe/errfile.h +++ b/src/include/ipxe/errfile.h @@ -265,6 +265,7 @@ FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); #define ERRFILE_peerblk ( ERRFILE_NET | 0x00460000 ) #define ERRFILE_peermux ( ERRFILE_NET | 0x00470000 ) #define ERRFILE_xsigo ( ERRFILE_NET | 0x00480000 ) +#define ERRFILE_ntp ( ERRFILE_NET | 0x00490000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/ipxe/ntp.h b/src/include/ipxe/ntp.h new file mode 100644 index 000000000..f5b3d2326 --- /dev/null +++ b/src/include/ipxe/ntp.h @@ -0,0 +1,109 @@ +#ifndef _IPXE_NTP_H +#define _IPXE_NTP_H + +/** @file + * + * Network Time Protocol + * + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include + +/** NTP port */ +#define NTP_PORT 123 + +/** An NTP short-format timestamp */ +struct ntp_short { + /** Seconds */ + uint16_t seconds; + /** Fraction of a second */ + uint16_t fraction; +} __attribute__ (( packed )); + +/** An NTP timestamp */ +struct ntp_timestamp { + /** Seconds */ + uint32_t seconds; + /** Fraction of a second */ + uint32_t fraction; +} __attribute__ (( packed )); + +/** An NTP reference identifier */ +union ntp_id { + /** Textual identifier */ + char text[4]; + /** IPv4 address */ + struct in_addr in; + /** Opaque integer */ + uint32_t opaque; +}; + +/** An NTP header */ +struct ntp_header { + /** Flags */ + uint8_t flags; + /** Stratum */ + uint8_t stratum; + /** Polling rate */ + int8_t poll; + /** Precision */ + int8_t precision; + /** Root delay */ + struct ntp_short delay; + /** Root dispersion */ + struct ntp_short dispersion; + /** Reference clock identifier */ + union ntp_id id; + /** Reference timestamp */ + struct ntp_timestamp reference; + /** Originate timestamp */ + struct ntp_timestamp originate; + /** Receive timestamp */ + struct ntp_timestamp receive; + /** Transmit timestamp */ + struct ntp_timestamp transmit; +} __attribute__ (( packed )); + +/** Leap second indicator: unknown */ +#define NTP_FL_LI_UNKNOWN 0xc0 + +/** NTP version: 1 */ +#define NTP_FL_VN_1 0x20 + +/** NTP mode: client */ +#define NTP_FL_MODE_CLIENT 0x03 + +/** NTP mode: server */ +#define NTP_FL_MODE_SERVER 0x04 + +/** NTP mode mask */ +#define NTP_FL_MODE_MASK 0x07 + +/** NTP timestamp for start of Unix epoch */ +#define NTP_EPOCH 2208988800UL + +/** NTP fraction of a second magic value + * + * This is a policy decision. + */ +#define NTP_FRACTION_MAGIC 0x69505845UL + +/** NTP minimum retransmission timeout + * + * This is a policy decision. + */ +#define NTP_MIN_TIMEOUT ( 1 * TICKS_PER_SEC ) + +/** NTP maximum retransmission timeout + * + * This is a policy decision. + */ +#define NTP_MAX_TIMEOUT ( 10 * TICKS_PER_SEC ) + +extern int start_ntp ( struct interface *job, const char *hostname ); + +#endif /* _IPXE_NTP_H */ diff --git a/src/net/udp/ntp.c b/src/net/udp/ntp.c new file mode 100644 index 000000000..11f8ccc00 --- /dev/null +++ b/src/net/udp/ntp.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2016 Michael Brown . + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA + * 02110-1301, USA. + * + * You can also choose to distribute this program under the terms of + * the Unmodified Binary Distribution Licence (as given in the file + * COPYING.UBDL), provided that you have satisfied its requirements. + */ + +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL ); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * Network Time Protocol + * + */ + +/** An NTP client */ +struct ntp_client { + /** Reference count */ + struct refcnt refcnt; + /** Job control interface */ + struct interface job; + /** Data transfer interface */ + struct interface xfer; + /** Retransmission timer */ + struct retry_timer timer; +}; + +/** + * Close NTP client + * + * @v ntp NTP client + * @v rc Reason for close + */ +static void ntp_close ( struct ntp_client *ntp, int rc ) { + + /* Stop timer */ + stop_timer ( &ntp->timer ); + + /* Shut down interfaces */ + intf_shutdown ( &ntp->xfer, rc ); + intf_shutdown ( &ntp->job, rc ); +} + +/** + * Send NTP request + * + * @v ntp NTP client + * @ret rc Return status code + */ +static int ntp_request ( struct ntp_client *ntp ) { + struct ntp_header hdr; + int rc; + + DBGC ( ntp, "NTP %p sending request\n", ntp ); + + /* Construct header */ + memset ( &hdr, 0, sizeof ( hdr ) ); + hdr.flags = ( NTP_FL_LI_UNKNOWN | NTP_FL_VN_1 | NTP_FL_MODE_CLIENT ); + hdr.transmit.seconds = htonl ( time ( NULL ) + NTP_EPOCH ); + hdr.transmit.fraction = htonl ( NTP_FRACTION_MAGIC ); + + /* Send request */ + if ( ( rc = xfer_deliver_raw ( &ntp->xfer, &hdr, + sizeof ( hdr ) ) ) != 0 ) { + DBGC ( ntp, "NTP %p could not send request: %s\n", + ntp, strerror ( rc ) ); + return rc; + } + + return 0; +} + +/** + * Handle NTP response + * + * @v ntp NTP client + * @v iobuf I/O buffer + * @v meta Data transfer metadata + * @ret rc Return status code + */ +static int ntp_deliver ( struct ntp_client *ntp, struct io_buffer *iobuf, + struct xfer_metadata *meta ) { + struct ntp_header *hdr; + struct sockaddr_tcpip *st_src; + int32_t delta; + int rc; + + /* Check source port */ + st_src = ( ( struct sockaddr_tcpip * ) meta->src ); + if ( st_src->st_port != htons ( NTP_PORT ) ) { + DBGC ( ntp, "NTP %p received non-NTP packet:\n", ntp ); + DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) ); + goto ignore; + } + + /* Check packet length */ + if ( iob_len ( iobuf ) < sizeof ( *hdr ) ) { + DBGC ( ntp, "NTP %p received malformed packet:\n", ntp ); + DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) ); + goto ignore; + } + hdr = iobuf->data; + + /* Check mode */ + if ( ( hdr->flags & NTP_FL_MODE_MASK ) != NTP_FL_MODE_SERVER ) { + DBGC ( ntp, "NTP %p received non-server packet:\n", ntp ); + DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) ); + goto ignore; + } + + /* Check magic value */ + if ( hdr->originate.fraction != htonl ( NTP_FRACTION_MAGIC ) ) { + DBGC ( ntp, "NTP %p received unrecognised packet:\n", ntp ); + DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) ); + goto ignore; + } + + /* Check for Kiss-o'-Death packets */ + if ( ! hdr->stratum ) { + DBGC ( ntp, "NTP %p received kiss-o'-death:\n", ntp ); + DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) ); + rc = -EPROTO; + goto close; + } + + /* Calculate clock delta */ + delta = ( ntohl ( hdr->receive.seconds ) - + ntohl ( hdr->originate.seconds ) ); + DBGC ( ntp, "NTP %p delta %d seconds\n", ntp, delta ); + + /* Adjust system clock */ + time_adjust ( delta ); + + /* Success */ + rc = 0; + + close: + ntp_close ( ntp, rc ); + ignore: + free_iob ( iobuf ); + return 0; +} + +/** + * Handle data transfer window change + * + * @v ntp NTP client + */ +static void ntp_window_changed ( struct ntp_client *ntp ) { + + /* Start timer to send initial request */ + start_timer_nodelay ( &ntp->timer ); +} + +/** Data transfer interface operations */ +static struct interface_operation ntp_xfer_op[] = { + INTF_OP ( xfer_deliver, struct ntp_client *, ntp_deliver ), + INTF_OP ( xfer_window_changed, struct ntp_client *, + ntp_window_changed ), + INTF_OP ( intf_close, struct ntp_client *, ntp_close ), +}; + +/** Data transfer interface descriptor */ +static struct interface_descriptor ntp_xfer_desc = + INTF_DESC_PASSTHRU ( struct ntp_client, xfer, ntp_xfer_op, job ); + +/** Job control interface operations */ +static struct interface_operation ntp_job_op[] = { + INTF_OP ( intf_close, struct ntp_client *, ntp_close ), +}; + +/** Job control interface descriptor */ +static struct interface_descriptor ntp_job_desc = + INTF_DESC_PASSTHRU ( struct ntp_client, job, ntp_job_op, xfer ); + +/** + * Handle NTP timer expiry + * + * @v timer Retransmission timer + * @v fail Failure indicator + */ +static void ntp_expired ( struct retry_timer *timer, int fail ) { + struct ntp_client *ntp = + container_of ( timer, struct ntp_client, timer ); + + /* Shut down client if we have failed */ + if ( fail ) { + ntp_close ( ntp, -ETIMEDOUT ); + return; + } + + /* Otherwise, restart timer and (re)transmit request */ + start_timer ( &ntp->timer ); + ntp_request ( ntp ); +} + +/** + * Start NTP client + * + * @v job Job control interface + * @v hostname NTP server + * @ret rc Return status code + */ +int start_ntp ( struct interface *job, const char *hostname ) { + struct ntp_client *ntp; + union { + struct sockaddr_tcpip st; + struct sockaddr sa; + } server; + int rc; + + /* Allocate and initialise structure*/ + ntp = zalloc ( sizeof ( *ntp ) ); + if ( ! ntp ) { + rc = -ENOMEM; + goto err_alloc; + } + ref_init ( &ntp->refcnt, NULL ); + intf_init ( &ntp->job, &ntp_job_desc, &ntp->refcnt ); + intf_init ( &ntp->xfer, &ntp_xfer_desc, &ntp->refcnt ); + timer_init ( &ntp->timer, ntp_expired, &ntp->refcnt ); + set_timer_limits ( &ntp->timer, NTP_MIN_TIMEOUT, NTP_MAX_TIMEOUT ); + + /* Open socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st.st_port = htons ( NTP_PORT ); + if ( ( rc = xfer_open_named_socket ( &ntp->xfer, SOCK_DGRAM, &server.sa, + hostname, NULL ) ) != 0 ) { + DBGC ( ntp, "NTP %p could not open socket: %s\n", + ntp, strerror ( rc ) ); + goto err_open; + } + + /* Attach parent interface, mortalise self, and return */ + intf_plug_plug ( &ntp->job, job ); + ref_put ( &ntp->refcnt ); + return 0; + + err_open: + ntp_close ( ntp, rc ); + ref_put ( &ntp->refcnt ); + err_alloc: + return rc; +}