/* * Copyright (C) 2013 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 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 /** @file * * ICMP ping sender * */ /* Disambiguate the various error causes */ #define EPROTO_LEN __einfo_error ( EINFO_EPROTO_LEN ) #define EINFO_EPROTO_LEN __einfo_uniqify ( EINFO_EPROTO, 0x01, \ "Incorrect reply length" ) #define EPROTO_DATA __einfo_error ( EINFO_EPROTO_DATA ) #define EINFO_EPROTO_DATA __einfo_uniqify ( EINFO_EPROTO, 0x02, \ "Incorrect reply data" ) #define EPROTO_SEQ __einfo_error ( EINFO_EPROTO_SEQ ) #define EINFO_EPROTO_SEQ __einfo_uniqify ( EINFO_EPROTO, 0x03, \ "Delayed or out-of-sequence reply" ) /** A pinger */ struct pinger { /** Reference count */ struct refcnt refcnt; /** Job control interface */ struct interface job; /** Data transfer interface */ struct interface xfer; /** Timer */ struct retry_timer timer; /** Timeout */ unsigned long timeout; /** Payload length */ size_t len; /** Current sequence number */ uint16_t sequence; /** Response for current sequence number is still pending */ int pending; /** Number of remaining expiry events (zero to continue indefinitely) */ unsigned int remaining; /** Return status */ int rc; /** Callback function * * @v src Source socket address, or NULL * @v sequence Sequence number * @v len Payload length * @v rc Status code */ void ( * callback ) ( struct sockaddr *src, unsigned int sequence, size_t len, int rc ); }; /** * Generate payload * * @v pinger Pinger * @v data Data buffer */ static void pinger_generate ( struct pinger *pinger, void *data ) { uint8_t *bytes = data; unsigned int i; /* Generate byte sequence */ for ( i = 0 ; i < pinger->len ; i++ ) bytes[i] = ( i & 0xff ); } /** * Verify payload * * @v pinger Pinger * @v data Data buffer * @ret rc Return status code */ static int pinger_verify ( struct pinger *pinger, const void *data ) { const uint8_t *bytes = data; unsigned int i; /* Check byte sequence */ for ( i = 0 ; i < pinger->len ; i++ ) { if ( bytes[i] != ( i & 0xff ) ) return -EPROTO_DATA; } return 0; } /** * Close pinger * * @v pinger Pinger * @v rc Reason for close */ static void pinger_close ( struct pinger *pinger, int rc ) { /* Stop timer */ stop_timer ( &pinger->timer ); /* Shut down interfaces */ intf_shutdown ( &pinger->xfer, rc ); intf_shutdown ( &pinger->job, rc ); } /** * Handle data transfer window change * * @v pinger Pinger */ static void pinger_window_changed ( struct pinger *pinger ) { /* Do nothing if timer is already running */ if ( timer_running ( &pinger->timer ) ) return; /* Start timer when window opens for the first time */ if ( xfer_window ( &pinger->xfer ) ) start_timer_nodelay ( &pinger->timer ); } /** * Handle timer expiry * * @v timer Timer * @v over Failure indicator */ static void pinger_expired ( struct retry_timer *timer, int over __unused ) { struct pinger *pinger = container_of ( timer, struct pinger, timer ); struct xfer_metadata meta; struct io_buffer *iobuf; int rc; /* If no response has been received, notify the callback function */ if ( pinger->pending && pinger->callback ) pinger->callback ( NULL, pinger->sequence, 0, -ETIMEDOUT ); /* Check for termination */ if ( pinger->remaining && ( --pinger->remaining == 0 ) ) { pinger_close ( pinger, pinger->rc ); return; } /* Increase sequence number */ pinger->sequence++; /* Restart timer. Do this before attempting to transmit, in * case the transmission attempt fails. */ start_timer_fixed ( &pinger->timer, pinger->timeout ); pinger->pending = 1; /* Allocate I/O buffer */ iobuf = xfer_alloc_iob ( &pinger->xfer, pinger->len ); if ( ! iobuf ) { DBGC ( pinger, "PINGER %p could not allocate I/O buffer\n", pinger ); return; } /* Generate payload */ pinger_generate ( pinger, iob_put ( iobuf, pinger->len ) ); /* Generate metadata */ memset ( &meta, 0, sizeof ( meta ) ); meta.flags = XFER_FL_ABS_OFFSET; meta.offset = pinger->sequence; /* Transmit packet */ if ( ( rc = xfer_deliver ( &pinger->xfer, iobuf, &meta ) ) != 0 ) { DBGC ( pinger, "PINGER %p could not transmit: %s\n", pinger, strerror ( rc ) ); return; } } /** * Handle received data * * @v pinger Pinger * @v iobuf I/O buffer * @v meta Data transfer metadata * @ret rc Return status code */ static int pinger_deliver ( struct pinger *pinger, struct io_buffer *iobuf, struct xfer_metadata *meta ) { size_t len = iob_len ( iobuf ); uint16_t sequence = meta->offset; int terminate = 0; int rc; /* Clear response pending flag, if applicable */ if ( sequence == pinger->sequence ) pinger->pending = 0; /* Check for errors */ if ( len != pinger->len ) { /* Incorrect length: terminate immediately if we are * not pinging indefinitely. */ DBGC ( pinger, "PINGER %p received incorrect length %zd " "(expected %zd)\n", pinger, len, pinger->len ); rc = -EPROTO_LEN; terminate = ( pinger->remaining != 0 ); } else if ( ( rc = pinger_verify ( pinger, iobuf->data ) ) != 0 ) { /* Incorrect data: terminate immediately if we are not * pinging indefinitely. */ DBGC ( pinger, "PINGER %p received incorrect data:\n", pinger ); DBGC_HDA ( pinger, 0, iobuf->data, iob_len ( iobuf ) ); terminate = ( pinger->remaining != 0 ); } else if ( sequence != pinger->sequence ) { /* Incorrect sequence number (probably a delayed response): * report via callback but otherwise ignore. */ DBGC ( pinger, "PINGER %p received sequence %d (expected %d)\n", pinger, sequence, pinger->sequence ); rc = -EPROTO_SEQ; terminate = 0; } else { /* Success: record that a packet was successfully received, * and terminate if we expect to send no further packets. */ rc = 0; pinger->rc = 0; terminate = ( pinger->remaining == 1 ); } /* Discard I/O buffer */ free_iob ( iobuf ); /* Notify callback function, if applicable */ if ( pinger->callback ) pinger->callback ( meta->src, sequence, len, rc ); /* Terminate if applicable */ if ( terminate ) pinger_close ( pinger, rc ); return rc; } /** Pinger data transfer interface operations */ static struct interface_operation pinger_xfer_op[] = { INTF_OP ( xfer_deliver, struct pinger *, pinger_deliver ), INTF_OP ( xfer_window_changed, struct pinger *, pinger_window_changed ), INTF_OP ( intf_close, struct pinger *, pinger_close ), }; /** Pinger data transfer interface descriptor */ static struct interface_descriptor pinger_xfer_desc = INTF_DESC ( struct pinger, xfer, pinger_xfer_op ); /** Pinger job control interface operations */ static struct interface_operation pinger_job_op[] = { INTF_OP ( intf_close, struct pinger *, pinger_close ), }; /** Pinger job control interface descriptor */ static struct interface_descriptor pinger_job_desc = INTF_DESC ( struct pinger, job, pinger_job_op ); /** * Create pinger * * @v job Job control interface * @v hostname Hostname to ping * @v timeout Timeout (in ticks) * @v len Payload length * @v count Number of packets to send (or zero for no limit) * @v callback Callback function (or NULL) * @ret rc Return status code */ int create_pinger ( struct interface *job, const char *hostname, unsigned long timeout, size_t len, unsigned int count, void ( * callback ) ( struct sockaddr *src, unsigned int sequence, size_t len, int rc ) ) { struct pinger *pinger; int rc; /* Sanity check */ if ( ! timeout ) return -EINVAL; /* Allocate and initialise structure */ pinger = zalloc ( sizeof ( *pinger ) ); if ( ! pinger ) return -ENOMEM; ref_init ( &pinger->refcnt, NULL ); intf_init ( &pinger->job, &pinger_job_desc, &pinger->refcnt ); intf_init ( &pinger->xfer, &pinger_xfer_desc, &pinger->refcnt ); timer_init ( &pinger->timer, pinger_expired, &pinger->refcnt ); pinger->timeout = timeout; pinger->len = len; pinger->remaining = ( count ? ( count + 1 /* Initial packet */ ) : 0 ); pinger->callback = callback; pinger->rc = -ETIMEDOUT; /* Open socket */ if ( ( rc = xfer_open_named_socket ( &pinger->xfer, SOCK_ECHO, NULL, hostname, NULL ) ) != 0 ) { DBGC ( pinger, "PINGER %p could not open socket: %s\n", pinger, strerror ( rc ) ); goto err; } /* Attach parent interface, mortalise self, and return */ intf_plug_plug ( &pinger->job, job ); ref_put ( &pinger->refcnt ); return 0; err: pinger_close ( pinger, rc ); ref_put ( &pinger->refcnt ); return rc; }