2 This file is part of GNUnet.
3 Copyright (C) 2010-2015 GNUnet e.V.
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.
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.
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., 51 Franklin Street, Fifth Floor,
18 Boston, MA 02110-1301, USA.
21 * @file ats/ats_api_scheduling.c
22 * @brief automatic transport selection and outbound bandwidth determination
23 * @author Christian Grothoff
24 * @author Matthias Wachs
27 * - we could avoid a linear scan over the
28 * active addresses in some cases, so if
29 * there is need, we can still optimize here
30 * - we might want to split off the logic to
31 * determine LAN vs. WAN, as it has nothing
32 * to do with accessing the ATS service.
35 #include "gnunet_ats_service.h"
39 * How frequently do we scan the interfaces for changes to the addresses?
41 #define INTERFACE_PROCESSING_INTERVAL GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MINUTES, 2)
43 #define LOG(kind,...) GNUNET_log_from(kind, "ats-scheduling-api", __VA_ARGS__)
46 * Session ID we use if there is no session / slot.
52 * Information we track per address, incoming or outgoing. It also
53 * doesn't matter if we have a session, any address that ATS is
54 * allowed to suggest right now should be tracked.
56 struct GNUNET_ATS_AddressRecord
60 * Scheduling handle this address record belongs to.
62 struct GNUNET_ATS_SchedulingHandle *sh;
67 struct GNUNET_HELLO_Address *address;
70 * Session handle. NULL if we have an address but no
71 * active session for this address.
73 struct GNUNET_ATS_Session *session;
76 * Performance data about the address.
78 struct GNUNET_ATS_PropertiesNBO properties;
81 * Which slot (index) in the session array does
82 * this record correspond to?
83 * FIXME: a linear search on this is really crappy!
84 * Maybe switch to a 64-bit global counter and be
85 * done with it? Or does that then cause too much
86 * trouble on the ATS-service side?
91 * We're about to destroy this address record, just ATS does
92 * not know this yet. Once ATS confirms its destruction,
100 * Handle to the ATS subsystem for bandwidth/transport scheduling information.
102 struct GNUNET_ATS_SchedulingHandle
108 const struct GNUNET_CONFIGURATION_Handle *cfg;
111 * Callback to invoke on suggestions.
113 GNUNET_ATS_AddressSuggestionCallback suggest_cb;
116 * Closure for @e suggest_cb.
118 void *suggest_cb_cls;
121 * Connection to ATS service.
123 struct GNUNET_CLIENT_Connection *client;
126 * Message queue for sending requests to the ATS service.
128 struct GNUNET_MQ_Handle *mq;
131 * Array of session objects (we need to translate them to numbers and back
132 * for the protocol; the offset in the array is the session number on the
133 * network). Index 0 is always NULL and reserved to represent the NULL pointer.
134 * Unused entries are also NULL.
136 struct GNUNET_ATS_AddressRecord **session_array;
139 * Task to trigger reconnect.
141 struct GNUNET_SCHEDULER_Task *task;
144 * Reconnect backoff delay.
146 struct GNUNET_TIME_Relative backoff;
149 * Size of the @e session_array.
151 unsigned int session_array_size;
157 * Re-establish the connection to the ATS service.
159 * @param sh handle to use to re-connect.
162 reconnect (struct GNUNET_ATS_SchedulingHandle *sh);
166 * Re-establish the connection to the ATS service.
168 * @param cls handle to use to re-connect.
171 reconnect_task (void *cls)
173 struct GNUNET_ATS_SchedulingHandle *sh = cls;
181 * Disconnect from ATS and then reconnect.
183 * @param sh our handle
186 force_reconnect (struct GNUNET_ATS_SchedulingHandle *sh)
190 GNUNET_MQ_destroy (sh->mq);
193 if (NULL != sh->client)
195 GNUNET_CLIENT_disconnect (sh->client);
198 sh->suggest_cb (sh->suggest_cb_cls,
200 GNUNET_BANDWIDTH_ZERO,
201 GNUNET_BANDWIDTH_ZERO);
202 sh->backoff = GNUNET_TIME_STD_BACKOFF (sh->backoff);
203 sh->task = GNUNET_SCHEDULER_add_delayed (sh->backoff,
210 * Find the session object corresponding to the given session ID.
212 * @param sh our handle
213 * @param session_id current session ID
214 * @param peer peer the session belongs to
215 * @return the session object (or NULL)
217 static struct GNUNET_ATS_AddressRecord *
218 find_session (struct GNUNET_ATS_SchedulingHandle *sh,
220 const struct GNUNET_PeerIdentity *peer)
222 struct GNUNET_ATS_AddressRecord *ar;
224 if (session_id >= sh->session_array_size)
231 ar = sh->session_array[session_id];
237 if (NULL == ar->address)
239 /* address was destroyed in the meantime, this can happen
240 as we communicate asynchronously with the ATS service. */
243 if (0 != memcmp (peer,
245 sizeof (struct GNUNET_PeerIdentity)))
255 * Get an available session ID.
257 * @param sh our handle
258 * @return an unused slot, but never NOT_FOUND (0)
261 find_empty_session_slot (struct GNUNET_ATS_SchedulingHandle *sh)
267 while ( ( (NOT_FOUND == off) ||
268 (NULL != sh->session_array[off % sh->session_array_size]) ) &&
269 (i < sh->session_array_size) )
274 if ( (NOT_FOUND != off % sh->session_array_size) &&
275 (NULL == sh->session_array[off % sh->session_array_size]) )
277 i = sh->session_array_size;
278 GNUNET_array_grow (sh->session_array,
279 sh->session_array_size,
280 sh->session_array_size * 2);
286 * Get the ID for the given session object.
288 * @param sh our handle
289 * @param session session object
290 * @param address the address we are looking for
291 * @return the session id or NOT_FOUND for error
294 find_session_id (struct GNUNET_ATS_SchedulingHandle *sh,
295 struct GNUNET_ATS_Session *session,
296 const struct GNUNET_HELLO_Address *address)
305 for (i = 1; i < sh->session_array_size; i++)
306 if ( (NULL != sh->session_array[i]) &&
307 (GNUNET_NO == sh->session_array[i]->in_destroy) &&
308 ( (session == sh->session_array[i]->session) ||
309 (NULL == sh->session_array[i]->session) ) &&
310 (0 == memcmp (&address->peer,
311 &sh->session_array[i]->address->peer,
312 sizeof (struct GNUNET_PeerIdentity))) &&
313 (0 == GNUNET_HELLO_address_cmp (address,
314 sh->session_array[i]->address)) )
321 * Release the session slot from the session table (ATS service is
322 * also done using it).
324 * @param sh our handle
325 * @param session_id identifies session that is no longer valid
328 release_session (struct GNUNET_ATS_SchedulingHandle *sh,
331 struct GNUNET_ATS_AddressRecord *ar;
333 if (NOT_FOUND == session_id)
335 if (session_id >= sh->session_array_size)
338 force_reconnect (sh);
341 /* this slot should have been removed from remove_session before */
342 ar = sh->session_array[session_id];
343 if (NULL != ar->session)
346 force_reconnect (sh);
349 GNUNET_HELLO_address_free (ar->address);
351 sh->session_array[session_id] = NULL;
356 * Type of a function to call when we receive a session release
357 * message from the service.
359 * @param cls the `struct GNUNET_ATS_SchedulingHandle`
360 * @param srm message received
363 handle_ats_session_release (void *cls,
364 const struct GNUNET_ATS_SessionReleaseMessage *srm)
366 struct GNUNET_ATS_SchedulingHandle *sh = cls;
368 /* Note: peer field in srm not necessary right now,
369 but might be good to have in the future */
371 ntohl (srm->session_id));
376 * Type of a function to call when we receive a address suggestion
377 * message from the service.
379 * @param cls the `struct GNUNET_ATS_SchedulingHandle`
380 * @param m message received
383 handle_ats_address_suggestion (void *cls,
384 const struct AddressSuggestionMessage *m)
386 struct GNUNET_ATS_SchedulingHandle *sh = cls;
387 struct GNUNET_ATS_AddressRecord *ar;
390 session_id = ntohl (m->session_id);
394 force_reconnect (sh);
397 ar = find_session (sh,
403 force_reconnect (sh);
406 if (NULL == sh->suggest_cb)
408 if (GNUNET_YES == ar->in_destroy)
410 /* ignore suggestion, as this address is dying, unless BW is 0,
411 in that case signal 'disconnect' via BW 0 */
412 if ( (0 == ntohl (m->bandwidth_out.value__)) &&
413 (0 == ntohl (m->bandwidth_in.value__)) )
415 LOG (GNUNET_ERROR_TYPE_DEBUG,
416 "ATS suggests disconnect from peer `%s' with BW %u/%u\n",
417 GNUNET_i2s (&ar->address->peer),
418 (unsigned int) ntohl (m->bandwidth_out.value__),
419 (unsigned int) ntohl (m->bandwidth_in.value__));
420 sh->suggest_cb (sh->suggest_cb_cls,
429 if ( (NULL == ar->session) &&
430 (GNUNET_HELLO_address_check_option (ar->address,
431 GNUNET_HELLO_ADDRESS_INFO_INBOUND)) )
436 sh->backoff = GNUNET_TIME_UNIT_ZERO;
437 LOG (GNUNET_ERROR_TYPE_DEBUG,
438 "ATS suggests address slot %u for peer `%s' using plugin %s\n",
440 GNUNET_i2s (&ar->address->peer),
441 ar->address->transport_name);
442 sh->suggest_cb (sh->suggest_cb_cls,
452 * We encountered an error handling the MQ to the
453 * ATS service. Reconnect.
455 * @param cls the `struct GNUNET_ATS_SchedulingHandle`
456 * @param error details about the error
459 error_handler (void *cls,
460 enum GNUNET_MQ_Error error)
462 struct GNUNET_ATS_SchedulingHandle *sh = cls;
464 LOG (GNUNET_ERROR_TYPE_DEBUG,
465 "ATS connection died (code %d), reconnecting\n",
467 force_reconnect (sh);
472 * Generate and transmit the `struct AddressAddMessage` for the given
475 * @param sh the scheduling handle to use for transmission
476 * @param ar the address to inform the ATS service about
479 send_add_address_message (struct GNUNET_ATS_SchedulingHandle *sh,
480 const struct GNUNET_ATS_AddressRecord *ar)
482 struct GNUNET_MQ_Envelope *ev;
483 struct AddressAddMessage *m;
489 return; /* disconnected, skip for now */
490 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != ar->properties.scope);
491 namelen = strlen (ar->address->transport_name) + 1;
492 msize = ar->address->address_length + namelen;
493 ev = GNUNET_MQ_msg_extra (m, msize, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_ADD);
494 m->peer = ar->address->peer;
495 m->address_length = htons (ar->address->address_length);
496 m->address_local_info = htonl ((uint32_t) ar->address->local_info);
497 m->plugin_name_length = htons (namelen);
498 m->session_id = htonl (ar->slot);
499 m->properties = ar->properties;
501 LOG (GNUNET_ERROR_TYPE_DEBUG,
502 "Adding address for peer `%s', plugin `%s', session %p slot %u\n",
503 GNUNET_i2s (&ar->address->peer),
504 ar->address->transport_name,
509 ar->address->address,
510 ar->address->address_length);
511 if (NULL != ar->address->transport_name)
512 memcpy (&pm[ar->address->address_length],
513 ar->address->transport_name,
515 GNUNET_MQ_send (sh->mq, ev);
520 * Re-establish the connection to the ATS service.
522 * @param sh handle to use to re-connect.
525 reconnect (struct GNUNET_ATS_SchedulingHandle *sh)
527 GNUNET_MQ_hd_fixed_size (ats_session_release,
528 GNUNET_MESSAGE_TYPE_ATS_SESSION_RELEASE,
529 struct GNUNET_ATS_SessionReleaseMessage);
530 GNUNET_MQ_hd_fixed_size (ats_address_suggestion,
531 GNUNET_MESSAGE_TYPE_ATS_ADDRESS_SUGGESTION,
532 struct AddressSuggestionMessage);
533 struct GNUNET_MQ_MessageHandler handlers[] = {
534 make_ats_session_release_handler (sh),
535 make_ats_address_suggestion_handler (sh),
536 GNUNET_MQ_handler_end ()
538 struct GNUNET_MQ_Envelope *ev;
539 struct ClientStartMessage *init;
541 struct GNUNET_ATS_AddressRecord *ar;
543 GNUNET_assert (NULL == sh->client);
544 sh->client = GNUNET_CLIENT_connect ("ats",
546 if (NULL == sh->client)
549 force_reconnect (sh);
552 sh->mq = GNUNET_MQ_queue_for_connection_client (sh->client,
556 ev = GNUNET_MQ_msg (init,
557 GNUNET_MESSAGE_TYPE_ATS_START);
558 init->start_flag = htonl (START_FLAG_SCHEDULING);
559 GNUNET_MQ_send (sh->mq, ev);
562 for (i=0;i<sh->session_array_size;i++)
564 ar = sh->session_array[i];
567 send_add_address_message (sh, ar);
575 * Initialize the ATS subsystem.
577 * @param cfg configuration to use
578 * @param suggest_cb notification to call whenever the suggestation changed
579 * @param suggest_cb_cls closure for @a suggest_cb
580 * @return ats context
582 struct GNUNET_ATS_SchedulingHandle *
583 GNUNET_ATS_scheduling_init (const struct GNUNET_CONFIGURATION_Handle *cfg,
584 GNUNET_ATS_AddressSuggestionCallback suggest_cb,
585 void *suggest_cb_cls)
587 struct GNUNET_ATS_SchedulingHandle *sh;
589 sh = GNUNET_new (struct GNUNET_ATS_SchedulingHandle);
591 sh->suggest_cb = suggest_cb;
592 sh->suggest_cb_cls = suggest_cb_cls;
593 GNUNET_array_grow (sh->session_array,
594 sh->session_array_size,
602 * Client is done with ATS scheduling, release resources.
604 * @param sh handle to release
607 GNUNET_ATS_scheduling_done (struct GNUNET_ATS_SchedulingHandle *sh)
609 struct GNUNET_ATS_AddressRecord *ar;
614 GNUNET_MQ_destroy (sh->mq);
617 if (NULL != sh->client)
619 GNUNET_CLIENT_disconnect (sh->client);
622 if (NULL != sh->task)
624 GNUNET_SCHEDULER_cancel (sh->task);
627 for (i=0;i<sh->session_array_size;i++)
629 if (NULL != (ar = sh->session_array[i]))
631 GNUNET_HELLO_address_free (ar->address);
633 sh->session_array[i] = NULL;
636 GNUNET_array_grow (sh->session_array,
637 sh->session_array_size,
644 * We have a new address ATS should know. Addresses have to be added
645 * with this function before they can be: updated, set in use and
649 * @param address the address
650 * @param session session handle, can be NULL
651 * @param prop performance data for the address
652 * @return handle to the address representation inside ATS, NULL
653 * on error (i.e. ATS knows this exact address already)
655 struct GNUNET_ATS_AddressRecord *
656 GNUNET_ATS_address_add (struct GNUNET_ATS_SchedulingHandle *sh,
657 const struct GNUNET_HELLO_Address *address,
658 struct GNUNET_ATS_Session *session,
659 const struct GNUNET_ATS_Properties *prop)
661 struct GNUNET_ATS_AddressRecord *ar;
668 /* we need a valid address */
672 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
673 namelen = strlen (address->transport_name) + 1;
674 msize = address->address_length + namelen;
675 if ((msize + sizeof (struct AddressUpdateMessage) >= GNUNET_SERVER_MAX_MESSAGE_SIZE) ||
676 (address->address_length >= GNUNET_SERVER_MAX_MESSAGE_SIZE) ||
677 (namelen >= GNUNET_SERVER_MAX_MESSAGE_SIZE) )
679 /* address too large for us, this should not happen */
689 /* Already existing, nothing todo, but this should not happen */
693 s = find_empty_session_slot (sh);
694 ar = GNUNET_new (struct GNUNET_ATS_AddressRecord);
697 ar->session = session;
698 ar->address = GNUNET_HELLO_address_copy (address);
699 GNUNET_ATS_properties_hton (&ar->properties,
701 sh->session_array[s] = ar;
702 send_add_address_message (sh, ar);
708 * An address was used to initiate a session.
710 * @param ar address record to update information for
711 * @param session session handle
714 GNUNET_ATS_address_add_session (struct GNUNET_ATS_AddressRecord *ar,
715 struct GNUNET_ATS_Session *session)
717 GNUNET_break (NULL == ar->session);
718 ar->session = session;
723 * A session was destroyed, disassociate it from the
724 * given address record. If this was an incoming
725 * addess, destroy the address as well.
727 * @param ar address record to update information for
728 * @param session session handle
729 * @return #GNUNET_YES if the @a ar was destroyed because
730 * it was an incoming address,
731 * #GNUNET_NO if the @ar was kept because we can
732 * use it still to establish a new session
735 GNUNET_ATS_address_del_session (struct GNUNET_ATS_AddressRecord *ar,
736 struct GNUNET_ATS_Session *session)
738 GNUNET_assert (session == ar->session);
740 if (GNUNET_HELLO_address_check_option (ar->address,
741 GNUNET_HELLO_ADDRESS_INFO_INBOUND))
743 GNUNET_ATS_address_destroy (ar);
751 * We have updated performance statistics for a given address. Note
752 * that this function can be called for addresses that are currently
753 * in use as well as addresses that are valid but not actively in use.
754 * Furthermore, the peer may not even be connected to us right now (in
755 * which case the call may be ignored or the information may be stored
756 * for later use). Update bandwidth assignments.
758 * @param ar address record to update information for
759 * @param prop performance data for the address
762 GNUNET_ATS_address_update (struct GNUNET_ATS_AddressRecord *ar,
763 const struct GNUNET_ATS_Properties *prop)
765 struct GNUNET_ATS_SchedulingHandle *sh = ar->sh;
766 struct GNUNET_MQ_Envelope *ev;
767 struct AddressUpdateMessage *m;
769 LOG (GNUNET_ERROR_TYPE_DEBUG,
770 "Updating address for peer `%s', plugin `%s', session %p slot %u\n",
771 GNUNET_i2s (&ar->address->peer),
772 ar->address->transport_name,
775 GNUNET_break (GNUNET_ATS_NET_UNSPECIFIED != prop->scope);
776 GNUNET_ATS_properties_hton (&ar->properties,
779 return; /* disconnected, skip for now */
780 ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_UPDATE);
781 m->session_id = htonl (ar->slot);
782 m->peer = ar->address->peer;
783 m->properties = ar->properties;
784 GNUNET_MQ_send (sh->mq,
790 * An address got destroyed, stop using it as a valid address.
792 * @param ar address to destroy
795 GNUNET_ATS_address_destroy (struct GNUNET_ATS_AddressRecord *ar)
797 struct GNUNET_ATS_SchedulingHandle *sh = ar->sh;
798 struct GNUNET_MQ_Envelope *ev;
799 struct AddressDestroyedMessage *m;
801 LOG (GNUNET_ERROR_TYPE_DEBUG,
802 "Deleting address for peer `%s', plugin `%s', slot %u session %p\n",
803 GNUNET_i2s (&ar->address->peer),
804 ar->address->transport_name,
807 GNUNET_break (NULL == ar->session);
809 ar->in_destroy = GNUNET_YES;
812 ev = GNUNET_MQ_msg (m, GNUNET_MESSAGE_TYPE_ATS_ADDRESS_DESTROYED);
813 m->session_id = htonl (ar->slot);
814 m->peer = ar->address->peer;
815 GNUNET_MQ_send (sh->mq, ev);
819 /* end of ats_api_scheduling.c */