int main(int argc, char **argv) { int ret = EX_OK; int i; struct group *grgid; struct passwd *pwuid; timer_t timerid_mld, timerid_pim; struct pollfd fds[4]; nfds_t nfds = 0; struct sockaddr_storage from, to; socklen_t addrlen = sizeof(struct sockaddr_storage); unsigned int from_ifindex; char *buf; ret = parse_args(argc, argv); if (ret) return -ret; if (getuid()) { fprintf(stderr, "need to run as root\n"); return EX_NOPERM; } if (!nofork) { pid_t pid = fork(); if (pid < 0) { perror("fork()"); return EX_OSERR; } else if (pid > 0) return EX_OK; if (setsid() < 0) { perror("setsid()"); return EX_OSERR; } if (chdir("/") < 0) { perror("chdir(\"/\")"); return EX_OSERR; } openlog(basename(argv[0]), LOG_PID, LOG_DAEMON); } else openlog(basename(argv[0]), LOG_PID | LOG_PERROR, LOG_DAEMON); setlogmask(LOG_UPTO(debug)); logger(LOG_NOTICE, 0, "started"); errno = 0; mroute4 = socket(AF_INET, SOCK_RAW, IPPROTO_IGMP); if (mroute4 >= 0) { if (pktinfo(mroute4) < 0) { close(mroute4); mroute4 = -1; } else { pim4 = pim_init(mroute4); if (pim4 < 0) { close(mroute4); mroute4 = -1; } else { add_poll(fds, &nfds, mroute4); add_poll(fds, &nfds, pim4); } } } if (mroute4 < 0) logger(LOG_WARNING, errno, "no IPv4 support"); errno = 0; mroute6 = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (mroute6 >= 0) { if (pktinfo(mroute6) < 0) { close(mroute6); mroute6 = -1; } else { pim6 = pim_init(mroute6); if (pim6 < 0) { close(mroute6); mroute6 = -1; } else { add_poll(fds, &nfds, mroute6); add_poll(fds, &nfds, pim6); } } } if (mroute6 < 0) logger(LOG_WARNING, errno, "no IPv6 support"); if (mroute4 < 0 && mroute6 < 0) { logger(LOG_ERR, 0, "multicast routing unavailable"); ret = -EX_OSERR; goto exit; } ret = route_init(); if (ret) goto mroute; errno = 0; grgid = getgrnam(gid); if (grgid) { if (setgid(grgid->gr_gid)) logger(LOG_WARNING, errno, "unable to drop group privileges"); } else logger(LOG_WARNING, errno, "unable to find group '%s' to drop privileges to", gid); errno = 0; pwuid = getpwnam(uid); if (pwuid) { if (setuid(pwuid->pw_uid)) logger(LOG_WARNING, errno, "unable to drop user privileges"); } else logger(LOG_WARNING, errno, "unable to find user '%s' to drop privileges to", uid); ret = signals(&sig_handler); if (ret) goto route; ret = prime_timers(&timerid_mld, &timerid_pim); if (ret) goto signal; buf = malloc(SOCK_BUFLEN); if (buf == NULL) { logger(LOG_ERR, 0, "malloc()"); ret = -EX_OSERR; goto timer; } while (running) { ret = poll(fds, nfds, -1); if (ret == -1) { if (errno == EINTR) continue; logger(LOG_ERR, errno, "poll()"); ret = -EX_OSERR; running = 0; continue; } for (i = 0; i < nfds; i++) { /* TODO handle errors */ assert(!(fds[i].revents & (POLLERR | POLLHUP))); /* either a non-event or there is something to read */ assert(!fds[i].revents || fds[i].revents & POLLIN); if (!fds[i].revents) continue; if (fds[i].revents & POLLIN) { ret = recvfromto(fds[i].fd, buf, SOCK_BUFLEN, 0, (struct sockaddr *)&from, (struct sockaddr *)&to, &addrlen, &from_ifindex); if (ret == -1) continue; if (fds[i].fd == pim4 || fds[i].fd == pim6) pim_recv(fds[i].fd, buf, ret, &from, &to, addrlen, from_ifindex); else mld_recv(fds[i].fd, buf, ret, &from, &to, addrlen, from_ifindex); } } } free(buf); timer: timer_delete(timerid_mld); timer_delete(timerid_pim); signal: signals(SIG_IGN); route: route_shutdown(); mroute: if (mroute4 > 0) { close(pim4); pim_shutdown(mroute4); } if (mroute6 > 0) { close(pim6); pim_shutdown(mroute6); } exit: logger(LOG_NOTICE, 0, "exiting"); closelog(); assert(ret <= 0); return -ret; }
/* NB: this will never set port# in 'to'! * _Only_ IP/IPv6 address part of 'to' is _maybe_ modified. * Typical usage is to preinit 'to' with "default" value * before calling recv_from_to(). */ ssize_t recv_from_to(int fd, void *buf, size_t len, int flags, struct sockaddr *from, struct sockaddr *to, socklen_t sa_size) { #ifndef IP_PKTINFO return recvfrom(fd, buf, len, flags, from, &sa_size); #else /* man recvmsg and man cmsg is needed to make sense of code below */ struct iovec iov[1]; union { char cmsg[CMSG_SPACE(sizeof(struct in_pktinfo))]; #if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO) char cmsg6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; #endif } u; struct cmsghdr *cmsgptr; struct msghdr msg; socklen_t recv_length; iov[0].iov_base = buf; iov[0].iov_len = len; memset(&msg, 0, sizeof(msg)); msg.msg_name = (struct sockaddr *)from; msg.msg_namelen = sa_size; msg.msg_iov = iov; msg.msg_iovlen = 1; msg.msg_control = &u; msg.msg_controllen = sizeof(u); recv_length = recvmsg(fd, &msg, flags); if (recv_length < 0) return recv_length; /* Here we try to retrieve destination IP and memorize it */ for (cmsgptr = CMSG_FIRSTHDR(&msg); cmsgptr != NULL; cmsgptr = CMSG_NXTHDR(&msg, cmsgptr) ) { if (cmsgptr->cmsg_level == IPPROTO_IP && cmsgptr->cmsg_type == IP_PKTINFO ) { #define pktinfo(cmsgptr) ( (struct in_pktinfo*)(CMSG_DATA(cmsgptr)) ) to->sa_family = AF_INET; ((struct sockaddr_in*)to)->sin_addr = pktinfo(cmsgptr)->ipi_addr; /* ((struct sockaddr_in*)to)->sin_port = 123; */ #undef pktinfo break; } #if ENABLE_FEATURE_IPV6 && defined(IPV6_PKTINFO) if (cmsgptr->cmsg_level == IPPROTO_IPV6 && cmsgptr->cmsg_type == IPV6_PKTINFO ) { #define pktinfo(cmsgptr) ( (struct in6_pktinfo*)(CMSG_DATA(cmsgptr)) ) to->sa_family = AF_INET6; ((struct sockaddr_in6*)to)->sin6_addr = pktinfo(cmsgptr)->ipi6_addr; /* ((struct sockaddr_in6*)to)->sin6_port = 123; */ #undef pktinfo break; } #endif } return recv_length; #endif }