aboutsummaryrefslogtreecommitdiff
path: root/src/service/peerstore/gnunet-service-peerstore.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/service/peerstore/gnunet-service-peerstore.c')
-rw-r--r--src/service/peerstore/gnunet-service-peerstore.c724
1 files changed, 724 insertions, 0 deletions
diff --git a/src/service/peerstore/gnunet-service-peerstore.c b/src/service/peerstore/gnunet-service-peerstore.c
new file mode 100644
index 000000000..514b173bb
--- /dev/null
+++ b/src/service/peerstore/gnunet-service-peerstore.c
@@ -0,0 +1,724 @@
1/*
2 This file is part of GNUnet.
3 Copyright (C) 2014, 2015, 2016 GNUnet e.V.
4
5 GNUnet is free software: you can redistribute it and/or modify it
6 under the terms of the GNU Affero General Public License as published
7 by the Free Software Foundation, either version 3 of the License,
8 or (at your option) any later version.
9
10 GNUnet is distributed in the hope that it will be useful, but
11 WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 Affero General Public License for more details.
14
15 You should have received a copy of the GNU Affero General Public License
16 along with this program. If not, see <http://www.gnu.org/licenses/>.
17
18 SPDX-License-Identifier: AGPL3.0-or-later
19 */
20
21/**
22 * @file peerstore/gnunet-service-peerstore.c
23 * @brief peerstore service implementation
24 * @author Omar Tarabai
25 */
26#include "platform.h"
27#include "gnunet_util_lib.h"
28#include "peerstore.h"
29#include "gnunet_peerstore_plugin.h"
30#include "peerstore_common.h"
31#include "gnunet_hello_uri_lib.h"
32
33
34/**
35 * Interval for expired records cleanup (in seconds)
36 */
37#define EXPIRED_RECORDS_CLEANUP_INTERVAL 300 /* 5mins */
38
39/**
40 * Our configuration.
41 */
42static const struct GNUNET_CONFIGURATION_Handle *cfg;
43
44/**
45 * Database plugin library name
46 */
47static char *db_lib_name;
48
49/**
50 * Database handle
51 */
52static struct GNUNET_PEERSTORE_PluginFunctions *db;
53
54/**
55 * Hashmap with all watch requests
56 */
57static struct GNUNET_CONTAINER_MultiHashMap *watchers;
58
59/**
60 * Task run to clean up expired records.
61 */
62static struct GNUNET_SCHEDULER_Task *expire_task;
63
64/**
65 * Are we in the process of shutting down the service? #GNUNET_YES / #GNUNET_NO
66 */
67static int in_shutdown;
68
69/**
70 * Number of connected clients.
71 */
72static unsigned int num_clients;
73
74
75/**
76 * Perform the actual shutdown operations
77 */
78static void
79do_shutdown ()
80{
81 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
82 "Shutting down peerstore, bye.\n");
83 if (NULL != db_lib_name)
84 {
85 GNUNET_break (NULL == GNUNET_PLUGIN_unload (db_lib_name, db));
86 GNUNET_free (db_lib_name);
87 db_lib_name = NULL;
88 }
89 if (NULL != watchers)
90 {
91 GNUNET_CONTAINER_multihashmap_destroy (watchers);
92 watchers = NULL;
93 }
94 if (NULL != expire_task)
95 {
96 GNUNET_SCHEDULER_cancel (expire_task);
97 expire_task = NULL;
98 }
99 GNUNET_SCHEDULER_shutdown ();
100}
101
102
103/**
104 * Task run during shutdown.
105 *
106 * @param cls unused
107 */
108static void
109shutdown_task (void *cls)
110{
111 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
112 "Priming PEERSTORE for shutdown.\n");
113 in_shutdown = GNUNET_YES;
114 if (0 == num_clients) /* Only when no connected clients. */
115 do_shutdown ();
116}
117
118
119/* Forward declaration */
120static void
121expire_records_continuation (void *cls, int success);
122
123
124/**
125 * Deletes any expired records from storage
126 */
127static void
128cleanup_expired_records (void *cls)
129{
130 int ret;
131
132 expire_task = NULL;
133 GNUNET_assert (NULL != db);
134 ret = db->expire_records (db->cls,
135 GNUNET_TIME_absolute_get (),
136 &expire_records_continuation,
137 NULL);
138 if (GNUNET_OK != ret)
139 {
140 GNUNET_assert (NULL == expire_task);
141 expire_task = GNUNET_SCHEDULER_add_delayed (
142 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
143 EXPIRED_RECORDS_CLEANUP_INTERVAL),
144 &cleanup_expired_records,
145 NULL);
146 }
147}
148
149
150/**
151 * Continuation to expire_records called by the peerstore plugin
152 *
153 * @param cls unused
154 * @param success count of records deleted or #GNUNET_SYSERR
155 */
156static void
157expire_records_continuation (void *cls, int success)
158{
159 if (success > 0)
160 GNUNET_log (GNUNET_ERROR_TYPE_INFO, "%d records expired.\n", success);
161 GNUNET_assert (NULL == expire_task);
162 expire_task = GNUNET_SCHEDULER_add_delayed (
163 GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS,
164 EXPIRED_RECORDS_CLEANUP_INTERVAL),
165 &cleanup_expired_records,
166 NULL);
167}
168
169
170/**
171 * A client disconnected. Remove all of its data structure entries.
172 *
173 * @param cls closure, NULL
174 * @param client identification of the client
175 * @param mq the message queue
176 * @return
177 */
178static void *
179client_connect_cb (void *cls,
180 struct GNUNET_SERVICE_Client *client,
181 struct GNUNET_MQ_Handle *mq)
182{
183 num_clients++;
184 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
185 "A client connected (now %u)\n", num_clients);
186 return client;
187}
188
189
190/**
191 * Search for a disconnected client and remove it
192 *
193 * @param cls closuer, a `struct GNUNET_SERVICE_Client`
194 * @param key hash of record key
195 * @param value the watcher client, a `struct GNUNET_SERVICE_Client *`
196 * @return #GNUNET_OK to continue iterating
197 */
198static int
199client_disconnect_it (void *cls, const struct GNUNET_HashCode *key, void *value)
200{
201 if (value == cls)
202 {
203 GNUNET_assert (GNUNET_YES ==
204 GNUNET_CONTAINER_multihashmap_remove (watchers, key, value));
205 num_clients++; /* Watchers do not count */
206 }
207 return GNUNET_OK;
208}
209
210
211/**
212 * A client disconnected. Remove all of its data structure entries.
213 *
214 * @param cls closure, NULL
215 * @param client identification of the client
216 */
217static void
218client_disconnect_cb (void *cls,
219 struct GNUNET_SERVICE_Client *client,
220 void *app_cls)
221{
222 num_clients--;
223 if (NULL != watchers)
224 GNUNET_CONTAINER_multihashmap_iterate (watchers,
225 &client_disconnect_it,
226 client);
227 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
228 "A client disconnected (%u remaining).\n",
229 num_clients);
230 if ((0 == num_clients) && in_shutdown)
231 do_shutdown ();
232}
233
234
235/**
236 * Function called by for each matching record.
237 *
238 * @param cls closure
239 * @param record peerstore record found
240 * @param emsg error message or NULL if no errors
241 * @return #GNUNET_YES to continue iteration
242 */
243static void
244record_iterator (void *cls,
245 const struct GNUNET_PEERSTORE_Record *record,
246 const char *emsg)
247{
248 struct GNUNET_PEERSTORE_Record *cls_record = cls;
249 struct GNUNET_MQ_Envelope *env;
250
251 if (NULL == record)
252 {
253 /* No more records */
254 struct GNUNET_MessageHeader *endmsg;
255
256 env = GNUNET_MQ_msg (endmsg, GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_END);
257 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (cls_record->client), env);
258 if (NULL == emsg)
259 {
260 GNUNET_SERVICE_client_continue (cls_record->client);
261 }
262 else
263 {
264 GNUNET_break (0);
265 GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failed to iterate: %s\n", emsg);
266 GNUNET_SERVICE_client_drop (cls_record->client);
267 }
268 PEERSTORE_destroy_record (cls_record);
269 return;
270 }
271
272 env = PEERSTORE_create_record_mq_envelope (
273 record->sub_system,
274 &record->peer,
275 record->key,
276 record->value,
277 record->value_size,
278 record->expiry,
279 0,
280 GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE_RECORD);
281 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (cls_record->client), env);
282}
283
284
285/**
286 * Iterator over all watcher clients
287 * to notify them of a new record
288 *
289 * @param cls closure, a `struct GNUNET_PEERSTORE_Record *`
290 * @param key hash of record key
291 * @param value the watcher client, a `struct GNUNET_SERVICE_Client *`
292 * @return #GNUNET_YES to continue iterating
293 */
294static int
295watch_notifier_it (void *cls, const struct GNUNET_HashCode *key, void *value)
296{
297 struct GNUNET_PEERSTORE_Record *record = cls;
298 struct GNUNET_SERVICE_Client *client = value;
299 struct GNUNET_MQ_Envelope *env;
300
301 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Found a watcher to update.\n");
302 env = PEERSTORE_create_record_mq_envelope (
303 record->sub_system,
304 &record->peer,
305 record->key,
306 record->value,
307 record->value_size,
308 record->expiry,
309 0,
310 GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_RECORD);
311 GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env);
312 return GNUNET_YES;
313}
314
315
316/**
317 * Given a new record, notifies watchers
318 *
319 * @param record changed record to update watchers with
320 */
321static void
322watch_notifier (struct GNUNET_PEERSTORE_Record *record)
323{
324 struct GNUNET_HashCode keyhash;
325
326 PEERSTORE_hash_key (record->sub_system, &record->peer, record->key, &keyhash);
327 GNUNET_CONTAINER_multihashmap_get_multiple (watchers,
328 &keyhash,
329 &watch_notifier_it,
330 record);
331}
332
333
334/**
335 * Handle a watch cancel request from client
336 *
337 * @param cls identification of the client
338 * @param hm the actual message
339 */
340static void
341handle_watch_cancel (void *cls, const struct StoreKeyHashMessage *hm)
342{
343 struct GNUNET_SERVICE_Client *client = cls;
344
345 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received a watch cancel request.\n");
346 if (GNUNET_OK !=
347 GNUNET_CONTAINER_multihashmap_remove (watchers, &hm->keyhash, client))
348 {
349 GNUNET_break (0);
350 GNUNET_SERVICE_client_drop (client);
351 return;
352 }
353 num_clients++;
354 GNUNET_SERVICE_client_continue (client);
355}
356
357
358/**
359 * Handle a watch request from client
360 *
361 * @param cls identification of the client
362 * @param hm the actual message
363 */
364static void
365handle_watch (void *cls, const struct StoreKeyHashMessage *hm)
366{
367 struct GNUNET_SERVICE_Client *client = cls;
368
369 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Received a watch request.\n");
370 num_clients--; /* do not count watchers */
371 GNUNET_SERVICE_client_mark_monitor (client);
372 GNUNET_CONTAINER_multihashmap_put (watchers,
373 &hm->keyhash,
374 client,
375 GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE);
376 GNUNET_SERVICE_client_continue (client);
377}
378
379
380/**
381 * Check an iterate request from client
382 *
383 * @param cls client identification of the client
384 * @param srm the actual message
385 * @return #GNUNET_OK if @a srm is well-formed
386 */
387static int
388check_iterate (void *cls, const struct StoreRecordMessage *srm)
389{
390 struct GNUNET_PEERSTORE_Record *record;
391
392 record = PEERSTORE_parse_record_message (srm);
393 if (NULL == record)
394 {
395 GNUNET_break (0);
396 return GNUNET_SYSERR;
397 }
398 if (NULL == record->sub_system)
399 {
400 GNUNET_break (0);
401 PEERSTORE_destroy_record (record);
402 return GNUNET_SYSERR;
403 }
404 PEERSTORE_destroy_record (record);
405 return GNUNET_OK;
406}
407
408
409/**
410 * Handle an iterate request from client
411 *
412 * @param cls identification of the client
413 * @param srm the actual message
414 */
415static void
416handle_iterate (void *cls, const struct StoreRecordMessage *srm)
417{
418 struct GNUNET_SERVICE_Client *client = cls;
419 struct GNUNET_PEERSTORE_Record *record;
420
421 record = PEERSTORE_parse_record_message (srm);
422 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
423 "Iterate request: ss `%s', peer `%s', key `%s'\n",
424 record->sub_system,
425 GNUNET_i2s (&record->peer),
426 (NULL == record->key) ? "NULL" : record->key);
427 record->client = client;
428 if (GNUNET_OK !=
429 db->iterate_records (db->cls,
430 record->sub_system,
431 (ntohs (srm->peer_set)) ? &record->peer : NULL,
432 record->key,
433 &record_iterator,
434 record))
435 {
436 GNUNET_break (0);
437 GNUNET_SERVICE_client_drop (client);
438 PEERSTORE_destroy_record (record);
439 }
440}
441
442
443/**
444 * Continuation of store_record called by the peerstore plugin
445 *
446 * @param cls closure
447 * @param success result
448 */
449static void
450store_record_continuation (void *cls, int success)
451{
452 struct GNUNET_PEERSTORE_Record *record = cls;
453
454 if (GNUNET_OK == success)
455 {
456 watch_notifier (record);
457 GNUNET_SERVICE_client_continue (record->client);
458 }
459 else
460 {
461 GNUNET_break (0);
462 GNUNET_SERVICE_client_drop (record->client);
463 }
464 PEERSTORE_destroy_record (record);
465}
466
467
468/**
469 * Check a store request from client
470 *
471 * @param cls client identification of the client
472 * @param srm the actual message
473 * @return #GNUNET_OK if @a srm is well-formed
474 */
475static int
476check_store (void *cls, const struct StoreRecordMessage *srm)
477{
478 struct GNUNET_PEERSTORE_Record *record;
479
480 record = PEERSTORE_parse_record_message (srm);
481 if (NULL == record)
482 {
483 GNUNET_break (0);
484 return GNUNET_SYSERR;
485 }
486 if ((NULL == record->sub_system) || (NULL == record->key))
487 {
488 GNUNET_break (0);
489 PEERSTORE_destroy_record (record);
490 return GNUNET_SYSERR;
491 }
492 PEERSTORE_destroy_record (record);
493 return GNUNET_OK;
494}
495
496
497/**
498 * Handle a store request from client
499 *
500 * @param cls client identification of the client
501 * @param srm the actual message
502 */
503static void
504handle_store (void *cls, const struct StoreRecordMessage *srm)
505{
506 struct GNUNET_SERVICE_Client *client = cls;
507 struct GNUNET_PEERSTORE_Record *record;
508
509 record = PEERSTORE_parse_record_message (srm);
510 GNUNET_log (
511 GNUNET_ERROR_TYPE_INFO,
512 "Received a store request. Sub system `%s' Peer `%s Key `%s' Options: %u.\n",
513 record->sub_system,
514 GNUNET_i2s (&record->peer),
515 record->key,
516 (uint32_t) ntohl (srm->options));
517 record->client = client;
518 if (GNUNET_OK != db->store_record (db->cls,
519 record->sub_system,
520 &record->peer,
521 record->key,
522 record->value,
523 record->value_size,
524 record->expiry,
525 ntohl (srm->options),
526 &store_record_continuation,
527 record))
528 {
529 GNUNET_break (0);
530 PEERSTORE_destroy_record (record);
531 GNUNET_SERVICE_client_drop (client);
532 return;
533 }
534}
535
536static void
537store_hello_continuation (void *cls, int success)
538{
539 (void) cls;
540
541 if (GNUNET_OK != success)
542 {
543 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
544 "Error storing bootstrap hello!\n");
545 GNUNET_break (0);
546 }
547}
548
549
550static int
551hosts_directory_scan_callback (void *cls, const char *fullname)
552{
553 (void) cls;
554 ssize_t size_total;
555 char buffer[GNUNET_MAX_MESSAGE_SIZE - 1] GNUNET_ALIGN;
556 const struct GNUNET_MessageHeader *hello;
557 struct GNUNET_HELLO_Builder *builder;
558 struct GNUNET_PeerIdentity *pid;
559
560 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
561 "1 hosts_directory_scan_callback filename %s\n",
562 fullname);
563
564 if (GNUNET_YES != GNUNET_DISK_file_test (fullname))
565 return GNUNET_OK; /* ignore non-files */
566
567 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
568 "2 hosts_directory_scan_callback filename %s\n",
569 fullname);
570
571 /* filename = strrchr (fullname, DIR_SEPARATOR); */
572 /* if ((NULL == filename) || (1 > strlen (filename))) */
573 /* filename = fullname; */
574 /* else */
575 /* filename++; */
576
577 /* GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, */
578 /* "3 hosts_directory_scan_callback filename %s\n", */
579 /* filename); */
580
581 /* if (GNUNET_YES != GNUNET_DISK_file_test (filename)) */
582 /* return GNUNET_OK; */
583 size_total = GNUNET_DISK_fn_read (fullname, buffer, sizeof(buffer));
584 GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
585 "Read %d bytes from `%s'\n",
586 (int) size_total,
587 fullname);
588 if ((size_total < 0) ||
589 (((size_t) size_total) < sizeof(struct GNUNET_MessageHeader)))
590 {
591 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
592 _ ("Failed to parse HELLO in file `%s': %s\n"),
593 fullname,
594 "File has invalid size");
595 return GNUNET_OK;
596 }
597 hello = (const struct GNUNET_MessageHeader *) &buffer[0];
598 builder = GNUNET_HELLO_builder_from_msg (hello);
599 pid = GNUNET_HELLO_builder_get_id (builder);
600
601 if (GNUNET_OK != db->store_record (db->cls,
602 "peerstore",
603 pid,
604 "",
605 hello,
606 size_total,
607 GNUNET_TIME_UNIT_FOREVER_ABS,
608 GNUNET_PEERSTORE_STOREOPTION_REPLACE,
609 &store_hello_continuation,
610 NULL))
611 {
612 GNUNET_break (0);
613 }
614 GNUNET_HELLO_builder_free (builder);
615 return GNUNET_OK;
616}
617
618
619/**
620 * Peerstore service runner.
621 *
622 * @param cls closure
623 * @param c configuration to use
624 * @param service the initialized service
625 */
626static void
627run (void *cls,
628 const struct GNUNET_CONFIGURATION_Handle *c,
629 struct GNUNET_SERVICE_Handle *service)
630{
631 char *database;
632 int use_included;
633 char *ip;
634 char *peerdir;
635
636 in_shutdown = GNUNET_NO;
637 num_clients = 0;
638 cfg = c;
639
640 if (GNUNET_OK != GNUNET_CONFIGURATION_get_value_string (cfg,
641 "peerstore",
642 "DATABASE",
643 &database))
644 {
645 GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR,
646 "peerstore",
647 "DATABASE");
648 GNUNET_SCHEDULER_shutdown ();
649 return;
650 }
651 GNUNET_asprintf (&db_lib_name, "libgnunet_plugin_peerstore_%s", database);
652 db = GNUNET_PLUGIN_load (db_lib_name, (void *) cfg);
653 GNUNET_free (database);
654 if (NULL == db)
655 {
656 GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
657 _ ("Could not load database backend `%s'\n"),
658 db_lib_name);
659 GNUNET_SCHEDULER_shutdown ();
660 return;
661 }
662 watchers = GNUNET_CONTAINER_multihashmap_create (10, GNUNET_NO);
663 expire_task = GNUNET_SCHEDULER_add_now (&cleanup_expired_records, NULL);
664
665 use_included = GNUNET_CONFIGURATION_get_value_yesno (cfg,
666 "peerstore",
667 "USE_INCLUDED_HELLOS");
668 if (GNUNET_SYSERR == use_included)
669 use_included = GNUNET_NO;
670 if (GNUNET_YES == use_included)
671 {
672 ip = GNUNET_OS_installation_get_path (GNUNET_OS_IPK_DATADIR);
673 GNUNET_asprintf (&peerdir, "%shellos", ip);
674 GNUNET_free (ip);
675
676 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
677 _ ("Importing HELLOs from `%s'\n"),
678 peerdir);
679
680 GNUNET_DISK_directory_scan (peerdir,
681 &hosts_directory_scan_callback,
682 NULL);
683 GNUNET_free (peerdir);
684 }
685 else
686 {
687 GNUNET_log (GNUNET_ERROR_TYPE_INFO,
688 _ ("Skipping import of included HELLOs\n"));
689 }
690
691 GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL);
692}
693
694
695/**
696 * Define "main" method using service macro.
697 */
698GNUNET_SERVICE_MAIN (
699 "peerstore",
700 GNUNET_SERVICE_OPTION_SOFT_SHUTDOWN,
701 &run,
702 &client_connect_cb,
703 &client_disconnect_cb,
704 NULL,
705 GNUNET_MQ_hd_var_size (store,
706 GNUNET_MESSAGE_TYPE_PEERSTORE_STORE,
707 struct StoreRecordMessage,
708 NULL),
709 GNUNET_MQ_hd_var_size (iterate,
710 GNUNET_MESSAGE_TYPE_PEERSTORE_ITERATE,
711 struct StoreRecordMessage,
712 NULL),
713 GNUNET_MQ_hd_fixed_size (watch,
714 GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH,
715 struct StoreKeyHashMessage,
716 NULL),
717 GNUNET_MQ_hd_fixed_size (watch_cancel,
718 GNUNET_MESSAGE_TYPE_PEERSTORE_WATCH_CANCEL,
719 struct StoreKeyHashMessage,
720 NULL),
721 GNUNET_MQ_handler_end ());
722
723
724/* end of gnunet-service-peerstore.c */