257 lines
6.2 KiB
C
257 lines
6.2 KiB
C
|
#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;
|
||
|
}
|