/* * Copyright 2010 Nathan Phillip Brink * * This file is a part of DistRen. * * DistRen is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * DistRen 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with DistRen. If not, see . */ #include "common/config.h" #include "distrend.h" #include "listen.h" #include "tabletennis.h" #include "common/request.h" #include "common/protocol.h" #include #include #include #include struct tabletennis { unsigned int ping_interval; unsigned int pong_time; /* of type (struct distrend_client *) */ queue_t clients_to_ping; /* of type (struct distrend_client *) */ queue_t clients_need_pong; struct timespec time_last_check; }; static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data); static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data); tabletennis_t tabletennis_new(struct distrend_listens *listens, unsigned int ping_interval, unsigned int pong_time) { tabletennis_t tabletennis; tabletennis = malloc(sizeof(struct tabletennis)); tabletennis->ping_interval = ping_interval; tabletennis->pong_time = pong_time; tabletennis->clients_to_ping = q_init(); tabletennis->clients_need_pong = q_init(); clock_gettime(CLOCK_MONOTONIC, &tabletennis->time_last_check); distrend_listen_handler_add(listens, DISTREN_REQUEST_PING, &tabletennis_ping_request_handle); distrend_listen_handler_add(listens, DISTREN_REQUEST_PONG, &tabletennis_pong_request_handle); return tabletennis; } int tabletennis_add_client(tabletennis_t tabletennis, struct distrend_client *client) { client->tabletennis_client.state = TABLETENNIS_NEED_PING; client->tabletennis_client.time_next_check = tabletennis->time_last_check.tv_sec + tabletennis->ping_interval; q_enqueue(tabletennis->clients_to_ping, client, 0); return 0; } int tabletennis_serve(tabletennis_t tabletennis) { struct timespec time_now; struct distrend_client *client; time_t time_next_check; struct distren_request *req; void *req_data; clock_gettime(CLOCK_MONOTONIC, &time_now); time_next_check = time_now.tv_sec + tabletennis->pong_time; for(client = q_front(tabletennis->clients_to_ping); client && client->tabletennis_client.time_next_check < time_now.tv_sec; client = q_front(tabletennis->clients_to_ping)) { q_dequeue(tabletennis->clients_to_ping); /* use time_next_check as the ping data */ distren_request_poing(&req, &req_data, 1, &time_next_check, sizeof(time_next_check)); distrend_client_write_request(client, req, req_data); distren_request_free_with_data(req, req_data); client->tabletennis_client.state = TABLETENNIS_NEED_PONG; client->tabletennis_client.time_next_check = time_next_check; q_enqueue(tabletennis->clients_need_pong, client, 0); } time_next_check = time_now.tv_sec + tabletennis->ping_interval; for(client = q_front(tabletennis->clients_need_pong); client && client->tabletennis_client.time_next_check < time_now.tv_sec; client = q_front(tabletennis->clients_need_pong)) { q_dequeue(tabletennis->clients_need_pong); if(client->tabletennis_client.state == TABLETENNIS_NEED_PONG) { distrend_send_disconnect(client, "You failed to respond to a PING packet after some seconds!"); client->tabletennis_client.state = TABLETENNIS_DELINQUENT; } else /* state must be TABLETENNIS_HAS_PONG */ { client->tabletennis_client.time_next_check = time_next_check; client->tabletennis_client.state = TABLETENNIS_NEED_PING; q_enqueue(tabletennis->clients_to_ping, client, 0); } } /* for tabletennis_add_client() */ memcpy(&tabletennis->time_last_check, &time_now, sizeof(struct timespec)); return 0; } /** * helper for tabletennis_del_client() which just looks for a * particular pointer in the queue (a.k.a., list). */ static int tabletennis_del_client_traverse(void *trav_data, void *client) { if(trav_data == client) return FALSE; return TRUE; } int tabletennis_del_client(tabletennis_t tabletennis, struct distrend_client *client) { list_t thelist; thelist = NULL; switch(client->tabletennis_client.state) { case TABLETENNIS_NEED_PING: thelist = tabletennis->clients_to_ping; break; case TABLETENNIS_NEED_PONG: case TABLETENNIS_HAS_PONG: thelist = tabletennis->clients_need_pong; break; case TABLETENNIS_DELINQUENT: thelist = NULL; break; } if(!thelist) return 0; list_traverse(thelist, (void *)client, (list_traverse_func_t)&tabletennis_del_client_traverse, LIST_FORW | LIST_FRNT | LIST_SAVE); if(list_curr(thelist) == client) list_remove_curr(thelist); return 0; } void tabletennis_free(tabletennis_t tabletennis) { q_free(tabletennis->clients_to_ping, LIST_NODEALLOC); q_free(tabletennis->clients_need_pong, LIST_NODEALLOC); free(tabletennis); } /* implementations of locals */ /** * Handles a DISTREN_REQUEST_PING from a client. * * @todo throttling? */ static int tabletennis_ping_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data) { struct distren_request *pong_req; if(req_len > 32) distrend_send_disconnect(client, "You have tried to send a PING packet with a length longer than 32 bytes."); /** respond to the client using the data he sent in his PONG command. */ distren_request_new(&pong_req, req_len, DISTREN_REQUEST_PONG); distrend_client_write_request(client, pong_req, req_data); distren_request_free(pong_req); return 0; } static int tabletennis_pong_request_handle(struct general_info *geninfo, struct distrend_client *client, size_t req_len, void *req_data) { fprintf(stderr, "got pong\n"); /* * The only place that sends PINGs from distrend is * tabletennis_serve() and it uses * client->tabletennis_client.time_next_check as the cookie. */ if(client->tabletennis_client.state == TABLETENNIS_NEED_PONG && req_len == sizeof(client->tabletennis_client.time_next_check) && !memcmp(req_data, &client->tabletennis_client.time_next_check, sizeof(client->tabletennis_client.time_next_check))) { /* valid match */ client->tabletennis_client.state = TABLETENNIS_HAS_PONG; return 0; } /* no match or invalid state */ distrend_send_disconnect(client, "You tried to send a PONG packet without first receiving a PING packet."); return 0; }