Merge branch 'master' of gnunet.org:gnunet
[oweals/gnunet.git] / src / ats / gnunet-service-ats-new.c
1 /*
2      This file is part of GNUnet.
3      Copyright (C) 2011, 2018 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  * @file ats/gnunet-service-ats-new.c
22  * @brief ats service
23  * @author Matthias Wachs
24  * @author Christian Grothoff
25  */
26 #include "platform.h"
27 #include "gnunet_util_lib.h"
28 #include "gnunet_statistics_service.h"
29 #include "gnunet_ats_plugin_new.h"
30 #include "ats2.h"
31
32
33 /**
34  * What type of client is this client?
35  */
36 enum ClientType {
37   /**
38    * We don't know yet.
39    */
40   CT_NONE = 0,
41
42   /**
43    * Transport service.
44    */
45   CT_TRANSPORT,
46
47   /**
48    * Application.
49    */
50   CT_APPLICATION
51 };
52
53
54 /**
55  * Information we track per client.
56  */
57 struct Client;
58
59 /**
60  * Preferences expressed by a client are kept in a DLL per client.
61  */
62 struct ClientPreference
63 {
64   /**
65    * DLL pointer.
66    */
67   struct ClientPreference *next;
68
69   /**
70    * DLL pointer.
71    */
72   struct ClientPreference *prev;
73
74   /**
75    * Which client expressed the preference?
76    */
77   struct Client *client;
78
79   /**
80    * Plugin's representation of the preference.
81    */
82   struct GNUNET_ATS_PreferenceHandle *ph;
83
84   /**
85    * Details about the preference.
86    */
87   struct GNUNET_ATS_Preference pref;
88 };
89
90
91 /**
92  * Information about ongoing sessions of the transport client.
93  */
94 struct GNUNET_ATS_Session
95 {
96
97   /**
98    * Session data exposed to the plugin.
99    */
100   struct GNUNET_ATS_SessionData data;
101
102   /**
103    * The transport client that provided the session.
104    */
105   struct Client *client;
106
107   /**
108    * Session state in the plugin.
109    */
110   struct GNUNET_ATS_SessionHandle *sh;
111
112   /**
113    * Unique ID for the session when talking with the client.
114    */
115   uint32_t session_id;
116
117 };
118
119
120 /**
121  * Information we track per client.
122  */
123 struct Client
124 {
125   /**
126    * Type of the client, initially #CT_NONE.
127    */
128   enum ClientType type;
129
130   /**
131    * Service handle of the client.
132    */
133   struct GNUNET_SERVICE_Client *client;
134
135   /**
136    * Message queue to talk to the client.
137    */
138   struct GNUNET_MQ_Handle *mq;
139
140   /**
141    * Details depending on @e type.
142    */
143   union {
144
145     struct {
146
147       /**
148        * Head of DLL of preferences expressed by this client.
149        */
150       struct ClientPreference *cp_head;
151
152       /**
153        * Tail of DLL of preferences expressed by this client.
154        */
155       struct ClientPreference *cp_tail;
156
157     } application;
158
159     struct {
160
161       /**
162        * Map from session IDs to `struct GNUNET_ATS_Session` objects.
163        */
164       struct GNUNET_CONTAINER_MultiHashMap32 *sessions;
165
166     } transport;
167
168   } details;
169
170 };
171
172
173 /**
174  * Handle for statistics.
175  */
176 static struct GNUNET_STATISTICS_Handle *stats;
177
178 /**
179  * Our solver.
180  */
181 static struct GNUNET_ATS_SolverFunctions *plugin;
182
183 /**
184  * Solver plugin name as string
185  */
186 static char *plugin_name;
187
188 /**
189  * The transport client (there can only be one at a time).
190  */
191 static struct Client *transport_client;
192
193
194 /**
195  * Function called by the solver to prompt the transport to
196  * try out a new address.
197  *
198  * @param cls closure, NULL
199  * @param pid peer this is about
200  * @param address address the transport should try
201  */
202 static void
203 suggest_cb (void *cls,
204             const struct GNUNET_PeerIdentity *pid,
205             const char *address)
206 {
207   struct GNUNET_MQ_Envelope *env;
208   size_t slen = strlen (address) + 1;
209   struct AddressSuggestionMessage *as;
210
211   if (NULL == transport_client)
212   {
213     // FIXME: stats!
214     return;
215   }
216   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
217               "Suggesting address `%s' of peer `%s'\n",
218               address,
219               GNUNET_i2s (pid));
220   env = GNUNET_MQ_msg_extra (as,
221                              slen,
222                              GNUNET_MESSAGE_TYPE_ATS_ADDRESS_SUGGESTION);
223   as->peer = *pid;
224   memcpy (&as[1],
225           address,
226           slen);
227   GNUNET_MQ_send (transport_client->mq,
228                   env);
229 }
230
231
232 /**
233  * Function called by the solver to tell the transpor to
234  * allocate bandwidth for the specified session.
235  *
236  * @param cls closure, NULL
237  * @param session session this is about
238  * @param peer peer this is about
239  * @param bw_in suggested bandwidth for receiving
240  * @param bw_out suggested bandwidth for transmission
241  */
242 static void
243 allocate_cb (void *cls,
244              struct GNUNET_ATS_Session *session,
245              const struct GNUNET_PeerIdentity *peer,
246              struct GNUNET_BANDWIDTH_Value32NBO bw_in,
247              struct GNUNET_BANDWIDTH_Value32NBO bw_out)
248 {
249   struct GNUNET_MQ_Envelope *env;
250   struct SessionAllocationMessage *sam;
251
252   (void) cls;
253   if ( (NULL == transport_client) ||
254        (session->client != transport_client) )
255   {
256     /* transport must have just died and solver is addressing the
257        losses of sessions (possibly of previous transport), ignore! */
258     return;
259   }
260   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
261               "Allocating %u/%u bytes for %p of peer `%s'\n",
262               ntohl (bw_in.value__),
263               ntohl (bw_out.value__),
264               session,
265               GNUNET_i2s (peer));
266   env = GNUNET_MQ_msg (sam,
267                        GNUNET_MESSAGE_TYPE_ATS_SESSION_ALLOCATION);
268   sam->session_id = session->session_id;
269   sam->peer = *peer;
270   sam->bandwidth_in = bw_in;
271   sam->bandwidth_out = bw_out;
272   GNUNET_MQ_send (transport_client->mq,
273                   env);
274 }
275
276
277 /**
278  * Convert @a properties to @a prop
279  *
280  * @param properties in NBO
281  * @param prop[out] in HBO
282  */
283 static void
284 prop_ntoh (const struct PropertiesNBO *properties,
285            struct GNUNET_ATS_Properties *prop)
286 {
287   prop->delay = GNUNET_TIME_relative_ntoh (properties->delay);
288   prop->goodput_out = ntohl (properties->goodput_out);
289   prop->goodput_in = ntohl (properties->goodput_in);
290   prop->utilization_out = ntohl (properties->utilization_out);
291   prop->utilization_in = ntohl (properties->utilization_in);
292   prop->distance = ntohl (properties->distance);
293   prop->mtu = ntohl (properties->mtu);
294   prop->nt = (enum GNUNET_NetworkType) ntohl (properties->nt);
295   prop->cc = (enum GNUNET_TRANSPORT_CommunicatorCharacteristics) ntohl (properties->cc);
296 }
297
298
299 /**
300  * We have received a `struct ExpressPreferenceMessage` from an application client.
301  *
302  * @param cls handle to the client
303  * @param msg the start message
304  */
305 static void
306 handle_suggest (void *cls,
307                 const struct ExpressPreferenceMessage *msg)
308 {
309   struct Client *c = cls;
310   struct ClientPreference *cp;
311
312   if (CT_NONE == c->type)
313     c->type = CT_APPLICATION;
314   if (CT_APPLICATION != c->type)
315   {
316     GNUNET_break (0);
317     GNUNET_SERVICE_client_drop (c->client);
318     return;
319   }
320   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
321               "Client suggested we talk to %s with preference %d at rate %u\n",
322               GNUNET_i2s (&msg->peer),
323               (int) ntohl (msg->pk),
324               (int) ntohl (msg->bw.value__));
325   cp = GNUNET_new (struct ClientPreference);
326   cp->client = c;
327   cp->pref.peer = msg->peer;
328   cp->pref.bw = msg->bw;
329   cp->pref.pk = (enum GNUNET_MQ_PreferenceKind) ntohl (msg->pk);
330   cp->ph = plugin->preference_add (plugin->cls,
331                                    &cp->pref);
332   GNUNET_CONTAINER_DLL_insert (c->details.application.cp_head,
333                                c->details.application.cp_tail,
334                                cp);
335   GNUNET_SERVICE_client_continue (c->client);
336 }
337
338
339 /**
340  * We have received a `struct ExpressPreferenceMessage` from an application client.
341  *
342  * @param cls handle to the client
343  * @param msg the start message
344  */
345 static void
346 handle_suggest_cancel (void *cls,
347                        const struct ExpressPreferenceMessage *msg)
348 {
349   struct Client *c = cls;
350   struct ClientPreference *cp;
351
352   if (CT_NONE == c->type)
353     c->type = CT_APPLICATION;
354   if (CT_APPLICATION != c->type)
355   {
356     GNUNET_break (0);
357     GNUNET_SERVICE_client_drop (c->client);
358     return;
359   }
360   for (cp = c->details.application.cp_head;
361        NULL != cp;
362        cp = cp->next)
363     if ( (cp->pref.pk == (enum GNUNET_MQ_PreferenceKind) ntohl (msg->pk)) &&
364          (cp->pref.bw.value__ == msg->bw.value__) &&
365          (0 == memcmp (&cp->pref.peer,
366                        &msg->peer,
367                        sizeof (struct GNUNET_PeerIdentity))) )
368       break;
369   if (NULL == cp)
370   {
371     GNUNET_break (0);
372     GNUNET_SERVICE_client_drop (c->client);
373     return;
374   }
375   plugin->preference_del (plugin->cls,
376                           cp->ph,
377                           &cp->pref);
378   GNUNET_CONTAINER_DLL_remove (c->details.application.cp_head,
379                                c->details.application.cp_tail,
380                                cp);
381   GNUNET_free (cp);
382   GNUNET_SERVICE_client_continue (c->client);
383 }
384
385
386 /**
387  * Handle 'start' messages from transport clients.
388  *
389  * @param cls client that sent the request
390  * @param message the request message
391  */
392 static void
393 handle_start (void *cls,
394               const struct GNUNET_MessageHeader *hdr)
395 {
396   struct Client *c = cls;
397
398   if (CT_NONE != c->type)
399   {
400     GNUNET_break (0);
401     GNUNET_SERVICE_client_drop (c->client);
402     return;
403   }
404   c->type = CT_TRANSPORT;
405   c->details.transport.sessions
406     = GNUNET_CONTAINER_multihashmap32_create (128);
407   if (NULL != transport_client)
408   {
409     GNUNET_SERVICE_client_drop (transport_client->client);
410     transport_client = NULL;
411   }
412   transport_client = c;
413   GNUNET_SERVICE_client_continue (c->client);
414 }
415
416
417 /**
418  * Check 'session_add' message is well-formed and comes from a
419  * transport client.
420  *
421  * @param cls client that sent the request
422  * @param message the request message
423  * @return #GNUNET_OK if @a message is well-formed
424  */
425 static int
426 check_session_add (void *cls,
427                    const struct SessionAddMessage *message)
428 {
429   struct Client *c = cls;
430
431   GNUNET_MQ_check_zero_termination (message);
432   if (CT_TRANSPORT != c->type)
433   {
434     GNUNET_break (0);
435     return GNUNET_SYSERR;
436   }
437   return GNUNET_OK;
438 }
439
440
441 /**
442  * Handle 'session add' messages from transport clients.
443  *
444  * @param cls client that sent the request
445  * @param message the request message
446  */
447 static void
448 handle_session_add (void *cls,
449                     const struct SessionAddMessage *message)
450 {
451   struct Client *c = cls;
452   const char *address = (const char *) &message[1];
453   struct GNUNET_ATS_Session *session;
454   int inbound_only = (GNUNET_MESSAGE_TYPE_ATS_SESSION_ADD_INBOUND_ONLY ==
455                       ntohs (message->header.type));
456
457   session = GNUNET_CONTAINER_multihashmap32_get (c->details.transport.sessions,
458                                                  message->session_id);
459   if (NULL != session)
460   {
461     GNUNET_break (0);
462     GNUNET_SERVICE_client_drop (c->client);
463     return;
464   }
465   session = GNUNET_new (struct GNUNET_ATS_Session);
466   session->data.session = session;
467   session->client = c;
468   session->session_id = message->session_id;
469   session->data.peer = message->peer;
470   prop_ntoh (&message->properties,
471              &session->data.prop);
472   session->data.inbound_only = inbound_only;
473   GNUNET_assert (GNUNET_YES ==
474                  GNUNET_CONTAINER_multihashmap32_put (c->details.transport.sessions,
475                                                       message->session_id,
476                                                       session,
477                                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY));
478   session->sh = plugin->session_add (plugin->cls,
479                                      &session->data,
480                                      address);
481   GNUNET_assert (NULL != session->sh);
482   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
483               "Transport has new session %p to %s\n",
484               session,
485               GNUNET_i2s (&message->peer));
486   GNUNET_SERVICE_client_continue (c->client);
487 }
488
489
490 /**
491  * Handle 'session update' messages from transport clients.
492  *
493  * @param cls client that sent the request
494  * @param msg the request message
495  */
496 static void
497 handle_session_update (void *cls,
498                        const struct SessionUpdateMessage *msg)
499 {
500   struct Client *c = cls;
501   struct GNUNET_ATS_Session *session;
502
503   if (CT_TRANSPORT != c->type)
504   {
505     GNUNET_break (0);
506     GNUNET_SERVICE_client_drop (c->client);
507     return;
508   }
509   session = GNUNET_CONTAINER_multihashmap32_get (c->details.transport.sessions,
510                                                  msg->session_id);
511   if (NULL == session)
512   {
513     GNUNET_break (0);
514     GNUNET_SERVICE_client_drop (c->client);
515     return;
516   }
517   prop_ntoh (&msg->properties,
518              &session->data.prop);
519   plugin->session_update (plugin->cls,
520                           session->sh,
521                           &session->data);
522   GNUNET_SERVICE_client_continue (c->client);
523 }
524
525
526 /**
527  * Handle 'session delete' messages from transport clients.
528  *
529  * @param cls client that sent the request
530  * @param message the request message
531  */
532 static void
533 handle_session_del (void *cls,
534                     const struct SessionDelMessage *message)
535 {
536   struct Client *c = cls;
537   struct GNUNET_ATS_Session *session;
538
539   if (CT_TRANSPORT != c->type)
540   {
541     GNUNET_break (0);
542     GNUNET_SERVICE_client_drop (c->client);
543     return;
544   }
545   session = GNUNET_CONTAINER_multihashmap32_get (c->details.transport.sessions,
546                                                  message->session_id);
547   if (NULL == session)
548   {
549     GNUNET_break (0);
550     GNUNET_SERVICE_client_drop (c->client);
551     return;
552   }
553   GNUNET_assert (NULL != session->sh);
554   plugin->session_del (plugin->cls,
555                        session->sh,
556                        &session->data);
557   session->sh = NULL;
558   GNUNET_assert (GNUNET_YES ==
559                  GNUNET_CONTAINER_multihashmap32_remove (c->details.transport.sessions,
560                                                          session->session_id,
561                                                          session));
562   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
563               "Transport lost session %p to %s\n",
564               session,
565               GNUNET_i2s (&session->data.peer));
566   GNUNET_free (session);
567   GNUNET_SERVICE_client_continue (c->client);
568 }
569
570
571 /**
572  * A client connected to us. Setup the local client
573  * record.
574  *
575  * @param cls unused
576  * @param client handle of the client
577  * @param mq message queue to talk to @a client
578  * @return @a client
579  */
580 static void *
581 client_connect_cb (void *cls,
582                    struct GNUNET_SERVICE_Client *client,
583                    struct GNUNET_MQ_Handle *mq)
584 {
585   struct Client *c = GNUNET_new (struct Client);
586
587   c->client = client;
588   c->mq = mq;
589   return c;
590 }
591
592
593 /**
594  * Function called on each session to release associated state
595  * on transport disconnect.
596  *
597  * @param cls the `struct Client`
598  * @param key unused (session_id)
599  * @param value a `struct GNUNET_ATS_Session`
600  */
601 static int
602 free_session (void *cls,
603               uint32_t key,
604               void *value)
605 {
606   struct Client *c = cls;
607   struct GNUNET_ATS_Session *session = value;
608
609   (void) key;
610   GNUNET_assert (c == session->client);
611   GNUNET_assert (NULL != session->sh);
612   plugin->session_del (plugin->cls,
613                        session->sh,
614                        &session->data);
615   session->sh = NULL;
616   GNUNET_free (session);
617   return GNUNET_OK;
618 }
619
620
621 /**
622  * A client disconnected from us.  Tear down the local client
623  * record.
624  *
625  * @param cls unused
626  * @param client handle of the client
627  * @param app_ctx our `struct Client`
628  */
629 static void
630 client_disconnect_cb (void *cls,
631                       struct GNUNET_SERVICE_Client *client,
632                       void *app_ctx)
633 {
634   struct Client *c = app_ctx;
635
636   (void) cls;
637   GNUNET_assert (c->client == client);
638   switch (c->type)
639   {
640   case CT_NONE:
641     break;
642   case CT_APPLICATION:
643     for (struct ClientPreference *cp = c->details.application.cp_head;
644          NULL != cp;
645          cp = c->details.application.cp_head)
646     {
647       plugin->preference_del (plugin->cls,
648                               cp->ph,
649                               &cp->pref);
650       GNUNET_CONTAINER_DLL_remove (c->details.application.cp_head,
651                                    c->details.application.cp_tail,
652                                    cp);
653       GNUNET_free (cp);
654     }
655     break;
656   case CT_TRANSPORT:
657     if (transport_client == c)
658       transport_client = NULL;
659     GNUNET_CONTAINER_multihashmap32_iterate (c->details.transport.sessions,
660                                              &free_session,
661                                              c);
662     GNUNET_CONTAINER_multihashmap32_destroy (c->details.transport.sessions);
663     break;
664   }
665   GNUNET_free (c);
666 }
667
668
669 /**
670  * Task run at the end during shutdown.
671  *
672  * @param cls unused
673  */
674 static void
675 final_cleanup (void *cls)
676 {
677   (void) cls;
678   if (NULL != stats)
679   {
680     GNUNET_STATISTICS_destroy (stats,
681                                GNUNET_NO);
682     stats = NULL;
683   }
684   if (NULL != plugin)
685   {
686     GNUNET_PLUGIN_unload (plugin_name,
687                           plugin);
688     plugin = NULL;
689   }
690   if (NULL != plugin_name)
691   {
692     GNUNET_free (plugin_name);
693     plugin_name = NULL;
694   }
695 }
696
697
698 /**
699  * Task run during shutdown.
700  *
701  * @param cls unused
702  */
703 static void
704 cleanup_task (void *cls)
705 {
706   (void) cls;
707   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
708               "ATS shutdown initiated\n");
709   GNUNET_SCHEDULER_add_now (&final_cleanup,
710                             NULL);
711 }
712
713
714 /**
715  * Process template requests.
716  *
717  * @param cls closure
718  * @param cfg configuration to use
719  * @param service the initialized service
720  */
721 static void
722 run (void *cls,
723      const struct GNUNET_CONFIGURATION_Handle *cfg,
724      struct GNUNET_SERVICE_Handle *service)
725 {
726   static struct GNUNET_ATS_PluginEnvironment env;
727   char *solver;
728
729   stats = GNUNET_STATISTICS_create ("ats",
730                                     cfg);
731   if (GNUNET_SYSERR ==
732       GNUNET_CONFIGURATION_get_value_string (cfg,
733                                              "ats",
734                                              "SOLVER",
735                                              &solver))
736   {
737     GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
738                 "No ATS solver configured, using 'simple' approach\n");
739     solver = GNUNET_strdup ("simple");
740   }
741   GNUNET_SCHEDULER_add_shutdown (&cleanup_task,
742                                  NULL);
743   env.cls = NULL;
744   env.cfg = cfg;
745   env.stats = stats;
746   env.suggest_cb = &suggest_cb;
747   env.allocate_cb = &allocate_cb;
748   GNUNET_asprintf (&plugin_name,
749                    "libgnunet_plugin_ats2_%s",
750                    solver);
751   GNUNET_free (solver);
752   if (NULL == (plugin = GNUNET_PLUGIN_load (plugin_name,
753                                             &env)))
754   {
755     GNUNET_log (GNUNET_ERROR_TYPE_ERROR,
756                 _("Failed to initialize solver `%s'!\n"),
757                 plugin_name);
758     GNUNET_SCHEDULER_shutdown ();
759     return;
760   }
761 }
762
763
764 /**
765  * Define "main" method using service macro.
766  */
767 GNUNET_SERVICE_MAIN
768 ("ats",
769  GNUNET_SERVICE_OPTION_NONE,
770  &run,
771  &client_connect_cb,
772  &client_disconnect_cb,
773  NULL,
774  GNUNET_MQ_hd_fixed_size (suggest,
775                           GNUNET_MESSAGE_TYPE_ATS_SUGGEST,
776                           struct ExpressPreferenceMessage,
777                           NULL),
778  GNUNET_MQ_hd_fixed_size (suggest_cancel,
779                           GNUNET_MESSAGE_TYPE_ATS_SUGGEST_CANCEL,
780                           struct ExpressPreferenceMessage,
781                           NULL),
782  GNUNET_MQ_hd_fixed_size (start,
783                           GNUNET_MESSAGE_TYPE_ATS_START,
784                           struct GNUNET_MessageHeader,
785                           NULL),
786  GNUNET_MQ_hd_var_size (session_add,
787                         GNUNET_MESSAGE_TYPE_ATS_SESSION_ADD,
788                         struct SessionAddMessage,
789                         NULL),
790  GNUNET_MQ_hd_var_size (session_add,
791                         GNUNET_MESSAGE_TYPE_ATS_SESSION_ADD_INBOUND_ONLY,
792                         struct SessionAddMessage,
793                         NULL),
794  GNUNET_MQ_hd_fixed_size (session_update,
795                           GNUNET_MESSAGE_TYPE_ATS_SESSION_UPDATE,
796                           struct SessionUpdateMessage,
797                           NULL),
798  GNUNET_MQ_hd_fixed_size (session_del,
799                           GNUNET_MESSAGE_TYPE_ATS_SESSION_DEL,
800                           struct SessionDelMessage,
801                           NULL),
802  GNUNET_MQ_handler_end ());
803
804
805 /* end of gnunet-service-ats.c */