Broadcast forwarder for ip-bound gameservers
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

256 lines
6.2 KiB

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netinet/udp.h>
#include <time.h>
#include "configfile.h"
#include "event.h"
#include "list.h"
#include "logging.h"
#include "sockaddr.h"
struct host_entry {
struct list_head list;
struct sockaddr_in address;
int flags;
};
#define HOST_ACTIVE 0x0001
#define HOST_LOCAL 0x0010
#define HOST_REMOTE 0x0020
static LIST_HEAD(host_list);
static int raw_sock;
static int listen_sock;
static struct event_fd *listen_event;
static struct event_timeout *local_timeout;
static struct event_timeout *remote_timeout;
static int add_host_cb(const char *parameter, void *privdata)
{
struct host_entry *entry = malloc(sizeof(struct host_entry));
if (entry == NULL) {
log_print(LOG_WARN, "add_host_cb(): out of memory");
return -1;
}
if (parse_sockaddr(parameter, &entry->address)) {
log_print(LOG_WARN, "add_host_cb(): invalid host: '%s'", parameter);
free(entry);
return -1;
}
entry->flags = (int)privdata | HOST_ACTIVE;
log_print(LOG_INFO, "Forwarding to %s (%s)", get_sockaddr_buf(&entry->address), (entry->flags == HOST_LOCAL) ? "local" : "remote");
list_add_tail(&entry->list, &host_list);
return 0;
}
static int check_local_host_cb(void *privdata)
{
struct host_entry *entry;
list_for_each_entry(entry, &host_list, list) {
if (!(entry->flags & HOST_LOCAL))
continue;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
log_print(LOG_WARN, "check_local_host_cb(): socket()");
continue;
}
if (bind(sock,(struct sockaddr *)&entry->address, sizeof(struct sockaddr_in)) < 0) {
/* port in use, host seems to be active */
if (!(entry->flags & HOST_ACTIVE)) {
log_print(LOG_INFO, "Host active: %s", get_sockaddr_buf(&entry->address));
entry->flags |= HOST_ACTIVE;
}
} else {
/* port not in use, host can not be active */
if (entry->flags & HOST_ACTIVE) {
log_print(LOG_INFO, "Host not active: %s", get_sockaddr_buf(&entry->address));
entry->flags &= ~(HOST_ACTIVE);
}
}
close(sock);
}
return 0;
}
#if 0
static int check_remote_host_cb(void *privdata)
{
// TODO: don't know how to check
return 0;
}
#endif
static int send_raw_udp(int fd, struct sockaddr_in *src, struct sockaddr_in *dst, const char *buf, int offset, int size)
{
if (offset < sizeof(struct iphdr) + sizeof(struct udphdr))
return -1;
struct iphdr *ip = (struct iphdr *)buf;
struct udphdr *udp = (struct udphdr *)(buf + sizeof(struct iphdr));
ip->version = 4; /* IPv4 */
ip->ihl = sizeof(struct iphdr) / sizeof(int);
ip->tos = 0;
ip->id = 0;
ip->frag_off = 0;
ip->ttl = 64;
ip->protocol = IPPROTO_UDP;
ip->check = 0; /* computed by kernel */
ip->saddr = src->sin_addr.s_addr;
ip->daddr = dst->sin_addr.s_addr;
int packetsize = sizeof(struct iphdr) + sizeof(struct udphdr) + size;
ip->tot_len = htons(packetsize);
udp->source = src->sin_port;
udp->dest = dst->sin_port;
udp->check = 0; /* ignore */
udp->len = htons(sizeof(struct udphdr) + size);
if (sendto(fd, buf, packetsize, 0, (struct sockaddr *)dst, sizeof(struct sockaddr)) < 0) {
log_print(LOG_WARN, "send_raw_udp(): sendto()");
return -1;
}
return 0;
}
static int listen_cb(int fd, void *privdata)
{
struct sockaddr_in src_addr;
char buf[512];
socklen_t i = sizeof(src_addr);
int offset = sizeof(struct iphdr) + sizeof(struct udphdr);
int len = recvfrom(fd, buf + offset, sizeof(buf) - offset, 0, (struct sockaddr *)&src_addr, &i);
if (len <= 0) {
log_print(LOG_WARN, "listen_cb(): recvfrom()");
return -1;
}
struct host_entry *entry;
list_for_each_entry(entry, &host_list, list) {
if (!(entry->flags & HOST_ACTIVE))
continue;
send_raw_udp(raw_sock, &src_addr, &entry->address, buf, offset, len);
}
return 0;
}
int bcast_close(void)
{
if (remote_timeout != NULL) {
event_remove_timeout(remote_timeout);
remote_timeout = NULL;
}
if (local_timeout != NULL) {
event_remove_timeout(local_timeout);
local_timeout = NULL;
}
struct host_entry *entry, *tmp;
list_for_each_entry_safe(entry, tmp, &host_list, list) {
list_del(&entry->list);
free(entry);
}
event_remove_fd(listen_event);
close(listen_sock);
close(raw_sock);
return 0;
}
int bcast_init(void)
{
raw_sock = socket(PF_INET, SOCK_RAW, IPPROTO_UDP);
if (raw_sock < 0) {
log_print(LOG_ERROR, "bcast_init(): socket(raw)");
return -1;
}
int i = 1;
if (setsockopt(raw_sock, IPPROTO_IP, IP_HDRINCL, &i, sizeof(i)) < 0 ) {
log_print(LOG_ERROR, "bcast_init(): setsockopt(IP_HDRINCL)");
close(raw_sock);
return -1;
}
int listen_port = 0;
if (config_get_int("global", "listen", &listen_port, 0)) {
log_print(LOG_ERROR, "bcast_init(): Invalid listen port: %d", listen_port);
close(raw_sock);
return -1;
}
struct sockaddr_in listen_addr;
listen_addr.sin_family = AF_INET,
listen_addr.sin_addr.s_addr = INADDR_ANY;
listen_addr.sin_port = htons(listen_port);
listen_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (listen_sock < 0) {
log_print(LOG_ERROR, "bcast_init(): socket(listen)");
close(raw_sock);
return -1;
}
if (bind(listen_sock, (struct sockaddr *)&listen_addr, sizeof(struct sockaddr_in)) < 0) {
log_print(LOG_ERROR, "bcast_init(): bind()");
close(listen_sock);
close(raw_sock);
return -1;
}
log_print(LOG_INFO, "Listening on %s", get_sockaddr_buf(&listen_addr));
listen_event = event_add_readfd(NULL, listen_sock, listen_cb, NULL);
int localcount, remotecount;
localcount = config_get_strings("local", "host", add_host_cb, (void *)HOST_LOCAL);
remotecount = config_get_strings("remote", "host", add_host_cb, (void *)HOST_REMOTE);
if ((localcount + remotecount) == 0) {
log_print(LOG_ERROR, "bcast_init(): no hosts given");
event_remove_fd(listen_event);
close(listen_sock);
close(raw_sock);
return -1;
}
int check_interval = 0;
struct timeval tv;
tv.tv_usec = 0;
if (localcount > 0) {
config_get_int("local", "check_interval", &check_interval, 10);
tv.tv_sec = check_interval;
local_timeout = event_add_timeout(&tv, check_local_host_cb, NULL);
}
#if 0
if (remotecount > 0) {
config_get_int("remote", "check_interval", &check_interval, 60);
tv.tv_sec = check_interval;
remote_timeout = event_add_timeout(&tv, check_remote_host_cb, NULL);
}
#endif
return 0;
}