/* 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 #include #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; }