diff --git a/src/include/gpxe/errfile.h b/src/include/gpxe/errfile.h index 42952d7d..a0591952 100644 --- a/src/include/gpxe/errfile.h +++ b/src/include/gpxe/errfile.h @@ -132,6 +132,7 @@ #define ERRFILE_infiniband ( ERRFILE_NET | 0x00130000 ) #define ERRFILE_netdev_settings ( ERRFILE_NET | 0x00140000 ) #define ERRFILE_dhcppkt ( ERRFILE_NET | 0x00150000 ) +#define ERRFILE_slam ( ERRFILE_NET | 0x00160000 ) #define ERRFILE_image ( ERRFILE_IMAGE | 0x00000000 ) #define ERRFILE_elf ( ERRFILE_IMAGE | 0x00010000 ) diff --git a/src/include/gpxe/features.h b/src/include/gpxe/features.h index 54498b5f..70daac37 100644 --- a/src/include/gpxe/features.h +++ b/src/include/gpxe/features.h @@ -41,6 +41,7 @@ #define DHCP_EB_FEATURE_DNS 0x17 /**< DNS protocol */ #define DHCP_EB_FEATURE_BZIMAGE 0x18 /**< bzImage format */ #define DHCP_EB_FEATURE_MULTIBOOT 0x19 /**< Multiboot format */ +#define DHCP_EB_FEATURE_SLAM 0x1a /**< SLAM protocol */ #define DHCP_EB_FEATURE_NBI 0x20 /**< NBI format */ #define DHCP_EB_FEATURE_PXE 0x21 /**< PXE format */ #define DHCP_EB_FEATURE_ELF 0x22 /**< ELF format */ diff --git a/src/net/udp/slam.c b/src/net/udp/slam.c new file mode 100644 index 00000000..67af8cba --- /dev/null +++ b/src/net/udp/slam.c @@ -0,0 +1,749 @@ +/* + * Copyright (C) 2008 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** @file + * + * Scalable Local Area Multicast protocol + * + * The SLAM protocol is supported only by Etherboot; it was designed + * and implemented by Eric Biederman. A server implementation is + * available in contrib/mini-slamd. There does not appear to be any + * documentation beyond a few sparse comments in Etherboot's + * proto_slam.c. + * + * SLAM packets use three types of data field: + * + * Nul : A single NUL (0) byte, used as a list terminator + * + * Raw : A block of raw data + * + * Int : A variable-length integer, in big-endian order. The length + * of the integer is encoded in the most significant three bits. + * + * Packets received by the client have the following layout: + * + * Int : Transaction identifier. This is an opaque value. + * + * Int : Total number of bytes in the transfer. + * + * Int : Block size, in bytes. + * + * Int : Packet sequence number within the transfer (if this packet + * contains data). + * + * Raw : Packet data (if this packet contains data). + * + * Packets transmitted by the client consist of a run-length-encoded + * representation of the received-blocks bitmap, looking something + * like: + * + * Int : Number of consecutive successfully-received packets + * Int : Number of consecutive missing packets + * Int : Number of consecutive successfully-received packets + * Int : Number of consecutive missing packets + * .... + * Nul + * + */ + +FEATURE ( FEATURE_PROTOCOL, "SLAM", DHCP_EB_FEATURE_SLAM, 1 ); + +/** Default SLAM server port */ +#define SLAM_DEFAULT_PORT 10000 + +/** Default SLAM multicast IP address */ +#define SLAM_DEFAULT_MULTICAST_IP \ + ( ( 239 << 24 ) | ( 255 << 16 ) | ( 1 << 8 ) | ( 1 << 0 ) ) + +/** Default SLAM multicast port */ +#define SLAM_DEFAULT_MULTICAST_PORT 10000 + +/** Maximum SLAM header length */ +#define SLAM_MAX_HEADER_LEN ( 8 /* transaction id */ + 8 /* total_bytes */ + \ + 8 /* block_size */ ) + +/** A SLAM request */ +struct slam_request { + /** Reference counter */ + struct refcnt refcnt; + + /** Data transfer interface */ + struct xfer_interface xfer; + /** Unicast socket */ + struct xfer_interface socket; + /** Multicast socket */ + struct xfer_interface mc_socket; + + /** NACK timer */ + struct retry_timer timer; + + /** Cached header */ + uint8_t header[SLAM_MAX_HEADER_LEN]; + /** Size of cached header */ + size_t header_len; + /** Total number of bytes in transfer */ + unsigned long total_bytes; + /** Transfer block size */ + unsigned long block_size; + /** Number of blocks in transfer */ + unsigned long num_blocks; + /** Block bitmap */ + struct bitmap bitmap; + /** NACK sent flag */ + int nack_sent; +}; + +/** + * Free a SLAM request + * + * @v refcnt Reference counter + */ +static void slam_free ( struct refcnt *refcnt ) { + struct slam_request *slam = + container_of ( refcnt, struct slam_request, refcnt ); + + bitmap_free ( &slam->bitmap ); + free ( slam ); +} + +/** + * Mark SLAM request as complete + * + * @v slam SLAM request + * @v rc Return status code + */ +static void slam_finished ( struct slam_request *slam, int rc ) { + static const uint8_t slam_disconnect[] = { 0 }; + + DBGC ( slam, "SLAM %p finished with status code %d (%s)\n", + slam, rc, strerror ( rc ) ); + + /* Send a disconnect message if we ever sent anything to the + * server. + */ + if ( slam->nack_sent ) { + xfer_deliver_raw ( &slam->socket, slam_disconnect, + sizeof ( slam_disconnect ) ); + } + + /* Stop the retry timer */ + stop_timer ( &slam->timer ); + + /* Close all data transfer interfaces */ + xfer_nullify ( &slam->socket ); + xfer_close ( &slam->socket, rc ); + xfer_nullify ( &slam->mc_socket ); + xfer_close ( &slam->mc_socket, rc ); + xfer_nullify ( &slam->xfer ); + xfer_close ( &slam->xfer, rc ); +} + +/**************************************************************************** + * + * TX datapath + * + */ + +/** + * Add a variable-length value to a SLAM packet + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @v value Value to add + * @ret rc Return status code + * + * Adds a variable-length value to the end of an I/O buffer. Will + * refuse to use the last byte of the I/O buffer; this is to allow + * space for the terminating NUL. + */ +static int slam_put_value ( struct slam_request *slam, + struct io_buffer *iobuf, unsigned long value ) { + uint8_t *data; + size_t len; + unsigned int i; + + /* Calculate variable length required to store value. Always + * leave at least one byte in the I/O buffer. + */ + len = ( ( flsl ( value ) + 10 ) / 8 ); + if ( len >= iob_tailroom ( iobuf ) ) { + DBGC ( slam, "SLAM %p cannot add %d-byte value\n", + slam, len ); + return -ENOBUFS; + } + /* There is no valid way within the protocol that we can end + * up trying to push a full-sized long (i.e. without space for + * the length encoding). + */ + assert ( len <= sizeof ( value ) ); + + /* Add value */ + data = iob_put ( iobuf, len ); + for ( i = len ; i-- ; ) { + data[i] = value; + value >>= 8; + } + *data |= ( len << 5 ); + assert ( value == 0 ); + + return 0; +} + +/** + * Send SLAM NACK packet + * + * @v slam SLAM request + * @ret rc Return status code + */ +static int slam_tx_nack ( struct slam_request *slam ) { + struct io_buffer *iobuf; + unsigned int block; + unsigned int block_count; + int block_present; + int last_block_present; + uint8_t *nul; + + DBGC ( slam, "SLAM %p transmitting NACK\n", slam ); + + /* Mark NACK as sent, so that we know we have to disconnect later */ + slam->nack_sent = 1; + + /* Use the current block size as a good estimate of how much + * data we can fit in a packet. If we overrun, it seems to be + * acceptable to drop information anyway. + */ + iobuf = xfer_alloc_iob ( &slam->socket, slam->block_size ); + if ( ! iobuf ) { + DBGC ( slam, "SLAM %p could not allocate I/O buffer\n", + slam ); + return -ENOMEM; + } + + /* Walk bitmap to construct list */ + block_count = 0; + last_block_present = ( ! 0 ); + for ( block = 0 ; block < slam->num_blocks ; block++ ) { + block_present = ( !! bitmap_test ( &slam->bitmap, block ) ); + if ( block_present != last_block_present ) { + slam_put_value ( slam, iobuf, block_count ); + block_count = 0; + last_block_present = block_present; + } + block_count++; + } + slam_put_value ( slam, iobuf, block_count ); + + /* Add NUL terminator */ + nul = iob_put ( iobuf, 1 ); + *nul = 0; + + /* Transmit packet */ + return xfer_deliver_iob ( &slam->socket, iobuf ); +} + +/** + * Handle SLAM retransmission timer expiry + * + * @v timer Retry timer + * @v fail Failure indicator + */ +static void slam_timer_expired ( struct retry_timer *timer, int fail ) { + struct slam_request *slam = + container_of ( timer, struct slam_request, timer ); + + if ( fail ) { + slam_finished ( slam, -ETIMEDOUT ); + } else { + start_timer ( timer ); + slam_tx_nack ( slam ); + } +} + +/**************************************************************************** + * + * RX datapath + * + */ + +/** + * Read and strip a variable-length value from a SLAM packet + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @v value Value to fill in, or NULL to ignore value + * @ret rc Return status code + * + * Reads a variable-length value from the start of the I/O buffer. + */ +static int slam_pull_value ( struct slam_request *slam, + struct io_buffer *iobuf, + unsigned long *value ) { + uint8_t *data; + size_t len; + + /* Sanity check */ + if ( iob_len ( iobuf ) == 0 ) { + DBGC ( slam, "SLAM %p empty value\n", slam ); + return -EINVAL; + } + + /* Read and verify length of value */ + data = iobuf->data; + len = ( *data >> 5 ); + if ( ( len == 0 ) || + ( value && ( len > sizeof ( *value ) ) ) ) { + DBGC ( slam, "SLAM %p invalid value length %d bytes\n", + slam, len ); + return -EINVAL; + } + if ( len > iob_len ( iobuf ) ) { + DBGC ( slam, "SLAM %p value extends beyond I/O buffer\n", + slam ); + return -EINVAL; + } + + /* Read value */ + iob_pull ( iobuf, len ); + *value = ( *data & 0x1f ); + while ( --len ) { + *value <<= 8; + *value |= *(++data); + } + + return 0; +} + +/** + * Read and strip SLAM header + * + * @v slam SLAM request + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slam_pull_header ( struct slam_request *slam, + struct io_buffer *iobuf ) { + void *header = iobuf->data; + int rc; + + /* If header matches cached header, just pull it and return */ + if ( ( slam->header_len <= iob_len ( iobuf ) ) && + ( memcmp ( slam->header, iobuf->data, slam->header_len ) == 0 )){ + iob_pull ( iobuf, slam->header_len ); + return 0; + } + + DBGC ( slam, "SLAM %p detected changed header; resetting\n", slam ); + + /* Read and strip transaction ID, total number of bytes, and + * block size. + */ + if ( ( rc = slam_pull_value ( slam, iobuf, NULL ) ) != 0 ) + return rc; + if ( ( rc = slam_pull_value ( slam, iobuf, + &slam->total_bytes ) ) != 0 ) + return rc; + if ( ( rc = slam_pull_value ( slam, iobuf, + &slam->block_size ) ) != 0 ) + return rc; + + /* Update the cached header */ + slam->header_len = ( iobuf->data - header ); + assert ( slam->header_len <= sizeof ( slam->header ) ); + memcpy ( slam->header, header, slam->header_len ); + + /* Calculate number of blocks */ + slam->num_blocks = ( ( slam->total_bytes + slam->block_size - 1 ) / + slam->block_size ); + + DBGC ( slam, "SLAM %p has total bytes %ld, block size %ld, num " + "blocks %ld\n", slam, slam->total_bytes, slam->block_size, + slam->num_blocks ); + + /* Discard and reset the bitmap */ + bitmap_free ( &slam->bitmap ); + memset ( &slam->bitmap, 0, sizeof ( slam->bitmap ) ); + + /* Allocate a new bitmap */ + if ( ( rc = bitmap_resize ( &slam->bitmap, + slam->num_blocks ) ) != 0 ) { + /* Failure to allocate a bitmap is fatal */ + DBGC ( slam, "SLAM %p could not allocate bitmap for %ld " + "blocks: %s\n", slam, slam->num_blocks, + strerror ( rc ) ); + slam_finished ( slam, rc ); + return rc; + } + + /* Notify recipient of file size */ + xfer_seek ( &slam->xfer, slam->total_bytes, SEEK_SET ); + + return 0; +} + +/** + * Receive SLAM data packet + * + * @v mc_socket SLAM multicast socket + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slam_mc_socket_deliver ( struct xfer_interface *mc_socket, + struct io_buffer *iobuf, + struct xfer_metadata *rx_meta __unused ) { + struct slam_request *slam = + container_of ( mc_socket, struct slam_request, mc_socket ); + struct xfer_metadata meta; + unsigned long packet; + size_t len; + int rc; + + /* Hit the timer */ + stop_timer ( &slam->timer ); + start_timer ( &slam->timer ); + + /* Read and strip packet header */ + if ( ( rc = slam_pull_header ( slam, iobuf ) ) != 0 ) + goto err_discard; + + /* Read and strip packet number */ + if ( ( rc = slam_pull_value ( slam, iobuf, &packet ) ) != 0 ) + goto err_discard; + + /* Sanity check packet number */ + if ( packet >= slam->num_blocks ) { + DBGC ( slam, "SLAM %p received out-of-range packet %ld " + "(num_blocks=%ld)\n", slam, packet, slam->num_blocks ); + rc = -EINVAL; + goto err_discard; + } + + /* Sanity check length */ + len = iob_len ( iobuf ); + if ( len > slam->block_size ) { + DBGC ( slam, "SLAM %p received oversize packet of %zd bytes " + "(block_size=%ld)\n", slam, len, slam->block_size ); + rc = -EINVAL; + goto err_discard; + } + if ( ( packet != ( slam->num_blocks - 1 ) ) && + ( len < slam->block_size ) ) { + DBGC ( slam, "SLAM %p received short packet of %zd bytes " + "(block_size=%ld)\n", slam, len, slam->block_size ); + rc = -EINVAL; + goto err_discard; + } + + /* If we have already seen this packet, discard it */ + if ( bitmap_test ( &slam->bitmap, packet ) ) { + goto discard; + } + + /* Pass to recipient */ + memset ( &meta, 0, sizeof ( meta ) ); + meta.whence = SEEK_SET; + meta.offset = ( packet * slam->block_size ); + if ( ( rc = xfer_deliver_iob_meta ( &slam->xfer, iobuf, + &meta ) ) != 0 ) + goto err; + + /* Mark block as received */ + bitmap_set ( &slam->bitmap, packet ); + + /* If we have received all blocks, terminate */ + if ( bitmap_full ( &slam->bitmap ) ) + slam_finished ( slam, 0 ); + + return 0; + + err_discard: + discard: + free_iob ( iobuf ); + err: + return rc; +} + +/** + * Receive SLAM non-data packet + * + * @v socket SLAM unicast socket + * @v iobuf I/O buffer + * @ret rc Return status code + */ +static int slam_socket_deliver ( struct xfer_interface *socket, + struct io_buffer *iobuf, + struct xfer_metadata *rx_meta __unused ) { + struct slam_request *slam = + container_of ( socket, struct slam_request, socket ); + int rc; + + /* Hit the timer */ + stop_timer ( &slam->timer ); + start_timer ( &slam->timer ); + + /* Read and strip packet header */ + if ( ( rc = slam_pull_header ( slam, iobuf ) ) != 0 ) + goto discard; + + /* Sanity check */ + if ( iob_len ( iobuf ) != 0 ) { + DBGC ( slam, "SLAM %p received trailing garbage:\n", slam ); + DBGC_HD ( slam, iobuf->data, iob_len ( iobuf ) ); + rc = -EINVAL; + goto discard; + } + + /* Discard packet */ + free_iob ( iobuf ); + + /* Send NACK in reply */ + slam_tx_nack ( slam ); + + return 0; + + discard: + free_iob ( iobuf ); + return rc; + +} + +/** + * Close SLAM unicast socket + * + * @v socket SLAM unicast socket + * @v rc Reason for close + */ +static void slam_socket_close ( struct xfer_interface *socket, int rc ) { + struct slam_request *slam = + container_of ( socket, struct slam_request, socket ); + + DBGC ( slam, "SLAM %p unicast socket closed: %s\n", + slam, strerror ( rc ) ); + + slam_finished ( slam, rc ); +} + +/** SLAM unicast socket data transfer operations */ +static struct xfer_interface_operations slam_socket_operations = { + .close = slam_socket_close, + .vredirect = xfer_vopen, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = slam_socket_deliver, + .deliver_raw = xfer_deliver_as_iob, +}; + +/** + * Close SLAM multicast socket + * + * @v mc_socket SLAM multicast socket + * @v rc Reason for close + */ +static void slam_mc_socket_close ( struct xfer_interface *mc_socket, int rc ){ + struct slam_request *slam = + container_of ( mc_socket, struct slam_request, mc_socket ); + + DBGC ( slam, "SLAM %p multicast socket closed: %s\n", + slam, strerror ( rc ) ); + + slam_finished ( slam, rc ); +} + +/** SLAM multicast socket data transfer operations */ +static struct xfer_interface_operations slam_mc_socket_operations = { + .close = slam_mc_socket_close, + .vredirect = xfer_vopen, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = slam_mc_socket_deliver, + .deliver_raw = xfer_deliver_as_iob, +}; + +/**************************************************************************** + * + * Data transfer interface + * + */ + +/** + * Close SLAM data transfer interface + * + * @v xfer SLAM data transfer interface + * @v rc Reason for close + */ +static void slam_xfer_close ( struct xfer_interface *xfer, int rc ) { + struct slam_request *slam = + container_of ( xfer, struct slam_request, xfer ); + + DBGC ( slam, "SLAM %p data transfer interface closed: %s\n", + slam, strerror ( rc ) ); + + slam_finished ( slam, rc ); +} + +/** SLAM data transfer operations */ +static struct xfer_interface_operations slam_xfer_operations = { + .close = slam_xfer_close, + .vredirect = ignore_xfer_vredirect, + .window = unlimited_xfer_window, + .alloc_iob = default_xfer_alloc_iob, + .deliver_iob = xfer_deliver_as_raw, + .deliver_raw = ignore_xfer_deliver_raw, +}; + +/** + * Parse SLAM URI multicast address + * + * @v slam SLAM request + * @v path Path portion of x-slam:// URI + * @v address Socket address to fill in + * @ret rc Return status code + */ +static int slam_parse_multicast_address ( struct slam_request *slam, + const char *path, + struct sockaddr_in *address ) { + char path_dup[ strlen ( path ) + 1 ]; + char *sep; + + /* Create temporary copy of path */ + memcpy ( path_dup, path, sizeof ( path_dup ) ); + + /* Parse port, if present */ + sep = strchr ( path_dup, ':' ); + if ( sep ) { + *(sep++) = '\0'; + address->sin_port = htons ( strtoul ( sep, &sep, 0 ) ); + if ( *sep != '\0' ) { + DBGC ( slam, "SLAM %p invalid multicast port\n", + slam ); + return -EINVAL; + } + } + + /* Parse address */ + if ( inet_aton ( path_dup, &address->sin_addr ) == 0 ) { + DBGC ( slam, "SLAM %p invalid multicast address\n", slam ); + return -EINVAL; + } + + return 0; +} + +/** + * Initiate a SLAM request + * + * @v xfer Data transfer interface + * @v uri Uniform Resource Identifier + * @ret rc Return status code + */ +static int slam_open ( struct xfer_interface *xfer, struct uri *uri ) { + static const struct sockaddr_in default_multicast = { + .sin_family = AF_INET, + .sin_port = htons ( SLAM_DEFAULT_MULTICAST_PORT ), + .sin_addr = { htonl ( SLAM_DEFAULT_MULTICAST_IP ) }, + }; + struct slam_request *slam; + struct sockaddr_tcpip server; + struct sockaddr_in multicast; + int rc; + + /* Sanity checks */ + if ( ! uri->host ) + return -EINVAL; + + /* Allocate and populate structure */ + slam = zalloc ( sizeof ( *slam ) ); + if ( ! slam ) + return -ENOMEM; + slam->refcnt.free = slam_free; + xfer_init ( &slam->xfer, &slam_xfer_operations, &slam->refcnt ); + xfer_init ( &slam->socket, &slam_socket_operations, &slam->refcnt ); + xfer_init ( &slam->mc_socket, &slam_mc_socket_operations, + &slam->refcnt ); + slam->timer.expired = slam_timer_expired; + /* Fake an invalid cached header of { 0x00, ... } */ + slam->header_len = 1; + /* Fake parameters for initial NACK */ + slam->block_size = 512; + slam->num_blocks = 1; + if ( ( rc = bitmap_resize ( &slam->bitmap, 1 ) ) != 0 ) { + DBGC ( slam, "SLAM %p could not allocate initial bitmap: " + "%s\n", slam, strerror ( rc ) ); + goto err; + } + + /* Open unicast socket */ + memset ( &server, 0, sizeof ( server ) ); + server.st_port = htons ( uri_port ( uri, SLAM_DEFAULT_PORT ) ); + if ( ( rc = xfer_open_named_socket ( &slam->socket, SOCK_DGRAM, + ( struct sockaddr * ) &server, + uri->host, NULL ) ) != 0 ) { + DBGC ( slam, "SLAM %p could not open unicast socket: %s\n", + slam, strerror ( rc ) ); + goto err; + } + + /* Open multicast socket */ + memcpy ( &multicast, &default_multicast, sizeof ( multicast ) ); + if ( uri->path && + ( ( rc = slam_parse_multicast_address ( slam, uri->path, + &multicast ) ) != 0 ) ) { + goto err; + } + if ( ( rc = xfer_open_socket ( &slam->mc_socket, SOCK_DGRAM, + ( struct sockaddr * ) &multicast, + ( struct sockaddr * ) &multicast ) ) != 0 ) { + DBGC ( slam, "SLAM %p could not open multicast socket: %s\n", + slam, strerror ( rc ) ); + goto err; + } + + /* Start retry timer */ + start_timer ( &slam->timer ); + + /* Attach to parent interface, mortalise self, and return */ + xfer_plug_plug ( &slam->xfer, xfer ); + ref_put ( &slam->refcnt ); + return 0; + + err: + slam_finished ( slam, rc ); + ref_put ( &slam->refcnt ); + return rc; +} + +/** SLAM URI opener */ +struct uri_opener slam_uri_opener __uri_opener = { + .scheme = "x-slam", + .open = slam_open, +}; diff --git a/src/proto/slam.c b/src/proto/slam.c deleted file mode 100644 index a25c30de..00000000 --- a/src/proto/slam.c +++ /dev/null @@ -1,541 +0,0 @@ -#if 0 - -/* - * IMPORTANT - * - * This file should be rewritten to avoid the use of a bitmap. Our - * buffer routines can cope with being handed blocks in an arbitrary - * order, duplicate blocks, etc. This code could be substantially - * simplified by taking advantage of these features. - * - */ - -#define SLAM_PORT 10000 -#define SLAM_MULTICAST_IP ((239<<24)|(255<<16)|(1<<8)|(1<<0)) -#define SLAM_MULTICAST_PORT 10000 -#define SLAM_LOCAL_PORT 10000 - -/* Set the timeout intervals to at least 1 second so - * on a 100Mbit ethernet can receive 10000 packets - * in one second. - * - * The only case that is likely to trigger all of the nodes - * firing a nack packet is a slow server. The odds of this - * happening could be reduced being slightly smarter and utilizing - * the multicast channels for nacks. But that only improves the odds - * it doesn't improve the worst case. So unless this proves to be - * a common case having the control data going unicast should increase - * the odds of the data not being dropped. - * - * When doing exponential backoff we increase just the timeout - * interval and not the base to optimize for throughput. This is only - * expected to happen when the server is down. So having some nodes - * pinging immediately should get the transmission restarted quickly after a - * server restart. The host nic won't be to baddly swamped because of - * the random distribution of the nodes. - * - */ -#define SLAM_INITIAL_MIN_TIMEOUT (TICKS_PER_SEC/3) -#define SLAM_INITIAL_TIMEOUT_INTERVAL (TICKS_PER_SEC) -#define SLAM_BASE_MIN_TIMEOUT (2*TICKS_PER_SEC) -#define SLAM_BASE_TIMEOUT_INTERVAL (4*TICKS_PER_SEC) -#define SLAM_BACKOFF_LIMIT 5 -#define SLAM_MAX_RETRIES 20 - -/*** Packets Formats *** - * Data Packet: - * transaction - * total bytes - * block size - * packet # - * data - * - * Status Request Packet - * transaction - * total bytes - * block size - * - * Status Packet - * received packets - * requested packets - * received packets - * requested packets - * ... - * received packets - * requested packtes - * 0 - */ - -#define MAX_HDR (7 + 7 + 7) /* transaction, total size, block size */ -#define MIN_HDR (1 + 1 + 1) /* transactino, total size, block size */ - -#define MAX_SLAM_REQUEST MAX_HDR -#define MIN_SLAM_REQUEST MIN_HDR - -#define MIN_SLAM_DATA (MIN_HDR + 1) - -static struct slam_nack { - struct iphdr ip; - struct udphdr udp; - unsigned char data[ETH_MAX_MTU - - (sizeof(struct iphdr) + sizeof(struct udphdr))]; -} nack; - -struct slam_state { - unsigned char hdr[MAX_HDR]; - unsigned long hdr_len; - unsigned long block_size; - unsigned long total_bytes; - unsigned long total_packets; - - unsigned long received_packets; - - struct buffer *buffer; - unsigned char *image; - unsigned char *bitmap; -} state; - - -static void init_slam_state(void) -{ - state.hdr_len = sizeof(state.hdr); - memset(state.hdr, 0, state.hdr_len); - state.block_size = 0; - state.total_packets = 0; - - state.received_packets = 0; - - state.image = 0; - state.bitmap = 0; -} - -struct slam_info { - struct sockaddr_in server; - struct sockaddr_in local; - struct sockaddr_in multicast; - int sent_nack; - struct buffer *buffer; -}; - -#define SLAM_TIMEOUT 0 -#define SLAM_REQUEST 1 -#define SLAM_DATA 2 -static int await_slam(int ival __unused, void *ptr, - unsigned short ptype __unused, struct iphdr *ip, - struct udphdr *udp, struct tcphdr *tcp __unused) -{ - struct slam_info *info = ptr; - if (!udp) { - return 0; - } - /* I can receive two kinds of packets here, a multicast data packet, - * or a unicast request for information - */ - /* Check for a data request packet */ - if ((ip->dest.s_addr == arptable[ARP_CLIENT].ipaddr.s_addr) && - (ntohs(udp->dest) == info->local.sin_port) && - (nic.packetlen >= - ETH_HLEN + - sizeof(struct iphdr) + - sizeof(struct udphdr) + - MIN_SLAM_REQUEST)) { - return SLAM_REQUEST; - } - /* Check for a multicast data packet */ - if ((ip->dest.s_addr == info->multicast.sin_addr.s_addr) && - (ntohs(udp->dest) == info->multicast.sin_port) && - (nic.packetlen >= - ETH_HLEN + - sizeof(struct iphdr) + - sizeof(struct udphdr) + - MIN_SLAM_DATA)) { - return SLAM_DATA; - } -#if 0 - printf("#"); - printf("dest: %@ port: %d len: %d\n", - ip->dest.s_addr, ntohs(udp->dest), nic.packetlen); -#endif - return 0; - -} - -static int slam_encode( - unsigned char **ptr, unsigned char *end, unsigned long value) -{ - unsigned char *data = *ptr; - int bytes; - bytes = sizeof(value); - while ((bytes > 0) && ((0xff & (value >> ((bytes -1)<<3))) == 0)) { - bytes--; - } - if (bytes <= 0) { - bytes = 1; - } - if (data + bytes >= end) { - return -1; - } - if ((0xe0 & (value >> ((bytes -1)<<3))) == 0) { - /* packed together */ - *data = (bytes << 5) | (value >> ((bytes -1)<<3)); - } else { - bytes++; - *data = (bytes << 5); - } - bytes--; - data++; - while(bytes) { - *(data++) = 0xff & (value >> ((bytes -1)<<3)); - bytes--; - } - *ptr = data; - return 0; -} - -static int slam_skip(unsigned char **ptr, unsigned char *end) -{ - int bytes; - if (*ptr >= end) { - return -1; - } - bytes = ((**ptr) >> 5) & 7; - if (bytes == 0) { - return -1; - } - if (*ptr + bytes >= end) { - return -1; - } - (*ptr) += bytes; - return 0; - -} - -static unsigned long slam_decode(unsigned char **ptr, unsigned char *end, - int *err) -{ - unsigned long value; - unsigned bytes; - if (*ptr >= end) { - *err = -1; - } - bytes = ((**ptr) >> 5) & 7; - if ((bytes == 0) || (bytes > sizeof(unsigned long))) { - *err = -1; - return 0; - } - if ((*ptr) + bytes >= end) { - *err = -1; - } - value = (**ptr) & 0x1f; - bytes--; - (*ptr)++; - while(bytes) { - value <<= 8; - value |= **ptr; - (*ptr)++; - bytes--; - } - return value; -} - - -static long slam_sleep_interval(int exp) -{ - long range; - long divisor; - long interval; - range = SLAM_BASE_TIMEOUT_INTERVAL; - if (exp < 0) { - divisor = RAND_MAX/SLAM_INITIAL_TIMEOUT_INTERVAL; - } else { - if (exp > SLAM_BACKOFF_LIMIT) - exp = SLAM_BACKOFF_LIMIT; - divisor = RAND_MAX/(range << exp); - } - interval = random()/divisor; - if (exp < 0) { - interval += SLAM_INITIAL_MIN_TIMEOUT; - } else { - interval += SLAM_BASE_MIN_TIMEOUT; - } - return interval; -} - - -static unsigned char *reinit_slam_state( - unsigned char *header, unsigned char *end) -{ - unsigned long total_bytes; - unsigned long block_size; - - unsigned long bitmap_len; - unsigned long max_packet_len; - unsigned char *data; - int err; - -#if 0 - printf("reinit\n"); -#endif - data = header; - - state.hdr_len = 0; - err = slam_skip(&data, end); /* transaction id */ - total_bytes = slam_decode(&data, end, &err); - block_size = slam_decode(&data, end, &err); - if (err) { - printf("ALERT: slam size out of range\n"); - return 0; - } - state.block_size = block_size; - state.total_bytes = total_bytes; - state.total_packets = (total_bytes + block_size - 1)/block_size; - state.hdr_len = data - header; - state.received_packets = 0; - - data = state.hdr; - slam_encode(&data, &state.hdr[sizeof(state.hdr)], state.total_packets); - max_packet_len = data - state.hdr; - memcpy(state.hdr, header, state.hdr_len); - -#if 0 - printf("block_size: %ld\n", block_size); - printf("total_bytes: %ld\n", total_bytes); - printf("total_packets: %ld\n", state.total_packets); - printf("hdr_len: %ld\n", state.hdr_len); - printf("max_packet_len: %ld\n", max_packet_len); -#endif - - if (state.block_size > ETH_MAX_MTU - ( - sizeof(struct iphdr) + sizeof(struct udphdr) + - state.hdr_len + max_packet_len)) { - printf("ALERT: slam blocksize to large\n"); - return 0; - } - bitmap_len = (state.total_packets + 1 + 7)/8; - state.image = phys_to_virt ( state.buffer->addr ); - /* We don't use the buffer routines properly yet; fake it */ - state.buffer->fill = total_bytes; - state.bitmap = state.image + total_bytes; - if ((unsigned long)state.image < 1024*1024) { - printf("ALERT: slam filesize to large for available memory\n"); - return 0; - } - memset(state.bitmap, 0, bitmap_len); - - return header + state.hdr_len; -} - -static int slam_recv_data(unsigned char *data) -{ - unsigned long packet; - unsigned long data_len; - int err; - struct udphdr *udp; - udp = (struct udphdr *)&nic.packet[ETH_HLEN + sizeof(struct iphdr)]; - err = 0; - packet = slam_decode(&data, &nic.packet[nic.packetlen], &err); - if (err || (packet > state.total_packets)) { - printf("ALERT: Invalid packet number\n"); - return 0; - } - /* Compute the expected data length */ - if (packet != state.total_packets -1) { - data_len = state.block_size; - } else { - data_len = state.total_bytes % state.block_size; - } - /* If the packet size is wrong drop the packet and then continue */ - if (ntohs(udp->len) != (data_len + (data - (unsigned char*)udp))) { - printf("ALERT: udp packet is not the correct size\n"); - return 1; - } - if (nic.packetlen < data_len + (data - nic.packet)) { - printf("ALERT: Ethernet packet shorter than data_len\n"); - return 1; - } - if (data_len > state.block_size) { - data_len = state.block_size; - } - if (((state.bitmap[packet >> 3] >> (packet & 7)) & 1) == 0) { - /* Non duplicate packet */ - state.bitmap[packet >> 3] |= (1 << (packet & 7)); - memcpy(state.image + (packet*state.block_size), data, data_len); - state.received_packets++; - } else { -#ifdef MDEBUG - printf("\n"); -#endif - } - return 1; -} - -static void transmit_nack(unsigned char *ptr, struct slam_info *info) -{ - int nack_len; - /* Ensure the packet is null terminated */ - *ptr++ = 0; - nack_len = ptr - (unsigned char *)&nack; - build_udp_hdr(info->server.sin_addr.s_addr, info->local.sin_port, - info->server.sin_port, 1, nack_len, &nack); - ip_transmit(nack_len, &nack); -#if defined(MDEBUG) && 0 - printf("Sent NACK to %@ bytes: %d have:%ld/%ld\n", - info->server_ip, nack_len, - state.received_packets, state.total_packets); -#endif -} - -static void slam_send_nack(struct slam_info *info) -{ - unsigned char *ptr, *end; - /* Either I timed out or I was explicitly - * asked for a request packet - */ - ptr = &nack.data[0]; - /* Reserve space for the trailling null */ - end = &nack.data[sizeof(nack.data) -1]; - if (!state.bitmap) { - slam_encode(&ptr, end, 0); - slam_encode(&ptr, end, 1); - } - else { - /* Walk the bitmap */ - unsigned long i; - unsigned long len; - unsigned long max; - int value; - int last; - /* Compute the last bit and store an inverted trailer */ - max = state.total_packets; - value = ((state.bitmap[(max -1) >> 3] >> ((max -1) & 7) ) & 1); - value = !value; - state.bitmap[max >> 3] &= ~(1 << (max & 7)); - state.bitmap[max >> 3] |= value << (max & 7); - - len = 0; - last = 1; /* Start with the received packets */ - for(i = 0; i <= max; i++) { - value = (state.bitmap[i>>3] >> (i & 7)) & 1; - if (value == last) { - len++; - } else { - if (slam_encode(&ptr, end, len)) - break; - last = value; - len = 1; - } - } - } - info->sent_nack = 1; - transmit_nack(ptr, info); -} - -static void slam_send_disconnect(struct slam_info *info) -{ - if (info->sent_nack) { - /* A disconnect is a packet with just the null terminator */ - transmit_nack(&nack.data[0], info); - } - info->sent_nack = 0; -} - - -static int proto_slam(struct slam_info *info) -{ - int retry; - long timeout; - - init_slam_state(); - state.buffer = info->buffer; - - retry = -1; - rx_qdrain(); - /* Arp for my server */ - if (arptable[ARP_SERVER].ipaddr.s_addr != info->server.sin_addr.s_addr) { - arptable[ARP_SERVER].ipaddr.s_addr = info->server.sin_addr.s_addr; - memset(arptable[ARP_SERVER].node, 0, ETH_ALEN); - } - /* If I'm running over multicast join the multicast group */ - join_group(IGMP_SERVER, info->multicast.sin_addr.s_addr); - for(;;) { - unsigned char *header; - unsigned char *data; - int type; - header = data = 0; - - timeout = slam_sleep_interval(retry); - type = await_reply(await_slam, 0, info, timeout); - /* Compute the timeout for next time */ - if (type == SLAM_TIMEOUT) { - /* If I timeouted recompute the next timeout */ - if (retry++ > SLAM_MAX_RETRIES) { - return 0; - } - } else { - retry = 0; - } - if ((type == SLAM_DATA) || (type == SLAM_REQUEST)) { - /* Check the incomming packet and reinit the data - * structures if necessary. - */ - header = &nic.packet[ETH_HLEN + - sizeof(struct iphdr) + sizeof(struct udphdr)]; - data = header + state.hdr_len; - if (memcmp(state.hdr, header, state.hdr_len) != 0) { - /* Something is fishy reset the transaction */ - data = reinit_slam_state(header, &nic.packet[nic.packetlen]); - if (!data) { - return 0; - } - } - } - if (type == SLAM_DATA) { - if (!slam_recv_data(data)) { - return 0; - } - if (state.received_packets == state.total_packets) { - /* We are done get out */ - break; - } - } - if ((type == SLAM_TIMEOUT) || (type == SLAM_REQUEST)) { - /* Either I timed out or I was explicitly - * asked by a request packet - */ - slam_send_nack(info); - } - } - slam_send_disconnect(info); - - /* Leave the multicast group */ - leave_group(IGMP_SERVER); - /* FIXME don't overwrite myself */ - /* load file to correct location */ - return 1; -} - -static int url_slam ( char *url __unused, struct sockaddr_in *server, - char *file, struct buffer *buffer ) { - struct slam_info info; - /* Set the defaults */ - info.server = *server; - info.multicast.sin_addr.s_addr = htonl(SLAM_MULTICAST_IP); - info.multicast.sin_port = SLAM_MULTICAST_PORT; - info.local.sin_addr.s_addr = arptable[ARP_CLIENT].ipaddr.s_addr; - info.local.sin_port = SLAM_LOCAL_PORT; - info.buffer = buffer; - info.sent_nack = 0; - if (file[0]) { - printf("\nBad url\n"); - return 0; - } - return proto_slam(&info); -} - -struct protocol slam_protocol __protocol = { - .name = "x-slam", - .default_port = SLAM_PORT, - .load = url_slam, -}; - -#endif