|  | @@ -0,0 +1,275 @@
 | 
		
	
		
			
			|  | 1 | +/*
 | 
		
	
		
			
			|  | 2 | + * Copyright (C) 2016 Michael Brown <mbrown@fensystems.co.uk>.
 | 
		
	
		
			
			|  | 3 | + *
 | 
		
	
		
			
			|  | 4 | + * This program is free software; you can redistribute it and/or
 | 
		
	
		
			
			|  | 5 | + * modify it under the terms of the GNU General Public License as
 | 
		
	
		
			
			|  | 6 | + * published by the Free Software Foundation; either version 2 of the
 | 
		
	
		
			
			|  | 7 | + * License, or (at your option) any later version.
 | 
		
	
		
			
			|  | 8 | + *
 | 
		
	
		
			
			|  | 9 | + * This program is distributed in the hope that it will be useful, but
 | 
		
	
		
			
			|  | 10 | + * WITHOUT ANY WARRANTY; without even the implied warranty of
 | 
		
	
		
			
			|  | 11 | + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 | 
		
	
		
			
			|  | 12 | + * General Public License for more details.
 | 
		
	
		
			
			|  | 13 | + *
 | 
		
	
		
			
			|  | 14 | + * You should have received a copy of the GNU General Public License
 | 
		
	
		
			
			|  | 15 | + * along with this program; if not, write to the Free Software
 | 
		
	
		
			
			|  | 16 | + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
 | 
		
	
		
			
			|  | 17 | + * 02110-1301, USA.
 | 
		
	
		
			
			|  | 18 | + *
 | 
		
	
		
			
			|  | 19 | + * You can also choose to distribute this program under the terms of
 | 
		
	
		
			
			|  | 20 | + * the Unmodified Binary Distribution Licence (as given in the file
 | 
		
	
		
			
			|  | 21 | + * COPYING.UBDL), provided that you have satisfied its requirements.
 | 
		
	
		
			
			|  | 22 | + */
 | 
		
	
		
			
			|  | 23 | +
 | 
		
	
		
			
			|  | 24 | +FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
 | 
		
	
		
			
			|  | 25 | +
 | 
		
	
		
			
			|  | 26 | +#include <stdint.h>
 | 
		
	
		
			
			|  | 27 | +#include <string.h>
 | 
		
	
		
			
			|  | 28 | +#include <errno.h>
 | 
		
	
		
			
			|  | 29 | +#include <time.h>
 | 
		
	
		
			
			|  | 30 | +#include <ipxe/malloc.h>
 | 
		
	
		
			
			|  | 31 | +#include <ipxe/refcnt.h>
 | 
		
	
		
			
			|  | 32 | +#include <ipxe/iobuf.h>
 | 
		
	
		
			
			|  | 33 | +#include <ipxe/xfer.h>
 | 
		
	
		
			
			|  | 34 | +#include <ipxe/open.h>
 | 
		
	
		
			
			|  | 35 | +#include <ipxe/retry.h>
 | 
		
	
		
			
			|  | 36 | +#include <ipxe/timer.h>
 | 
		
	
		
			
			|  | 37 | +#include <ipxe/time.h>
 | 
		
	
		
			
			|  | 38 | +#include <ipxe/tcpip.h>
 | 
		
	
		
			
			|  | 39 | +#include <ipxe/ntp.h>
 | 
		
	
		
			
			|  | 40 | +
 | 
		
	
		
			
			|  | 41 | +/** @file
 | 
		
	
		
			
			|  | 42 | + *
 | 
		
	
		
			
			|  | 43 | + * Network Time Protocol
 | 
		
	
		
			
			|  | 44 | + *
 | 
		
	
		
			
			|  | 45 | + */
 | 
		
	
		
			
			|  | 46 | +
 | 
		
	
		
			
			|  | 47 | +/** An NTP client */
 | 
		
	
		
			
			|  | 48 | +struct ntp_client {
 | 
		
	
		
			
			|  | 49 | +	/** Reference count */
 | 
		
	
		
			
			|  | 50 | +	struct refcnt refcnt;
 | 
		
	
		
			
			|  | 51 | +	/** Job control interface */
 | 
		
	
		
			
			|  | 52 | +	struct interface job;
 | 
		
	
		
			
			|  | 53 | +	/** Data transfer interface */
 | 
		
	
		
			
			|  | 54 | +	struct interface xfer;
 | 
		
	
		
			
			|  | 55 | +	/** Retransmission timer */
 | 
		
	
		
			
			|  | 56 | +	struct retry_timer timer;
 | 
		
	
		
			
			|  | 57 | +};
 | 
		
	
		
			
			|  | 58 | +
 | 
		
	
		
			
			|  | 59 | +/**
 | 
		
	
		
			
			|  | 60 | + * Close NTP client
 | 
		
	
		
			
			|  | 61 | + *
 | 
		
	
		
			
			|  | 62 | + * @v ntp		NTP client
 | 
		
	
		
			
			|  | 63 | + * @v rc		Reason for close
 | 
		
	
		
			
			|  | 64 | + */
 | 
		
	
		
			
			|  | 65 | +static void ntp_close ( struct ntp_client *ntp, int rc ) {
 | 
		
	
		
			
			|  | 66 | +
 | 
		
	
		
			
			|  | 67 | +	/* Stop timer */
 | 
		
	
		
			
			|  | 68 | +	stop_timer ( &ntp->timer );
 | 
		
	
		
			
			|  | 69 | +
 | 
		
	
		
			
			|  | 70 | +	/* Shut down interfaces */
 | 
		
	
		
			
			|  | 71 | +	intf_shutdown ( &ntp->xfer, rc );
 | 
		
	
		
			
			|  | 72 | +	intf_shutdown ( &ntp->job, rc );
 | 
		
	
		
			
			|  | 73 | +}
 | 
		
	
		
			
			|  | 74 | +
 | 
		
	
		
			
			|  | 75 | +/**
 | 
		
	
		
			
			|  | 76 | + * Send NTP request
 | 
		
	
		
			
			|  | 77 | + *
 | 
		
	
		
			
			|  | 78 | + * @v ntp		NTP client
 | 
		
	
		
			
			|  | 79 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 80 | + */
 | 
		
	
		
			
			|  | 81 | +static int ntp_request ( struct ntp_client *ntp ) {
 | 
		
	
		
			
			|  | 82 | +	struct ntp_header hdr;
 | 
		
	
		
			
			|  | 83 | +	int rc;
 | 
		
	
		
			
			|  | 84 | +
 | 
		
	
		
			
			|  | 85 | +	DBGC ( ntp, "NTP %p sending request\n", ntp );
 | 
		
	
		
			
			|  | 86 | +
 | 
		
	
		
			
			|  | 87 | +	/* Construct header */
 | 
		
	
		
			
			|  | 88 | +	memset ( &hdr, 0, sizeof ( hdr ) );
 | 
		
	
		
			
			|  | 89 | +	hdr.flags = ( NTP_FL_LI_UNKNOWN | NTP_FL_VN_1 | NTP_FL_MODE_CLIENT );
 | 
		
	
		
			
			|  | 90 | +	hdr.transmit.seconds = htonl ( time ( NULL ) + NTP_EPOCH );
 | 
		
	
		
			
			|  | 91 | +	hdr.transmit.fraction = htonl ( NTP_FRACTION_MAGIC );
 | 
		
	
		
			
			|  | 92 | +
 | 
		
	
		
			
			|  | 93 | +	/* Send request */
 | 
		
	
		
			
			|  | 94 | +	if ( ( rc = xfer_deliver_raw ( &ntp->xfer, &hdr,
 | 
		
	
		
			
			|  | 95 | +				       sizeof ( hdr ) ) ) != 0 ) {
 | 
		
	
		
			
			|  | 96 | +		DBGC ( ntp, "NTP %p could not send request: %s\n",
 | 
		
	
		
			
			|  | 97 | +		       ntp, strerror ( rc ) );
 | 
		
	
		
			
			|  | 98 | +		return rc;
 | 
		
	
		
			
			|  | 99 | +	}
 | 
		
	
		
			
			|  | 100 | +
 | 
		
	
		
			
			|  | 101 | +	return 0;
 | 
		
	
		
			
			|  | 102 | +}
 | 
		
	
		
			
			|  | 103 | +
 | 
		
	
		
			
			|  | 104 | +/**
 | 
		
	
		
			
			|  | 105 | + * Handle NTP response
 | 
		
	
		
			
			|  | 106 | + *
 | 
		
	
		
			
			|  | 107 | + * @v ntp		NTP client
 | 
		
	
		
			
			|  | 108 | + * @v iobuf		I/O buffer
 | 
		
	
		
			
			|  | 109 | + * @v meta		Data transfer metadata
 | 
		
	
		
			
			|  | 110 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 111 | + */
 | 
		
	
		
			
			|  | 112 | +static int ntp_deliver ( struct ntp_client *ntp, struct io_buffer *iobuf,
 | 
		
	
		
			
			|  | 113 | +			 struct xfer_metadata *meta ) {
 | 
		
	
		
			
			|  | 114 | +	struct ntp_header *hdr;
 | 
		
	
		
			
			|  | 115 | +	struct sockaddr_tcpip *st_src;
 | 
		
	
		
			
			|  | 116 | +	int32_t delta;
 | 
		
	
		
			
			|  | 117 | +	int rc;
 | 
		
	
		
			
			|  | 118 | +
 | 
		
	
		
			
			|  | 119 | +	/* Check source port */
 | 
		
	
		
			
			|  | 120 | +	st_src = ( ( struct sockaddr_tcpip * ) meta->src );
 | 
		
	
		
			
			|  | 121 | +	if ( st_src->st_port != htons ( NTP_PORT ) ) {
 | 
		
	
		
			
			|  | 122 | +		DBGC ( ntp, "NTP %p received non-NTP packet:\n", ntp );
 | 
		
	
		
			
			|  | 123 | +		DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) );
 | 
		
	
		
			
			|  | 124 | +		goto ignore;
 | 
		
	
		
			
			|  | 125 | +	}
 | 
		
	
		
			
			|  | 126 | +
 | 
		
	
		
			
			|  | 127 | +	/* Check packet length */
 | 
		
	
		
			
			|  | 128 | +	if ( iob_len ( iobuf ) < sizeof ( *hdr ) ) {
 | 
		
	
		
			
			|  | 129 | +		DBGC ( ntp, "NTP %p received malformed packet:\n", ntp );
 | 
		
	
		
			
			|  | 130 | +		DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) );
 | 
		
	
		
			
			|  | 131 | +		goto ignore;
 | 
		
	
		
			
			|  | 132 | +	}
 | 
		
	
		
			
			|  | 133 | +	hdr = iobuf->data;
 | 
		
	
		
			
			|  | 134 | +
 | 
		
	
		
			
			|  | 135 | +	/* Check mode */
 | 
		
	
		
			
			|  | 136 | +	if ( ( hdr->flags & NTP_FL_MODE_MASK ) != NTP_FL_MODE_SERVER ) {
 | 
		
	
		
			
			|  | 137 | +		DBGC ( ntp, "NTP %p received non-server packet:\n", ntp );
 | 
		
	
		
			
			|  | 138 | +		DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) );
 | 
		
	
		
			
			|  | 139 | +		goto ignore;
 | 
		
	
		
			
			|  | 140 | +	}
 | 
		
	
		
			
			|  | 141 | +
 | 
		
	
		
			
			|  | 142 | +	/* Check magic value */
 | 
		
	
		
			
			|  | 143 | +	if ( hdr->originate.fraction != htonl ( NTP_FRACTION_MAGIC ) ) {
 | 
		
	
		
			
			|  | 144 | +		DBGC ( ntp, "NTP %p received unrecognised packet:\n", ntp );
 | 
		
	
		
			
			|  | 145 | +		DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) );
 | 
		
	
		
			
			|  | 146 | +		goto ignore;
 | 
		
	
		
			
			|  | 147 | +	}
 | 
		
	
		
			
			|  | 148 | +
 | 
		
	
		
			
			|  | 149 | +	/* Check for Kiss-o'-Death packets */
 | 
		
	
		
			
			|  | 150 | +	if ( ! hdr->stratum ) {
 | 
		
	
		
			
			|  | 151 | +		DBGC ( ntp, "NTP %p received kiss-o'-death:\n", ntp );
 | 
		
	
		
			
			|  | 152 | +		DBGC_HDA ( ntp, 0, iobuf->data, iob_len ( iobuf ) );
 | 
		
	
		
			
			|  | 153 | +		rc = -EPROTO;
 | 
		
	
		
			
			|  | 154 | +		goto close;
 | 
		
	
		
			
			|  | 155 | +	}
 | 
		
	
		
			
			|  | 156 | +
 | 
		
	
		
			
			|  | 157 | +	/* Calculate clock delta */
 | 
		
	
		
			
			|  | 158 | +	delta = ( ntohl ( hdr->receive.seconds ) -
 | 
		
	
		
			
			|  | 159 | +		  ntohl ( hdr->originate.seconds ) );
 | 
		
	
		
			
			|  | 160 | +	DBGC ( ntp, "NTP %p delta %d seconds\n", ntp, delta );
 | 
		
	
		
			
			|  | 161 | +
 | 
		
	
		
			
			|  | 162 | +	/* Adjust system clock */
 | 
		
	
		
			
			|  | 163 | +	time_adjust ( delta );
 | 
		
	
		
			
			|  | 164 | +
 | 
		
	
		
			
			|  | 165 | +	/* Success */
 | 
		
	
		
			
			|  | 166 | +	rc = 0;
 | 
		
	
		
			
			|  | 167 | +
 | 
		
	
		
			
			|  | 168 | + close:
 | 
		
	
		
			
			|  | 169 | +	ntp_close ( ntp, rc );
 | 
		
	
		
			
			|  | 170 | + ignore:
 | 
		
	
		
			
			|  | 171 | +	free_iob ( iobuf );
 | 
		
	
		
			
			|  | 172 | +	return 0;
 | 
		
	
		
			
			|  | 173 | +}
 | 
		
	
		
			
			|  | 174 | +
 | 
		
	
		
			
			|  | 175 | +/**
 | 
		
	
		
			
			|  | 176 | + * Handle data transfer window change
 | 
		
	
		
			
			|  | 177 | + *
 | 
		
	
		
			
			|  | 178 | + * @v ntp		NTP client
 | 
		
	
		
			
			|  | 179 | + */
 | 
		
	
		
			
			|  | 180 | +static void ntp_window_changed ( struct ntp_client *ntp ) {
 | 
		
	
		
			
			|  | 181 | +
 | 
		
	
		
			
			|  | 182 | +	/* Start timer to send initial request */
 | 
		
	
		
			
			|  | 183 | +	start_timer_nodelay ( &ntp->timer );
 | 
		
	
		
			
			|  | 184 | +}
 | 
		
	
		
			
			|  | 185 | +
 | 
		
	
		
			
			|  | 186 | +/** Data transfer interface operations */
 | 
		
	
		
			
			|  | 187 | +static struct interface_operation ntp_xfer_op[] = {
 | 
		
	
		
			
			|  | 188 | +	INTF_OP ( xfer_deliver, struct ntp_client *, ntp_deliver ),
 | 
		
	
		
			
			|  | 189 | +	INTF_OP ( xfer_window_changed, struct ntp_client *,
 | 
		
	
		
			
			|  | 190 | +		  ntp_window_changed ),
 | 
		
	
		
			
			|  | 191 | +	INTF_OP ( intf_close, struct ntp_client *, ntp_close ),
 | 
		
	
		
			
			|  | 192 | +};
 | 
		
	
		
			
			|  | 193 | +
 | 
		
	
		
			
			|  | 194 | +/** Data transfer interface descriptor */
 | 
		
	
		
			
			|  | 195 | +static struct interface_descriptor ntp_xfer_desc =
 | 
		
	
		
			
			|  | 196 | +	INTF_DESC_PASSTHRU ( struct ntp_client, xfer, ntp_xfer_op, job );
 | 
		
	
		
			
			|  | 197 | +
 | 
		
	
		
			
			|  | 198 | +/** Job control interface operations */
 | 
		
	
		
			
			|  | 199 | +static struct interface_operation ntp_job_op[] = {
 | 
		
	
		
			
			|  | 200 | +	INTF_OP ( intf_close, struct ntp_client *, ntp_close ),
 | 
		
	
		
			
			|  | 201 | +};
 | 
		
	
		
			
			|  | 202 | +
 | 
		
	
		
			
			|  | 203 | +/** Job control interface descriptor */
 | 
		
	
		
			
			|  | 204 | +static struct interface_descriptor ntp_job_desc =
 | 
		
	
		
			
			|  | 205 | +	INTF_DESC_PASSTHRU ( struct ntp_client, job, ntp_job_op, xfer );
 | 
		
	
		
			
			|  | 206 | +
 | 
		
	
		
			
			|  | 207 | +/**
 | 
		
	
		
			
			|  | 208 | + * Handle NTP timer expiry
 | 
		
	
		
			
			|  | 209 | + *
 | 
		
	
		
			
			|  | 210 | + * @v timer		Retransmission timer
 | 
		
	
		
			
			|  | 211 | + * @v fail		Failure indicator
 | 
		
	
		
			
			|  | 212 | + */
 | 
		
	
		
			
			|  | 213 | +static void ntp_expired ( struct retry_timer *timer, int fail ) {
 | 
		
	
		
			
			|  | 214 | +	struct ntp_client *ntp =
 | 
		
	
		
			
			|  | 215 | +		container_of ( timer, struct ntp_client, timer );
 | 
		
	
		
			
			|  | 216 | +
 | 
		
	
		
			
			|  | 217 | +	/* Shut down client if we have failed */
 | 
		
	
		
			
			|  | 218 | +	if ( fail ) {
 | 
		
	
		
			
			|  | 219 | +		ntp_close ( ntp, -ETIMEDOUT );
 | 
		
	
		
			
			|  | 220 | +		return;
 | 
		
	
		
			
			|  | 221 | +	}
 | 
		
	
		
			
			|  | 222 | +
 | 
		
	
		
			
			|  | 223 | +	/* Otherwise, restart timer and (re)transmit request */
 | 
		
	
		
			
			|  | 224 | +	start_timer ( &ntp->timer );
 | 
		
	
		
			
			|  | 225 | +	ntp_request ( ntp );
 | 
		
	
		
			
			|  | 226 | +}
 | 
		
	
		
			
			|  | 227 | +
 | 
		
	
		
			
			|  | 228 | +/**
 | 
		
	
		
			
			|  | 229 | + * Start NTP client
 | 
		
	
		
			
			|  | 230 | + *
 | 
		
	
		
			
			|  | 231 | + * @v job		Job control interface
 | 
		
	
		
			
			|  | 232 | + * @v hostname		NTP server
 | 
		
	
		
			
			|  | 233 | + * @ret rc		Return status code
 | 
		
	
		
			
			|  | 234 | + */
 | 
		
	
		
			
			|  | 235 | +int start_ntp ( struct interface *job, const char *hostname ) {
 | 
		
	
		
			
			|  | 236 | +	struct ntp_client *ntp;
 | 
		
	
		
			
			|  | 237 | +	union {
 | 
		
	
		
			
			|  | 238 | +		struct sockaddr_tcpip st;
 | 
		
	
		
			
			|  | 239 | +		struct sockaddr sa;
 | 
		
	
		
			
			|  | 240 | +	} server;
 | 
		
	
		
			
			|  | 241 | +	int rc;
 | 
		
	
		
			
			|  | 242 | +
 | 
		
	
		
			
			|  | 243 | +	/* Allocate and initialise structure*/
 | 
		
	
		
			
			|  | 244 | +	ntp = zalloc ( sizeof ( *ntp ) );
 | 
		
	
		
			
			|  | 245 | +	if ( ! ntp ) {
 | 
		
	
		
			
			|  | 246 | +		rc = -ENOMEM;
 | 
		
	
		
			
			|  | 247 | +		goto err_alloc;
 | 
		
	
		
			
			|  | 248 | +	}
 | 
		
	
		
			
			|  | 249 | +	ref_init ( &ntp->refcnt, NULL );
 | 
		
	
		
			
			|  | 250 | +	intf_init ( &ntp->job, &ntp_job_desc, &ntp->refcnt );
 | 
		
	
		
			
			|  | 251 | +	intf_init ( &ntp->xfer, &ntp_xfer_desc, &ntp->refcnt );
 | 
		
	
		
			
			|  | 252 | +	timer_init ( &ntp->timer, ntp_expired, &ntp->refcnt );
 | 
		
	
		
			
			|  | 253 | +	set_timer_limits ( &ntp->timer, NTP_MIN_TIMEOUT, NTP_MAX_TIMEOUT );
 | 
		
	
		
			
			|  | 254 | +
 | 
		
	
		
			
			|  | 255 | +	/* Open socket */
 | 
		
	
		
			
			|  | 256 | +	memset ( &server, 0, sizeof ( server ) );
 | 
		
	
		
			
			|  | 257 | +	server.st.st_port = htons ( NTP_PORT );
 | 
		
	
		
			
			|  | 258 | +	if ( ( rc = xfer_open_named_socket ( &ntp->xfer, SOCK_DGRAM, &server.sa,
 | 
		
	
		
			
			|  | 259 | +					     hostname, NULL ) ) != 0 ) {
 | 
		
	
		
			
			|  | 260 | +		DBGC ( ntp, "NTP %p could not open socket: %s\n",
 | 
		
	
		
			
			|  | 261 | +		       ntp, strerror ( rc ) );
 | 
		
	
		
			
			|  | 262 | +		goto err_open;
 | 
		
	
		
			
			|  | 263 | +	}
 | 
		
	
		
			
			|  | 264 | +
 | 
		
	
		
			
			|  | 265 | +	/* Attach parent interface, mortalise self, and return */
 | 
		
	
		
			
			|  | 266 | +	intf_plug_plug ( &ntp->job, job );
 | 
		
	
		
			
			|  | 267 | +	ref_put ( &ntp->refcnt );
 | 
		
	
		
			
			|  | 268 | +	return 0;
 | 
		
	
		
			
			|  | 269 | +
 | 
		
	
		
			
			|  | 270 | + err_open:
 | 
		
	
		
			
			|  | 271 | +	ntp_close ( ntp, rc );
 | 
		
	
		
			
			|  | 272 | +	ref_put ( &ntp->refcnt );
 | 
		
	
		
			
			|  | 273 | + err_alloc:
 | 
		
	
		
			
			|  | 274 | +	return rc;
 | 
		
	
		
			
			|  | 275 | +}
 |