選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

peerdisc.c 16KB

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