* @author Christian Grothoff
*
* FIXME:
- * - connection management
- * + properly (evaluate, kill old ones, search for new ones)
+ * - proper connection evaluation during connection management:
* + when managing connections, distinguish those that
* have (recently) had traffic from those that were
* never ready (or not recently)
+ * + consider quality of current connection set when deciding
+ * how often to do maintenance
+ * + interact with PEER to drive DHT GET/PUT operations based
+ * on how much we like our connections
*/
#include "platform.h"
#include "gnunet_util_lib.h"
struct GNUNET_MQ_Handle *mq;
/**
- * DLL of connections that are actively used to reach the destination peer.
+ * DLL of ready connections that are actively used to reach the destination peer.
*/
- struct CadetTConnection *connection_head;
+ struct CadetTConnection *connection_ready_head;
/**
- * DLL of connections that are actively used to reach the destination peer.
+ * DLL of ready connections that are actively used to reach the destination peer.
*/
- struct CadetTConnection *connection_tail;
+ struct CadetTConnection *connection_ready_tail;
+
+ /**
+ * DLL of connections that we maintain that might be used to reach the destination peer.
+ */
+ struct CadetTConnection *connection_busy_head;
+
+ /**
+ * DLL of connections that we maintain that might be used to reach the destination peer.
+ */
+ struct CadetTConnection *connection_busy_tail;
/**
* Channels inside this tunnel. Maps
struct GNUNET_TIME_Absolute next_kx_attempt;
/**
- * Number of connections in the @e connection_head DLL.
+ * Number of connections in the @e connection_ready_head DLL.
+ */
+ unsigned int num_ready_connections;
+
+ /**
+ * Number of connections in the @e connection_busy_head DLL.
*/
- unsigned int num_connections;
+ unsigned int num_busy_connections;
/**
* How often have we tried and failed to decrypt a message using
};
+/**
+ * Connection @a ct is now unready, clear it's ready flag
+ * and move it from the ready DLL to the busy DLL.
+ *
+ * @param ct connection to move to unready status
+ */
+static void
+mark_connection_unready (struct CadetTConnection *ct)
+{
+ struct CadetTunnel *t = ct->t;
+
+ GNUNET_assert (GNUNET_YES == ct->is_ready);
+ GNUNET_CONTAINER_DLL_remove (t->connection_ready_head,
+ t->connection_ready_tail,
+ ct);
+ GNUNET_assert (0 < t->num_ready_connections);
+ t->num_ready_connections--;
+ ct->is_ready = GNUNET_NO;
+ GNUNET_CONTAINER_DLL_insert (t->connection_busy_head,
+ t->connection_busy_tail,
+ ct);
+ t->num_busy_connections++;
+}
+
+
/**
* Get the static string for the peer this tunnel is directed.
*
* @return Number of connections created, either being established or ready.
*/
unsigned int
-GCT_count_any_connections (struct CadetTunnel *t)
+GCT_count_any_connections (const struct CadetTunnel *t)
{
- return t->num_connections;
+ return t->num_ready_connections + t->num_busy_connections;
}
static struct CadetTConnection *
get_ready_connection (struct CadetTunnel *t)
{
- for (struct CadetTConnection *pos = t->connection_head;
- NULL != pos;
- pos = pos->next)
- if (GNUNET_YES == pos->is_ready)
- {
- if (pos != t->connection_tail)
- {
- /* move 'pos' to the end, so we try other ready connections
- first next time (round-robin, modulo availability) */
- GNUNET_CONTAINER_DLL_remove (t->connection_head,
- t->connection_tail,
- pos);
- GNUNET_CONTAINER_DLL_insert_tail (t->connection_head,
- t->connection_tail,
- pos);
- }
- return pos;
- }
- return NULL;
+ return t->connection_ready_head;
}
/**
* Send a KX message.
*
- * @param t Tunnel on which to send it.
+ * @param t tunnel on which to send the KX_AUTH
+ * @param ct Tunnel and connection on which to send the KX_AUTH, NULL if
+ * we are to find one that is ready.
* @param ax axolotl key context to use
*/
static void
send_kx (struct CadetTunnel *t,
+ struct CadetTConnection *ct,
struct CadetTunnelAxolotl *ax)
{
- struct CadetTConnection *ct;
struct CadetConnection *cc;
struct GNUNET_MQ_Envelope *env;
struct GNUNET_CADET_TunnelKeyExchangeMessage *msg;
enum GNUNET_CADET_KX_Flags flags;
- ct = get_ready_connection (t);
+ if (NULL == ct)
+ ct = get_ready_connection (t);
if (NULL == ct)
{
LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Wanted to send KX on %s, but no connection is ready, deferring\n",
- GCT_2s (t));
+ "Wanted to send %s in state %s, but no connection is ready, deferring\n",
+ GCT_2s (t),
+ estate2s (t->estate));
t->next_kx_attempt = GNUNET_TIME_absolute_get ();
return;
}
cc = ct->cc;
LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Sending KX on %s using %s in state %s\n",
+ "Sending KX on %s via %s using %s in state %s\n",
GCT_2s (t),
- GCC_2s (ct->cc),
+ GCC_2s (cc),
estate2s (t->estate));
-
env = GNUNET_MQ_msg (msg,
GNUNET_MESSAGE_TYPE_CADET_TUNNEL_KX);
flags = GNUNET_CADET_KX_FLAG_FORCE_REPLY; /* always for KX */
&msg->ephemeral_key);
GNUNET_CRYPTO_ecdhe_key_get_public (ax->DHRs,
&msg->ratchet_key);
- ct->is_ready = GNUNET_NO;
+ mark_connection_unready (ct);
t->kx_retry_delay = GNUNET_TIME_STD_BACKOFF (t->kx_retry_delay);
t->next_kx_attempt = GNUNET_TIME_relative_to_absolute (t->kx_retry_delay);
if (CADET_TUNNEL_KEY_UNINITIALIZED == t->estate)
/**
* Send a KX_AUTH message.
*
- * @param t Tunnel on which to send it.
+ * @param t tunnel on which to send the KX_AUTH
+ * @param ct Tunnel and connection on which to send the KX_AUTH, NULL if
+ * we are to find one that is ready.
* @param ax axolotl key context to use
* @param force_reply Force the other peer to reply with a KX_AUTH message
* (set if we would like to transmit right now, but cannot)
*/
static void
send_kx_auth (struct CadetTunnel *t,
+ struct CadetTConnection *ct,
struct CadetTunnelAxolotl *ax,
int force_reply)
{
- struct CadetTConnection *ct;
struct CadetConnection *cc;
struct GNUNET_MQ_Envelope *env;
struct GNUNET_CADET_TunnelKeyExchangeAuthMessage *msg;
enum GNUNET_CADET_KX_Flags flags;
- ct = get_ready_connection (t);
+ if ( (NULL == ct) ||
+ (GNUNET_NO == ct->is_ready) )
+ ct = get_ready_connection (t);
if (NULL == ct)
{
LOG (GNUNET_ERROR_TYPE_DEBUG,
= GNUNET_TIME_relative_to_absolute (t->kx_retry_delay);
/* Send via cc, mark it as unready */
- ct->is_ready = GNUNET_NO;
+ mark_connection_unready (ct);
/* Update state machine, unless we are already OK */
if (CADET_TUNNEL_KEY_OK != t->estate)
case CADET_TUNNEL_KEY_UNINITIALIZED: /* first attempt */
case CADET_TUNNEL_KEY_AX_SENT: /* trying again */
send_kx (t,
+ NULL,
&t->ax);
break;
case CADET_TUNNEL_KEY_AX_RECV:
ax = &t->ax;
}
send_kx_auth (t,
+ NULL,
ax,
(0 == GCT_count_channels (t))
? GNUNET_NO
ax = &t->ax;
}
send_kx_auth (t,
+ NULL,
ax,
(0 == GCT_count_channels (t))
? GNUNET_NO
ax = &t->ax;
}
send_kx_auth (t,
+ NULL,
ax,
GNUNET_NO);
break;
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Got duplicate KX. Firing back KX_AUTH.\n");
send_kx_auth (t,
+ ct,
&t->ax,
GNUNET_NO);
return;
"Got duplicate unverified KX on %s. Fire back KX_AUTH again.\n",
GCT_2s (t));
send_kx_auth (t,
+ ct,
t->unverified_ax,
GNUNET_NO);
return;
{
struct CadetTunnel *t = ct->t;
- GNUNET_CONTAINER_DLL_remove (t->connection_head,
- t->connection_tail,
- ct);
+ if (GNUNET_YES == ct->is_ready)
+ GNUNET_CONTAINER_DLL_remove (t->connection_ready_head,
+ t->connection_ready_tail,
+ ct);
+ else
+ GNUNET_CONTAINER_DLL_remove (t->connection_busy_head,
+ t->connection_busy_tail,
+ ct);
GNUNET_free (ct);
}
+/**
+ * Clean up connection @a ct of a tunnel.
+ *
+ * @param cls the `struct CadetTunnel`
+ * @param ct connection to clean up
+ */
+static void
+destroy_t_connection (void *cls,
+ struct CadetTConnection *ct)
+{
+ struct CadetTunnel *t = cls;
+ struct CadetConnection *cc = ct->cc;
+
+ GNUNET_assert (ct->t == t);
+ GCT_connection_lost (ct);
+ GCC_destroy_without_tunnel (cc);
+}
+
+
/**
* This tunnel is no longer used, destroy it.
*
destroy_tunnel (void *cls)
{
struct CadetTunnel *t = cls;
- struct CadetTConnection *ct;
struct CadetTunnelQueueEntry *tq;
t->destroy_task = NULL;
"Destroying idle %s\n",
GCT_2s (t));
GNUNET_assert (0 == GCT_count_channels (t));
- while (NULL != (ct = t->connection_head))
- {
- struct CadetConnection *cc;
-
- GNUNET_assert (ct->t == t);
- cc = ct->cc;
- GCT_connection_lost (ct);
- GCC_destroy_without_tunnel (cc);
- }
+ GCT_iterate_connections (t,
+ &destroy_t_connection,
+ t);
+ GNUNET_assert (NULL == t->connection_ready_head);
+ GNUNET_assert (NULL == t->connection_busy_head);
while (NULL != (tq = t->tq_head))
{
if (NULL != tq->cont)
tq);
if (NULL != tq->cid)
*tq->cid = *GCC_get_id (ct->cc);
- ct->is_ready = GNUNET_NO;
+ mark_connection_unready (ct);
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Sending payload of %s on %s\n",
GCT_2s (t),
"%s no longer ready for %s\n",
GCC_2s (ct->cc),
GCT_2s (t));
- ct->is_ready = GNUNET_NO;
+ mark_connection_unready (ct);
return;
}
+ GNUNET_assert (GNUNET_NO == ct->is_ready);
+ GNUNET_CONTAINER_DLL_remove (t->connection_busy_head,
+ t->connection_busy_tail,
+ ct);
+ GNUNET_assert (0 < t->num_busy_connections);
+ t->num_busy_connections--;
ct->is_ready = GNUNET_YES;
+ GNUNET_CONTAINER_DLL_insert_tail (t->connection_ready_head,
+ t->connection_ready_tail,
+ ct);
+ t->num_ready_connections++;
+
LOG (GNUNET_ERROR_TYPE_DEBUG,
"%s now ready for %s in state %s\n",
GCC_2s (ct->cc),
t->kx_task = NULL;
}
send_kx (t,
+ ct,
&t->ax);
break;
case CADET_TUNNEL_KEY_AX_RECV:
t->kx_task = NULL;
}
send_kx_auth (t,
+ ct,
&t->ax,
GNUNET_NO);
return;
}
+/**
+ * Closure for #evaluate_connection. Used to assemble summary information
+ * about the existing connections so we can evaluate a new path.
+ */
+struct EvaluationSummary
+{
+
+ /**
+ * Minimum length of any of our connections, `UINT_MAX` if we have none.
+ */
+ unsigned int min_length;
+
+ /**
+ * Maximum length of any of our connections, 0 if we have none.
+ */
+ unsigned int max_length;
+
+ /**
+ * Minimum desirability of any of our connections, UINT64_MAX if we have none.
+ */
+ GNUNET_CONTAINER_HeapCostType min_desire;
+
+ /**
+ * Maximum desirability of any of our connections, 0 if we have none.
+ */
+ GNUNET_CONTAINER_HeapCostType max_desire;
+
+ /**
+ * Path we are comparing against for #evaluate_connection, can be NULL.
+ */
+ struct CadetPeerPath *path;
+
+ /**
+ * Connection deemed the "worst" so far encountered by #evaluate_connection,
+ * NULL if we did not yet encounter any connections.
+ */
+ struct CadetTConnection *worst;
+
+ /**
+ * Numeric score of @e worst, only set if @e worst is non-NULL.
+ */
+ double worst_score;
+
+ /**
+ * Set to #GNUNET_YES if we have a connection over @e path already.
+ */
+ int duplicate;
+
+};
+
+
+/**
+ * Evaluate a connection, updating our summary information in @a cls about
+ * what kinds of connections we have.
+ *
+ * @param cls the `struct EvaluationSummary *` to update
+ * @param ct a connection to include in the summary
+ */
+static void
+evaluate_connection (void *cls,
+ struct CadetTConnection *ct)
+{
+ struct EvaluationSummary *es = cls;
+ struct CadetConnection *cc = ct->cc;
+ struct CadetPeerPath *ps = GCC_get_path (cc);
+ GNUNET_CONTAINER_HeapCostType ct_desirability;
+ uint32_t ct_length;
+ double score;
+
+ if (ps == es->path)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Ignoring duplicate path %s.\n",
+ GCPP_2s (es->path));
+ es->duplicate = GNUNET_YES;
+ return;
+ }
+ ct_desirability = GCPP_get_desirability (ps);
+ ct_length = GCPP_get_length (ps);
+
+ /* FIXME: calculate score on more than path,
+ include connection performance metrics like
+ last successful transmission, uptime, etc. */
+ score = ct_desirability + ct_length; /* FIXME: weigh these as well! */
+
+ if ( (NULL == es->worst) ||
+ (score < es->worst_score) )
+ {
+ es->worst = ct;
+ es->worst_score = score;
+ }
+ es->min_length = GNUNET_MIN (es->min_length,
+ ct_length);
+ es->max_length = GNUNET_MAX (es->max_length,
+ ct_length);
+ es->min_desire = GNUNET_MIN (es->min_desire,
+ ct_desirability);
+ es->max_desire = GNUNET_MAX (es->max_desire,
+ ct_desirability);
+}
+
+
/**
* Consider using the path @a p for the tunnel @a t.
* The tunnel destination is at offset @a off in path @a p.
unsigned int off)
{
struct CadetTunnel *t = cls;
- unsigned int min_length = UINT_MAX;
- GNUNET_CONTAINER_HeapCostType max_desire = 0;
+ struct EvaluationSummary es;
struct CadetTConnection *ct;
- /* Check if we care about the new path. */
- for (ct = t->connection_head;
- NULL != ct;
- ct = ct->next)
- {
- struct CadetPeerPath *ps;
-
- ps = GCC_get_path (ct->cc);
- if (ps == path)
- {
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Ignoring duplicate path %s for %s.\n",
- GCPP_2s (path),
- GCT_2s (t));
- return GNUNET_YES; /* duplicate */
- }
- min_length = GNUNET_MIN (min_length,
- GCPP_get_length (ps));
- max_desire = GNUNET_MAX (max_desire,
- GCPP_get_desirability (ps));
- }
+ es.min_length = UINT_MAX;
+ es.max_length = 0;
+ es.max_desire = 0;
+ es.min_desire = UINT64_MAX;
+ es.path = path;
+ es.duplicate = GNUNET_NO;
+
+ /* Compute evaluation summary over existing connections. */
+ GCT_iterate_connections (t,
+ &evaluate_connection,
+ &es);
+ if (GNUNET_YES == es.duplicate)
+ return GNUNET_YES;
/* FIXME: not sure we should really just count
'num_connections' here, as they may all have
/* We iterate by increasing path length; if we have enough paths and
this one is more than twice as long than what we are currently
using, then ignore all of these super-long ones! */
- if ( (t->num_connections > DESIRED_CONNECTIONS_PER_TUNNEL) &&
- (min_length * 2 < off) )
+ if ( (GCT_count_any_connections (t) > DESIRED_CONNECTIONS_PER_TUNNEL) &&
+ (es.min_length * 2 < off) &&
+ (es.max_length < off) )
{
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Ignoring paths of length %u, they are way too long.\n",
- min_length * 2);
+ es.min_length * 2);
return GNUNET_NO;
}
/* If we have enough paths and this one looks no better, ignore it. */
- if ( (t->num_connections >= DESIRED_CONNECTIONS_PER_TUNNEL) &&
- (min_length < GCPP_get_length (path)) &&
- (max_desire > GCPP_get_desirability (path)) )
+ if ( (GCT_count_any_connections (t) >= DESIRED_CONNECTIONS_PER_TUNNEL) &&
+ (es.min_length < GCPP_get_length (path)) &&
+ (es.min_desire > GCPP_get_desirability (path)) &&
+ (es.max_length < off) )
{
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Ignoring path (%u/%llu) to %s, got something better already.\n",
ct,
&connection_ready_cb,
ct);
+
/* FIXME: schedule job to kill connection (and path?) if it takes
too long to get ready! (And track performance data on how long
other connections took with the tunnel!)
=> Note: to be done within 'connection'-logic! */
- GNUNET_CONTAINER_DLL_insert (t->connection_head,
- t->connection_tail,
+ GNUNET_CONTAINER_DLL_insert (t->connection_busy_head,
+ t->connection_busy_tail,
ct);
- t->num_connections++;
+ t->num_busy_connections++;
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Found interesting path %s for %s, created %s\n",
GCPP_2s (path),
maintain_connections_cb (void *cls)
{
struct CadetTunnel *t = cls;
+ struct GNUNET_TIME_Relative delay;
+ struct EvaluationSummary es;
t->maintain_connections_task = NULL;
LOG (GNUNET_ERROR_TYPE_DEBUG,
"Performing connection maintenance for %s.\n",
GCT_2s (t));
+ es.min_length = UINT_MAX;
+ es.max_length = 0;
+ es.max_desire = 0;
+ es.min_desire = UINT64_MAX;
+ es.path = NULL;
+ es.worst = NULL;
+ es.duplicate = GNUNET_NO;
+ GCT_iterate_connections (t,
+ &evaluate_connection,
+ &es);
+ if ( (NULL != es.worst) &&
+ (GCT_count_any_connections (t) > DESIRED_CONNECTIONS_PER_TUNNEL) )
+ {
+ /* Clear out worst-performing connection 'es.worst'. */
+ destroy_t_connection (t,
+ es.worst);
+ }
+
+ /* Consider additional paths */
(void) GCP_iterate_paths (t->destination,
&consider_path_cb,
t);
- GNUNET_break (0); // FIXME: implement!
+ /* FIXME: calculate when to try again based on how well we are doing;
+ in particular, if we have to few connections, we might be able
+ to do without this (as PATHS should tell us whenever a new path
+ is available instantly; however, need to make sure this job is
+ restarted after that happens).
+ Furthermore, if the paths we do know are in a reasonably narrow
+ quality band and are plentyful, we might also consider us stabilized
+ and then reduce the frequency accordingly. */
+ delay = GNUNET_TIME_UNIT_MINUTES;
+ t->maintain_connections_task
+ = GNUNET_SCHEDULER_add_delayed (delay,
+ &maintain_connections_cb,
+ t);
}
too long to get ready! (And track performance data on how long
other connections took with the tunnel!)
=> Note: to be done within 'connection'-logic! */
- GNUNET_CONTAINER_DLL_insert (t->connection_head,
- t->connection_tail,
+ GNUNET_CONTAINER_DLL_insert (t->connection_busy_head,
+ t->connection_busy_tail,
ct);
- t->num_connections++;
+ t->num_busy_connections++;
LOG (GNUNET_ERROR_TYPE_DEBUG,
"%s has new %s\n",
GCT_2s (t),
t->kx_task = NULL;
}
send_kx_auth (t,
+ ct,
&t->ax,
GNUNET_YES);
return;
t->kx_task = NULL;
}
send_kx (t,
+ ct,
&t->ax);
return;
case CADET_TUNNEL_KEY_AX_AUTH_SENT:
GCT_ConnectionIterator iter,
void *iter_cls)
{
- for (struct CadetTConnection *ct = t->connection_head;
+ struct CadetTConnection *n;
+ for (struct CadetTConnection *ct = t->connection_ready_head;
NULL != ct;
- ct = ct->next)
+ ct = n)
+ {
+ n = ct->next;
+ iter (iter_cls,
+ ct);
+ }
+ for (struct CadetTConnection *ct = t->connection_busy_head;
+ NULL != ct;
+ ct = n)
+ {
+ n = ct->next;
iter (iter_cls,
- ct->cc);
+ ct);
+ }
}
GCT_2s (t),
estate2s (t->estate),
t->tq_len,
- t->num_connections);
+ GCT_count_any_connections (t));
LOG2 (level,
"TTT channels:\n");
GNUNET_CONTAINER_multihashmap32_iterate (t->channels,
&level);
LOG2 (level,
"TTT connections:\n");
- for (iter_c = t->connection_head; NULL != iter_c; iter_c = iter_c->next)
+ for (iter_c = t->connection_ready_head; NULL != iter_c; iter_c = iter_c->next)
+ GCC_debug (iter_c->cc,
+ level);
+ for (iter_c = t->connection_busy_head; NULL != iter_c; iter_c = iter_c->next)
GCC_debug (iter_c->cc,
level);