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 15KB

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