iPXE
peerdisc.c
Go to the documentation of this file.
00001 /*
00002  * Copyright (C) 2015 Michael Brown <mbrown@fensystems.co.uk>.
00003  *
00004  * This program is free software; you can redistribute it and/or
00005  * modify it under the terms of the GNU General Public License as
00006  * published by the Free Software Foundation; either version 2 of the
00007  * License, or (at your option) any later version.
00008  *
00009  * This program is distributed in the hope that it will be useful, but
00010  * WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012  * General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program; if not, write to the Free Software
00016  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
00017  * 02110-1301, USA.
00018  *
00019  * You can also choose to distribute this program under the terms of
00020  * the Unmodified Binary Distribution Licence (as given in the file
00021  * COPYING.UBDL), provided that you have satisfied its requirements.
00022  */
00023 
00024 FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
00025 
00026 #include <stdlib.h>
00027 #include <string.h>
00028 #include <ctype.h>
00029 #include <errno.h>
00030 #include <assert.h>
00031 #include <ipxe/xfer.h>
00032 #include <ipxe/iobuf.h>
00033 #include <ipxe/open.h>
00034 #include <ipxe/tcpip.h>
00035 #include <ipxe/uuid.h>
00036 #include <ipxe/base16.h>
00037 #include <ipxe/netdevice.h>
00038 #include <ipxe/timer.h>
00039 #include <ipxe/fault.h>
00040 #include <ipxe/pccrd.h>
00041 #include <ipxe/peerdisc.h>
00042 
00043 /** @file
00044  *
00045  * Peer Content Caching and Retrieval (PeerDist) protocol peer discovery
00046  *
00047  */
00048 
00049 /** List of discovery segments */
00050 static LIST_HEAD ( peerdisc_segments );
00051 
00052 /** Number of repeated discovery attempts */
00053 #define PEERDISC_REPEAT_COUNT 2
00054 
00055 /** Time between repeated discovery attempts */
00056 #define PEERDISC_REPEAT_TIMEOUT ( 1 * TICKS_PER_SEC )
00057 
00058 /** Default discovery timeout (in seconds) */
00059 #define PEERDISC_DEFAULT_TIMEOUT_SECS 2
00060 
00061 /** Recommended discovery timeout (in seconds)
00062  *
00063  * We reduce the recommended discovery timeout whenever a segment
00064  * fails to discover any peers, and restore the default value whenever
00065  * a valid discovery reply is received.  We continue to send discovery
00066  * requests even if the recommended timeout is reduced to zero.
00067  *
00068  * This strategy is intended to minimise discovery delays when no
00069  * peers are available on the network, while allowing downloads to
00070  * quickly switch back to using PeerDist acceleration if new peers
00071  * become available.
00072  */
00073 unsigned int peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS;
00074 
00075 static struct peerdisc_segment * peerdisc_find ( const char *id );
00076 static int peerdisc_discovered ( struct peerdisc_segment *segment,
00077                                  const char *location );
00078 
00079 /******************************************************************************
00080  *
00081  * Statistics reporting
00082  *
00083  ******************************************************************************
00084  */
00085 
00086 /**
00087  * Report peer discovery statistics
00088  *
00089  * @v intf              Interface
00090  * @v peer              Selected peer (or NULL)
00091  * @v peers             List of available peers
00092  */
00093 void peerdisc_stat ( struct interface *intf, struct peerdisc_peer *peer,
00094                      struct list_head *peers ) {
00095         struct interface *dest;
00096         peerdisc_stat_TYPE ( void * ) *op =
00097                 intf_get_dest_op ( intf, peerdisc_stat, &dest );
00098         void *object = intf_object ( dest );
00099 
00100         if ( op ) {
00101                 op ( object, peer, peers );
00102         } else {
00103                 /* Default is to do nothing */
00104         }
00105 
00106         intf_put ( dest );
00107 }
00108 
00109 /******************************************************************************
00110  *
00111  * Discovery sockets
00112  *
00113  ******************************************************************************
00114  */
00115 
00116 /**
00117  * Open all PeerDist discovery sockets
00118  *
00119  * @ret rc              Return status code
00120  */
00121 static int peerdisc_socket_open ( void ) {
00122         struct peerdisc_socket *socket;
00123         int rc;
00124 
00125         /* Open each socket */
00126         for_each_table_entry ( socket, PEERDISC_SOCKETS ) {
00127                 if ( ( rc = xfer_open_socket ( &socket->xfer, SOCK_DGRAM,
00128                                                &socket->address.sa,
00129                                                NULL ) ) != 0 ) {
00130                         DBGC ( socket, "PEERDISC %s could not open socket: "
00131                                "%s\n", socket->name, strerror ( rc ) );
00132                         goto err;
00133                 }
00134         }
00135 
00136         return 0;
00137 
00138  err:
00139         for_each_table_entry_continue_reverse ( socket, PEERDISC_SOCKETS )
00140                 intf_restart ( &socket->xfer, rc );
00141         return rc;
00142 }
00143 
00144 /**
00145  * Attempt to transmit PeerDist discovery requests on all sockets
00146  *
00147  * @v uuid              Message UUID string
00148  * @v id                Segment identifier string
00149  */
00150 static void peerdisc_socket_tx ( const char *uuid, const char *id ) {
00151         struct peerdisc_socket *socket;
00152         struct net_device *netdev;
00153         struct xfer_metadata meta;
00154         union {
00155                 struct sockaddr sa;
00156                 struct sockaddr_tcpip st;
00157         } address;
00158         char *request;
00159         size_t len;
00160         int rc;
00161 
00162         /* Construct discovery request */
00163         request = peerdist_discovery_request ( uuid, id );
00164         if ( ! request )
00165                 goto err_request;
00166         len = strlen ( request );
00167 
00168         /* Initialise data transfer metadata */
00169         memset ( &meta, 0, sizeof ( meta ) );
00170         meta.dest = &address.sa;
00171 
00172         /* Send message on each socket */
00173         for_each_table_entry ( socket, PEERDISC_SOCKETS ) {
00174 
00175                 /* Initialise socket address */
00176                 memcpy ( &address.sa, &socket->address.sa,
00177                          sizeof ( address.sa ) );
00178 
00179                 /* Send message on each open network device */
00180                 for_each_netdev ( netdev ) {
00181 
00182                         /* Skip unopened network devices */
00183                         if ( ! netdev_is_open ( netdev ) )
00184                                 continue;
00185                         address.st.st_scope_id = netdev->index;
00186 
00187                         /* Discard request (for test purposes) if applicable */
00188                         if ( inject_fault ( PEERDISC_DISCARD_RATE ) )
00189                                 continue;
00190 
00191                         /* Transmit request */
00192                         if ( ( rc = xfer_deliver_raw_meta ( &socket->xfer,
00193                                                             request, len,
00194                                                             &meta ) ) != 0 ) {
00195                                 DBGC ( socket, "PEERDISC %s could not transmit "
00196                                        "via %s: %s\n", socket->name,
00197                                        netdev->name, strerror ( rc ) );
00198                                 /* Contine to try other net devices/sockets */
00199                                 continue;
00200                         }
00201                 }
00202         }
00203 
00204         free ( request );
00205  err_request:
00206         return;
00207 }
00208 
00209 /**
00210  * Handle received PeerDist discovery reply
00211  *
00212  * @v socket            PeerDist discovery socket
00213  * @v iobuf             I/O buffer
00214  * @v meta              Data transfer metadata
00215  * @ret rc              Return status code
00216  */
00217 static int peerdisc_socket_rx ( struct peerdisc_socket *socket,
00218                                 struct io_buffer *iobuf,
00219                                 struct xfer_metadata *meta __unused ) {
00220         struct peerdist_discovery_reply reply;
00221         struct peerdisc_segment *segment;
00222         char *id;
00223         char *location;
00224         int rc;
00225 
00226         /* Discard reply (for test purposes) if applicable */
00227         if ( ( rc = inject_fault ( PEERDISC_DISCARD_RATE ) ) != 0 )
00228                 goto err;
00229 
00230         /* Parse reply */
00231         if ( ( rc = peerdist_discovery_reply ( iobuf->data, iob_len ( iobuf ),
00232                                                &reply ) ) != 0 ) {
00233                 DBGC ( socket, "PEERDISC %s could not parse reply: %s\n",
00234                        socket->name, strerror ( rc ) );
00235                 DBGC_HDA ( socket, 0, iobuf->data, iob_len ( iobuf ) );
00236                 goto err;
00237         }
00238 
00239         /* Any kind of discovery reply indicates that there are active
00240          * peers on a local network, so restore the recommended
00241          * discovery timeout to its default value for future requests.
00242          */
00243         if ( peerdisc_timeout_secs != PEERDISC_DEFAULT_TIMEOUT_SECS ) {
00244                 DBGC ( socket, "PEERDISC %s restoring timeout to %d seconds\n",
00245                        socket->name, PEERDISC_DEFAULT_TIMEOUT_SECS );
00246         }
00247         peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS;
00248 
00249         /* Iterate over segment IDs */
00250         for ( id = reply.ids ; *id ; id += ( strlen ( id ) + 1 /* NUL */ ) ) {
00251 
00252                 /* Find corresponding segment */
00253                 segment = peerdisc_find ( id );
00254                 if ( ! segment ) {
00255                         DBGC ( socket, "PEERDISC %s ignoring reply for %s\n",
00256                                socket->name, id );
00257                         continue;
00258                 }
00259 
00260                 /* Report all discovered peer locations */
00261                 for ( location = reply.locations ; *location ;
00262                       location += ( strlen ( location ) + 1 /* NUL */ ) ) {
00263 
00264                         /* Report discovered peer location */
00265                         if ( ( rc = peerdisc_discovered ( segment,
00266                                                           location ) ) != 0 )
00267                                 goto err;
00268                 }
00269         }
00270 
00271  err:
00272         free_iob ( iobuf );
00273         return rc;
00274 }
00275 
00276 /**
00277  * Close all PeerDist discovery sockets
00278  *
00279  * @v rc                Reason for close
00280  */
00281 static void peerdisc_socket_close ( int rc ) {
00282         struct peerdisc_socket *socket;
00283 
00284         /* Close all sockets */
00285         for_each_table_entry ( socket, PEERDISC_SOCKETS )
00286                 intf_restart ( &socket->xfer, rc );
00287 }
00288 
00289 /** PeerDist discovery socket interface operations */
00290 static struct interface_operation peerdisc_socket_operations[] = {
00291         INTF_OP ( xfer_deliver, struct peerdisc_socket *, peerdisc_socket_rx ),
00292 };
00293 
00294 /** PeerDist discovery socket interface descriptor */
00295 static struct interface_descriptor peerdisc_socket_desc =
00296         INTF_DESC ( struct peerdisc_socket, xfer, peerdisc_socket_operations );
00297 
00298 /** PeerDist discovery IPv4 socket */
00299 struct peerdisc_socket peerdisc_socket_ipv4 __peerdisc_socket = {
00300         .name = "IPv4",
00301         .address = {
00302                 .sin = {
00303                         .sin_family = AF_INET,
00304                         .sin_port = htons ( PEERDIST_DISCOVERY_PORT ),
00305                         .sin_addr.s_addr = htonl ( PEERDIST_DISCOVERY_IPV4 ),
00306                 },
00307         },
00308         .xfer = INTF_INIT ( peerdisc_socket_desc ),
00309 };
00310 
00311 /** PeerDist discovery IPv6 socket */
00312 struct peerdisc_socket peerdisc_socket_ipv6 __peerdisc_socket = {
00313         .name = "IPv6",
00314         .address = {
00315                 .sin6 = {
00316                         .sin6_family = AF_INET6,
00317                         .sin6_port = htons ( PEERDIST_DISCOVERY_PORT ),
00318                         .sin6_addr.s6_addr = PEERDIST_DISCOVERY_IPV6,
00319                 },
00320         },
00321         .xfer = INTF_INIT ( peerdisc_socket_desc ),
00322 };
00323 
00324 /******************************************************************************
00325  *
00326  * Discovery segments
00327  *
00328  ******************************************************************************
00329  */
00330 
00331 /**
00332  * Free PeerDist discovery segment
00333  *
00334  * @v refcnt            Reference count
00335  */
00336 static void peerdisc_free ( struct refcnt *refcnt ) {
00337         struct peerdisc_segment *segment =
00338                 container_of ( refcnt, struct peerdisc_segment, refcnt );
00339         struct peerdisc_peer *peer;
00340         struct peerdisc_peer *tmp;
00341 
00342         /* Free all discovered peers */
00343         list_for_each_entry_safe ( peer, tmp, &segment->peers, list ) {
00344                 list_del ( &peer->list );
00345                 free ( peer );
00346         }
00347 
00348         /* Free segment */
00349         free ( segment );
00350 }
00351 
00352 /**
00353  * Find PeerDist discovery segment
00354  *
00355  * @v id                Segment ID
00356  * @ret segment         PeerDist discovery segment, or NULL if not found
00357  */
00358 static struct peerdisc_segment * peerdisc_find ( const char *id ) {
00359         struct peerdisc_segment *segment;
00360 
00361         /* Look for a matching segment */
00362         list_for_each_entry ( segment, &peerdisc_segments, list ) {
00363                 if ( strcmp ( id, segment->id ) == 0 )
00364                         return segment;
00365         }
00366 
00367         return NULL;
00368 }
00369 
00370 /**
00371  * Add discovered PeerDist peer
00372  *
00373  * @v segment           PeerDist discovery segment
00374  * @v location          Peer location
00375  * @ret rc              Return status code
00376  */
00377 static int peerdisc_discovered ( struct peerdisc_segment *segment,
00378                                  const char *location ) {
00379         struct peerdisc_peer *peer;
00380         struct peerdisc_client *peerdisc;
00381         struct peerdisc_client *tmp;
00382 
00383         /* Ignore duplicate peers */
00384         list_for_each_entry ( peer, &segment->peers, list ) {
00385                 if ( strcmp ( peer->location, location ) == 0 ) {
00386                         DBGC2 ( segment, "PEERDISC %p duplicate %s\n",
00387                                 segment, location );
00388                         return 0;
00389                 }
00390         }
00391         DBGC2 ( segment, "PEERDISC %p discovered %s\n", segment, location );
00392 
00393         /* Allocate and initialise structure */
00394         peer = zalloc ( sizeof ( *peer ) + strlen ( location ) + 1 /* NUL */ );
00395         if ( ! peer )
00396                 return -ENOMEM;
00397         strcpy ( peer->location, location );
00398 
00399         /* Add to end of list of peers */
00400         list_add_tail ( &peer->list, &segment->peers );
00401 
00402         /* Notify all clients */
00403         list_for_each_entry_safe ( peerdisc, tmp, &segment->clients, list )
00404                 peerdisc->op->discovered ( peerdisc );
00405 
00406         return 0;
00407 }
00408 
00409 /**
00410  * Handle discovery timer expiry
00411  *
00412  * @v timer             Discovery timer
00413  * @v over              Failure indicator
00414  */
00415 static void peerdisc_expired ( struct retry_timer *timer, int over __unused ) {
00416         struct peerdisc_segment *segment =
00417                 container_of ( timer, struct peerdisc_segment, timer );
00418 
00419         /* Attempt to transmit discovery requests */
00420         peerdisc_socket_tx ( segment->uuid, segment->id );
00421 
00422         /* Schedule next transmission, if applicable */
00423         if ( timer->count < PEERDISC_REPEAT_COUNT )
00424                 start_timer_fixed ( &segment->timer, PEERDISC_REPEAT_TIMEOUT );
00425 }
00426 
00427 /**
00428  * Create PeerDist discovery segment
00429  *
00430  * @v id                Segment ID
00431  * @ret segment         PeerDist discovery segment, or NULL on error
00432  */
00433 static struct peerdisc_segment * peerdisc_create ( const char *id ) {
00434         struct peerdisc_segment *segment;
00435         union {
00436                 union uuid uuid;
00437                 uint32_t dword[ sizeof ( union uuid ) / sizeof ( uint32_t ) ];
00438         } random_uuid;
00439         size_t uuid_len;
00440         size_t id_len;
00441         const char *uuid;
00442         char *uuid_copy;
00443         char *id_copy;
00444         unsigned int i;
00445 
00446         /* Generate a random message UUID.  This does not require high
00447          * quality randomness.
00448          */
00449         for ( i = 0 ; i < ( sizeof ( random_uuid.dword ) /
00450                             sizeof ( random_uuid.dword[0] ) ) ; i++ )
00451                 random_uuid.dword[i] = random();
00452         uuid = uuid_ntoa ( &random_uuid.uuid );
00453 
00454         /* Calculate string lengths */
00455         id_len = ( strlen ( id ) + 1 /* NUL */ );
00456         uuid_len = ( strlen ( uuid ) + 1 /* NUL */ );
00457 
00458         /* Allocate and initialise structure */
00459         segment = zalloc ( sizeof ( *segment ) + id_len + uuid_len );
00460         if ( ! segment )
00461                 return NULL;
00462         id_copy = ( ( ( void * ) segment ) + sizeof ( *segment ) );
00463         memcpy ( id_copy, id, id_len );
00464         uuid_copy = ( ( ( void * ) id_copy ) + id_len );
00465         memcpy ( uuid_copy, uuid, uuid_len );
00466         ref_init ( &segment->refcnt, peerdisc_free );
00467         segment->id = id_copy;
00468         segment->uuid = uuid_copy;
00469         INIT_LIST_HEAD ( &segment->peers );
00470         INIT_LIST_HEAD ( &segment->clients );
00471         timer_init ( &segment->timer, peerdisc_expired, &segment->refcnt );
00472         DBGC2 ( segment, "PEERDISC %p discovering %s\n", segment, segment->id );
00473 
00474         /* Start discovery timer */
00475         start_timer_nodelay ( &segment->timer );
00476 
00477         /* Add to list of segments, transfer reference to list, and return */
00478         list_add_tail ( &segment->list, &peerdisc_segments );
00479         return segment;
00480 }
00481 
00482 /**
00483  * Destroy PeerDist discovery segment
00484  *
00485  * @v segment           PeerDist discovery segment
00486  */
00487 static void peerdisc_destroy ( struct peerdisc_segment *segment ) {
00488 
00489         /* Sanity check */
00490         assert ( list_empty ( &segment->clients ) );
00491 
00492         /* Stop timer */
00493         stop_timer ( &segment->timer );
00494 
00495         /* Remove from list of segments and drop list's reference */
00496         list_del ( &segment->list );
00497         ref_put ( &segment->refcnt );
00498 }
00499 
00500 /******************************************************************************
00501  *
00502  * Discovery clients
00503  *
00504  ******************************************************************************
00505  */
00506 
00507 /**
00508  * Open PeerDist discovery client
00509  *
00510  * @v peerdisc          PeerDist discovery client
00511  * @v id                Segment ID
00512  * @v len               Length of segment ID
00513  * @ret rc              Return status code
00514  */
00515 int peerdisc_open ( struct peerdisc_client *peerdisc, const void *id,
00516                     size_t len ) {
00517         struct peerdisc_segment *segment;
00518         char id_string[ base16_encoded_len ( len ) + 1 /* NUL */ ];
00519         char *id_chr;
00520         int rc;
00521 
00522         /* Construct ID string */
00523         base16_encode ( id, len, id_string, sizeof ( id_string ) );
00524         for ( id_chr = id_string ; *id_chr ; id_chr++ )
00525                 *id_chr = toupper ( *id_chr );
00526 
00527         /* Sanity check */
00528         assert ( peerdisc->segment == NULL );
00529 
00530         /* Open socket if this is the first segment */
00531         if ( list_empty ( &peerdisc_segments ) &&
00532              ( ( rc = peerdisc_socket_open() ) != 0 ) )
00533                 return rc;
00534 
00535         /* Find or create segment */
00536         if ( ! ( ( segment = peerdisc_find ( id_string ) ) ||
00537                  ( segment = peerdisc_create ( id_string ) ) ) )
00538                 return -ENOMEM;
00539 
00540         /* Add to list of clients */
00541         ref_get ( &segment->refcnt );
00542         peerdisc->segment = segment;
00543         list_add_tail ( &peerdisc->list, &segment->clients );
00544 
00545         return 0;
00546 }
00547 
00548 /**
00549  * Close PeerDist discovery client
00550  *
00551  * @v peerdisc          PeerDist discovery client
00552  */
00553 void peerdisc_close ( struct peerdisc_client *peerdisc ) {
00554         struct peerdisc_segment *segment = peerdisc->segment;
00555 
00556         /* Ignore if discovery is already closed */
00557         if ( ! segment )
00558                 return;
00559 
00560         /* If no peers were discovered, reduce the recommended
00561          * discovery timeout to minimise delays on future requests.
00562          */
00563         if ( list_empty ( &segment->peers ) && peerdisc_timeout_secs ) {
00564                 peerdisc_timeout_secs--;
00565                 DBGC ( segment, "PEERDISC %p reducing timeout to %d "
00566                        "seconds\n", peerdisc, peerdisc_timeout_secs );
00567         }
00568 
00569         /* Remove from list of clients */
00570         peerdisc->segment = NULL;
00571         list_del ( &peerdisc->list );
00572         ref_put ( &segment->refcnt );
00573 
00574         /* If this was the last clients, destroy the segment */
00575         if ( list_empty ( &segment->clients ) )
00576                 peerdisc_destroy ( segment );
00577 
00578         /* If there are no more segments, close the socket */
00579         if ( list_empty ( &peerdisc_segments ) )
00580                 peerdisc_socket_close ( 0 );
00581 }