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