2 This file is part of GNUnet.
3 Copyright (C) 2010, 2011, 2016 GNUnet e.V.
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.
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.
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/>.
18 SPDX-License-Identifier: AGPL3.0-or-later
21 * @file ats/ats_api_performance.c
22 * @brief automatic transport selection and outbound bandwidth determination
23 * @author Christian Grothoff
24 * @author Matthias Wachs
27 #include "gnunet_ats_service.h"
31 #define LOG(kind, ...) GNUNET_log_from(kind, "ats-performance-api", __VA_ARGS__)
35 * Linked list of pending reservations.
37 struct GNUNET_ATS_ReservationContext {
41 struct GNUNET_ATS_ReservationContext *next;
46 struct GNUNET_ATS_ReservationContext *prev;
51 struct GNUNET_PeerIdentity peer;
59 * Function to call on result.
61 GNUNET_ATS_ReservationCallback rcb;
69 * Do we need to undo this reservation if it succeeded? Set to
70 * #GNUNET_YES if a reservation is cancelled. (at that point, 'info'
71 * is also set to NULL; however, info will ALSO be NULL for the
72 * reservation context that is created to undo the original request,
73 * so 'info' being NULL cannot be used to check if undo is
81 * Linked list of pending reservations.
83 struct GNUNET_ATS_AddressListHandle {
87 struct GNUNET_ATS_AddressListHandle *next;
92 struct GNUNET_ATS_AddressListHandle *prev;
97 struct GNUNET_ATS_PerformanceHandle *ph;
102 GNUNET_ATS_AddressInformationCallback cb;
105 * Callback closure for @e cb
112 struct GNUNET_PeerIdentity peer;
115 * Return all or specific peer only
120 * Return all or used address only
125 * Request multiplexing
132 * ATS Handle to obtain and/or modify performance information.
134 struct GNUNET_ATS_PerformanceHandle {
138 const struct GNUNET_CONFIGURATION_Handle *cfg;
141 * Callback to invoke when an address has performance changes.
143 GNUNET_ATS_AddressInformationCallback addr_info_cb;
146 * Closure for @e addr_info_cb.
148 void *addr_info_cb_cls;
151 * Connection to ATS service.
153 struct GNUNET_MQ_Handle *mq;
156 * Head of linked list of pending reservation requests.
158 struct GNUNET_ATS_ReservationContext *reservation_head;
161 * Tail of linked list of pending reservation requests.
163 struct GNUNET_ATS_ReservationContext *reservation_tail;
166 * Head of linked list of pending address list requests.
168 struct GNUNET_ATS_AddressListHandle *addresslist_head;
171 * Tail of linked list of pending address list requests.
173 struct GNUNET_ATS_AddressListHandle *addresslist_tail;
176 * Current request for transmission to ATS.
178 struct GNUNET_CLIENT_TransmitHandle *th;
181 * Task to trigger reconnect.
183 struct GNUNET_SCHEDULER_Task *task;
186 * Reconnect backoff delay.
188 struct GNUNET_TIME_Relative backoff;
191 * Monitor request multiplexing
196 * Request multiplexing
201 * Is the receive loop active?
207 * Re-establish the connection to the ATS service.
209 * @param ph handle to use to re-connect.
212 reconnect(struct GNUNET_ATS_PerformanceHandle *ph);
216 * Re-establish the connection to the ATS service.
218 * @param cls handle to use to re-connect.
221 reconnect_task(void *cls)
223 struct GNUNET_ATS_PerformanceHandle *ph = cls;
231 * Reconnect to the ATS service, something went wrong.
233 * @param ph handle to reconnect
236 do_reconnect(struct GNUNET_ATS_PerformanceHandle *ph)
238 struct GNUNET_ATS_ReservationContext *rc;
239 struct GNUNET_ATS_AddressListHandle *alh;
240 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_zero;
244 GNUNET_MQ_destroy(ph->mq);
247 while (NULL != (rc = ph->reservation_head))
249 GNUNET_CONTAINER_DLL_remove(ph->reservation_head,
250 ph->reservation_tail,
256 GNUNET_TIME_UNIT_FOREVER_REL);
259 bandwidth_zero.value__ = htonl(0);
260 while (NULL != (alh = ph->addresslist_head))
262 GNUNET_CONTAINER_DLL_remove(ph->addresslist_head,
263 ph->addresslist_tail,
274 if (NULL != ph->addr_info_cb)
276 /* Indicate reconnect */
277 ph->addr_info_cb(ph->addr_info_cb_cls,
284 ph->backoff = GNUNET_TIME_STD_BACKOFF(ph->backoff);
285 ph->task = GNUNET_SCHEDULER_add_delayed(ph->backoff,
292 * We received a peer information message. Validate and process it.
294 * @param cls our context with the callback
295 * @param pi the message
296 * @return #GNUNET_OK if the message was well-formed
299 check_peer_information(void *cls,
300 const struct PeerInformationMessage *pi)
302 const char *plugin_address;
303 const char *plugin_name;
304 uint16_t plugin_address_length;
305 uint16_t plugin_name_length;
307 plugin_address_length = ntohs(pi->address_length);
308 plugin_name_length = ntohs(pi->plugin_name_length);
309 plugin_address = (const char *)&pi[1];
310 plugin_name = &plugin_address[plugin_address_length];
311 if ((plugin_address_length + plugin_name_length
312 + sizeof(struct PeerInformationMessage) != ntohs(pi->header.size)) ||
313 (plugin_name[plugin_name_length - 1] != '\0'))
316 return GNUNET_SYSERR;
323 * We received a peer information message. Validate and process it.
325 * @param cls our context with the callback
326 * @param pi the message
327 * @return #GNUNET_OK if the message was well-formed
330 handle_peer_information(void *cls,
331 const struct PeerInformationMessage *pi)
333 struct GNUNET_ATS_PerformanceHandle *ph = cls;
334 const char *plugin_address;
335 const char *plugin_name;
336 struct GNUNET_HELLO_Address address;
337 uint16_t plugin_address_length;
339 struct GNUNET_ATS_Properties prop;
341 if (NULL == ph->addr_info_cb)
343 plugin_address_length = ntohs(pi->address_length);
344 addr_active = (int)ntohl(pi->address_active);
345 plugin_address = (const char *)&pi[1];
346 plugin_name = &plugin_address[plugin_address_length];
348 GNUNET_ATS_properties_ntoh(&prop,
350 address.peer = pi->peer;
351 address.local_info = (enum GNUNET_HELLO_AddressInfo)ntohl(pi->address_local_info);
352 address.address = plugin_address;
353 address.address_length = plugin_address_length;
354 address.transport_name = plugin_name;
355 ph->addr_info_cb(ph->addr_info_cb_cls,
365 * We received a reservation result message. Validate and process it.
367 * @param cls our context with the callback
368 * @param rr the message
371 handle_reservation_result(void *cls,
372 const struct ReservationResultMessage *rr)
374 struct GNUNET_ATS_PerformanceHandle *ph = cls;
375 struct GNUNET_ATS_ReservationContext *rc;
378 amount = ntohl(rr->amount);
379 rc = ph->reservation_head;
380 if (0 != GNUNET_memcmp(&rr->peer,
387 GNUNET_CONTAINER_DLL_remove(ph->reservation_head,
388 ph->reservation_tail,
393 /* tell client if not cancelled */
398 GNUNET_TIME_relative_ntoh(rr->res_delay));
402 /* amount non-zero, but client cancelled, consider undo! */
403 if (GNUNET_YES != rc->undo)
406 return; /* do not try to undo failed undos or negative amounts */
409 (void)GNUNET_ATS_reserve_bandwidth(ph,
417 * We received a PeerInformationMessage. Validate it.
419 * @param cls our context with the callback
420 * @param pi the message
421 * @return #GNUNET_OK if the message was well-formed
424 check_address_list(void *cls,
425 const struct PeerInformationMessage *pi)
427 const char *plugin_address;
428 const char *plugin_name;
429 uint16_t plugin_address_length;
430 uint16_t plugin_name_length;
432 plugin_address_length = ntohs(pi->address_length);
433 plugin_name_length = ntohs(pi->plugin_name_length);
434 plugin_address = (const char *)&pi[1];
435 plugin_name = &plugin_address[plugin_address_length];
436 if ((plugin_address_length + plugin_name_length
437 + sizeof(struct PeerInformationMessage) != ntohs(pi->header.size)) ||
438 (plugin_name[plugin_name_length - 1] != '\0'))
441 return GNUNET_SYSERR;
448 * We received a #GNUNET_MESSAGE_TYPE_ATS_ADDRESSLIST_RESPONSE.
451 * @param cls our context with the callback
452 * @param pi the message
455 handle_address_list(void *cls,
456 const struct PeerInformationMessage *pi)
458 struct GNUNET_ATS_PerformanceHandle *ph = cls;
459 struct GNUNET_ATS_AddressListHandle *alh;
460 struct GNUNET_ATS_AddressListHandle *next;
461 const char *plugin_address;
462 const char *plugin_name;
463 struct GNUNET_HELLO_Address address;
464 struct GNUNET_PeerIdentity allzeros;
465 struct GNUNET_BANDWIDTH_Value32NBO bandwidth_zero;
466 struct GNUNET_ATS_Properties prop;
467 uint16_t plugin_address_length;
468 uint16_t plugin_name_length;
473 active = ntohl(pi->address_active);
474 plugin_address_length = ntohs(pi->address_length);
475 plugin_name_length = ntohs(pi->plugin_name_length);
476 plugin_address = (const char *)&pi[1];
477 plugin_name = &plugin_address[plugin_address_length];
478 LOG(GNUNET_ERROR_TYPE_DEBUG,
479 "Received ATS_ADDRESSLIST_RESPONSE message for peer %s and plugin %s\n",
480 GNUNET_i2s(&pi->peer),
483 next = ph->addresslist_head;
484 while (NULL != (alh = next))
491 return; /* was canceled */
493 memset(&allzeros, '\0', sizeof(allzeros));
494 if ((0 == GNUNET_is_zero(&pi->peer)) &&
495 (0 == plugin_name_length) &&
496 (0 == plugin_address_length))
499 LOG(GNUNET_ERROR_TYPE_DEBUG,
500 "Received last message for ATS_ADDRESSLIST_RESPONSE\n");
501 bandwidth_zero.value__ = htonl(0);
502 GNUNET_CONTAINER_DLL_remove(ph->addresslist_head,
503 ph->addresslist_tail,
516 address.peer = pi->peer;
517 address.address = plugin_address;
518 address.address_length = plugin_address_length;
519 address.transport_name = plugin_name;
520 if (((GNUNET_YES == alh->all_addresses) ||
521 (GNUNET_YES == active)) &&
524 GNUNET_ATS_properties_ntoh(&prop,
537 * Generic error handler, called with the appropriate error code and
538 * the same closure specified at the creation of the message queue.
539 * Not every message queue implementation supports an error handler.
541 * @param cls closure with the `struct GNUNET_ATS_PerformanceHandle *`
542 * @param error error code
545 mq_error_handler(void *cls,
546 enum GNUNET_MQ_Error error)
548 struct GNUNET_ATS_PerformanceHandle *ph = cls;
555 * Re-establish the connection to the ATS service.
557 * @param ph handle to use to re-connect.
560 reconnect(struct GNUNET_ATS_PerformanceHandle *ph)
562 struct GNUNET_MQ_MessageHandler handlers[] = {
563 GNUNET_MQ_hd_var_size(peer_information,
564 GNUNET_MESSAGE_TYPE_ATS_PEER_INFORMATION,
565 struct PeerInformationMessage,
567 GNUNET_MQ_hd_fixed_size(reservation_result,
568 GNUNET_MESSAGE_TYPE_ATS_RESERVATION_RESULT,
569 struct ReservationResultMessage,
571 GNUNET_MQ_hd_var_size(address_list,
572 GNUNET_MESSAGE_TYPE_ATS_ADDRESSLIST_RESPONSE,
573 struct PeerInformationMessage,
575 GNUNET_MQ_handler_end()
577 struct GNUNET_MQ_Envelope *env;
578 struct ClientStartMessage *init;
580 GNUNET_assert(NULL == ph->mq);
581 ph->mq = GNUNET_CLIENT_connect(ph->cfg,
588 env = GNUNET_MQ_msg(init,
589 GNUNET_MESSAGE_TYPE_ATS_START);
590 init->start_flag = htonl((NULL == ph->addr_info_cb)
591 ? START_FLAG_PERFORMANCE_NO_PIC
592 : START_FLAG_PERFORMANCE_WITH_PIC);
593 GNUNET_MQ_send(ph->mq,
599 * Get handle to access performance API of the ATS subsystem.
601 * @param cfg configuration to use
602 * @param addr_info_cb callback called when performance characteristics for
604 * @param addr_info_cb_cls closure for @a addr_info_cb
605 * @return ats performance context
607 struct GNUNET_ATS_PerformanceHandle *
608 GNUNET_ATS_performance_init(const struct GNUNET_CONFIGURATION_Handle *cfg,
609 GNUNET_ATS_AddressInformationCallback addr_info_cb,
610 void *addr_info_cb_cls)
612 struct GNUNET_ATS_PerformanceHandle *ph;
614 ph = GNUNET_new(struct GNUNET_ATS_PerformanceHandle);
616 ph->addr_info_cb = addr_info_cb;
617 ph->addr_info_cb_cls = addr_info_cb_cls;
629 * Client is done using the ATS performance subsystem, release resources.
634 GNUNET_ATS_performance_done(struct GNUNET_ATS_PerformanceHandle *ph)
636 struct GNUNET_ATS_ReservationContext *rc;
637 struct GNUNET_ATS_AddressListHandle *alh;
639 while (NULL != (alh = ph->addresslist_head))
641 GNUNET_CONTAINER_DLL_remove(ph->addresslist_head,
642 ph->addresslist_tail,
646 while (NULL != (rc = ph->reservation_head))
648 GNUNET_CONTAINER_DLL_remove(ph->reservation_head,
649 ph->reservation_tail,
651 GNUNET_break(NULL == rc->rcb);
654 if (NULL != ph->task)
656 GNUNET_SCHEDULER_cancel(ph->task);
661 GNUNET_MQ_destroy(ph->mq);
669 * Reserve inbound bandwidth from the given peer. ATS will look at
670 * the current amount of traffic we receive from the peer and ensure
671 * that the peer could add @a amount of data to its stream.
673 * @param ph performance handle
674 * @param peer identifies the peer
675 * @param amount reserve N bytes for receiving, negative
676 * amounts can be used to undo a (recent) reservation;
677 * @param rcb function to call with the resulting reservation information
678 * @param rcb_cls closure for @a rcb
679 * @return NULL on error
680 * @deprecated will be replaced soon
682 struct GNUNET_ATS_ReservationContext *
683 GNUNET_ATS_reserve_bandwidth(struct GNUNET_ATS_PerformanceHandle *ph,
684 const struct GNUNET_PeerIdentity *peer,
686 GNUNET_ATS_ReservationCallback rcb,
689 struct GNUNET_ATS_ReservationContext *rc;
690 struct GNUNET_MQ_Envelope *env;
691 struct ReservationRequestMessage *m;
695 rc = GNUNET_new(struct GNUNET_ATS_ReservationContext);
699 rc->rcb_cls = rcb_cls;
702 rc->undo = GNUNET_YES;
703 GNUNET_CONTAINER_DLL_insert_tail(ph->reservation_head,
704 ph->reservation_tail,
706 env = GNUNET_MQ_msg(m,
707 GNUNET_MESSAGE_TYPE_ATS_RESERVATION_REQUEST);
708 m->amount = htonl(amount);
710 GNUNET_MQ_send(ph->mq,
717 * Cancel request for reserving bandwidth.
719 * @param rc context returned by the original #GNUNET_ATS_reserve_bandwidth() call
722 GNUNET_ATS_reserve_bandwidth_cancel(struct GNUNET_ATS_ReservationContext *rc)
729 * Get information about addresses known to the ATS subsystem.
731 * @param ph the performance handle to use
732 * @param peer peer idm can be NULL for all peers
733 * @param all #GNUNET_YES to get information about all addresses or #GNUNET_NO to
734 * get only address currently used
735 * @param infocb callback to call with the addresses,
736 * will callback with address == NULL when done
737 * @param infocb_cls closure for @a infocb
738 * @return ats performance context
740 struct GNUNET_ATS_AddressListHandle*
741 GNUNET_ATS_performance_list_addresses(struct GNUNET_ATS_PerformanceHandle *ph,
742 const struct GNUNET_PeerIdentity *peer,
744 GNUNET_ATS_AddressInformationCallback infocb,
747 struct GNUNET_ATS_AddressListHandle *alh;
748 struct GNUNET_MQ_Envelope *env;
749 struct AddressListRequestMessage *m;
758 alh = GNUNET_new(struct GNUNET_ATS_AddressListHandle);
761 alh->cb_cls = infocb_cls;
763 alh->all_addresses = all;
766 alh->all_peers = GNUNET_YES;
770 alh->all_peers = GNUNET_NO;
773 GNUNET_CONTAINER_DLL_insert(ph->addresslist_head,
774 ph->addresslist_tail,
776 env = GNUNET_MQ_msg(m,
777 GNUNET_MESSAGE_TYPE_ATS_ADDRESSLIST_REQUEST);
779 m->id = htonl(alh->id);
782 GNUNET_MQ_send(ph->mq,
789 * Cancel a pending address listing operation
791 * @param alh the handle of the request to cancel
794 GNUNET_ATS_performance_list_addresses_cancel(struct GNUNET_ATS_AddressListHandle *alh)
796 struct GNUNET_ATS_PerformanceHandle *ph = alh->ph;
798 GNUNET_CONTAINER_DLL_remove(ph->addresslist_head,
799 ph->addresslist_tail,
806 * Convert a `enum GNUNET_ATS_PreferenceType` to a string
808 * @param type the preference type
809 * @return a string or NULL if invalid
812 GNUNET_ATS_print_preference_type(enum GNUNET_ATS_PreferenceKind type)
814 const char *prefs[] = GNUNET_ATS_PreferenceTypeString;
816 if (type < GNUNET_ATS_PREFERENCE_END)
823 * Change preferences for the given peer. Preference changes are forgotten if peers
826 * @param ph performance handle
827 * @param peer identifies the peer
828 * @param ... #GNUNET_ATS_PREFERENCE_END-terminated specification of the desired changes
831 GNUNET_ATS_performance_change_preference(struct GNUNET_ATS_PerformanceHandle *ph,
832 const struct GNUNET_PeerIdentity *peer,
835 struct GNUNET_MQ_Envelope *env;
836 struct ChangePreferenceMessage *m;
838 struct PreferenceInformation *pi;
840 enum GNUNET_ATS_PreferenceKind kind;
846 while (GNUNET_ATS_PREFERENCE_END !=
847 (kind = GNUNET_VA_ARG_ENUM(ap, GNUNET_ATS_PreferenceKind)))
851 case GNUNET_ATS_PREFERENCE_BANDWIDTH:
853 (void)va_arg(ap, double);
856 case GNUNET_ATS_PREFERENCE_LATENCY:
858 (void)va_arg(ap, double);
866 env = GNUNET_MQ_msg_extra(m,
867 count * sizeof(struct PreferenceInformation),
868 GNUNET_MESSAGE_TYPE_ATS_PREFERENCE_CHANGE);
869 m->num_preferences = htonl(count);
871 pi = (struct PreferenceInformation *)&m[1];
874 while (GNUNET_ATS_PREFERENCE_END != (kind =
875 GNUNET_VA_ARG_ENUM(ap, GNUNET_ATS_PreferenceKind)))
877 pi[count].preference_kind = htonl(kind);
880 case GNUNET_ATS_PREFERENCE_BANDWIDTH:
881 pi[count].preference_value = (float)va_arg(ap, double);
886 case GNUNET_ATS_PREFERENCE_LATENCY:
887 pi[count].preference_value = (float)va_arg(ap, double);
897 GNUNET_MQ_send(ph->mq,
903 * Send feedback to ATS on how good a the requirements for a peer and a
904 * preference is satisfied by ATS
906 * @param ph performance handle
907 * @param scope the time interval this valid for: [now - scope .. now]
908 * @param peer identifies the peer
909 * @param ... #GNUNET_ATS_PREFERENCE_END-terminated specification of the desired changes
912 GNUNET_ATS_performance_give_feedback(struct GNUNET_ATS_PerformanceHandle *ph,
913 const struct GNUNET_PeerIdentity *peer,
914 const struct GNUNET_TIME_Relative scope,
917 struct GNUNET_MQ_Envelope *env;
918 struct FeedbackPreferenceMessage *m;
920 struct PreferenceInformation *pi;
922 enum GNUNET_ATS_PreferenceKind kind;
928 while (GNUNET_ATS_PREFERENCE_END !=
929 (kind = GNUNET_VA_ARG_ENUM(ap, GNUNET_ATS_PreferenceKind)))
933 case GNUNET_ATS_PREFERENCE_BANDWIDTH:
935 (void)va_arg(ap, double);
938 case GNUNET_ATS_PREFERENCE_LATENCY:
940 (void)va_arg(ap, double);
948 env = GNUNET_MQ_msg_extra(m,
949 count * sizeof(struct PreferenceInformation),
950 GNUNET_MESSAGE_TYPE_ATS_PREFERENCE_FEEDBACK);
951 m->scope = GNUNET_TIME_relative_hton(scope);
952 m->num_feedback = htonl(count);
954 pi = (struct PreferenceInformation *)&m[1];
957 while (GNUNET_ATS_PREFERENCE_END != (kind =
958 GNUNET_VA_ARG_ENUM(ap, GNUNET_ATS_PreferenceKind)))
960 pi[count].preference_kind = htonl(kind);
963 case GNUNET_ATS_PREFERENCE_BANDWIDTH:
964 pi[count].preference_value = (float)va_arg(ap, double);
969 case GNUNET_ATS_PREFERENCE_LATENCY:
970 pi[count].preference_value = (float)va_arg(ap, double);
980 GNUNET_MQ_send(ph->mq,
984 /* end of ats_api_performance.c */