diff options
author | Michael Brown | 2016-06-13 16:55:49 +0200 |
---|---|---|
committer | Michael Brown | 2016-06-13 16:55:49 +0200 |
commit | fce6117ad983a9c53087860058769dc65fb09d73 (patch) | |
tree | 0d277f84c67acd3055656b4fec326fdf7d342daf /src/net/udp | |
parent | [time] Allow system clock to be adjusted at runtime (diff) | |
download | ipxe-fce6117ad983a9c53087860058769dc65fb09d73.tar.gz ipxe-fce6117ad983a9c53087860058769dc65fb09d73.tar.xz ipxe-fce6117ad983a9c53087860058769dc65fb09d73.zip |
[ntp] Add simple NTP client
Signed-off-by: Michael Brown <mcb30@ipxe.org>
Diffstat (limited to 'src/net/udp')
-rw-r--r-- | src/net/udp/ntp.c | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/src/net/udp/ntp.c b/src/net/udp/ntp.c new file mode 100644 index 00000000..11f8ccc0 --- /dev/null +++ b/src/net/udp/ntp.c @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>. + * + * 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 <stdint.h> +#include <string.h> +#include <errno.h> +#include <time.h> +#include <ipxe/malloc.h> +#include <ipxe/refcnt.h> +#include <ipxe/iobuf.h> +#include <ipxe/xfer.h> +#include <ipxe/open.h> +#include <ipxe/retry.h> +#include <ipxe/timer.h> +#include <ipxe/time.h> +#include <ipxe/tcpip.h> +#include <ipxe/ntp.h> + +/** @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; +} |