Logo Search packages:      
Sourcecode: tayga version File versions  Download package

tayga.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;
}

Generated by  Doxygen 1.6.0   Back to index