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