#define REKEY_WAIT GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 5)
+#define CONNECTIONS_PER_TUNNEL 3
+
/******************************************************************************/
/******************************** STRUCTS **********************************/
/******************************************************************************/
struct MeshChannel *ch;
};
+
+/**
+ * Connection list and metadata.
+ */
struct MeshTConnection
{
+ /**
+ * Next in DLL.
+ */
struct MeshTConnection *next;
+
+ /**
+ * Prev in DLL.
+ */
struct MeshTConnection *prev;
+
+ /**
+ * Connection handle.
+ */
struct MeshConnection *c;
+
+ /**
+ * Creation time, to keep oldest connection alive.
+ */
+ struct GNUNET_TIME_Absolute created;
+
+ /**
+ * Connection throughput, to keep fastest connection alive.
+ */
+ uint32_t throughput;
};
/**
/**
* Destroy flag: if true, destroy on last message.
*/
- int destroy;
+ GNUNET_SCHEDULER_TaskIdentifier destroy_task;
/**
* Queued messages, to transmit once tunnel gets connected.
struct MeshTunnelDelayed *prev;
/**
- * Channel.
+ * Tunnel.
*/
- struct MeshChannel *ch;
+ struct MeshTunnel3 *t;
+
+ /**
+ * Tunnel queue given to the channel to cancel request. Update on send_queued.
+ */
+ struct MeshTunnel3Queue *tq;
/**
* Message to send.
/**
* Connection queue handle, to cancel if necessary.
*/
- struct MeshConnectionQueue *q;
+ struct MeshConnectionQueue *cq;
+
+ /**
+ * Handle in case message hasn't been given to a connection yet.
+ */
+ struct MeshTunnelDelayed *tqd;
/**
* Continuation to call once sent.
extern struct GNUNET_PeerIdentity my_full_id;
+/**
+ * Don't try to recover tunnels if shutting down.
+ */
+extern int shutting_down;
+
+
/**
* Set of all tunnels, in order to trigger a new exchange on rekey.
* Indexed by peer's ID.
static int
is_ready (struct MeshTunnel3 *t)
{
- return (MESH_TUNNEL3_READY == t->cstate
- && MESH_TUNNEL3_KEY_OK == t->estate)
- || GMT_is_loopback (t);
+ int ready;
+
+ GMT_debug (t);
+ ready = (MESH_TUNNEL3_READY == t->cstate && MESH_TUNNEL3_KEY_OK == t->estate);
+ ready = ready || GMT_is_loopback (t);
+ return ready;
}
size_t size, uint32_t iv)
{
struct GNUNET_CRYPTO_SymmetricInitializationVector siv;
+ size_t out_size;
- GNUNET_CRYPTO_symmetric_derive_iv (&siv, &t->e_key, &iv, sizeof (uint32_t), NULL);
- return GNUNET_CRYPTO_symmetric_encrypt (src, size, &t->e_key, &siv, dst);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_encrypt start\n");
+ GNUNET_CRYPTO_symmetric_derive_iv (&siv, &t->e_key, &iv, sizeof (iv), NULL);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_encrypt IV derived\n");
+ out_size = GNUNET_CRYPTO_symmetric_encrypt (src, size, &t->e_key, &siv, dst);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_encrypt end\n");
+
+ return out_size;
}
size_t size, uint32_t iv)
{
struct GNUNET_CRYPTO_SymmetricInitializationVector siv;
+ struct GNUNET_CRYPTO_SymmetricSessionKey *key;
+ size_t out_size;
- GNUNET_CRYPTO_symmetric_derive_iv (&siv, &t->d_key, &iv, sizeof (uint32_t), NULL);
- return GNUNET_CRYPTO_symmetric_decrypt (src, size, &t->d_key, &siv, dst);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_decrypt start\n");
+ if (t->estate == MESH_TUNNEL3_KEY_OK || t->estate == MESH_TUNNEL3_KEY_PING)
+ {
+ key = &t->d_key;
+ }
+ else if (NULL != t->kx_ctx)
+ {
+ key = &t->kx_ctx->d_key_old;
+ }
+ else
+ {
+ GNUNET_STATISTICS_update (stats, "# non decryptable data", 1, GNUNET_NO);
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "WARNING got data on %s without a valid key\n",
+ GMT_2s (t));
+ GMT_debug (t);
+ return 0;
+ }
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_decrypt iv\n");
+ GNUNET_CRYPTO_symmetric_derive_iv (&siv, key, &iv, sizeof (iv), NULL);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_decrypt iv done\n");
+ out_size = GNUNET_CRYPTO_symmetric_decrypt (src, size, key, &siv, dst);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " t_decrypt end\n");
+
+ return out_size;
}
unsigned int qn;
unsigned int lowest_q;
- LOG (GNUNET_ERROR_TYPE_DEBUG, "tunnel_get_connection %s\n", GMP_2s (t->peer));
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "tunnel_get_connection %s\n", GMT_2s (t));
best = NULL;
lowest_q = UINT_MAX;
for (iter = t->connection_head; NULL != iter; iter = iter->next)
/**
- * Send all cached messages that we can, tunnel is online.
+ * Callback called when a queued message is sent.
*
- * @param t Tunnel that holds the messages. Cannot be loopback.
+ * Calculates the average time and connection packet tracking.
+ *
+ * @param cls Closure (TunnelQueue handle).
+ * @param c Connection this message was on.
+ * @param q Connection queue handle (unused).
+ * @param type Type of message sent.
+ * @param fwd Was this a FWD going message?
+ * @param size Size of the message.
*/
static void
-send_queued_data (struct MeshTunnel3 *t)
+message_sent (void *cls,
+ struct MeshConnection *c,
+ struct MeshConnectionQueue *q,
+ uint16_t type, int fwd, size_t size)
{
- struct MeshTunnelDelayed *tq;
- struct MeshTunnelDelayed *next;
- unsigned int room;
+ struct MeshTunnel3Queue *qt = cls;
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "GMT_send_queued_data on tunnel %s\n",
- GMT_2s (t));
+ GNUNET_assert (NULL != qt->cont);
+ qt->cont (qt->cont_cls, GMC_get_tunnel (c), qt, type, size);
+ GNUNET_free (qt);
+}
- if (GMT_is_loopback (t))
+
+/**
+ * Delete a queued message: either was sent or the channel was destroyed
+ * before the tunnel's key exchange had a chance to finish.
+ *
+ * @param tqd Delayed queue handle.
+ */
+static void
+unqueue_data (struct MeshTunnelDelayed *tqd)
+{
+ GNUNET_CONTAINER_DLL_remove (tqd->t->tq_head, tqd->t->tq_tail, tqd);
+ GNUNET_free (tqd);
+}
+
+
+/**
+ * Cache a message to be sent once tunnel is online.
+ *
+ * @param t Tunnel to hold the message.
+ * @param msg Message itself (copy will be made).
+ */
+static struct MeshTunnelDelayed *
+queue_data (struct MeshTunnel3 *t, const struct GNUNET_MessageHeader *msg)
+{
+ struct MeshTunnelDelayed *tqd;
+ uint16_t size = ntohs (msg->size);
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "queue data on Tunnel %s\n", GMT_2s (t));
+
+ if (GNUNET_YES == is_ready (t))
{
GNUNET_break (0);
- return;
+ return NULL;
}
+ tqd = GNUNET_malloc (sizeof (struct MeshTunnelDelayed) + size);
+
+ tqd->t = t;
+ memcpy (&tqd[1], msg, size);
+ GNUNET_CONTAINER_DLL_insert_tail (t->tq_head, t->tq_tail, tqd);
+ return tqd;
+}
+
+
+/**
+ * Calculate HMAC.
+ *
+ * @param t Tunnel to get keys from.
+ * @param plaintext Content to HMAC.
+ * @param size Size of @c plaintext.
+ * @param iv Initialization vector for the message.
+ * @param outgoing Is this an outgoing message that we encrypted?
+ * @param hmac Destination to store the HMAC.
+ */
+static void
+t_hmac (struct MeshTunnel3 *t, const void *plaintext, size_t size, uint32_t iv,
+ int outgoing, struct GNUNET_MeshHash *hmac)
+{
+ struct GNUNET_CRYPTO_AuthKey auth_key;
+ static const char ctx[] = "mesh authentication key";
+ struct GNUNET_CRYPTO_SymmetricSessionKey *key;
+ struct GNUNET_HashCode hash;
+
+ key = outgoing ? &t->e_key : &t->d_key;
+ GNUNET_CRYPTO_hmac_derive_key (&auth_key, key,
+ &iv, sizeof (iv),
+ key, sizeof (*key),
+ ctx, sizeof (ctx),
+ NULL);
+ GNUNET_CRYPTO_hmac (&auth_key, plaintext, size, &hash);
+ memcpy (hmac, &hash, sizeof (*hmac));
+}
+
+
+/**
+ * Sends an already built message on a tunnel, encrypting it and
+ * choosing the best connection.
+ *
+ * @param message Message to send. Function modifies it.
+ * @param t Tunnel on which this message is transmitted.
+ * @param c Connection to use (autoselect if NULL).
+ * @param force Force the tunnel to take the message (buffer overfill).
+ * @param cont Continuation to call once message is really sent.
+ * @param cont_cls Closure for @c cont.
+ * @param existing_q In case this a transmission of previously queued data,
+ * this should be TunnelQueue given to the client.
+ * Otherwise, NULL.
+ *
+ * @return Handle to cancel message. NULL if @c cont is NULL.
+ */
+static struct MeshTunnel3Queue *
+send_prebuilt_message (const struct GNUNET_MessageHeader *message,
+ struct MeshTunnel3 *t, struct MeshConnection *c,
+ int force, GMT_sent cont, void *cont_cls,
+ struct MeshTunnel3Queue *existing_q)
+{
+ struct MeshTunnel3Queue *tq;
+ struct GNUNET_MESH_Encrypted *msg;
+ size_t size = ntohs (message->size);
+ char cbuf[sizeof (struct GNUNET_MESH_Encrypted) + size];
+ uint32_t iv;
+ uint16_t type;
+ int fwd;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "GMT Send on Tunnel %s\n", GMT_2s (t));
+
if (GNUNET_NO == is_ready (t))
{
- LOG (GNUNET_ERROR_TYPE_DEBUG, " not ready yet: %s/%s\n",
- estate2s (t->estate), cstate2s (t->cstate));
- return;
+ struct MeshTunnelDelayed *tqd;
+ /* A non null existing_q indicates sending of queued data.
+ * Should only happen after tunnel becomes ready.
+ */
+ GNUNET_assert (NULL == existing_q);
+ tqd = queue_data (t, message);
+ if (NULL == cont)
+ return NULL;
+ tq = GNUNET_new (struct MeshTunnel3Queue);
+ tq->tqd = tqd;
+ tqd->tq = tq;
+ tq->cont = cont;
+ tq->cont_cls = cont_cls;
+ return tq;
}
- room = GMT_get_connections_buffer (t);
- LOG (GNUNET_ERROR_TYPE_DEBUG, " buffer space: %u\n", room);
- LOG (GNUNET_ERROR_TYPE_DEBUG, " tq head: %p\n", t->tq_head);
- for (tq = t->tq_head; NULL != tq && room > 0; tq = next)
+ GNUNET_assert (GNUNET_NO == GMT_is_loopback (t));
+
+ iv = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
+ msg = (struct GNUNET_MESH_Encrypted *) cbuf;
+ msg->header.type = htons (GNUNET_MESSAGE_TYPE_MESH_ENCRYPTED);
+ msg->iv = iv;
+ GNUNET_assert (t_encrypt (t, &msg[1], message, size, iv) == size);
+ t_hmac (t, &msg[1], size, iv, GNUNET_YES, &msg->hmac);
+ msg->header.size = htons (sizeof (struct GNUNET_MESH_Encrypted) + size);
+
+ if (NULL == c)
+ c = tunnel_get_connection (t);
+ if (NULL == c)
{
- LOG (GNUNET_ERROR_TYPE_DEBUG, " data on channel %s\n", GMCH_2s (tq->ch));
- next = tq->next;
- room--;
- GNUNET_CONTAINER_DLL_remove (t->tq_head, t->tq_tail, tq);
- GMCH_send_prebuilt_message ((struct GNUNET_MessageHeader *) &tq[1],
- tq->ch, GMCH_is_origin (tq->ch, GNUNET_YES),
- NULL);
+ if (GNUNET_SCHEDULER_NO_TASK != t->destroy_task
+ || MESH_TUNNEL3_SEARCHING != t->cstate)
+ {
+ GNUNET_break (0);
+ GMT_debug (t);
+ }
+ return NULL;
+ }
- GNUNET_free (tq);
+ type = ntohs (message->type);
+ switch (type)
+ {
+ case GNUNET_MESSAGE_TYPE_MESH_KEEPALIVE:
+ case GNUNET_MESSAGE_TYPE_MESH_DATA:
+ case GNUNET_MESSAGE_TYPE_MESH_DATA_ACK:
+ case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_CREATE:
+ case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_DESTROY:
+ case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_ACK:
+ case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_NACK:
+ msg->cid = *GMC_get_id (c);
+ msg->ttl = htonl (default_ttl);
+ break;
+ default:
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "unkown type %s\n",
+ GM_m2s (type));
+ GNUNET_break (0);
}
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "GMT_send_queued_data end\n",
- GMP_2s (t->peer));
-}
+ fwd = GMC_is_origin (c, GNUNET_YES);
+ if (NULL == cont)
+ {
+ (void) GMC_send_prebuilt_message (&msg->header, c, fwd, force, NULL, NULL);
+ return NULL;
+ }
+ if (NULL == existing_q)
+ {
+ tq = GNUNET_new (struct MeshTunnel3Queue); /* FIXME valgrind: leak*/
+ }
+ else
+ {
+ tq = existing_q;
+ tq->tqd = NULL;
+ }
+ tq->cq = GMC_send_prebuilt_message (&msg->header, c, fwd, force,
+ &message_sent, tq);
+ tq->cont = cont;
+ tq->cont_cls = cont_cls;
+
+ return tq;
+}
/**
- * Cache a message to be sent once tunnel is online.
+ * Send all cached messages that we can, tunnel is online.
*
- * @param t Tunnel to hold the message.
- * @param ch Channel the message is about.
- * @param msg Message itself (copy will be made).
+ * @param t Tunnel that holds the messages. Cannot be loopback.
*/
static void
-queue_data (struct MeshTunnel3 *t,
- struct MeshChannel *ch,
- const struct GNUNET_MessageHeader *msg)
+send_queued_data (struct MeshTunnel3 *t)
{
- struct MeshTunnelDelayed *tq;
- uint16_t size = ntohs (msg->size);
+ struct MeshTunnelDelayed *tqd;
+ struct MeshTunnelDelayed *next;
+ unsigned int room;
- LOG (GNUNET_ERROR_TYPE_DEBUG, "queue data on Tunnel %s\n", GMT_2s (t));
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "GMT_send_queued_data on tunnel %s\n",
+ GMT_2s (t));
- if (GNUNET_YES == is_ready (t))
+ if (GMT_is_loopback (t))
{
GNUNET_break (0);
return;
}
- tq = GNUNET_malloc (sizeof (struct MeshTunnelDelayed) + size);
+ if (GNUNET_NO == is_ready (t))
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " not ready yet: %s/%s\n",
+ estate2s (t->estate), cstate2s (t->cstate));
+ return;
+ }
- tq->ch = ch;
- memcpy (&tq[1], msg, size);
- GNUNET_CONTAINER_DLL_insert_tail (t->tq_head, t->tq_tail, tq);
+ room = GMT_get_connections_buffer (t);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " buffer space: %u\n", room);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " tq head: %p\n", t->tq_head);
+ for (tqd = t->tq_head; NULL != tqd && room > 0; tqd = next)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " sending queued data\n");
+ next = tqd->next;
+ room--;
+ send_prebuilt_message ((struct GNUNET_MessageHeader *) &tqd[1],
+ tqd->t, NULL, GNUNET_YES,
+ NULL != tqd->tq ? tqd->tq->cont : NULL,
+ NULL != tqd->tq ? tqd->tq->cont_cls : NULL,
+ tqd->tq);
+ unqueue_data (tqd);
+ }
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "GMT_send_queued_data end\n", GMP_2s (t->peer));
}
-
/**
* Sends key exchange message on a tunnel, choosing the best connection.
* Should not be called on loopback tunnels.
return;
}
+ if (GNUNET_SCHEDULER_NO_TASK != t->destroy_task)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " being destroyed, why bother\n");
+ return;
+ }
+
/* Must have a connection. */
if (NULL == t->connection_head)
{
- GNUNET_break (0);
+ GNUNET_break (MESH_TUNNEL3_SEARCHING == t->cstate);
+ GMT_debug (t);
return;
}
c = tunnel_get_connection (t);
if (NULL == c)
{
- GNUNET_break (GNUNET_YES == t->destroy);
+ GNUNET_break (GNUNET_SCHEDULER_NO_TASK != t->destroy_task
+ || MESH_TUNNEL3_READY != t->cstate);
+ GMT_debug (t);
return;
}
type = ntohs (message->type);
case GNUNET_MESSAGE_TYPE_MESH_KX_EPHEMERAL:
case GNUNET_MESSAGE_TYPE_MESH_KX_PING:
case GNUNET_MESSAGE_TYPE_MESH_KX_PONG:
- msg->reserved = htonl (0);
memcpy (&msg[1], message, size);
break;
default:
static void
send_ephemeral (struct MeshTunnel3 *t)
{
- LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __FUNCTION__);
+ LOG (GNUNET_ERROR_TYPE_INFO, "=> EPHM for %s\n", GMT_2s (t));
kx_msg.sender_status = htonl (t->estate);
send_kx (t, &kx_msg.header);
{
struct GNUNET_MESH_KX_Ping msg;
- LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __FUNCTION__);
+ LOG (GNUNET_ERROR_TYPE_INFO, "=> PING for %s\n", GMT_2s (t));
msg.header.size = htons (sizeof (msg));
msg.header.type = htons (GNUNET_MESSAGE_TYPE_MESH_KX_PING);
msg.iv = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
{
struct GNUNET_MESH_KX_Pong msg;
- LOG (GNUNET_ERROR_TYPE_DEBUG, "%s()\n", __FUNCTION__);
+ LOG (GNUNET_ERROR_TYPE_INFO, "=> PONG for %s\n", GMT_2s (t));
msg.header.size = htons (sizeof (msg));
msg.header.type = htons (GNUNET_MESSAGE_TYPE_MESH_KX_PONG);
msg.iv = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
t->rekey_task = GNUNET_SCHEDULER_NO_TASK;
- LOG (GNUNET_ERROR_TYPE_DEBUG, "Re-key Tunnel\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "Re-key Tunnel %s\n", GMT_2s (t));
if (NULL != tc && 0 != (GNUNET_SCHEDULER_REASON_SHUTDOWN & tc->reason))
return;
{
LOG (GNUNET_ERROR_TYPE_DEBUG, " new kx ctx\n");
t->kx_ctx = GNUNET_new (struct MeshTunnelKXCtx);
- t->kx_ctx->challenge = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE,
- UINT32_MAX);
+ t->kx_ctx->challenge =
+ GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
t->kx_ctx->d_key_old = t->d_key;
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " new challenge for %s: %u\n",
+ GMT_2s (t), t->kx_ctx->challenge);
}
send_ephemeral (t);
switch (t->estate)
if (GNUNET_SCHEDULER_NO_TASK != t->rekey_task)
return GNUNET_YES;
+ if (GNUNET_YES == GMT_is_loopback (t))
+ return GNUNET_YES;
+
r = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_WEAK, (uint32_t) n * 100);
delay = GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_MILLISECONDS, r);
t->rekey_task = GNUNET_SCHEDULER_add_delayed (delay, &rekey_tunnel, t);
}
+/**
+ * Notify remote peer that we don't know a channel he is talking about,
+ * probably CHANNEL_DESTROY was missed.
+ *
+ * @param t Tunnel on which to notify.
+ * @param gid ID of the channel.
+ */
+static void
+send_channel_destroy (struct MeshTunnel3 *t, unsigned int gid)
+{
+ struct GNUNET_MESH_ChannelManage msg;
+
+ msg.header.type = htons (GNUNET_MESSAGE_TYPE_MESH_CHANNEL_DESTROY);
+ msg.header.size = htons (sizeof (msg));
+ msg.chid = htonl (gid);
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "WARNING destroying unknown channel %u on tunnel %s\n",
+ gid, GMT_2s (t));
+ send_prebuilt_message (&msg.header, t, NULL, GNUNET_YES, NULL, NULL, NULL);
+}
+
+
/**
* Demultiplex data per channel and call appropriate channel handler.
*
{
GNUNET_STATISTICS_update (stats, "# data on unknown channel",
1, GNUNET_NO);
- LOG (GNUNET_ERROR_TYPE_DEBUG, "WARNING channel %u unknown\n",
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "WARNING channel 0x%X unknown\n",
ntohl (msg->chid));
+ send_channel_destroy (t, ntohl (msg->chid));
return;
}
const struct GNUNET_MESH_KX_Ephemeral *msg)
{
struct GNUNET_HashCode km;
- LOG (GNUNET_ERROR_TYPE_DEBUG, " ephemeral key message\n");
+ LOG (GNUNET_ERROR_TYPE_INFO, "<= EPHM for %s\n", GMT_2s (t));
if (GNUNET_OK != check_ephemeral (t, msg))
{
return;
}
- LOG (GNUNET_ERROR_TYPE_DEBUG, " ping message\n");
+ LOG (GNUNET_ERROR_TYPE_INFO, "<= PING for %s\n", GMT_2s (t));
t_decrypt (t, &res.target, &msg->target, ping_encryption_size (), msg->iv);
if (0 != memcmp (&my_full_id, &res.target, sizeof (my_full_id)))
{
- GNUNET_break_op (0);
+ GNUNET_STATISTICS_update (stats, "# malformed PINGs", 1, GNUNET_NO);
+ LOG (GNUNET_ERROR_TYPE_WARNING, " malformed PING\n");
LOG (GNUNET_ERROR_TYPE_DEBUG, " e got %u\n", msg->nonce);
LOG (GNUNET_ERROR_TYPE_DEBUG, " e towards %s\n", GNUNET_i2s (&msg->target));
LOG (GNUNET_ERROR_TYPE_DEBUG, " got %u\n", res.nonce);
{
uint32_t challenge;
- LOG (GNUNET_ERROR_TYPE_DEBUG, "PONG received\n");
+ LOG (GNUNET_ERROR_TYPE_INFO, "<= PONG for %s\n", GMT_2s (t));
if (GNUNET_SCHEDULER_NO_TASK == t->rekey_task)
{
- GNUNET_break_op (0);
+ GNUNET_STATISTICS_update (stats, "# duplicate PONG messages", 1, GNUNET_NO);
return;
}
t_decrypt (t, &challenge, &msg->nonce, sizeof (uint32_t), msg->iv);
if (challenge != t->kx_ctx->challenge)
{
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Wrong PONG challenge: %u (e: %u). Expected: %u.\n",
+ LOG (GNUNET_ERROR_TYPE_WARNING, "Wrong PONG challenge\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "PONG: %u (e: %u). Expected: %u.\n",
challenge, msg->nonce, t->kx_ctx->challenge);
GNUNET_break_op (0);
return;
uint16_t type;
type = ntohs (msgh->type);
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Got a %s message!\n",
- GM_m2s (type));
+ LOG (GNUNET_ERROR_TYPE_INFO, "<= %s on %s\n", GM_m2s (type), GMT_2s (t));
switch (type)
{
+ case GNUNET_MESSAGE_TYPE_MESH_KEEPALIVE:
+ /* Do nothing, connection aleady got updated. */
+ GNUNET_STATISTICS_update (stats, "# keepalives received", 1, GNUNET_NO);
+ break;
+
case GNUNET_MESSAGE_TYPE_MESH_DATA:
/* Don't send hop ACK, wait for client to ACK */
handle_data (t, (struct GNUNET_MESH_Data *) msgh, fwd);
default:
GNUNET_break_op (0);
- LOG (GNUNET_ERROR_TYPE_DEBUG,
+ LOG (GNUNET_ERROR_TYPE_WARNING,
"end-to-end message not known (%u)\n",
ntohs (msgh->type));
+ GMT_debug (t);
}
}
char cbuf [payload_size];
struct GNUNET_MessageHeader *msgh;
unsigned int off;
+ struct GNUNET_MeshHash hmac;
decrypted_size = t_decrypt (t, cbuf, &msg[1], payload_size, msg->iv);
+ t_hmac (t, &msg[1], payload_size, msg->iv, GNUNET_NO, &hmac);
+ if (0 != memcmp (&hmac, &msg->hmac, sizeof (hmac)))
+ {
+ /* checksum failed */
+ GNUNET_log (GNUNET_ERROR_TYPE_WARNING,
+ "Failed checksum validation for a message on tunnel `%s'\n",
+ GMT_2s (t));
+ GNUNET_STATISTICS_update (stats, "# wrong HMAC", 1, GNUNET_NO);
+ return;
+ }
off = 0;
while (off < decrypted_size)
{
{
if (NULL == t)
return;
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Tunnel %s cstate was %s\n",
- GMP_2s (t->peer), cstate2s (t->cstate));
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Tunnel %s cstate is now %s\n",
- GMP_2s (t->peer), cstate2s (cstate));
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "Tunnel %s cstate %s => %s\n",
+ GMP_2s (t->peer), cstate2s (t->cstate), cstate2s (cstate));
if (myid != GMP_get_short_id (t->peer) &&
MESH_TUNNEL3_READY != t->cstate &&
MESH_TUNNEL3_READY == cstate)
t->cstate = cstate;
if (MESH_TUNNEL3_KEY_OK == t->estate)
{
- LOG (GNUNET_ERROR_TYPE_DEBUG, " triggered send queued data\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " cstate triggered send queued data\n");
send_queued_data (t);
}
else if (MESH_TUNNEL3_KEY_UNINITIALIZED == t->estate)
{
- LOG (GNUNET_ERROR_TYPE_DEBUG, " triggered rekey\n");
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " cstate triggered rekey\n");
rekey_tunnel (t, NULL);
}
}
t->cstate = cstate;
- if (MESH_TUNNEL3_READY == cstate && 3 <= GMT_count_connections (t))
+ if (MESH_TUNNEL3_READY == cstate
+ && CONNECTIONS_PER_TUNNEL <= GMT_count_connections (t))
{
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " cstate triggered stop dht\n");
GMP_stop_search (t->peer);
}
}
}
+/**
+ * Check that the tunnel doesn't have too many connections,
+ * remove one if necessary.
+ *
+ * For the time being, this means the newest connection.
+ *
+ * @param t Tunnel to check.
+ */
+static void
+check_connection_count (struct MeshTunnel3 *t)
+{
+ if (GMT_count_connections (t) > CONNECTIONS_PER_TUNNEL)
+ {
+ struct MeshTConnection *iter;
+ struct MeshTConnection *c;
+
+ for (iter = t->connection_head; NULL != iter; iter = iter->next)
+ {
+ if (NULL == c || iter->created.abs_value_us > c->created.abs_value_us)
+ {
+ c = iter;
+ }
+ }
+ if (NULL != c)
+ GMC_destroy (c->c);
+ else
+ GNUNET_break (0);
+ }
+}
+
/**
* Add a connection to a tunnel.
*
{
struct MeshTConnection *aux;
+ GNUNET_assert (NULL != c);
+
for (aux = t->connection_head; aux != NULL; aux = aux->next)
if (aux->c == c)
return;
aux = GNUNET_new (struct MeshTConnection);
aux->c = c;
- GNUNET_CONTAINER_DLL_insert_tail (t->connection_head, t->connection_tail, aux);
+ aux->created = GNUNET_TIME_absolute_get ();
+
+ GNUNET_CONTAINER_DLL_insert (t->connection_head, t->connection_tail, aux);
+
+ check_connection_count (t);
+}
+
+
+/**
+ * Mark a path as no longer valid for this tunnel: has been tried and failed.
+ *
+ * @param t Tunnel to update.
+ * @param path Invalid path to remove. Is destroyed after removal.
+ */
+void
+GMT_remove_path (struct MeshTunnel3 *t, struct MeshPeerPath *path)
+{
+ GMP_remove_path (t->peer, path);
}
* @param c Connection.
*/
void
-GMT_remove_connection (struct MeshTunnel3 *t, struct MeshConnection *c)
+GMT_remove_connection (struct MeshTunnel3 *t,
+ struct MeshConnection *c)
{
struct MeshTConnection *aux;
+ struct MeshTConnection *next;
- for (aux = t->connection_head; aux != NULL; aux = aux->next)
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "Removing connection %s from tunnel %s\n",
+ GMC_2s (c), GMT_2s (t));
+ for (aux = t->connection_head; aux != NULL; aux = next)
+ {
+ next = aux->next;
if (aux->c == c)
{
GNUNET_CONTAINER_DLL_remove (t->connection_head, t->connection_tail, aux);
GNUNET_free (aux);
- return;
}
+ }
+
+ /* Start new connections if needed */
+ if (NULL == t->connection_head
+ && GNUNET_SCHEDULER_NO_TASK == t->destroy_task
+ && MESH_TUNNEL3_SHUTDOWN != t->cstate
+ && GNUNET_NO == shutting_down)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " no more connections, getting new ones\n");
+ t->cstate = MESH_TUNNEL3_SEARCHING;
+ GMP_connect (t->peer);
+ return;
+ }
+
+ /* If not marked as ready, no change is needed */
+ if (MESH_TUNNEL3_READY != t->cstate)
+ return;
+
+ /* Check if any connection is ready to maintaing cstate */
+ for (aux = t->connection_head; aux != NULL; aux = aux->next)
+ if (MESH_CONNECTION_READY == GMC_get_state (aux->c))
+ return;
+
+ t->cstate = MESH_TUNNEL3_WAITING;
}
LOG (GNUNET_ERROR_TYPE_DEBUG, " adding %p to %p\n", aux, t->channel_head);
GNUNET_CONTAINER_DLL_insert_tail (t->channel_head, t->channel_tail, aux);
- if (GNUNET_YES == t->destroy)
+ if (GNUNET_SCHEDULER_NO_TASK != t->destroy_task)
{
- t->destroy = GNUNET_NO;
+ GNUNET_SCHEDULER_cancel (t->destroy_task);
+ t->destroy_task = GNUNET_SCHEDULER_NO_TASK;
LOG (GNUNET_ERROR_TYPE_DEBUG, " undo destroy!\n");
}
}
}
+/**
+ * @brief Destroy a tunnel and free all resources.
+ *
+ * Should only be called a while after the tunnel has been marked as destroyed,
+ * in case there is a new channel added to the same peer shortly after marking
+ * the tunnel. This way we avoid a new public key handshake.
+ *
+ * @param cls Closure (tunnel to destroy).
+ * @param tc Task context.
+ */
+static void
+delayed_destroy (void *cls, const struct GNUNET_SCHEDULER_TaskContext *tc)
+{
+ struct MeshTunnel3 *t = cls;
+ struct MeshTConnection *iter;
+
+ t->destroy_task = GNUNET_SCHEDULER_NO_TASK;
+ t->cstate = MESH_TUNNEL3_SHUTDOWN;
+
+ for (iter = t->connection_head; NULL != iter; iter = iter->next)
+ {
+ GMC_send_destroy (iter->c);
+ }
+ GMT_destroy (t);
+}
+
+
/**
* Tunnel is empty: destroy it.
*
void
GMT_destroy_empty (struct MeshTunnel3 *t)
{
- struct MeshTConnection *iter;
-
- LOG (GNUNET_ERROR_TYPE_DEBUG, "Tunnel empty: destroying scheduled\n");
- for (iter = t->connection_head; NULL != iter; iter = iter->next)
+ if (GNUNET_SCHEDULER_NO_TASK != t->destroy_task)
{
- GMC_send_destroy (iter->c);
+ LOG (GNUNET_ERROR_TYPE_DEBUG,
+ "Tunnel %s is already scheduled for destruction\n",
+ GMT_2s (t));
+ GNUNET_break (0);
+ /* should never happen, tunnel can only become empty once, and the
+ * task identifier should be NO_TASK (cleaned when the tunnel was created
+ * or became un-empty)
+ */
+ return;
}
- t->cstate = MESH_TUNNEL3_NEW;
- t->destroy = GNUNET_YES;
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "Tunnel %s empty: destroying scheduled\n",
+ GMT_2s (t));
+
+ t->destroy_task = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MINUTES,
+ &delayed_destroy, t);
}
if (NULL == t)
return;
+ if (GNUNET_SCHEDULER_NO_TASK != t->destroy_task)
+ {
+ GNUNET_SCHEDULER_cancel (t->destroy_task);
+ t->destroy_task = GNUNET_SCHEDULER_NO_TASK;
+ }
+
LOG (GNUNET_ERROR_TYPE_DEBUG, "destroying tunnel %s\n", GMP_2s (t->peer));
GNUNET_break (GNUNET_YES ==
GMP_set_tunnel (t->peer, NULL);
if (GNUNET_SCHEDULER_NO_TASK != t->rekey_task)
+ {
GNUNET_SCHEDULER_cancel (t->rekey_task);
+ t->rekey_task = GNUNET_SCHEDULER_NO_TASK;
+ if (NULL != t->kx_ctx)
+ GNUNET_free (t->kx_ctx);
+ else
+ GNUNET_break (0);
+ }
GNUNET_free (t);
}
GMT_use_path (struct MeshTunnel3 *t, struct MeshPeerPath *p)
{
struct MeshConnection *c;
- struct GNUNET_HashCode cid;
+ struct GNUNET_MeshHash cid;
unsigned int own_pos;
if (NULL == t || NULL == p)
return NULL;
}
+ if (MESH_TUNNEL3_SHUTDOWN == t->cstate)
+ {
+ GNUNET_break (0);
+ return NULL;
+ }
+
for (own_pos = 0; own_pos < p->length; own_pos++)
{
if (p->peers[own_pos] == myid)
break;
}
- if (own_pos > p->length - 1)
+ if (own_pos >= p->length)
{
- GNUNET_break (0);
+ GNUNET_break_op (0);
return NULL;
}
- GNUNET_CRYPTO_hash_create_random (GNUNET_CRYPTO_QUALITY_NONCE, &cid);
+ GNUNET_CRYPTO_random_block (GNUNET_CRYPTO_QUALITY_NONCE, &cid, sizeof (cid));
c = GMC_new (&cid, t, p, own_pos);
+ if (NULL == c)
+ {
+ /* Path was flawed */
+ return NULL;
+ }
GMT_add_connection (t, c);
return c;
}
struct MeshTConnection *iter;
unsigned int count;
- for (count = 0, iter = t->connection_head;
- NULL != iter;
- iter = iter->next, count++);
+ for (count = 0, iter = t->connection_head; NULL != iter; iter = iter->next)
+ if (MESH_CONNECTION_DESTROYED != GMC_get_state (iter->c))
+ count++;
return count;
}
{
if (NULL == t)
{
- GNUNET_break (0);
+ GNUNET_assert (0);
return (enum MeshTunnel3CState) -1;
}
return t->cstate;
}
+/**
+ * Get the encryption state of a tunnel.
+ *
+ * @param t Tunnel.
+ *
+ * @return Tunnel's encryption state.
+ */
+enum MeshTunnel3EState
+GMT_get_estate (struct MeshTunnel3 *t)
+{
+ if (NULL == t)
+ {
+ GNUNET_assert (0);
+ return (enum MeshTunnel3EState) -1;
+ }
+ return t->estate;
+}
+
/**
* Get the maximum buffer space for a tunnel towards a local client.
*
{
if (GMC_get_state (iter->c) != MESH_CONNECTION_READY)
{
- iter = iter->next;
continue;
}
buffer += get_connection_buffer (iter);
unsigned int cs;
unsigned int buffer;
- LOG (GNUNET_ERROR_TYPE_DEBUG,
- "Tunnel send connection ACKs on %s\n",
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "Tunnel send connection ACKs on %s\n",
GMT_2s (t));
if (NULL == t)
}
buffer = GMT_get_channels_buffer (t);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " buffer %u\n", buffer);
/* Count connections, how many messages are already allowed */
cs = GMT_count_connections (t);
{
allowed += get_connection_allowed (iter);
}
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " allowed %u\n", allowed);
/* Make sure there is no overflow */
if (allowed > buffer)
/* Authorize connections to send more data */
to_allow = buffer; /* - allowed; */
- for (iter = t->connection_head; NULL != iter && to_allow > 0; iter = iter->next)
+ for (iter = t->connection_head;
+ NULL != iter && to_allow > 0;
+ iter = iter->next)
{
allow_per_connection = to_allow/cs;
to_allow -= allow_per_connection;
{
continue;
}
- GMC_allow (iter->c, buffer, GMC_is_origin (iter->c, GNUNET_YES));
+ GMC_allow (iter->c, allow_per_connection,
+ GMC_is_origin (iter->c, GNUNET_NO));
}
GNUNET_break (to_allow == 0);
}
-/**
- * Callback called when a queued message is sent.
- *
- * Calculates the average time and connection packet tracking.
- *
- * @param cls Closure (TunnelQueue handle).
- * @param c Connection this message was on.
- * @param q Connection queue handle (unused).
- * @param type Type of message sent.
- * @param fwd Was this a FWD going message?
- * @param size Size of the message.
- */
-static void
-message_sent (void *cls,
- struct MeshConnection *c,
- struct MeshConnectionQueue *q,
- uint16_t type, int fwd, size_t size)
-{
- struct MeshTunnel3Queue *qt = cls;
-
- GNUNET_assert (NULL != qt->cont);
- qt->cont (qt->cont_cls, GMC_get_tunnel (c), qt, type, size);
- GNUNET_free (qt);
-}
-
-
/**
* Cancel a previously sent message while it's in the queue.
*
void
GMT_cancel (struct MeshTunnel3Queue *q)
{
- GMC_cancel (q->q);
- /* message_sent() will be called and free q */
+ if (NULL != q->cq)
+ {
+ GMC_cancel (q->cq);
+ /* message_sent() will be called and free q */
+ }
+ else if (NULL != q->tqd)
+ {
+ unqueue_data (q->tqd);
+ q->tqd = NULL;
+ if (NULL != q->cont)
+ q->cont (q->cont_cls, NULL, q, 0, 0);
+ GNUNET_free (q);
+ }
+ else
+ {
+ GNUNET_break (0);
+ }
}
/**
* Sends an already built message on a tunnel, encrypting it and
- * choosing the best connection.
+ * choosing the best connection if not provided.
*
* @param message Message to send. Function modifies it.
* @param t Tunnel on which this message is transmitted.
- * @param ch Channel on which this message is transmitted.
+ * @param c Connection to use (autoselect if NULL).
* @param force Force the tunnel to take the message (buffer overfill).
* @param cont Continuation to call once message is really sent.
* @param cont_cls Closure for @c cont.
*/
struct MeshTunnel3Queue *
GMT_send_prebuilt_message (const struct GNUNET_MessageHeader *message,
- struct MeshTunnel3 *t, struct MeshChannel *ch,
- int force,
- GMT_sent cont, void *cont_cls)
+ struct MeshTunnel3 *t, struct MeshConnection *c,
+ int force, GMT_sent cont, void *cont_cls)
{
- struct MeshTunnel3Queue *q;
- struct MeshConnection *c;
- struct GNUNET_MESH_Encrypted *msg;
- size_t size = ntohs (message->size);
- size_t encrypted_size;
- char cbuf[sizeof (struct GNUNET_MESH_Encrypted) + size];
- uint32_t iv;
- uint16_t type;
- int fwd;
-
- LOG (GNUNET_ERROR_TYPE_DEBUG, "GMT Send on Tunnel %s\n", GMT_2s (t));
-
- if (GNUNET_NO == is_ready (t))
- {
- queue_data (t, ch, message);
- /* FIXME */
- return NULL;
- }
-
- GNUNET_assert (GNUNET_NO == GMT_is_loopback (t));
-
- iv = GNUNET_CRYPTO_random_u32 (GNUNET_CRYPTO_QUALITY_NONCE, UINT32_MAX);
- msg = (struct GNUNET_MESH_Encrypted *) cbuf;
- msg->header.type = htons (GNUNET_MESSAGE_TYPE_MESH_ENCRYPTED);
- msg->iv = iv;
- encrypted_size = t_encrypt (t, &msg[1], message, size, iv);
- msg->header.size = htons (sizeof (struct GNUNET_MESH_Encrypted) + encrypted_size);
- c = tunnel_get_connection (t);
- if (NULL == c)
- {
- GNUNET_break (GNUNET_YES == t->destroy);
- return NULL;
- }
- type = ntohs (message->type);
- switch (type)
- {
- case GNUNET_MESSAGE_TYPE_MESH_DATA:
- case GNUNET_MESSAGE_TYPE_MESH_DATA_ACK:
- case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_CREATE:
- case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_DESTROY:
- case GNUNET_MESSAGE_TYPE_MESH_CHANNEL_ACK:
- msg->cid = *GMC_get_id (c);
- msg->ttl = htonl (default_ttl);
- break;
- default:
- LOG (GNUNET_ERROR_TYPE_DEBUG, "unkown type %s\n",
- GM_m2s (type));
- GNUNET_break (0);
- }
-
- fwd = GMC_is_origin (c, GNUNET_YES);
-
- if (NULL == cont)
- {
- (void) GMC_send_prebuilt_message (&msg->header, c, fwd, force, NULL, NULL);
- return NULL;
- }
- q = GNUNET_new (struct MeshTunnel3Queue); /* FIXME valgrind: leak*/
- q->q = GMC_send_prebuilt_message (&msg->header, c, fwd, force,
- &message_sent, q);
- q->cont = cont;
- q->cont_cls = cont_cls;
-
- return q;
+ return send_prebuilt_message (message, t, c, force, cont, cont_cls, NULL);
}
+
/**
* Is the tunnel directed towards the local peer?
*
}
}
}
- return (path->length + overlap) * (path->score * -1);
+ return path->length + overlap;
}
return GMP_2s (t->peer);
}
+
+
+/******************************************************************************/
+/***************************** INFO/DEBUG *******************************/
+/******************************************************************************/
+
+
+/**
+ * Log all possible info about the tunnel state to stderr.
+ *
+ * @param t Tunnel to debug.
+ */
+void
+GMT_debug (const struct MeshTunnel3 *t)
+{
+ struct MeshTChannel *iterch;
+ struct MeshTConnection *iterc;
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "DEBUG TUNNEL TOWARDS %s\n", GMT_2s (t));
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " cstate %s, estate %s\n",
+ cstate2s (t->cstate), estate2s (t->estate));
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " kx_ctx %p, rekey_task %u\n",
+ t->kx_ctx, t->rekey_task);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " tq_head %p, tq_tail %p\n",
+ t->tq_head, t->tq_tail);
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " destroy %u\n", t->destroy_task);
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " channels:\n");
+ for (iterch = t->channel_head; NULL != iterch; iterch = iterch->next)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " - %s\n", GMCH_2s (iterch->ch));
+ }
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " connections:\n");
+ for (iterc = t->connection_head; NULL != iterc; iterc = iterc->next)
+ {
+ LOG (GNUNET_ERROR_TYPE_DEBUG, " - %s [%u] buf: %u/%u (qn %u/%u)\n",
+ GMC_2s (iterc->c), GMC_get_state (iterc->c),
+ GMC_get_buffer (iterc->c, GNUNET_YES),
+ GMC_get_buffer (iterc->c, GNUNET_NO),
+ GMC_get_qn (iterc->c, GNUNET_YES),
+ GMC_get_qn (iterc->c, GNUNET_NO));
+ }
+
+ LOG (GNUNET_ERROR_TYPE_DEBUG, "DEBUG TUNNEL END\n");
+}
+
+
+/**
+ * Iterate all tunnels.
+ *
+ * @param iter Iterator.
+ * @param cls Closure for @c iter.
+ */
+void
+GMT_iterate_all (GNUNET_CONTAINER_PeerMapIterator iter, void *cls)
+{
+ GNUNET_CONTAINER_multipeermap_iterate (tunnels, iter, cls);
+}
+
+
+/**
+ * Count all tunnels.
+ *
+ * @return Number of tunnels to remote peers kept by this peer.
+ */
+unsigned int
+GMT_count_all (void)
+{
+ return GNUNET_CONTAINER_multipeermap_size (tunnels);
+}
+
+
+/**
+ * Iterate all connections of a tunnel.
+ *
+ * @param t Tunnel whose connections to iterate.
+ * @param iter Iterator.
+ * @param cls Closure for @c iter.
+ */
+void
+GMT_iterate_connections (struct MeshTunnel3 *t, GMT_conn_iter iter, void *cls)
+{
+ struct MeshTConnection *ct;
+
+ for (ct = t->connection_head; NULL != ct; ct = ct->next)
+ iter (cls, ct->c);
+}
+
+
+/**
+ * Iterate all channels of a tunnel.
+ *
+ * @param t Tunnel whose channels to iterate.
+ * @param iter Iterator.
+ * @param cls Closure for @c iter.
+ */
+void
+GMT_iterate_channels (struct MeshTunnel3 *t, GMT_chan_iter iter, void *cls)
+{
+ struct MeshTChannel *cht;
+
+ for (cht = t->channel_head; NULL != cht; cht = cht->next)
+ iter (cls, cht->ch);
+}