1f31b9c5c00037551de3984ff87c805426943dcc
[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   aa->mlp_information = NULL;
158   aa->next = NULL;
159   aa->prev = NULL;
160   return aa;
161 }
162
163
164 /**
165  * Destroy the given address.
166  *
167  * @param addr address to destroy
168  * @return GNUNET_YES if bandwidth allocations should be recalcualted
169  */
170 static int
171 destroy_address (struct ATS_Address *addr)
172 {
173   int ret;
174
175   ret = GNUNET_NO;
176   GNUNET_assert (GNUNET_YES ==
177                  GNUNET_CONTAINER_multihashmap_remove (addresses,
178                                                        &addr->peer.hashPubKey,
179                                                        addr));
180
181 #if HAVE_LIBGLPK
182   if (ats_mode == MLP)
183     GAS_mlp_address_delete (mlp, addresses, addr);
184 #endif
185
186   if (GNUNET_YES == addr->active)
187   {
188     active_addr_count--;
189     addr->active = GNUNET_NO;
190     ret = GNUNET_YES;
191   }
192   free_address (addr);
193   return ret;
194 }
195
196
197 struct CompareAddressContext
198 {
199   const struct ATS_Address *search;
200   struct ATS_Address *result;
201 };
202
203
204 static int
205 compare_address_it (void *cls, const GNUNET_HashCode * key, void *value)
206 {
207   struct CompareAddressContext *cac = cls;
208   struct ATS_Address *aa = value;
209
210   if (((aa->addr_len != cac->search->addr_len) ||
211        (0 != strcmp (aa->plugin, cac->search->plugin)) ||
212        (0 != memcmp (aa->addr, cac->search->addr, aa->addr_len))) &&
213       ((aa->session_id != cac->search->session_id) ||
214        (cac->search->session_id == 0)))
215     return GNUNET_YES;
216   cac->result = aa;
217   return GNUNET_NO;
218 }
219
220
221 /**
222  * Find an existing equivalent address record.
223  * Compares by peer identity and network address OR by session ID
224  * (one of the two must match).
225  *
226  * @param peer peer to lookup addresses for
227  * @param addr existing address record
228  * @return existing address record, NULL for none
229  */
230 struct ATS_Address *
231 find_address (const struct GNUNET_PeerIdentity *peer,
232               const struct ATS_Address *addr)
233 {
234   struct CompareAddressContext cac;
235
236   cac.result = NULL;
237   cac.search = addr;
238   GNUNET_CONTAINER_multihashmap_get_multiple (addresses, &peer->hashPubKey,
239                                               &compare_address_it, &cac);
240   return cac.result;
241 }
242
243
244 void
245 GAS_addresses_update (const struct GNUNET_PeerIdentity *peer,
246                       const char *plugin_name, const void *plugin_addr,
247                       size_t plugin_addr_len, uint32_t session_id,
248                       const struct GNUNET_ATS_Information *atsi,
249                       uint32_t atsi_count)
250 {
251   struct ATS_Address *aa;
252   struct ATS_Address *old;
253   uint32_t i;
254
255   aa = create_address (peer,
256                        plugin_name,
257                        plugin_addr, plugin_addr_len,
258                        session_id);
259
260   aa->mlp_information = NULL;
261   aa->ats = GNUNET_malloc (atsi_count * sizeof (struct GNUNET_ATS_Information));
262   aa->ats_count = atsi_count;
263   memcpy (aa->ats, atsi, atsi_count * sizeof (struct GNUNET_ATS_Information));
264
265   old = find_address (peer, aa);
266   if (old == NULL)
267   {
268     GNUNET_assert (GNUNET_OK ==
269                    GNUNET_CONTAINER_multihashmap_put (addresses,
270                                                       &peer->hashPubKey, aa,
271                                                       GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE));
272     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Added new address for peer `%s' %X\n",
273                 GNUNET_i2s (peer), aa);
274     old = aa;
275   }
276   else
277   {
278     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
279                 "Updated existing address for peer `%s' %X \n",
280                 GNUNET_i2s (peer), old);
281     GNUNET_free_non_null (old->ats);
282     old->session_id = session_id;
283     old->ats = NULL;
284     old->ats_count = 0;
285     old->ats = aa->ats;
286     old->ats_count = aa->ats_count;
287     GNUNET_free (aa->plugin);
288     GNUNET_free (aa);
289   }
290   for (i = 0; i < atsi_count; i++)
291     switch (ntohl (atsi[i].type))
292     {
293     case GNUNET_ATS_UTILIZATION_UP:
294       old->atsp_utilization_out.value__ = atsi[i].value;
295       break;
296     case GNUNET_ATS_UTILIZATION_DOWN:
297       old->atsp_utilization_in.value__ = atsi[i].value;
298       break;
299     case GNUNET_ATS_QUALITY_NET_DELAY:
300       old->atsp_latency.rel_value = ntohl (atsi[i].value);
301       break;
302     case GNUNET_ATS_QUALITY_NET_DISTANCE:
303       old->atsp_distance = ntohl (atsi[i].value);
304       break;
305     case GNUNET_ATS_COST_WAN:
306       old->atsp_cost_wan = ntohl (atsi[i].value);
307       break;
308     case GNUNET_ATS_COST_LAN:
309       old->atsp_cost_lan = ntohl (atsi[i].value);
310       break;
311     case GNUNET_ATS_COST_WLAN:
312       old->atsp_cost_wlan = ntohl (atsi[i].value);
313       break;
314     case GNUNET_ATS_NETWORK_TYPE:
315       old->atsp_network_type = ntohl (atsi[i].value);
316       break;
317
318     default:
319       GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
320                   "Received unsupported ATS type %u\n", ntohl (atsi[i].type));
321       GNUNET_break (0);
322       break;
323     }
324 #if HAVE_LIBGLPK
325   if (ats_mode == MLP)
326     GAS_mlp_address_update (mlp, addresses, old);
327 #endif
328 }
329
330
331 /**
332  * Delete an address
333  *
334  * If session != 0, just the session is deleted, the address itself still exists
335  * If session == 0, remove full address
336  * If session == 0 and addrlen == 0, destroy inbound address
337  *
338  * @param cls unused
339  * @param key unused
340  * @param value the 'struct ATS_Address'
341  * @return GNUNET_OK (continue to iterate)
342  */
343 static int
344 destroy_by_session_id (void *cls, const GNUNET_HashCode * key, void *value)
345 {
346   const struct ATS_Address *info = cls;
347   struct ATS_Address *aa = value;
348
349   GNUNET_assert (0 ==
350                  memcmp (&aa->peer, &info->peer,
351                          sizeof (struct GNUNET_PeerIdentity)));
352   /* session == 0, remove full address  */
353   if ((info->session_id == 0) && (0 == strcmp (info->plugin, aa->plugin)) &&
354       (aa->addr_len == info->addr_len) &&
355       (0 == memcmp (info->addr, aa->addr, aa->addr_len)))
356   {
357     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
358                 "Deleting address for peer `%s': `%s'\n",
359                 GNUNET_i2s (&aa->peer), aa->plugin);
360     if (GNUNET_YES == destroy_address (aa))
361       recalculate_assigned_bw ();
362     return GNUNET_OK;
363   }
364   /* session != 0, just remove session */
365   if (aa->session_id != info->session_id)
366     return GNUNET_OK;           /* irrelevant */
367   if (aa->session_id != 0)
368     GNUNET_break (0 == strcmp (info->plugin, aa->plugin));
369   /* session died */
370   aa->session_id = 0;
371
372   if (GNUNET_YES == aa->active)
373   {
374     aa->active = GNUNET_NO;
375     active_addr_count--;
376     recalculate_assigned_bw ();
377   }
378
379   /* session == 0 and addrlen == 0 : destroy address */
380   if (aa->addr_len == 0)
381   {
382     (void) destroy_address (aa);
383   }
384   else
385   {
386     /* session was set to 0, update address */
387 #if HAVE_LIBGLPK
388   if (ats_mode == MLP)
389     GAS_mlp_address_update (mlp, addresses, aa);
390 #endif
391   }
392
393   return GNUNET_OK;
394 }
395
396 void
397 GAS_addresses_destroy (const struct GNUNET_PeerIdentity *peer,
398                        const char *plugin_name, const void *plugin_addr,
399                        size_t plugin_addr_len, uint32_t session_id)
400 {
401   struct ATS_Address *aa;
402
403   GNUNET_break (0 < strlen (plugin_name));
404   aa = create_address (peer, plugin_name, plugin_addr, plugin_addr_len, session_id);
405
406   GNUNET_CONTAINER_multihashmap_get_multiple (addresses, &peer->hashPubKey,
407                                               &destroy_by_session_id, aa);
408
409   free_address (aa);
410 }
411
412
413 /**
414  * Find a "good" address to use for a peer.  If we already have an existing
415  * address, we stick to it.  Otherwise, we pick by lowest distance and then
416  * by lowest latency.
417  *
418  * @param cls the 'struct ATS_Address**' where we store the result
419  * @param key unused
420  * @param value another 'struct ATS_Address*' to consider using
421  * @return GNUNET_OK (continue to iterate)
422  */
423 static int
424 find_address_it (void *cls, const GNUNET_HashCode * key, void *value)
425 {
426   struct ATS_Address **ap = cls;
427   struct ATS_Address *aa = (struct ATS_Address *) value;
428   struct ATS_Address *ab = *ap;
429
430   if (NULL == ab)
431   {
432     *ap = aa;
433     return GNUNET_OK;
434   }
435   if ((ntohl (ab->assigned_bw_in.value__) == 0) &&
436       (ntohl (aa->assigned_bw_in.value__) > 0))
437   {
438     /* stick to existing connection */
439     *ap = aa;
440     return GNUNET_OK;
441   }
442   if (ab->atsp_distance > aa->atsp_distance)
443   {
444     /* user shorter distance */
445     *ap = aa;
446     return GNUNET_OK;
447   }
448   if (ab->atsp_latency.rel_value > aa->atsp_latency.rel_value)
449   {
450     /* user lower latency */
451     *ap = aa;
452     return GNUNET_OK;
453   }
454   /* don't care */
455   return GNUNET_OK;
456 }
457
458
459 void
460 GAS_addresses_in_use (const struct GNUNET_PeerIdentity *peer,
461                       const char *plugin_name, const void *plugin_addr,
462                       size_t plugin_addr_len, uint32_t session_id, int in_use)
463 {
464 #if DEBUG_ATS
465   GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
466               "Received `%s' message for peer `%s': %i\n", "ADDRESS_IN_USE",
467               GNUNET_i2s (peer), in_use);
468 #endif
469
470   struct ATS_Address *aa;
471   struct ATS_Address *old;
472
473   aa = create_address(peer, plugin_name, plugin_addr, plugin_addr_len, session_id);
474   old = find_address (peer, aa);
475   free_address (aa);
476
477   GNUNET_assert (old != NULL);
478   GNUNET_assert (in_use != old->used);
479   old->used = in_use;
480
481 #if HAVE_LIBGLPK
482   if (ats_mode == MLP)
483      GAS_mlp_address_update (mlp, addresses, old);
484 #endif
485 }
486
487 void
488 GAS_addresses_request_address (const struct GNUNET_PeerIdentity *peer)
489 {
490   struct ATS_Address *aa;
491
492   aa = NULL;
493   GNUNET_CONTAINER_multihashmap_get_multiple (addresses, &peer->hashPubKey,
494                                               &find_address_it, &aa);
495   if (aa == NULL)
496   {
497     GNUNET_log (GNUNET_ERROR_TYPE_DEBUG,
498                 "Cannot suggest address for peer `%s'\n", GNUNET_i2s (peer));
499     return;
500   }
501   if (aa->active == GNUNET_NO)
502   {
503     aa->active = GNUNET_YES;
504     active_addr_count++;
505     recalculate_assigned_bw ();
506   }
507   else
508   {
509     /* just to be sure... */
510     GAS_scheduling_transmit_address_suggestion (peer, aa->plugin, aa->addr,
511                                                 aa->addr_len, aa->session_id,
512                                                 aa->ats, aa->ats_count,
513                                                 aa->assigned_bw_out,
514                                                 aa->assigned_bw_in);
515   }
516 }
517
518
519 // FIXME: this function should likely end up in the LP-subsystem and
520 // not with 'addresses' in the future...
521 void
522 GAS_addresses_change_preference (const struct GNUNET_PeerIdentity *peer,
523                                  enum GNUNET_ATS_PreferenceKind kind,
524                                  float score)
525 {
526 #if HAVE_LIBGLPK
527   if (ats_mode == MLP)
528     GAS_mlp_address_change_preference (mlp, peer, kind, score);
529 #endif
530 }
531
532
533
534 /**
535  * Initialize address subsystem.
536  *
537  * @param cfg configuration to use
538  * @param stats the statistics handle to use
539  */
540 void
541 GAS_addresses_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
542                     const struct GNUNET_STATISTICS_Handle *stats)
543 {
544   GNUNET_assert (GNUNET_OK ==
545                  GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
546                                                       "WAN_QUOTA_IN",
547                                                       &wan_quota_in));
548   GNUNET_assert (GNUNET_OK ==
549                  GNUNET_CONFIGURATION_get_value_size (cfg, "ats",
550                                                       "WAN_QUOTA_OUT",
551                                                       &wan_quota_out));
552
553   switch (GNUNET_CONFIGURATION_get_value_yesno (cfg, "ats", "MLP"))
554   {
555         /* MLP = YES */
556         case GNUNET_YES:
557 #if HAVE_LIBGLPK
558           ats_mode = MLP;
559           /* Init the MLP solver with default values */
560           mlp = GAS_mlp_init (cfg, stats, MLP_MAX_EXEC_DURATION, MLP_MAX_ITERATIONS);
561           break;
562 #else
563
564           GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "MLP mode was configured, but libglpk is not installed, switching to simple mode");
565           ats_mode = SIMPLE;
566           break;
567 #endif
568         /* MLP = NO */
569         case GNUNET_NO:
570                 ats_mode = SIMPLE;
571                 break;
572         /* No configuration value */
573         case GNUNET_SYSERR:
574                 ats_mode = SIMPLE;
575                 break;
576         default:
577                 break;
578   }
579
580   addresses = GNUNET_CONTAINER_multihashmap_create (128);
581 }
582
583
584 /**
585  * Free memory of address.
586  *
587  * @param cls NULL
588  * @param key peer identity (unused)
589  * @param value the 'struct ATS_Address' to free
590  * @return GNUNET_OK (continue to iterate)
591  */
592 static int
593 free_address_it (void *cls, const GNUNET_HashCode * key, void *value)
594 {
595   struct ATS_Address *aa = value;
596
597   destroy_address (aa);
598   return GNUNET_OK;
599 }
600
601
602 void
603 GAS_addresses_destroy_all ()
604 {
605   if (addresses != NULL)
606     GNUNET_CONTAINER_multihashmap_iterate (addresses, &free_address_it, NULL);
607   GNUNET_assert (active_addr_count == 0);
608 }
609
610
611 /**
612  * Shutdown address subsystem.
613  */
614 void
615 GAS_addresses_done ()
616 {
617   GAS_addresses_destroy_all ();
618   GNUNET_CONTAINER_multihashmap_destroy (addresses);
619   addresses = NULL;
620 #if HAVE_LIBGLPK
621   if (ats_mode == MLP)
622   {
623     GAS_mlp_done (mlp);
624   }
625 #endif
626
627 }
628
629
630 /* end of gnunet-service-ats_addresses.c */