- improved error logging
[oweals/gnunet.git] / src / ats / gnunet-service-ats_addresses.c
1 /*
2      This file is part of GNUnet.
3      (C) 2011 Christian Grothoff (and other contributing authors)
4
5      GNUnet is free software; you can redistribute it and/or modify
6      it under the terms of the GNU General Public License as published
7      by the Free Software Foundation; either version 3, or (at your
8      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      General Public License for more details.
14
15      You should have received a copy of the GNU General Public License
16      along with GNUnet; see the file COPYING.  If not, write to the
17      Free Software Foundation, Inc., 59 Temple Place - Suite 330,
18      Boston, MA 02111-1307, USA.
19 */
20
21 /**
22  * @file ats/gnunet-service-ats_addresses.c
23  * @brief ats service address management
24  * @author Matthias Wachs
25  * @author Christian Grothoff
26  */
27 #include "platform.h"
28 #include "gnunet_ats_service.h"
29 #include "gnunet-service-ats.h"
30 #include "gnunet-service-ats_addresses.h"
31 #include "gnunet-service-ats_performance.h"
32 #include "gnunet-service-ats_scheduling.h"
33 #include "gnunet-service-ats_reservations.h"
34 #if HAVE_LIBGLPK
35 #include "gnunet-service-ats_addresses_mlp.h"
36 #endif
37
38 enum ATS_Mode
39 {
40   /**
41    * Assign each peer an equal amount of bandwidth (bw)
42    *
43    * bw_per_peer = bw_total / #active addresses
44    */
45   SIMPLE,
46
47   /**
48    * Use MLP solver to assign bandwidth
49    */
50   MLP
51 };
52
53 static struct GNUNET_CONTAINER_MultiHashMap *addresses;
54
55 #if HAVE_LIBGLPK
56 static struct GAS_MLP_Handle *mlp;
57 #endif
58
59 static unsigned long long wan_quota_in;
60
61 static unsigned long long wan_quota_out;
62
63 static unsigned int active_addr_count;
64
65 static int ats_mode;
66
67
68 /**
69  * Update a bandwidth assignment for a peer.  This trivial method currently
70  * simply assigns the same share to all active connections.
71  *
72  * @param cls unused
73  * @param key unused
74  * @param value the 'struct ATS_Address'
75  * @return GNUNET_OK (continue to iterate)
76  */
77 static int
78 update_bw_it (void *cls, const GNUNET_HashCode * key, void *value)
79 {
80   struct ATS_Address *aa = value;
81
82
83   /* Simple method */
84   if (GNUNET_YES != aa->active)
85     return GNUNET_OK;
86   GNUNET_assert (active_addr_count > 0);
87   aa->assigned_bw_in.value__ = htonl (wan_quota_in / active_addr_count);
88   aa->assigned_bw_out.value__ = htonl (wan_quota_out / active_addr_count);
89   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "New bandwidth for peer %s is %u/%u\n",
90               GNUNET_i2s (&aa->peer), ntohl (aa->assigned_bw_in.value__),
91               ntohl (aa->assigned_bw_out.value__));
92   GAS_scheduling_transmit_address_suggestion (&aa->peer, aa->plugin, aa->addr,
93                                               aa->addr_len, aa->session_id,
94                                               aa->ats, aa->ats_count,
95                                               aa->assigned_bw_out,
96                                               aa->assigned_bw_in);
97   GAS_reservations_set_bandwidth (&aa->peer, aa->assigned_bw_in);
98   GAS_performance_notify_clients (&aa->peer, aa->plugin, aa->addr, aa->addr_len,
99                                   aa->ats, aa->ats_count, aa->assigned_bw_out,
100                                   aa->assigned_bw_in);
101   return GNUNET_OK;
102 }
103
104
105 /**
106  * Some (significant) input changed, recalculate bandwidth assignment
107  * for all peers.
108  */
109 static void
110 recalculate_assigned_bw ()
111 {
112   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
113               "Recalculating bandwidth for all active connections\n");
114   GNUNET_STATISTICS_update (GSA_stats, "# bandwidth recalculations performed",
115                             1, GNUNET_NO);
116   GNUNET_STATISTICS_set (GSA_stats, "# active addresses", active_addr_count,
117                          GNUNET_NO);
118   GNUNET_CONTAINER_multihashmap_iterate (addresses, &update_bw_it, NULL);
119 }
120
121 /**
122  * Free the given address
123  * @param addr address to destroy
124  */
125 static void
126 free_address (struct ATS_Address *addr)
127 {
128   GNUNET_free_non_null (addr->ats);
129   GNUNET_free (addr->plugin);
130   GNUNET_free (addr);
131 }
132
133 /**
134  * Create a ATS_address with the given information
135  * @param peer peer
136  * @param plugin_name plugin
137  * @param plugin_addr address
138  * @param plugin_addr_len address length
139  * @param session_id session
140  * @return the ATS_Address
141  */
142 static struct ATS_Address *
143 create_address (const struct GNUNET_PeerIdentity *peer,
144                 const char *plugin_name,
145                 const void *plugin_addr, size_t plugin_addr_len,
146                 uint32_t session_id)
147 {
148   struct ATS_Address *aa = NULL;
149
150   aa = GNUNET_malloc (sizeof (struct ATS_Address) + plugin_addr_len);
151   aa->peer = *peer;
152   aa->addr_len = plugin_addr_len;
153   aa->addr = &aa[1];
154   memcpy (&aa[1], plugin_addr, plugin_addr_len);
155   aa->plugin = GNUNET_strdup (plugin_name);
156   aa->session_id = session_id;
157
158   return aa;
159 }
160
161
162 /**
163  * Destroy the given address.
164  *
165  * @param addr address to destroy
166  * @return GNUNET_YES if bandwidth allocations should be recalcualted
167  */
168 static int
169 destroy_address (struct ATS_Address *addr)
170 {
171   int ret;
172
173   ret = GNUNET_NO;
174   GNUNET_assert (GNUNET_YES ==
175                  GNUNET_CONTAINER_multihashmap_remove (addresses,
176                                                        &addr->peer.hashPubKey,
177                                                        addr));
178
179 #if HAVE_LIBGLPK
180   if (ats_mode == MLP)
181     GAS_mlp_address_delete (mlp, addresses, addr);
182 #endif
183
184   if (GNUNET_YES == addr->active)
185   {
186     active_addr_count--;
187     addr->active = GNUNET_NO;
188     ret = GNUNET_YES;
189   }
190   free_address (addr);
191   return ret;
192 }
193
194
195 struct CompareAddressContext
196 {
197   const struct ATS_Address *search;
198   struct ATS_Address *result;
199 };
200
201
202 static int
203 compare_address_it (void *cls, const GNUNET_HashCode * key, void *value)
204 {
205   struct CompareAddressContext *cac = cls;
206   struct ATS_Address *aa = value;
207
208   if (((aa->addr_len != cac->search->addr_len) ||
209        (0 != strcmp (aa->plugin, cac->search->plugin)) ||
210        (0 != memcmp (aa->addr, cac->search->addr, aa->addr_len))) &&
211       ((aa->session_id != cac->search->session_id) ||
212        (cac->search->session_id == 0)))
213     return GNUNET_YES;
214   cac->result = aa;
215   return GNUNET_NO;
216 }
217
218
219 /**
220  * Find an existing equivalent address record.
221  * Compares by peer identity and network address OR by session ID
222  * (one of the two must match).
223  *
224  * @param peer peer to lookup addresses for
225  * @param addr existing address record
226  * @return existing address record, NULL for none
227  */
228 struct ATS_Address *
229 find_address (const struct GNUNET_PeerIdentity *peer,
230               const struct ATS_Address *addr)
231 {
232   struct CompareAddressContext cac;
233
234   cac.result = NULL;
235   cac.search = addr;
236   GNUNET_CONTAINER_multihashmap_get_multiple (addresses, &peer->hashPubKey,
237                                               &compare_address_it, &cac);
238   return cac.result;
239 }
240
241
242 void
243 GAS_addresses_update (const struct GNUNET_PeerIdentity *peer,
244                       const char *plugin_name, const void *plugin_addr,
245                       size_t plugin_addr_len, uint32_t session_id,
246                       const struct GNUNET_ATS_Information *atsi,
247                       uint32_t atsi_count)
248 {
249   struct ATS_Address *aa;
250   struct ATS_Address *old;
251   uint32_t i;
252
253   aa = create_address (peer,
254                        plugin_name,
255                        plugin_addr, plugin_addr_len,
256                        session_id);
257
258   aa->mlp_information = NULL;
259   aa->ats = GNUNET_malloc (atsi_count * sizeof (struct GNUNET_ATS_Information));
260   aa->ats_count = atsi_count;
261   memcpy (aa->ats, atsi, atsi_count * sizeof (struct GNUNET_ATS_Information));
262
263   old = find_address (peer, aa);
264   if (old == NULL)
265   {
266     GNUNET_assert (GNUNET_OK ==
267                    GNUNET_CONTAINER_multihashmap_put (addresses,
268                                                       &peer->hashPubKey, aa,
269                                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
270     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Added new address for peer `%s' %X\n",
271                 GNUNET_i2s (peer), aa);
272     old = aa;
273   }
274   else
275   {
276     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
277                 "Updated existing address for peer `%s' %X \n",
278                 GNUNET_i2s (peer), old);
279     GNUNET_free_non_null (old->ats);
280     old->session_id = session_id;
281     old->ats = NULL;
282     old->ats_count = 0;
283     old->ats = aa->ats;
284     old->ats_count = aa->ats_count;
285     GNUNET_free (aa->plugin);
286     GNUNET_free (aa);
287   }
288   for (i = 0; i < atsi_count; i++)
289     switch (ntohl (atsi[i].type))
290     {
291     case GNUNET_ATS_UTILIZATION_UP:
292       old->atsp_utilization_out.value__ = atsi[i].value;
293       break;
294     case GNUNET_ATS_UTILIZATION_DOWN:
295       old->atsp_utilization_in.value__ = atsi[i].value;
296       break;
297     case GNUNET_ATS_QUALITY_NET_DELAY:
298       old->atsp_latency.rel_value = ntohl (atsi[i].value);
299       break;
300     case GNUNET_ATS_QUALITY_NET_DISTANCE:
301       old->atsp_distance = ntohl (atsi[i].value);
302       break;
303     case GNUNET_ATS_COST_WAN:
304       old->atsp_cost_wan = ntohl (atsi[i].value);
305       break;
306     case GNUNET_ATS_COST_LAN:
307       old->atsp_cost_lan = ntohl (atsi[i].value);
308       break;
309     case GNUNET_ATS_COST_WLAN:
310       old->atsp_cost_wlan = ntohl (atsi[i].value);
311       break;
312     case GNUNET_ATS_NETWORK_TYPE:
313       old->atsp_network_type = ntohl (atsi[i].value);
314       break;
315
316     default:
317       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
318                   "Received unsupported ATS type %u\n", ntohl (atsi[i].type));
319       GNUNET_break (0);
320       break;
321     }
322 #if HAVE_LIBGLPK
323   if (ats_mode == MLP)
324     GAS_mlp_address_update (mlp, addresses, old);
325 #endif
326 }
327
328
329 /**
330  * Delete an address
331  *
332  * If session != 0, just the session is deleted, the address itself still exists
333  * If session == 0, remove full address
334  * If session == 0 and addrlen == 0, destroy inbound address
335  *
336  * @param cls unused
337  * @param key unused
338  * @param value the 'struct ATS_Address'
339  * @return GNUNET_OK (continue to iterate)
340  */
341 static int
342 destroy_by_session_id (void *cls, const GNUNET_HashCode * key, void *value)
343 {
344   const struct ATS_Address *info = cls;
345   struct ATS_Address *aa = value;
346
347   GNUNET_assert (0 ==
348                  memcmp (&aa->peer, &info->peer,
349                          sizeof (struct GNUNET_PeerIdentity)));
350   /* session == 0, remove full address  */
351   if ((info->session_id == 0) && (0 == strcmp (info->plugin, aa->plugin)) &&
352       (aa->addr_len == info->addr_len) &&
353       (0 == memcmp (info->addr, aa->addr, aa->addr_len)))
354   {
355     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
356                 "Deleting address for peer `%s': `%s'\n",
357                 GNUNET_i2s (&aa->peer), aa->plugin);
358     if (GNUNET_YES == destroy_address (aa))
359       recalculate_assigned_bw ();
360     return GNUNET_OK;
361   }
362   /* session != 0, just remove session */
363   if (aa->session_id != info->session_id)
364     return GNUNET_OK;           /* irrelevant */
365   if (aa->session_id != 0)
366     GNUNET_break (0 == strcmp (info->plugin, aa->plugin));
367   /* session died */
368   aa->session_id = 0;
369
370   if (GNUNET_YES == aa->active)
371   {
372     aa->active = GNUNET_NO;
373     active_addr_count--;
374     recalculate_assigned_bw ();
375   }
376
377   /* session == 0 and addrlen == 0 : destroy address */
378   if (aa->addr_len == 0)
379   {
380     (void) destroy_address (aa);
381   }
382   else
383   {
384     /* session was set to 0, update address */
385 #if HAVE_LIBGLPK
386   if (ats_mode == MLP)
387     GAS_mlp_address_update (mlp, addresses, aa);
388 #endif
389   }
390
391   return GNUNET_OK;
392 }
393
394 void
395 GAS_addresses_destroy (const struct GNUNET_PeerIdentity *peer,
396                        const char *plugin_name, const void *plugin_addr,
397                        size_t plugin_addr_len, uint32_t session_id)
398 {
399   struct ATS_Address *aa;
400
401   GNUNET_break (0 < strlen (plugin_name));
402   aa = create_address (peer, plugin_name, plugin_addr, plugin_addr_len, session_id);
403
404   GNUNET_CONTAINER_multihashmap_get_multiple (addresses, &peer->hashPubKey,
405                                               &destroy_by_session_id, aa);
406
407   free_address (aa);
408 }
409
410
411 /**
412  * Find a "good" address to use for a peer.  If we already have an existing
413  * address, we stick to it.  Otherwise, we pick by lowest distance and then
414  * by lowest latency.
415  *
416  * @param cls the 'struct ATS_Address**' where we store the result
417  * @param key unused
418  * @param value another 'struct ATS_Address*' to consider using
419  * @return GNUNET_OK (continue to iterate)
420  */
421 static int
422 find_address_it (void *cls, const GNUNET_HashCode * key, void *value)
423 {
424   struct ATS_Address **ap = cls;
425   struct ATS_Address *aa = (struct ATS_Address *) value;
426   struct ATS_Address *ab = *ap;
427
428   if (NULL == ab)
429   {
430     *ap = aa;
431     return GNUNET_OK;
432   }
433   if ((ntohl (ab->assigned_bw_in.value__) == 0) &&
434       (ntohl (aa->assigned_bw_in.value__) > 0))
435   {
436     /* stick to existing connection */
437     *ap = aa;
438     return GNUNET_OK;
439   }
440   if (ab->atsp_distance > aa->atsp_distance)
441   {
442     /* user shorter distance */
443     *ap = aa;
444     return GNUNET_OK;
445   }
446   if (ab->atsp_latency.rel_value > aa->atsp_latency.rel_value)
447   {
448     /* user lower latency */
449     *ap = aa;
450     return GNUNET_OK;
451   }
452   /* don't care */
453   return GNUNET_OK;
454 }
455
456
457 void
458 GAS_addresses_in_use (const struct GNUNET_PeerIdentity *peer,
459                       const char *plugin_name, const void *plugin_addr,
460                       size_t plugin_addr_len, uint32_t session_id, int in_use)
461 {
462 #if DEBUG_ATS
463   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
464               "Received `%s' message for peer `%s': %i\n", "ADDRESS_IN_USE",
465               GNUNET_i2s (peer), in_use);
466 #endif
467
468   struct ATS_Address *aa;
469   struct ATS_Address *old;
470
471   aa = create_address(peer, plugin_name, plugin_addr, plugin_addr_len, session_id);
472   old = find_address (peer, aa);
473   free_address (aa);
474
475   GNUNET_assert (old != NULL);
476   GNUNET_assert (in_use != old->used);
477   old->used = in_use;
478
479 #if HAVE_LIBGLPK
480   if (ats_mode == MLP)
481      GAS_mlp_address_update (mlp, addresses, old);
482 #endif
483 }
484
485 void
486 GAS_addresses_request_address (const struct GNUNET_PeerIdentity *peer)
487 {
488   struct ATS_Address *aa;
489
490   aa = NULL;
491   GNUNET_CONTAINER_multihashmap_get_multiple (addresses, &peer->hashPubKey,
492                                               &find_address_it, &aa);
493   if (aa == NULL)
494   {
495     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
496                 "Cannot suggest address for peer `%s'\n", GNUNET_i2s (peer));
497     return;
498   }
499   if (aa->active == GNUNET_NO)
500   {
501     aa->active = GNUNET_YES;
502     active_addr_count++;
503     recalculate_assigned_bw ();
504   }
505   else
506   {
507     /* just to be sure... */
508     GAS_scheduling_transmit_address_suggestion (peer, aa->plugin, aa->addr,
509                                                 aa->addr_len, aa->session_id,
510                                                 aa->ats, aa->ats_count,
511                                                 aa->assigned_bw_out,
512                                                 aa->assigned_bw_in);
513   }
514 }
515
516
517 // FIXME: this function should likely end up in the LP-subsystem and
518 // not with 'addresses' in the future...
519 void
520 GAS_addresses_change_preference (const struct GNUNET_PeerIdentity *peer,
521                                  enum GNUNET_ATS_PreferenceKind kind,
522                                  float score)
523 {
524   // do nothing for now...
525 }
526
527
528
529 /**
530  * Initialize address subsystem.
531  *
532  * @param cfg configuration to use
533  * @param stats the statistics handle to use
534  */
535 void
536 GAS_addresses_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
537                     const struct GNUNET_STATISTICS_Handle *stats)
538 {
539   GNUNET_assert (GNUNET_OK ==
540                  GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
541                                                       "WAN_QUOTA_IN",
542                                                       &wan_quota_in));
543   GNUNET_assert (GNUNET_OK ==
544                  GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
545                                                       "WAN_QUOTA_OUT",
546                                                       &wan_quota_out));
547
548   switch (GNUNET_CONFIGURATION_get_value_yesno (cfg, "ats", "MLP"))
549   {
550         /* MLP = YES */
551         case GNUNET_YES:
552 #if HAVE_LIBGLPK
553           ats_mode = MLP;
554           /* Init the MLP solver with default values */
555           mlp = GAS_mlp_init (cfg, stats, MLP_MAX_EXEC_DURATION, MLP_MAX_ITERATIONS);
556           break;
557 #else
558
559           GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "MLP mode was configured, but libglpk is not installed, switching to simple mode");
560           ats_mode = SIMPLE;
561           break;
562 #endif
563         /* MLP = NO */
564         case GNUNET_NO:
565                 ats_mode = SIMPLE;
566                 break;
567         /* No configuration value */
568         case GNUNET_SYSERR:
569                 ats_mode = SIMPLE;
570                 break;
571         default:
572                 break;
573   }
574
575   addresses = GNUNET_CONTAINER_multihashmap_create (128);
576 }
577
578
579 /**
580  * Free memory of address.
581  *
582  * @param cls NULL
583  * @param key peer identity (unused)
584  * @param value the 'struct ATS_Address' to free
585  * @return GNUNET_OK (continue to iterate)
586  */
587 static int
588 free_address_it (void *cls, const GNUNET_HashCode * key, void *value)
589 {
590   struct ATS_Address *aa = value;
591
592   destroy_address (aa);
593   return GNUNET_OK;
594 }
595
596
597 void
598 GAS_addresses_destroy_all ()
599 {
600   if (addresses != NULL)
601     GNUNET_CONTAINER_multihashmap_iterate (addresses, &free_address_it, NULL);
602   GNUNET_assert (active_addr_count == 0);
603 }
604
605
606 /**
607  * Shutdown address subsystem.
608  */
609 void
610 GAS_addresses_done ()
611 {
612   GAS_addresses_destroy_all ();
613   GNUNET_CONTAINER_multihashmap_destroy (addresses);
614   addresses = NULL;
615 #if HAVE_LIBGLPK
616   if (ats_mode == MLP)
617   {
618     GAS_mlp_done (mlp);
619   }
620 #endif
621
622 }
623
624
625 /* end of gnunet-service-ats_addresses.c */