You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

peerdisc.c 17KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642
  1. /*
  2. * Copyright (C) 2015 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. FILE_LICENCE ( GPL2_OR_LATER_OR_UBDL );
  24. #include <stdlib.h>
  25. #include <string.h>
  26. #include <ctype.h>
  27. #include <errno.h>
  28. #include <assert.h>
  29. #include <ipxe/xfer.h>
  30. #include <ipxe/iobuf.h>
  31. #include <ipxe/open.h>
  32. #include <ipxe/tcpip.h>
  33. #include <ipxe/uuid.h>
  34. #include <ipxe/base16.h>
  35. #include <ipxe/netdevice.h>
  36. #include <ipxe/timer.h>
  37. #include <ipxe/fault.h>
  38. #include <ipxe/settings.h>
  39. #include <ipxe/pccrd.h>
  40. #include <ipxe/peerdisc.h>
  41. /** @file
  42. *
  43. * Peer Content Caching and Retrieval (PeerDist) protocol peer discovery
  44. *
  45. */
  46. /** List of discovery segments */
  47. static LIST_HEAD ( peerdisc_segments );
  48. /** Number of repeated discovery attempts */
  49. #define PEERDISC_REPEAT_COUNT 2
  50. /** Time between repeated discovery attempts */
  51. #define PEERDISC_REPEAT_TIMEOUT ( 1 * TICKS_PER_SEC )
  52. /** Default discovery timeout (in seconds) */
  53. #define PEERDISC_DEFAULT_TIMEOUT_SECS 2
  54. /** Recommended discovery timeout (in seconds)
  55. *
  56. * We reduce the recommended discovery timeout whenever a segment
  57. * fails to discover any peers, and restore the default value whenever
  58. * a valid discovery reply is received. We continue to send discovery
  59. * requests even if the recommended timeout is reduced to zero.
  60. *
  61. * This strategy is intended to minimise discovery delays when no
  62. * peers are available on the network, while allowing downloads to
  63. * quickly switch back to using PeerDist acceleration if new peers
  64. * become available.
  65. */
  66. unsigned int peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS;
  67. /** Hosted cache server */
  68. static char *peerhost;
  69. static struct peerdisc_segment * peerdisc_find ( const char *id );
  70. static int peerdisc_discovered ( struct peerdisc_segment *segment,
  71. const char *location );
  72. /******************************************************************************
  73. *
  74. * Statistics reporting
  75. *
  76. ******************************************************************************
  77. */
  78. /**
  79. * Report peer discovery statistics
  80. *
  81. * @v intf Interface
  82. * @v peer Selected peer (or NULL)
  83. * @v peers List of available peers
  84. */
  85. void peerdisc_stat ( struct interface *intf, struct peerdisc_peer *peer,
  86. struct list_head *peers ) {
  87. struct interface *dest;
  88. peerdisc_stat_TYPE ( void * ) *op =
  89. intf_get_dest_op ( intf, peerdisc_stat, &dest );
  90. void *object = intf_object ( dest );
  91. if ( op ) {
  92. op ( object, peer, peers );
  93. } else {
  94. /* Default is to do nothing */
  95. }
  96. intf_put ( dest );
  97. }
  98. /******************************************************************************
  99. *
  100. * Discovery sockets
  101. *
  102. ******************************************************************************
  103. */
  104. /**
  105. * Open all PeerDist discovery sockets
  106. *
  107. * @ret rc Return status code
  108. */
  109. static int peerdisc_socket_open ( void ) {
  110. struct peerdisc_socket *socket;
  111. int rc;
  112. /* Open each socket */
  113. for_each_table_entry ( socket, PEERDISC_SOCKETS ) {
  114. if ( ( rc = xfer_open_socket ( &socket->xfer, SOCK_DGRAM,
  115. &socket->address.sa,
  116. NULL ) ) != 0 ) {
  117. DBGC ( socket, "PEERDISC %s could not open socket: "
  118. "%s\n", socket->name, strerror ( rc ) );
  119. goto err;
  120. }
  121. }
  122. return 0;
  123. err:
  124. for_each_table_entry_continue_reverse ( socket, PEERDISC_SOCKETS )
  125. intf_restart ( &socket->xfer, rc );
  126. return rc;
  127. }
  128. /**
  129. * Attempt to transmit PeerDist discovery requests on all sockets
  130. *
  131. * @v uuid Message UUID string
  132. * @v id Segment identifier string
  133. */
  134. static void peerdisc_socket_tx ( const char *uuid, const char *id ) {
  135. struct peerdisc_socket *socket;
  136. struct net_device *netdev;
  137. struct xfer_metadata meta;
  138. union {
  139. struct sockaddr sa;
  140. struct sockaddr_tcpip st;
  141. } address;
  142. char *request;
  143. size_t len;
  144. int rc;
  145. /* Construct discovery request */
  146. request = peerdist_discovery_request ( uuid, id );
  147. if ( ! request )
  148. goto err_request;
  149. len = strlen ( request );
  150. /* Initialise data transfer metadata */
  151. memset ( &meta, 0, sizeof ( meta ) );
  152. meta.dest = &address.sa;
  153. /* Send message on each socket */
  154. for_each_table_entry ( socket, PEERDISC_SOCKETS ) {
  155. /* Initialise socket address */
  156. memcpy ( &address.sa, &socket->address.sa,
  157. sizeof ( address.sa ) );
  158. /* Send message on each open network device */
  159. for_each_netdev ( netdev ) {
  160. /* Skip unopened network devices */
  161. if ( ! netdev_is_open ( netdev ) )
  162. continue;
  163. address.st.st_scope_id = netdev->index;
  164. /* Discard request (for test purposes) if applicable */
  165. if ( inject_fault ( PEERDISC_DISCARD_RATE ) )
  166. continue;
  167. /* Transmit request */
  168. if ( ( rc = xfer_deliver_raw_meta ( &socket->xfer,
  169. request, len,
  170. &meta ) ) != 0 ) {
  171. DBGC ( socket, "PEERDISC %s could not transmit "
  172. "via %s: %s\n", socket->name,
  173. netdev->name, strerror ( rc ) );
  174. /* Contine to try other net devices/sockets */
  175. continue;
  176. }
  177. }
  178. }
  179. free ( request );
  180. err_request:
  181. return;
  182. }
  183. /**
  184. * Handle received PeerDist discovery reply
  185. *
  186. * @v socket PeerDist discovery socket
  187. * @v iobuf I/O buffer
  188. * @v meta Data transfer metadata
  189. * @ret rc Return status code
  190. */
  191. static int peerdisc_socket_rx ( struct peerdisc_socket *socket,
  192. struct io_buffer *iobuf,
  193. struct xfer_metadata *meta __unused ) {
  194. struct peerdist_discovery_reply reply;
  195. struct peerdisc_segment *segment;
  196. char *id;
  197. char *location;
  198. int rc;
  199. /* Discard reply (for test purposes) if applicable */
  200. if ( ( rc = inject_fault ( PEERDISC_DISCARD_RATE ) ) != 0 )
  201. goto err;
  202. /* Parse reply */
  203. if ( ( rc = peerdist_discovery_reply ( iobuf->data, iob_len ( iobuf ),
  204. &reply ) ) != 0 ) {
  205. DBGC ( socket, "PEERDISC %s could not parse reply: %s\n",
  206. socket->name, strerror ( rc ) );
  207. DBGC_HDA ( socket, 0, iobuf->data, iob_len ( iobuf ) );
  208. goto err;
  209. }
  210. /* Any kind of discovery reply indicates that there are active
  211. * peers on a local network, so restore the recommended
  212. * discovery timeout to its default value for future requests.
  213. */
  214. if ( peerdisc_timeout_secs != PEERDISC_DEFAULT_TIMEOUT_SECS ) {
  215. DBGC ( socket, "PEERDISC %s restoring timeout to %d seconds\n",
  216. socket->name, PEERDISC_DEFAULT_TIMEOUT_SECS );
  217. }
  218. peerdisc_timeout_secs = PEERDISC_DEFAULT_TIMEOUT_SECS;
  219. /* Iterate over segment IDs */
  220. for ( id = reply.ids ; *id ; id += ( strlen ( id ) + 1 /* NUL */ ) ) {
  221. /* Find corresponding segment */
  222. segment = peerdisc_find ( id );
  223. if ( ! segment ) {
  224. DBGC ( socket, "PEERDISC %s ignoring reply for %s\n",
  225. socket->name, id );
  226. continue;
  227. }
  228. /* Report all discovered peer locations */
  229. for ( location = reply.locations ; *location ;
  230. location += ( strlen ( location ) + 1 /* NUL */ ) ) {
  231. /* Report discovered peer location */
  232. if ( ( rc = peerdisc_discovered ( segment,
  233. location ) ) != 0 )
  234. goto err;
  235. }
  236. }
  237. err:
  238. free_iob ( iobuf );
  239. return rc;
  240. }
  241. /**
  242. * Close all PeerDist discovery sockets
  243. *
  244. * @v rc Reason for close
  245. */
  246. static void peerdisc_socket_close ( int rc ) {
  247. struct peerdisc_socket *socket;
  248. /* Close all sockets */
  249. for_each_table_entry ( socket, PEERDISC_SOCKETS )
  250. intf_restart ( &socket->xfer, rc );
  251. }
  252. /** PeerDist discovery socket interface operations */
  253. static struct interface_operation peerdisc_socket_operations[] = {
  254. INTF_OP ( xfer_deliver, struct peerdisc_socket *, peerdisc_socket_rx ),
  255. };
  256. /** PeerDist discovery socket interface descriptor */
  257. static struct interface_descriptor peerdisc_socket_desc =
  258. INTF_DESC ( struct peerdisc_socket, xfer, peerdisc_socket_operations );
  259. /** PeerDist discovery IPv4 socket */
  260. struct peerdisc_socket peerdisc_socket_ipv4 __peerdisc_socket = {
  261. .name = "IPv4",
  262. .address = {
  263. .sin = {
  264. .sin_family = AF_INET,
  265. .sin_port = htons ( PEERDIST_DISCOVERY_PORT ),
  266. .sin_addr.s_addr = htonl ( PEERDIST_DISCOVERY_IPV4 ),
  267. },
  268. },
  269. .xfer = INTF_INIT ( peerdisc_socket_desc ),
  270. };
  271. /** PeerDist discovery IPv6 socket */
  272. struct peerdisc_socket peerdisc_socket_ipv6 __peerdisc_socket = {
  273. .name = "IPv6",
  274. .address = {
  275. .sin6 = {
  276. .sin6_family = AF_INET6,
  277. .sin6_port = htons ( PEERDIST_DISCOVERY_PORT ),
  278. .sin6_addr.s6_addr = PEERDIST_DISCOVERY_IPV6,
  279. },
  280. },
  281. .xfer = INTF_INIT ( peerdisc_socket_desc ),
  282. };
  283. /******************************************************************************
  284. *
  285. * Discovery segments
  286. *
  287. ******************************************************************************
  288. */
  289. /**
  290. * Free PeerDist discovery segment
  291. *
  292. * @v refcnt Reference count
  293. */
  294. static void peerdisc_free ( struct refcnt *refcnt ) {
  295. struct peerdisc_segment *segment =
  296. container_of ( refcnt, struct peerdisc_segment, refcnt );
  297. struct peerdisc_peer *peer;
  298. struct peerdisc_peer *tmp;
  299. /* Free all discovered peers */
  300. list_for_each_entry_safe ( peer, tmp, &segment->peers, list ) {
  301. list_del ( &peer->list );
  302. free ( peer );
  303. }
  304. /* Free segment */
  305. free ( segment );
  306. }
  307. /**
  308. * Find PeerDist discovery segment
  309. *
  310. * @v id Segment ID
  311. * @ret segment PeerDist discovery segment, or NULL if not found
  312. */
  313. static struct peerdisc_segment * peerdisc_find ( const char *id ) {
  314. struct peerdisc_segment *segment;
  315. /* Look for a matching segment */
  316. list_for_each_entry ( segment, &peerdisc_segments, list ) {
  317. if ( strcmp ( id, segment->id ) == 0 )
  318. return segment;
  319. }
  320. return NULL;
  321. }
  322. /**
  323. * Add discovered PeerDist peer
  324. *
  325. * @v segment PeerDist discovery segment
  326. * @v location Peer location
  327. * @ret rc Return status code
  328. */
  329. static int peerdisc_discovered ( struct peerdisc_segment *segment,
  330. const char *location ) {
  331. struct peerdisc_peer *peer;
  332. struct peerdisc_client *peerdisc;
  333. struct peerdisc_client *tmp;
  334. /* Ignore duplicate peers */
  335. list_for_each_entry ( peer, &segment->peers, list ) {
  336. if ( strcmp ( peer->location, location ) == 0 ) {
  337. DBGC2 ( segment, "PEERDISC %p duplicate %s\n",
  338. segment, location );
  339. return 0;
  340. }
  341. }
  342. DBGC2 ( segment, "PEERDISC %p discovered %s\n", segment, location );
  343. /* Allocate and initialise structure */
  344. peer = zalloc ( sizeof ( *peer ) + strlen ( location ) + 1 /* NUL */ );
  345. if ( ! peer )
  346. return -ENOMEM;
  347. strcpy ( peer->location, location );
  348. /* Add to end of list of peers */
  349. list_add_tail ( &peer->list, &segment->peers );
  350. /* Notify all clients */
  351. list_for_each_entry_safe ( peerdisc, tmp, &segment->clients, list )
  352. peerdisc->op->discovered ( peerdisc );
  353. return 0;
  354. }
  355. /**
  356. * Handle discovery timer expiry
  357. *
  358. * @v timer Discovery timer
  359. * @v over Failure indicator
  360. */
  361. static void peerdisc_expired ( struct retry_timer *timer, int over __unused ) {
  362. struct peerdisc_segment *segment =
  363. container_of ( timer, struct peerdisc_segment, timer );
  364. /* Attempt to transmit discovery requests */
  365. peerdisc_socket_tx ( segment->uuid, segment->id );
  366. /* Schedule next transmission, if applicable */
  367. if ( timer->count < PEERDISC_REPEAT_COUNT )
  368. start_timer_fixed ( &segment->timer, PEERDISC_REPEAT_TIMEOUT );
  369. }
  370. /**
  371. * Create PeerDist discovery segment
  372. *
  373. * @v id Segment ID
  374. * @ret segment PeerDist discovery segment, or NULL on error
  375. */
  376. static struct peerdisc_segment * peerdisc_create ( const char *id ) {
  377. struct peerdisc_segment *segment;
  378. union {
  379. union uuid uuid;
  380. uint32_t dword[ sizeof ( union uuid ) / sizeof ( uint32_t ) ];
  381. } random_uuid;
  382. size_t uuid_len;
  383. size_t id_len;
  384. const char *uuid;
  385. char *uuid_copy;
  386. char *id_copy;
  387. unsigned int i;
  388. int rc;
  389. /* Generate a random message UUID. This does not require high
  390. * quality randomness.
  391. */
  392. for ( i = 0 ; i < ( sizeof ( random_uuid.dword ) /
  393. sizeof ( random_uuid.dword[0] ) ) ; i++ )
  394. random_uuid.dword[i] = random();
  395. uuid = uuid_ntoa ( &random_uuid.uuid );
  396. /* Calculate string lengths */
  397. id_len = ( strlen ( id ) + 1 /* NUL */ );
  398. uuid_len = ( strlen ( uuid ) + 1 /* NUL */ );
  399. /* Allocate and initialise structure */
  400. segment = zalloc ( sizeof ( *segment ) + id_len + uuid_len );
  401. if ( ! segment )
  402. goto err_alloc;
  403. id_copy = ( ( ( void * ) segment ) + sizeof ( *segment ) );
  404. memcpy ( id_copy, id, id_len );
  405. uuid_copy = ( ( ( void * ) id_copy ) + id_len );
  406. memcpy ( uuid_copy, uuid, uuid_len );
  407. ref_init ( &segment->refcnt, peerdisc_free );
  408. segment->id = id_copy;
  409. segment->uuid = uuid_copy;
  410. INIT_LIST_HEAD ( &segment->peers );
  411. INIT_LIST_HEAD ( &segment->clients );
  412. timer_init ( &segment->timer, peerdisc_expired, &segment->refcnt );
  413. /* Add hosted cache server or initiate discovery */
  414. if ( peerhost ) {
  415. /* Add hosted cache server to list of peers */
  416. if ( ( rc = peerdisc_discovered ( segment, peerhost ) ) != 0 )
  417. goto err_peerhost;
  418. } else {
  419. /* Start discovery timer */
  420. start_timer_nodelay ( &segment->timer );
  421. DBGC2 ( segment, "PEERDISC %p discovering %s\n",
  422. segment, segment->id );
  423. }
  424. /* Add to list of segments, transfer reference to list, and return */
  425. list_add_tail ( &segment->list, &peerdisc_segments );
  426. return segment;
  427. err_peerhost:
  428. ref_put ( &segment->refcnt );
  429. err_alloc:
  430. return NULL;
  431. }
  432. /**
  433. * Destroy PeerDist discovery segment
  434. *
  435. * @v segment PeerDist discovery segment
  436. */
  437. static void peerdisc_destroy ( struct peerdisc_segment *segment ) {
  438. /* Sanity check */
  439. assert ( list_empty ( &segment->clients ) );
  440. /* Stop timer */
  441. stop_timer ( &segment->timer );
  442. /* Remove from list of segments and drop list's reference */
  443. list_del ( &segment->list );
  444. ref_put ( &segment->refcnt );
  445. }
  446. /******************************************************************************
  447. *
  448. * Discovery clients
  449. *
  450. ******************************************************************************
  451. */
  452. /**
  453. * Open PeerDist discovery client
  454. *
  455. * @v peerdisc PeerDist discovery client
  456. * @v id Segment ID
  457. * @v len Length of segment ID
  458. * @ret rc Return status code
  459. */
  460. int peerdisc_open ( struct peerdisc_client *peerdisc, const void *id,
  461. size_t len ) {
  462. struct peerdisc_segment *segment;
  463. char id_string[ base16_encoded_len ( len ) + 1 /* NUL */ ];
  464. char *id_chr;
  465. int rc;
  466. /* Construct ID string */
  467. base16_encode ( id, len, id_string, sizeof ( id_string ) );
  468. for ( id_chr = id_string ; *id_chr ; id_chr++ )
  469. *id_chr = toupper ( *id_chr );
  470. /* Sanity check */
  471. assert ( peerdisc->segment == NULL );
  472. /* Open socket if this is the first segment */
  473. if ( list_empty ( &peerdisc_segments ) &&
  474. ( ( rc = peerdisc_socket_open() ) != 0 ) )
  475. return rc;
  476. /* Find or create segment */
  477. if ( ! ( ( segment = peerdisc_find ( id_string ) ) ||
  478. ( segment = peerdisc_create ( id_string ) ) ) )
  479. return -ENOMEM;
  480. /* Add to list of clients */
  481. ref_get ( &segment->refcnt );
  482. peerdisc->segment = segment;
  483. list_add_tail ( &peerdisc->list, &segment->clients );
  484. return 0;
  485. }
  486. /**
  487. * Close PeerDist discovery client
  488. *
  489. * @v peerdisc PeerDist discovery client
  490. */
  491. void peerdisc_close ( struct peerdisc_client *peerdisc ) {
  492. struct peerdisc_segment *segment = peerdisc->segment;
  493. /* Ignore if discovery is already closed */
  494. if ( ! segment )
  495. return;
  496. /* If no peers were discovered, reduce the recommended
  497. * discovery timeout to minimise delays on future requests.
  498. */
  499. if ( list_empty ( &segment->peers ) && peerdisc_timeout_secs ) {
  500. peerdisc_timeout_secs--;
  501. DBGC ( segment, "PEERDISC %p reducing timeout to %d "
  502. "seconds\n", peerdisc, peerdisc_timeout_secs );
  503. }
  504. /* Remove from list of clients */
  505. peerdisc->segment = NULL;
  506. list_del ( &peerdisc->list );
  507. ref_put ( &segment->refcnt );
  508. /* If this was the last clients, destroy the segment */
  509. if ( list_empty ( &segment->clients ) )
  510. peerdisc_destroy ( segment );
  511. /* If there are no more segments, close the socket */
  512. if ( list_empty ( &peerdisc_segments ) )
  513. peerdisc_socket_close ( 0 );
  514. }
  515. /******************************************************************************
  516. *
  517. * Settings
  518. *
  519. ******************************************************************************
  520. */
  521. /** PeerDist hosted cache server setting */
  522. const struct setting peerhost_setting __setting ( SETTING_MISC, peerhost ) = {
  523. .name = "peerhost",
  524. .description = "PeerDist hosted cache",
  525. .type = &setting_type_string,
  526. };
  527. /**
  528. * Apply PeerDist discovery settings
  529. *
  530. * @ret rc Return status code
  531. */
  532. static int apply_peerdisc_settings ( void ) {
  533. /* Free any existing hosted cache server */
  534. free ( peerhost );
  535. peerhost = NULL;
  536. /* Fetch hosted cache server */
  537. fetch_string_setting_copy ( NULL, &peerhost_setting, &peerhost );
  538. if ( peerhost ) {
  539. DBGC ( &peerhost, "PEERDISC using hosted cache %s\n",
  540. peerhost );
  541. }
  542. return 0;
  543. }
  544. /** PeerDist discovery settings applicator */
  545. struct settings_applicator peerdisc_applicator __settings_applicator = {
  546. .apply = apply_peerdisc_settings,
  547. };