569 lines
14 KiB
C
569 lines
14 KiB
C
/*
|
|
* tayga.c -- main server code
|
|
*
|
|
* part of TAYGA <http://www.litech.org/tayga/>
|
|
* Copyright (C) 2010 Nathan Lutchansky <lutchann@litech.org>
|
|
*
|
|
* 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.
|
|
*/
|
|
|
|
#include <tayga.h>
|
|
|
|
#include <stdarg.h>
|
|
#include <signal.h>
|
|
#include <getopt.h>
|
|
#include <pwd.h>
|
|
#include <grp.h>
|
|
|
|
#define USAGE_TEXT \
|
|
"Usage: %s [-c|--config CONFIGFILE] [-d] [-n|--nodetach] [-u|--user USERID]\n" \
|
|
" [-g|--group GROUPID] [-r|--chroot] [-p|--pidfile PIDFILE]\n\n" \
|
|
"--config FILE : Read configuration options from FILE\n" \
|
|
"-d : Enable debug messages (implies --nodetach)\n" \
|
|
"--nodetach : Do not detach from terminal\n" \
|
|
"--user USERID : Set uid to USERID after initialization\n" \
|
|
"--group GROUPID : Set gid to GROUPID after initialization\n" \
|
|
"--chroot : chroot() to data-dir (specified in config file)\n\n" \
|
|
"--pidfile FILE : Write process ID of daemon to FILE\n"
|
|
|
|
extern struct config *gcfg;
|
|
time_t now;
|
|
|
|
static int signalfds[2];
|
|
static int use_stdout;
|
|
|
|
void slog(int priority, const char *format, ...)
|
|
{
|
|
va_list ap;
|
|
|
|
va_start(ap, format);
|
|
if (use_stdout)
|
|
vprintf(format, ap);
|
|
else if (priority != LOG_DEBUG)
|
|
vsyslog(priority, format, ap);
|
|
va_end(ap);
|
|
}
|
|
|
|
static void set_nonblock(int fd)
|
|
{
|
|
int flags;
|
|
|
|
flags = fcntl(fd, F_GETFL);
|
|
if (flags < 0) {
|
|
slog(LOG_CRIT, "fcntl F_GETFL returned %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
flags |= O_NONBLOCK;
|
|
if (fcntl(fd, F_SETFL, flags) < 0) {
|
|
slog(LOG_CRIT, "fcntl F_SETFL returned %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
void read_random_bytes(void *d, int len)
|
|
{
|
|
int ret;
|
|
|
|
ret = read(gcfg->urandom_fd, d, len);
|
|
if (ret < 0) {
|
|
slog(LOG_CRIT, "read /dev/urandom returned %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (ret < len) {
|
|
slog(LOG_CRIT, "read /dev/urandom returned EOF\n");
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
static void tun_setup(int do_mktun, int do_rmtun)
|
|
{
|
|
struct ifreq ifr;
|
|
int fd;
|
|
|
|
gcfg->tun_fd = open("/dev/net/tun", O_RDWR);
|
|
if (gcfg->tun_fd < 0) {
|
|
slog(LOG_CRIT, "Unable to open /dev/net/tun, aborting: %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
ifr.ifr_flags = IFF_TUN;
|
|
strcpy(ifr.ifr_name, gcfg->tundev);
|
|
if (ioctl(gcfg->tun_fd, TUNSETIFF, &ifr) < 0) {
|
|
slog(LOG_CRIT, "Unable to attach tun device %s, aborting: "
|
|
"%s\n", gcfg->tundev, strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
if (do_mktun) {
|
|
if (ioctl(gcfg->tun_fd, TUNSETPERSIST, 1) < 0) {
|
|
slog(LOG_CRIT, "Unable to set persist flag on %s, "
|
|
"aborting: %s\n", gcfg->tundev,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (ioctl(gcfg->tun_fd, TUNSETOWNER, 0) < 0) {
|
|
slog(LOG_CRIT, "Unable to set owner on %s, "
|
|
"aborting: %s\n", gcfg->tundev,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (ioctl(gcfg->tun_fd, TUNSETGROUP, 0) < 0) {
|
|
slog(LOG_CRIT, "Unable to set group on %s, "
|
|
"aborting: %s\n", gcfg->tundev,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
slog(LOG_NOTICE, "Created persistent tun device %s\n",
|
|
gcfg->tundev);
|
|
return;
|
|
} else if (do_rmtun) {
|
|
if (ioctl(gcfg->tun_fd, TUNSETPERSIST, 0) < 0) {
|
|
slog(LOG_CRIT, "Unable to clear persist flag on %s, "
|
|
"aborting: %s\n", gcfg->tundev,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
slog(LOG_NOTICE, "Removed persistent tun device %s\n",
|
|
gcfg->tundev);
|
|
return;
|
|
}
|
|
|
|
set_nonblock(gcfg->tun_fd);
|
|
|
|
fd = socket(PF_INET, SOCK_DGRAM, 0);
|
|
if (fd < 0) {
|
|
slog(LOG_CRIT, "Unable to create socket, aborting: %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
memset(&ifr, 0, sizeof(ifr));
|
|
strcpy(ifr.ifr_name, gcfg->tundev);
|
|
if (ioctl(fd, SIOCGIFMTU, &ifr) < 0) {
|
|
slog(LOG_CRIT, "Unable to query MTU, aborting: %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
close(fd);
|
|
|
|
gcfg->mtu = ifr.ifr_mtu;
|
|
|
|
slog(LOG_INFO, "Using tun device %s with MTU %d\n", gcfg->tundev,
|
|
gcfg->mtu);
|
|
}
|
|
|
|
static void signal_handler(int signal)
|
|
{
|
|
write(signalfds[1], &signal, sizeof(signal));
|
|
}
|
|
|
|
static void signal_setup(void)
|
|
{
|
|
struct sigaction act;
|
|
|
|
if (pipe(signalfds) < 0) {
|
|
slog(LOG_INFO, "unable to create signal pipe, aborting: %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
set_nonblock(signalfds[0]);
|
|
set_nonblock(signalfds[1]);
|
|
memset(&act, 0, sizeof(act));
|
|
act.sa_handler = signal_handler;
|
|
sigaction(SIGINT, &act, NULL);
|
|
sigaction(SIGHUP, &act, NULL);
|
|
sigaction(SIGUSR1, &act, NULL);
|
|
sigaction(SIGUSR2, &act, NULL);
|
|
sigaction(SIGQUIT, &act, NULL);
|
|
sigaction(SIGTERM, &act, NULL);
|
|
}
|
|
|
|
static void read_from_tun(void)
|
|
{
|
|
int ret;
|
|
struct tun_pi *pi = (struct tun_pi *)gcfg->recv_buf;
|
|
struct pkt pbuf, *p = &pbuf;
|
|
|
|
ret = read(gcfg->tun_fd, gcfg->recv_buf, gcfg->recv_buf_size);
|
|
if (ret < 0) {
|
|
if (errno == EAGAIN)
|
|
return;
|
|
slog(LOG_ERR, "received error when reading from tun "
|
|
"device: %s\n", strerror(errno));
|
|
return;
|
|
}
|
|
if (ret < sizeof(struct tun_pi)) {
|
|
slog(LOG_WARNING, "short read from tun device "
|
|
"(%d bytes)\n", ret);
|
|
return;
|
|
}
|
|
if (ret == gcfg->recv_buf_size) {
|
|
slog(LOG_WARNING, "dropping oversized packet\n");
|
|
return;
|
|
}
|
|
memset(p, 0, sizeof(struct pkt));
|
|
p->data = gcfg->recv_buf + sizeof(struct tun_pi);
|
|
p->data_len = ret - sizeof(struct tun_pi);
|
|
switch (ntohs(pi->proto)) {
|
|
case ETH_P_IP:
|
|
handle_ip4(p);
|
|
break;
|
|
case ETH_P_IPV6:
|
|
handle_ip6(p);
|
|
break;
|
|
default:
|
|
slog(LOG_WARNING, "Dropping unknown proto %04x from "
|
|
"tun device\n", ntohs(pi->proto));
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void read_from_signalfd(void)
|
|
{
|
|
int ret, sig;
|
|
|
|
for (;;) {
|
|
ret = read(signalfds[0], &sig, sizeof(sig));
|
|
if (ret < 0) {
|
|
if (errno == EAGAIN)
|
|
return;
|
|
slog(LOG_CRIT, "got error %s from signalfd\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (ret == 0) {
|
|
slog(LOG_CRIT, "signal fd was closed\n");
|
|
exit(1);
|
|
}
|
|
if (gcfg->dynamic_pool)
|
|
dynamic_maint(gcfg->dynamic_pool, 1);
|
|
slog(LOG_NOTICE, "exiting on signal %d\n", sig);
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
int main(int argc, char **argv)
|
|
{
|
|
int c, ret, longind;
|
|
int pidfd;
|
|
struct pollfd pollfds[2];
|
|
struct map6 *m6;
|
|
char addrbuf[INET6_ADDRSTRLEN];
|
|
|
|
char *conffile = TAYGA_CONF_PATH;
|
|
char *user = NULL;
|
|
char *group = NULL;
|
|
char *pidfile = NULL;
|
|
int do_chroot = 0;
|
|
int detach = 1;
|
|
int do_mktun = 0;
|
|
int do_rmtun = 0;
|
|
struct passwd *pw = NULL;
|
|
struct group *gr = NULL;
|
|
|
|
static struct option longopts[] = {
|
|
{ "mktun", 0, 0, 0 },
|
|
{ "rmtun", 0, 0, 0 },
|
|
{ "help", 0, 0, 0 },
|
|
{ "config", 1, 0, 'c' },
|
|
{ "nodetach", 0, 0, 'n' },
|
|
{ "user", 1, 0, 'u' },
|
|
{ "group", 1, 0, 'g' },
|
|
{ "chroot", 0, 0, 'r' },
|
|
{ "pidfile", 1, 0, 'p' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
for (;;) {
|
|
c = getopt_long(argc, argv, "c:dnu:g:rp:", longopts, &longind);
|
|
if (c == -1)
|
|
break;
|
|
switch (c) {
|
|
case 0:
|
|
if (longind == 0) {
|
|
if (do_rmtun) {
|
|
fprintf(stderr, "Error: both --mktun "
|
|
"and --rmtun specified.\n");
|
|
exit(1);
|
|
}
|
|
do_mktun = 1;
|
|
} else if (longind == 1) {
|
|
if (do_mktun) {
|
|
fprintf(stderr, "Error: both --mktun "
|
|
"and --rmtun specified.\n");
|
|
exit(1);
|
|
}
|
|
do_rmtun = 1;
|
|
} else if (longind == 2) {
|
|
fprintf(stderr, USAGE_TEXT, argv[0]);
|
|
exit(0);
|
|
}
|
|
break;
|
|
case 'c':
|
|
conffile = optarg;
|
|
break;
|
|
case 'd':
|
|
use_stdout = 1;
|
|
detach = 0;
|
|
break;
|
|
case 'n':
|
|
detach = 0;
|
|
break;
|
|
case 'u':
|
|
user = optarg;
|
|
break;
|
|
case 'g':
|
|
group = optarg;
|
|
break;
|
|
case 'r':
|
|
do_chroot = 1;
|
|
break;
|
|
case 'p':
|
|
pidfile = optarg;
|
|
break;
|
|
default:
|
|
fprintf(stderr, "Try `%s --help' for more "
|
|
"information.\n", argv[0]);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (do_mktun || do_rmtun) {
|
|
use_stdout = 1;
|
|
if (user) {
|
|
fprintf(stderr, "Error: cannot specify -u or --user "
|
|
"with mktun/rmtun operation\n");
|
|
exit(1);
|
|
}
|
|
if (group) {
|
|
fprintf(stderr, "Error: cannot specify -g or --group "
|
|
"with mktun/rmtun operation\n");
|
|
exit(1);
|
|
}
|
|
if (do_chroot) {
|
|
fprintf(stderr, "Error: cannot specify -r or --chroot "
|
|
"with mktun/rmtun operation\n");
|
|
exit(1);
|
|
}
|
|
read_config(conffile);
|
|
tun_setup(do_mktun, do_rmtun);
|
|
return 0;
|
|
}
|
|
|
|
if (!use_stdout)
|
|
openlog("tayga", LOG_PID | LOG_NDELAY, LOG_DAEMON);
|
|
|
|
if (user) {
|
|
pw = getpwnam(user);
|
|
if (!pw) {
|
|
slog(LOG_CRIT, "Error: user %s does not exist\n", user);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (group) {
|
|
gr = getgrnam(group);
|
|
if (!gr) {
|
|
slog(LOG_CRIT, "Error: group %s does not exist\n",
|
|
group);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
read_config(conffile);
|
|
|
|
if (!gcfg->data_dir[0]) {
|
|
if (do_chroot) {
|
|
slog(LOG_CRIT, "Error: cannot chroot when no data-dir "
|
|
"is specified in %s\n", conffile);
|
|
exit(1);
|
|
}
|
|
chdir("/");
|
|
} else if (chdir(gcfg->data_dir) < 0) {
|
|
if (user || errno != ENOENT) {
|
|
slog(LOG_CRIT, "Error: unable to chdir to %s, "
|
|
"aborting: %s\n", gcfg->data_dir,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (mkdir(gcfg->data_dir, 0777) < 0) {
|
|
slog(LOG_CRIT, "Error: unable to create %s, aborting: "
|
|
"%s\n", gcfg->data_dir,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
if (chdir(gcfg->data_dir) < 0) {
|
|
slog(LOG_CRIT, "Error: created %s but unable to chdir "
|
|
"to it!?? (%s)\n", gcfg->data_dir,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (do_chroot && (!pw || pw->pw_uid == 0)) {
|
|
slog(LOG_CRIT, "Error: chroot is ineffective without also "
|
|
"specifying the -u option to switch to an "
|
|
"unprivileged user\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (pidfile) {
|
|
pidfd = open(pidfile, O_WRONLY | O_CREAT | O_TRUNC, 0666);
|
|
if (pidfd < 0) {
|
|
slog(LOG_CRIT, "Error, unable to open %s for "
|
|
"writing: %s\n", pidfile,
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (detach && daemon(1, 0) < 0) {
|
|
slog(LOG_CRIT, "Error, unable to fork and detach: %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
|
|
if (pidfile) {
|
|
snprintf(addrbuf, sizeof(addrbuf), "%ld\n", (long)getpid());
|
|
write(pidfd, addrbuf, strlen(addrbuf));
|
|
close(pidfd);
|
|
}
|
|
|
|
slog(LOG_INFO, "starting TAYGA " VERSION "\n");
|
|
|
|
if (gcfg->cache_size) {
|
|
gcfg->urandom_fd = open("/dev/urandom", O_RDONLY);
|
|
if (gcfg->urandom_fd < 0) {
|
|
slog(LOG_CRIT, "Unable to open /dev/urandom, "
|
|
"aborting: %s\n", strerror(errno));
|
|
exit(1);
|
|
}
|
|
read_random_bytes(gcfg->rand, 8 * sizeof(uint32_t));
|
|
gcfg->rand[0] |= 1; /* need an odd number for IPv4 hash */
|
|
}
|
|
|
|
tun_setup(0, 0);
|
|
|
|
if (do_chroot) {
|
|
if (chroot(gcfg->data_dir) < 0) {
|
|
slog(LOG_CRIT, "Unable to chroot to %s: %s\n",
|
|
gcfg->data_dir, strerror(errno));
|
|
exit(1);
|
|
}
|
|
chdir("/");
|
|
}
|
|
|
|
if (gr) {
|
|
if (setregid(gr->gr_gid, gr->gr_gid) < 0 ||
|
|
setregid(gr->gr_gid, gr->gr_gid) < 0 ||
|
|
setgroups(1, &gr->gr_gid) < 0) {
|
|
slog(LOG_CRIT, "Error: cannot set gid to %d: %s\n",
|
|
gr->gr_gid, strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
if (pw) {
|
|
if (setreuid(pw->pw_uid, pw->pw_uid) < 0 ||
|
|
setreuid(pw->pw_uid, pw->pw_uid) < 0) {
|
|
slog(LOG_CRIT, "Error: cannot set uid to %d: %s\n",
|
|
pw->pw_uid, strerror(errno));
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
signal_setup();
|
|
|
|
inet_ntop(AF_INET, &gcfg->local_addr4, addrbuf, sizeof(addrbuf));
|
|
slog(LOG_INFO, "TAYGA's IPv4 address: %s\n", addrbuf);
|
|
inet_ntop(AF_INET6, &gcfg->local_addr6, addrbuf, sizeof(addrbuf));
|
|
slog(LOG_INFO, "TAYGA's IPv6 address: %s\n", addrbuf);
|
|
m6 = list_entry(gcfg->map6_list.prev, struct map6, list);
|
|
if (m6->type == MAP_TYPE_RFC6052) {
|
|
inet_ntop(AF_INET6, &m6->addr, addrbuf, sizeof(addrbuf));
|
|
slog(LOG_INFO, "NAT64 prefix: %s/%d\n",
|
|
addrbuf, m6->prefix_len);
|
|
if (m6->addr.s6_addr32[0] == WKPF)
|
|
slog(LOG_INFO, "Note: traffic between IPv6 hosts and "
|
|
"private IPv4 addresses (i.e. to/from "
|
|
"64:ff9b::10.0.0.0/104, "
|
|
"64:ff9b::192.168.0.0/112, etc) "
|
|
"will be dropped. Use a translation "
|
|
"prefix within your organization's "
|
|
"IPv6 address space instead of "
|
|
"64:ff9b::/96 if you need your "
|
|
"IPv6 hosts to communicate with "
|
|
"private IPv4 addresses.\n");
|
|
}
|
|
if (gcfg->dynamic_pool) {
|
|
inet_ntop(AF_INET, &gcfg->dynamic_pool->map4.addr,
|
|
addrbuf, sizeof(addrbuf));
|
|
slog(LOG_INFO, "Dynamic pool: %s/%d\n", addrbuf,
|
|
gcfg->dynamic_pool->map4.prefix_len);
|
|
if (gcfg->data_dir[0])
|
|
load_dynamic(gcfg->dynamic_pool);
|
|
else
|
|
slog(LOG_INFO, "Note: dynamically-assigned mappings "
|
|
"will not be saved across restarts. "
|
|
"Specify data-dir in %s if you would "
|
|
"like dynamic mappings to be "
|
|
"persistent.\n", conffile);
|
|
}
|
|
|
|
if (gcfg->cache_size)
|
|
create_cache();
|
|
|
|
gcfg->recv_buf = (uint8_t *)malloc(gcfg->recv_buf_size);
|
|
if (!gcfg->recv_buf) {
|
|
slog(LOG_CRIT, "Error: unable to allocate %d bytes for "
|
|
"receive buffer\n", gcfg->recv_buf_size);
|
|
exit(1);
|
|
}
|
|
|
|
memset(pollfds, 0, 2 * sizeof(struct pollfd));
|
|
pollfds[0].fd = signalfds[0];
|
|
pollfds[0].events = POLLIN;
|
|
pollfds[1].fd = gcfg->tun_fd;
|
|
pollfds[1].events = POLLIN;
|
|
|
|
for (;;) {
|
|
ret = poll(pollfds, 2, POOL_CHECK_INTERVAL * 1000);
|
|
if (ret < 0) {
|
|
if (errno == EINTR)
|
|
continue;
|
|
slog(LOG_ERR, "poll returned error %s\n",
|
|
strerror(errno));
|
|
exit(1);
|
|
}
|
|
time(&now);
|
|
if (pollfds[0].revents)
|
|
read_from_signalfd();
|
|
if (pollfds[1].revents)
|
|
read_from_tun();
|
|
if (gcfg->cache_size && (gcfg->last_cache_maint +
|
|
CACHE_CHECK_INTERVAL < now ||
|
|
gcfg->last_cache_maint > now)) {
|
|
addrmap_maint();
|
|
gcfg->last_cache_maint = now;
|
|
}
|
|
if (gcfg->dynamic_pool && (gcfg->last_dynamic_maint +
|
|
POOL_CHECK_INTERVAL < now ||
|
|
gcfg->last_dynamic_maint > now)) {
|
|
dynamic_maint(gcfg->dynamic_pool, 0);
|
|
gcfg->last_dynamic_maint = now;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|