/* BTP library - Banana Tree Protocol
* Copyright (C) 1999-2001 The Regents of the University of Michigan
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include <sys/types.h>
#include <sys/time.h>
#include "b_conn_ping.h"
#include "b_packet.h"
#include "util.h"
#include "btp_debug.h"
static void receive_any (BConn* conn, BPacket* packet, gpointer user_data);
static void receive_ping (BConn* conn, BPacket* packet, gpointer user_data);
static void receive_pong (BConn* conn, BPacket* packet, gpointer user_data);
static void send_ping (BConn* conn);
static gboolean send_ping_cb (gpointer data);
static gboolean conn_timeout_cb (gpointer data);
/* **************************************** */
/**
Send a ping to a host. If we already know the distance to the
host, we set a timer to send the ping in the future. Otherwise, we
send the ping now. We will continue pinging the host until reset
is called.
*/
void
b_conn_ping_start (BConn* conn)
{
g_return_if_fail (conn);
g_return_if_fail (b_conn_is_connected (conn));
g_return_if_fail (!b_conn_is_closing (conn));
BTPP (1, "b_conn_ping_start\n");
if (conn->is_pinging) return;
conn->is_pinging = TRUE;
/* Set conn timeout */
rand_timer_set (&conn->conn_timeout_timer, BTP_PING_TIMEOUT,
0, conn_timeout_cb, conn);
/* Start handling any packet */
b_conn_handler_add (conn, -1, -1, receive_any, NULL);
/* Start handling PONGs */
b_conn_handler_add (conn, B_PACKET_TYPE_INFO_REPLY, B_PACKET_INFO_SUBTYPE_PING,
receive_pong, NULL);
/* If we don't know the distance, send a PING immediately. */
if (conn->distance == 0)
{
send_ping (conn);
}
/* Otherwise, schedule one soon */
else
{
/* Start sending PINGs */
rand_timer_set (&conn->send_ping_timer, BTP_PING_SEND_TIME,
BTP_PING_SEND_RANGE, send_ping_cb, conn);
}
}
/**
Reset the ping timers. This will stop us from pinging the host.
*/
void
b_conn_ping_stop (BConn* conn)
{
g_return_if_fail (conn);
BTPP (1, "b_conn_ping_stop\n");
if (!conn->is_pinging) return;
conn->is_pinging = FALSE;
/* Stop sending PINGs */
rand_timer_cancel (&conn->send_ping_timer);
/* Don't timeout the connection */
rand_timer_cancel (&conn->conn_timeout_timer);
/* Stop handline PONGs */
b_conn_handler_remove (conn, B_PACKET_TYPE_INFO_REPLY,
B_PACKET_INFO_SUBTYPE_PING, NULL);
/* Stop handling any packet */
b_conn_handler_remove (conn, -1, -1, NULL);
/* Remove all pings from the queue */
b_conn_remove_packets_by_subtype (conn, B_PACKET_TYPE_INFO_REQUEST,
B_PACKET_INFO_SUBTYPE_PING);
}
void
b_conn_ping_handle (BConn* conn)
{
g_return_if_fail (conn);
g_return_if_fail (b_conn_is_connected (conn));
g_return_if_fail (!b_conn_is_closing (conn));
/* Start handling PINGs */
b_conn_handler_add (conn, B_PACKET_TYPE_INFO_REQUEST, B_PACKET_INFO_SUBTYPE_PING,
receive_ping, NULL);
}
void
b_conn_ping_unhandle (BConn* conn)
{
g_return_if_fail (conn);
/* Stop handling PINGs */
b_conn_handler_remove (conn, B_PACKET_TYPE_INFO_REQUEST,
B_PACKET_INFO_SUBTYPE_PING, NULL);
}
/* **************************************** */
/**
Called when a packet has been received for the host. This resets
the connection timeout timer.
*/
static void
receive_any (BConn* conn, BPacket* packet, gpointer user_data)
{
BTPP (1, "receive_any\n");
g_return_if_fail (conn->is_pinging);
/* Reset conn timeout */
rand_timer_reset (&conn->conn_timeout_timer, BTP_PING_TIMEOUT,
0, conn_timeout_cb, conn);
}
/**
Process a PING packet received from this host. We immediately send
back a PONG.
This callback is set once we have connect.
*/
static void
receive_ping (BConn* conn, BPacket* packet, gpointer user_data)
{
BPacket* new_packet;
g_return_if_fail (conn);
g_return_if_fail (packet);
g_return_if_fail (packet->type == B_PACKET_TYPE_INFO_REQUEST);
g_return_if_fail (packet->subtype == B_PACKET_INFO_SUBTYPE_PING);
BTPP (1, "receive_ping\n");
/* Make sure the ping isn't too big */
if (g_ntohs(packet->length) > 64)
{
g_warning ("Received very long PING - ignoring\n");
return;
}
/* Send a pong back */
new_packet = b_packet_new_info_reply_ping (conn, packet);
b_conn_send_packet (conn, new_packet);
}
/**
Process a PONG packet received from this host. We calculate the
distance to the host.
TODO: Check if pong was sent. Worst case, we get messed up
distance estimate.
*/
static void
receive_pong (BConn* conn, BPacket* packet, gpointer user_data)
{
struct timeval* time_sent;
struct timeval timeofday;
guint distance;
g_return_if_fail (conn);
g_return_if_fail (packet);
g_return_if_fail (packet->type == B_PACKET_TYPE_INFO_REPLY);
g_return_if_fail (packet->subtype == B_PACKET_INFO_SUBTYPE_PING);
BTPP (1, "receive_pong\n");
/* Make sure payload is the correct length */
if (g_ntohs(packet->length) != sizeof(struct timeval))
{
g_warning ("Received weird pong\n");
b_conn_func_fail (conn);
return;
}
time_sent = (struct timeval*) packet->data;
gettimeofday (&timeofday, NULL);
/* Make sure the pong isn't from the future */
if (timercmp (time_sent, &timeofday, >))
{
g_warning ("Received pong from future.\n");
b_conn_func_fail (conn);
return;
}
/* Get the distance (in ms) */
timersub (&timeofday, time_sent, &timeofday);
distance = timeofday.tv_sec * 1000 + timeofday.tv_usec / 1000;
distance = MAX(distance, 1);
/* Save the distance. If we have an old estimate, take the weighted
average. */
if (conn->distance)
{
conn->distance = (((double) conn->distance) * (BTP_PING_AVG_FACTOR)) +
(((double) distance) * (1.0 - BTP_PING_AVG_FACTOR));
if (conn->distance <= 0)
conn->distance = 1;
}
else
{
conn->distance = distance;
}
BTPP (5, "PING: %s:%d is %d (%d)\n",
conn->conn->hostname, conn->conn->port,
conn->distance, distance);
b_conn_func_ping (conn);
}
/* **************************************** */
void
b_conn_ping_send (BConn* conn)
{
/* Clear ping timer */
rand_timer_cancel (&conn->send_ping_timer);
/* Send ping now. The timer will be reset in send_ping() */
send_ping (conn);
}
static void
send_ping (BConn* conn)
{
g_return_if_fail (conn != NULL);
BTPP (1, "send_ping\n");
/* Send a ping only if there isn't one already in the queue. */
if (!b_conn_has_packet_by_subtype(conn, B_PACKET_TYPE_INFO_REQUEST,
B_PACKET_INFO_SUBTYPE_PING))
{
BPacket* packet;
/* Send a ping to the host */
packet = b_packet_new_info_request_ping (conn);
b_conn_send_packet (conn, packet);
}
/* Reset timer */
rand_timer_set (&conn->send_ping_timer, BTP_PING_SEND_TIME,
BTP_PING_SEND_RANGE, send_ping_cb, conn);
}
/* **************************************** */
static gboolean
send_ping_cb (gpointer data)
{
BConn* conn = (BConn*) data;
BTPP (1, "send_ping_cb\n");
g_return_val_if_fail (conn, FALSE);
g_return_val_if_fail (conn->send_ping_timer, FALSE);
conn->send_ping_timer = 0;
send_ping (conn);
return FALSE;
}
static gboolean
conn_timeout_cb (gpointer data)
{
BConn* conn = (BConn*) data;
BTPP (1, "conn_timeout_cb\n");
g_return_val_if_fail (conn, FALSE);
g_return_val_if_fail (conn->conn_timeout_timer, FALSE);
conn->conn_timeout_timer = 0;
b_conn_func_fail (conn);
return FALSE;
}
syntax highlighted by Code2HTML, v. 0.9.1