diff options
Diffstat (limited to 'mdns-repeater.c')
| -rw-r--r-- | mdns-repeater.c | 652 |
1 files changed, 652 insertions, 0 deletions
diff --git a/mdns-repeater.c b/mdns-repeater.c new file mode 100644 index 0000000..2e743c3 --- /dev/null +++ b/mdns-repeater.c @@ -0,0 +1,652 @@ +/* + * mdns-repeater.c - mDNS repeater daemon + * Copyright (C) 2011 Darell Tan + * + * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include <sys/socket.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <signal.h> +#include <string.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <syslog.h> +#include <unistd.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <net/if.h> +#include <errno.h> + +#define PACKAGE "mdns-repeater" +#define MDNS_ADDR "224.0.0.251" +#define MDNS_PORT 5353 + +#ifndef PIDFILE +#define PIDFILE "/var/run/" PACKAGE ".pid" +#endif + +#define MAX_SOCKS 16 +#define MAX_SUBNETS 16 + +struct if_sock { + const char *ifname; /* interface name */ + int sockfd; /* socket filedesc */ + struct in_addr addr; /* interface addr */ + struct in_addr mask; /* interface mask */ + struct in_addr net; /* interface network (computed) */ +}; + +struct subnet { + struct in_addr addr; /* subnet addr */ + struct in_addr mask; /* subnet mask */ + struct in_addr net; /* subnet net (computed) */ +}; + +int server_sockfd = -1; + +int num_socks = 0; +struct if_sock socks[MAX_SOCKS]; + +int num_blacklisted_subnets = 0; +struct subnet blacklisted_subnets[MAX_SUBNETS]; + +int num_whitelisted_subnets = 0; +struct subnet whitelisted_subnets[MAX_SUBNETS]; + +#define PACKET_SIZE 65536 +void *pkt_data = NULL; + +int foreground = 0; +int debug = 0; +int shutdown_flag = 0; + +char *pid_file = PIDFILE; + +void log_message(int loglevel, char *fmt_str, ...) { + va_list ap; + char buf[2048]; + + va_start(ap, fmt_str); + vsnprintf(buf, 2047, fmt_str, ap); + va_end(ap); + buf[2047] = 0; + + if (foreground) { + fprintf(stderr, "%s: %s\n", PACKAGE, buf); + } else { + syslog(loglevel, "%s", buf); + } +} + +static int create_recv_sock() { + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + log_message(LOG_ERR, "recv socket(): %s", strerror(errno)); + return sd; + } + + int r = -1; + + int on = 1; + if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) { + log_message(LOG_ERR, "recv setsockopt(SO_REUSEADDR): %s", strerror(errno)); + return r; + } + + /* bind to an address */ + struct sockaddr_in serveraddr; + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_port = htons(MDNS_PORT); + serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); /* receive multicast */ + if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { + log_message(LOG_ERR, "recv bind(): %s", strerror(errno)); + } + + // enable loopback in case someone else needs the data + if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on))) < 0) { + log_message(LOG_ERR, "recv setsockopt(IP_MULTICAST_LOOP): %s", strerror(errno)); + return r; + } + +#ifdef IP_PKTINFO + if ((r = setsockopt(sd, SOL_IP, IP_PKTINFO, &on, sizeof(on))) < 0) { + log_message(LOG_ERR, "recv setsockopt(IP_PKTINFO): %s", strerror(errno)); + return r; + } +#endif + + return sd; +} + +static int create_send_sock(int recv_sockfd, const char *ifname, struct if_sock *sockdata) { + int sd = socket(AF_INET, SOCK_DGRAM, 0); + if (sd < 0) { + log_message(LOG_ERR, "send socket(): %s", strerror(errno)); + return sd; + } + + sockdata->ifname = ifname; + sockdata->sockfd = sd; + + int r = -1; + + struct ifreq ifr; + memset(&ifr, 0, sizeof(ifr)); + strncpy(ifr.ifr_name, ifname, IFNAMSIZ); + struct in_addr *if_addr = &((struct sockaddr_in *) &ifr.ifr_addr)->sin_addr; + +#ifdef SO_BINDTODEVICE + if ((r = setsockopt(sd, SOL_SOCKET, SO_BINDTODEVICE, &ifr, sizeof(struct ifreq))) < 0) { + log_message(LOG_ERR, "send setsockopt(SO_BINDTODEVICE): %s", strerror(errno)); + return r; + } +#endif + + // get netmask + if (ioctl(sd, SIOCGIFNETMASK, &ifr) == 0) { + memcpy(&sockdata->mask, if_addr, sizeof(struct in_addr)); + } + + // .. and interface address + if (ioctl(sd, SIOCGIFADDR, &ifr) == 0) { + memcpy(&sockdata->addr, if_addr, sizeof(struct in_addr)); + } + + // compute network (address & mask) + sockdata->net.s_addr = sockdata->addr.s_addr & sockdata->mask.s_addr; + + int on = 1; + if ((r = setsockopt(sd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on))) < 0) { + log_message(LOG_ERR, "send setsockopt(SO_REUSEADDR): %s", strerror(errno)); + return r; + } + + // bind to an address + struct sockaddr_in serveraddr; + memset(&serveraddr, 0, sizeof(serveraddr)); + serveraddr.sin_family = AF_INET; + serveraddr.sin_port = htons(MDNS_PORT); + serveraddr.sin_addr.s_addr = if_addr->s_addr; + if ((r = bind(sd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) < 0) { + log_message(LOG_ERR, "send bind(): %s", strerror(errno)); + } + +#if __FreeBSD__ + if((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_IF, &serveraddr.sin_addr, sizeof(serveraddr.sin_addr))) < 0) { + log_message(LOG_ERR, "send ip_multicast_if(): errno %d: %s", errno, strerror(errno)); + } +#endif + + // add membership to receiving socket + struct ip_mreq mreq; + memset(&mreq, 0, sizeof(struct ip_mreq)); + mreq.imr_interface.s_addr = if_addr->s_addr; + mreq.imr_multiaddr.s_addr = inet_addr(MDNS_ADDR); + if ((r = setsockopt(recv_sockfd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreq, sizeof(mreq))) < 0) { + log_message(LOG_ERR, "recv setsockopt(IP_ADD_MEMBERSHIP): %s", strerror(errno)); + return r; + } + + // enable loopback in case someone else needs the data + if ((r = setsockopt(sd, IPPROTO_IP, IP_MULTICAST_LOOP, &on, sizeof(on))) < 0) { + log_message(LOG_ERR, "send setsockopt(IP_MULTICAST_LOOP): %s", strerror(errno)); + return r; + } + + char *addr_str = strdup(inet_ntoa(sockdata->addr)); + char *mask_str = strdup(inet_ntoa(sockdata->mask)); + char *net_str = strdup(inet_ntoa(sockdata->net)); + log_message(LOG_INFO, "dev %s addr %s mask %s net %s", ifr.ifr_name, addr_str, mask_str, net_str); + free(addr_str); + free(mask_str); + free(net_str); + + return sd; +} + +static ssize_t send_packet(int fd, const void *data, size_t len) { + static struct sockaddr_in toaddr; + if (toaddr.sin_family != AF_INET) { + memset(&toaddr, 0, sizeof(struct sockaddr_in)); + toaddr.sin_family = AF_INET; + toaddr.sin_port = htons(MDNS_PORT); + toaddr.sin_addr.s_addr = inet_addr(MDNS_ADDR); + } + + return sendto(fd, data, len, 0, (struct sockaddr *) &toaddr, sizeof(struct sockaddr_in)); +} + +static void mdns_repeater_shutdown(int sig) { + shutdown_flag = 1; +} + +static pid_t already_running() { + FILE *f; + int count; + pid_t pid; + + f = fopen(pid_file, "r"); + if (f != NULL) { + count = fscanf(f, "%d", &pid); + fclose(f); + if (count == 1) { + if (kill(pid, 0) == 0) + return pid; + } + } + + return -1; +} + +static int write_pidfile() { + FILE *f; + int r; + + f = fopen(pid_file, "w"); + if (f != NULL) { + r = fprintf(f, "%d", getpid()); + fclose(f); + return (r > 0); + } + + return 0; +} + +static void daemonize() { + pid_t running_pid; + pid_t pid = fork(); + if (pid < 0) { + log_message(LOG_ERR, "fork(): %s", strerror(errno)); + exit(1); + } + + // exit parent process + if (pid > 0) + exit(0); + + // signals + signal(SIGCHLD, SIG_IGN); + signal(SIGHUP, SIG_IGN); + signal(SIGTERM, mdns_repeater_shutdown); + + setsid(); + umask(0027); + chdir("/"); + + // close all std fd and reopen /dev/null for them + int i; + for (i = 0; i < 3; i++) { + close(i); + if (open("/dev/null", O_RDWR) != i) { + log_message(LOG_ERR, "unable to open /dev/null for fd %d", i); + exit(1); + } + } + + // check for pid file + running_pid = already_running(); + if (running_pid != -1) { + log_message(LOG_ERR, "already running as pid %d", running_pid); + exit(1); + } else if (! write_pidfile()) { + log_message(LOG_ERR, "unable to write pid file %s", pid_file); + exit(1); + } +} + +static void show_help(const char *progname) { + fprintf(stderr, "mDNS repeater (version " MDNS_REPEATER_VERSION ")\n"); + fprintf(stderr, "Copyright (C) 2011 Darell Tan\n\n"); + fprintf(stderr, "usage: %s [ -f ] <ifdev> ...\n", progname); + fprintf(stderr, "\n" + "<ifdev> specifies an interface like \"eth0\"\n" + "packets received on an interface is repeated across all other specified interfaces\n" + "maximum number of interfaces is 5\n" + "\n" + " flags:\n" + " -f runs in foreground\n" + " -d log debug messages when runs in foreground\n" + " -b blacklist subnet (eg. 192.168.1.1/24)\n" + " -w whitelist subnet (eg. 192.168.1.1/24)\n" + " -p specifies the pid file path (default: " PIDFILE ")\n" + " -h shows this help\n" + "\n" + ); +} + +int parse(char *input, struct subnet *s) { + int delim = 0; + int end = 0; + while (input[end] != 0) { + if (input[end] == '/') { + delim = end; + } + end++; + } + + if (end == 0 || delim == 0 || end == delim) { + return -1; + } + + char *addr = (char*) malloc(end); + + memset(addr, 0, end); + strncpy(addr, input, delim); + if (inet_pton(AF_INET, addr, &s->addr) != 1) { + free(addr); + return -2; + } + + memset(addr, 0, end); + strncpy(addr, input+delim+1, end-delim-1); + int mask = atoi(addr); + free(addr); + + if (mask < 0 || mask > 32) { + return -3; + } + + s->mask.s_addr = ntohl((uint32_t)0xFFFFFFFF << (32 - mask)); + s->net.s_addr = s->addr.s_addr & s->mask.s_addr; + + return 0; +} + +int tostring(struct subnet *s, char* buf, int len) { + char *addr_str = strdup(inet_ntoa(s->addr)); + char *mask_str = strdup(inet_ntoa(s->mask)); + char *net_str = strdup(inet_ntoa(s->net)); + int l = snprintf(buf, len, "addr %s mask %s net %s", addr_str, mask_str, net_str); + free(addr_str); + free(mask_str); + free(net_str); + + return l; +} + +static int parse_opts(int argc, char *argv[]) { + int c, res; + int help = 0; + struct subnet *ss; + char *msg; + while ((c = getopt(argc, argv, "hfdp:b:w:")) != -1) { + switch (c) { + case 'h': help = 1; break; + case 'f': foreground = 1; break; + case 'd': debug = 1; break; + case 'p': + if (optarg[0] != '/') + log_message(LOG_ERR, "pid file path must be absolute"); + else + pid_file = optarg; + break; + + case 'b': + if (num_blacklisted_subnets >= MAX_SUBNETS) { + log_message(LOG_ERR, "too many blacklisted subnets (maximum is %d)", MAX_SUBNETS); + exit(2); + } + + if (num_whitelisted_subnets != 0) { + log_message(LOG_ERR, "simultaneous whitelisting and blacklisting does not make sense"); + exit(2); + } + + ss = &blacklisted_subnets[num_blacklisted_subnets]; + res = parse(optarg, ss); + switch (res) { + case -1: + log_message(LOG_ERR, "invalid blacklist argument"); + exit(2); + case -2: + log_message(LOG_ERR, "could not parse netmask"); + exit(2); + case -3: + log_message(LOG_ERR, "invalid netmask"); + exit(2); + } + + num_blacklisted_subnets++; + + msg = malloc(128); + memset(msg, 0, 128); + tostring(ss, msg, 128); + log_message(LOG_INFO, "blacklist %s", msg); + free(msg); + break; + case 'w': + if (num_whitelisted_subnets >= MAX_SUBNETS) { + log_message(LOG_ERR, "too many whitelisted subnets (maximum is %d)", MAX_SUBNETS); + exit(2); + } + + if (num_blacklisted_subnets != 0) { + log_message(LOG_ERR, "simultaneous whitelisting and blacklisting does not make sense"); + exit(2); + } + + ss = &whitelisted_subnets[num_whitelisted_subnets]; + res = parse(optarg, ss); + switch (res) { + case -1: + log_message(LOG_ERR, "invalid whitelist argument"); + exit(2); + case -2: + log_message(LOG_ERR, "could not parse netmask"); + exit(2); + case -3: + log_message(LOG_ERR, "invalid netmask"); + exit(2); + } + + num_whitelisted_subnets++; + + msg = malloc(128); + memset(msg, 0, 128); + tostring(ss, msg, 128); + log_message(LOG_INFO, "whitelist %s", msg); + free(msg); + break; + case '?': + case ':': + fputs("\n", stderr); + break; + + default: + log_message(LOG_ERR, "unknown option %c", optopt); + exit(2); + } + } + + if (help) { + show_help(argv[0]); + exit(0); + } + + return optind; +} + +int main(int argc, char *argv[]) { + pid_t running_pid; + fd_set sockfd_set; + int r = 0; + + parse_opts(argc, argv); + + if ((argc - optind) <= 1) { + show_help(argv[0]); + log_message(LOG_ERR, "error: at least 2 interfaces must be specified"); + exit(2); + } + + openlog(PACKAGE, LOG_PID | LOG_CONS, LOG_DAEMON); + if (! foreground) + daemonize(); + else { + // check for pid file when running in foreground + running_pid = already_running(); + if (running_pid != -1) { + log_message(LOG_ERR, "already running as pid %d", running_pid); + exit(1); + } + } + + // create receiving socket + server_sockfd = create_recv_sock(); + if (server_sockfd < 0) { + log_message(LOG_ERR, "unable to create server socket"); + r = 1; + goto end_main; + } + + // create sending sockets + int i; + for (i = optind; i < argc; i++) { + if (num_socks >= MAX_SOCKS) { + log_message(LOG_ERR, "too many sockets (maximum is %d)", MAX_SOCKS); + exit(2); + } + + int sockfd = create_send_sock(server_sockfd, argv[i], &socks[num_socks]); + if (sockfd < 0) { + log_message(LOG_ERR, "unable to create socket for interface %s", argv[i]); + r = 1; + goto end_main; + } + num_socks++; + } + + pkt_data = malloc(PACKET_SIZE); + if (pkt_data == NULL) { + log_message(LOG_ERR, "cannot malloc() packet buffer: %s", strerror(errno)); + r = 1; + goto end_main; + } + + while (! shutdown_flag) { + struct timeval tv = { + .tv_sec = 10, + .tv_usec = 0, + }; + + FD_ZERO(&sockfd_set); + FD_SET(server_sockfd, &sockfd_set); + int numfd = select(server_sockfd + 1, &sockfd_set, NULL, NULL, &tv); + if (numfd <= 0) + continue; + + if (FD_ISSET(server_sockfd, &sockfd_set)) { + struct sockaddr_in fromaddr; + socklen_t sockaddr_size = sizeof(struct sockaddr_in); + + ssize_t recvsize = recvfrom(server_sockfd, pkt_data, PACKET_SIZE, 0, + (struct sockaddr *) &fromaddr, &sockaddr_size); + if (recvsize < 0) { + log_message(LOG_ERR, "recv(): %s", strerror(errno)); + } + + int j; + char self_generated_packet = 0; + for (j = 0; j < num_socks; j++) { + // check for loopback + if (fromaddr.sin_addr.s_addr == socks[j].addr.s_addr) { + self_generated_packet = 1; + break; + } + } + + if (self_generated_packet) + continue; + + if (num_whitelisted_subnets != 0) { + char whitelisted_packet = 0; + for (j = 0; j < num_whitelisted_subnets; j++) { + // check for whitelist + if ((fromaddr.sin_addr.s_addr & whitelisted_subnets[j].mask.s_addr) == whitelisted_subnets[j].net.s_addr) { + whitelisted_packet = 1; + break; + } + } + + if (!whitelisted_packet) { + if (foreground && debug) + printf("skipping packet from=%s size=%zd\n", inet_ntoa(fromaddr.sin_addr), recvsize); + continue; + } + } else { + char blacklisted_packet = 0; + for (j = 0; j < num_blacklisted_subnets; j++) { + // check for blacklist + if ((fromaddr.sin_addr.s_addr & blacklisted_subnets[j].mask.s_addr) == blacklisted_subnets[j].net.s_addr) { + blacklisted_packet = 1; + break; + } + } + + if (blacklisted_packet) { + if (foreground && debug) + printf("skipping packet from=%s size=%zd\n", inet_ntoa(fromaddr.sin_addr), recvsize); + continue; + } + } + + for (j = 0; j < num_socks; j++) { + // do not repeat packet back to the same network from which it originated + if ((fromaddr.sin_addr.s_addr & socks[j].mask.s_addr) == socks[j].net.s_addr) + continue; + + if (foreground && debug) + printf("%s (%zd bytes) -> %s\n", inet_ntoa(fromaddr.sin_addr), recvsize, socks[j].ifname); + + // repeat data + ssize_t sentsize = send_packet(socks[j].sockfd, pkt_data, (size_t) recvsize); + if (sentsize != recvsize) { + if (sentsize < 0) + log_message(LOG_ERR, "send(): %s", strerror(errno)); + else + log_message(LOG_ERR, "send_packet size differs: sent=%zd actual=%zd", + recvsize, sentsize); + } + } + } + } + + log_message(LOG_INFO, "shutting down..."); + +end_main: + + if (pkt_data != NULL) + free(pkt_data); + + if (server_sockfd >= 0) + close(server_sockfd); + + for (i = 0; i < num_socks; i++) + close(socks[i].sockfd); + + // remove pid file if it belongs to us + if (already_running() == getpid()) + unlink(pid_file); + + log_message(LOG_INFO, "exit."); + + return r; +} |