syndilights/blib-1.1.7/blib/bproxyserver.c

371 lines
9.6 KiB
C

/* blib - Library of useful things to hack the Blinkenlights
*
* Copyright (c) 2001-2004 The Blinkenlights Crew
* Sven Neumann <sven@gimp.org>
* Michael Natterer <mitch@gimp.org>
* Daniel Mack <daniel@yoobay.net>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program 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 General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <glib-object.h>
#ifdef G_OS_WIN32
#include <winsock2.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#endif
#include "btypes.h"
#include "bmarshal.h"
#include "bpacket.h"
#include "bproxyserver.h"
#include "breceiver.h"
#include "bsocket.h"
#define HEARTBEAT_TIMEOUT (B_HEARTBEAT_INTERVAL * 12)
enum
{
CLIENT_ADDED,
CLIENT_REMOVED,
LAST_SIGNAL
};
typedef struct
{
BProxyServer *server;
gulong addr;
gushort port;
GSource *timeout;
} BClient;
static void b_proxy_server_class_init (BProxyServerClass *klass);
static void b_proxy_server_init (BProxyServer *server);
static void b_proxy_server_finalize (GObject *object);
static gboolean b_proxy_server_heartbeat_callback (BProxyServer *server,
BPacket *packet,
gpointer data);
static guint b_client_hash (const BClient *client);
static gboolean b_client_equal (const BClient *a,
const BClient *b);
static void b_client_add (const BClient *template);
static gboolean b_client_remove (BClient *client);
static void b_client_send_packet (gpointer key,
BClient *client,
BPacket *packet);
static guint server_signals[LAST_SIGNAL] = { 0 };
static GObjectClass *parent_class = NULL;
GType
b_proxy_server_get_type (void)
{
static GType server_type = 0;
if (!server_type)
{
static const GTypeInfo server_info =
{
sizeof (BProxyServerClass),
NULL, /* base_init */
NULL, /* base_finalize */
(GClassInitFunc) b_proxy_server_class_init,
NULL, /* class_finalize */
NULL, /* class_data */
sizeof (BProxyServer),
0, /* n_preallocs */
(GInstanceInitFunc) b_proxy_server_init,
};
server_type = g_type_register_static (B_TYPE_RECEIVER,
"BProxyServer", &server_info, 0);
}
return server_type;
}
static void
b_proxy_server_class_init (BProxyServerClass *klass)
{
GObjectClass *object_class;
parent_class = g_type_class_peek_parent (klass);
object_class = G_OBJECT_CLASS (klass);
object_class->finalize = b_proxy_server_finalize;
server_signals[CLIENT_ADDED] =
g_signal_new ("client_added",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (BProxyServerClass, client_added),
NULL, NULL,
b_marshal_VOID__STRING_INT,
G_TYPE_NONE,
2, G_TYPE_STRING, G_TYPE_INT);
server_signals[CLIENT_REMOVED] =
g_signal_new ("client_removed",
G_TYPE_FROM_CLASS (klass),
G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (BProxyServerClass, client_removed),
NULL, NULL,
b_marshal_VOID__STRING_INT,
G_TYPE_NONE,
2, G_TYPE_STRING, G_TYPE_INT);
}
static void
b_proxy_server_init (BProxyServer *server)
{
server->fd = -1;
}
static void
b_proxy_server_finalize (GObject *object)
{
BProxyServer *server = B_PROXY_SERVER (object);
if (server->fd != -1)
{
close (server->fd);
server->fd = -1;
}
if (server->clients)
{
g_hash_table_destroy (server->clients);
server->clients = NULL;
}
G_OBJECT_CLASS (parent_class)->finalize (object);
}
/**
* b_proxy_server_new:
* @port: the local port to listen to for heartbeat packages
* @error: return location for a possible error
*
* Creates a new #BProxyServer object, ready to use.
*
* Return value: a newly allocate #BProxyServer object
**/
BProxyServer *
b_proxy_server_new (gint port,
GError **error)
{
BProxyServer *server;
struct sockaddr_in sock;
gint fd;
g_return_val_if_fail (port > 0, NULL);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (! b_socket_init (error))
return NULL;
fd = b_socket_udp_new (B_SO_REUSEADDR, error);
if (fd < 0)
return NULL;
sock.sin_addr.s_addr = INADDR_ANY;
sock.sin_family = AF_INET;
sock.sin_port = g_htons (port);
if (bind (fd, (struct sockaddr *) &sock, sizeof (sock)) < 0)
{
g_set_error (error, 0, 0,
"Can't bind socket to local port %d: %s\n",
port, g_strerror (errno));
close (fd);
return NULL;
}
server = g_object_new (B_TYPE_PROXY_SERVER,
"callback", b_proxy_server_heartbeat_callback,
NULL);
server->fd = fd;
server->clients = g_hash_table_new_full ((GHashFunc) b_client_hash,
(GEqualFunc) b_client_equal,
(GDestroyNotify) NULL,
(GDestroyNotify) g_free);
b_receiver_listen_fd (B_RECEIVER (server), fd);
return server;
}
gboolean
b_proxy_server_send_packet (BProxyServer *server,
BPacket *packet)
{
g_return_val_if_fail (B_IS_PROXY_SERVER (server), FALSE);
g_return_val_if_fail (packet != NULL, FALSE);
if (server->clients)
g_hash_table_foreach (server->clients,
(GHFunc) b_client_send_packet, packet);
return TRUE;
}
gint
b_proxy_server_num_clients (BProxyServer *server)
{
g_return_val_if_fail (B_IS_PROXY_SERVER (server), 0);
return (server->clients ? g_hash_table_size (server->clients) : 0);
}
static gboolean
b_proxy_server_heartbeat_callback (BProxyServer *server,
BPacket *packet,
gpointer data)
{
BClient lookup;
BClient *client;
if (packet->header.heartbeat_h.magic != MAGIC_HEARTBEAT ||
packet->header.heartbeat_h.version != 0)
return TRUE;
lookup.server = server;
lookup.addr = B_RECEIVER (server)->addr;
lookup.port = B_RECEIVER (server)->port;
client = g_hash_table_lookup (server->clients, &lookup);
if (client)
{
g_source_destroy (client->timeout);
client->timeout = g_timeout_source_new (HEARTBEAT_TIMEOUT);
g_source_set_callback (client->timeout,
(GSourceFunc) b_client_remove, client, NULL);
g_source_attach (client->timeout, NULL);
}
else
{
b_client_add (&lookup);
}
return TRUE;
}
static guint
b_client_hash (const BClient *client)
{
return (guint) client->addr;
}
static gboolean
b_client_equal (const BClient *a,
const BClient *b)
{
return (a->addr == b->addr && a->port == b->port);
}
static void
b_client_add (const BClient *template)
{
BClient *client = g_memdup (template, sizeof (BClient));
struct in_addr addr;
const gchar *host;
gint port;
client->timeout = g_timeout_source_new (HEARTBEAT_TIMEOUT);
g_source_set_callback (client->timeout,
(GSourceFunc) b_client_remove, client, NULL);
g_source_attach (client->timeout, NULL);
g_hash_table_insert (client->server->clients, client, client);
addr.s_addr = client->addr;
host = inet_ntoa (addr);
port = g_ntohs (client->port);
g_signal_emit (client->server,
server_signals[CLIENT_ADDED], 0,
host, port);
}
static gboolean
b_client_remove (BClient *client)
{
BProxyServer *server = client->server;
struct in_addr addr;
const gchar *host;
gint port;
addr.s_addr = client->addr;
host = inet_ntoa (addr);
port = g_ntohs (client->port);
g_hash_table_remove (server->clients, client);
g_signal_emit (server,
server_signals[CLIENT_REMOVED], 0,
host, port);
return FALSE;
}
static void
b_client_send_packet (gpointer key,
BClient *client,
BPacket *packet)
{
struct sockaddr_in addr;
gsize size = b_packet_size (packet);
BPacket *copy = g_memdup (packet, size);
b_packet_hton (copy);
addr.sin_family = AF_INET;
addr.sin_port = client->port;
addr.sin_addr.s_addr = client->addr;
sendto (client->server->fd, copy, size, 0,
(struct sockaddr *) &addr, sizeof (addr));
g_free (copy);
}