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