src/devices/emutap/tap-manager.cc
author Craig Dowell <craigdo@ee.washington.edu>
Mon, 27 Oct 2008 13:01:28 -0700
changeset 3826 40c5841b616d
permissions -rw-r--r--
merge in tap device

#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/un.h>
#include <linux/if.h>
#include <linux/if_tun.h>
#include <linux/route.h>
#include <netinet/in.h>
#include <stdint.h>
#include <iostream>
#include <sstream>
#include <iomanip>
#include <string.h>
#include <errno.h>
#include <stdlib.h>

#define noENABLE_LOG

#define EXIT_ERROR(x, err)						\
  std::cout << __FILE__ << ":" << __LINE__ << ": Unrecoverable Error: " << x; \
  std::cout << " errno=" << strerror (errno) << std::endl;		\
  exit (err)
#ifdef ENABLE_LOG
#define LOG(x) \
  std::cout << x << std::endl;
#else
#define LOG(x)
#endif


#define CHECK_ARG(el,var) \
  {						\
    char start[] = "--"  el  "=";		     \
    if (strncmp (*argv, start, strlen (start)) == 0) \
      {						     \
	var = *argv + strlen (start);		     \
	LOG ("--" << el << "=" << var);		     \
      }						     \
  }

#define ASCII_DOT (0x2e)
#define ASCII_ZERO (0x30)
#define ASCII_a (0x41)
#define ASCII_z (0x5a)
#define ASCII_A (0x61)
#define ASCII_Z (0x7a)
#define ASCII_COLON (0x3a)
#define ASCII_ZERO (0x30)
static char
AsciiToLowCase (char c)
{
  if (c >= ASCII_a && c <= ASCII_z) {
    return c;
  } else if (c >= ASCII_A && c <= ASCII_Z) {
    return c + (ASCII_a - ASCII_A);
  } else {
    return c;
  }
}
static uint32_t 
AsciiToIpv4 (const char *address)
{
  uint32_t host = 0;
  while (true) {
    uint8_t byte = 0;
    while (*address != ASCII_DOT &&
           *address != 0) {
      byte *= 10;
      byte += *address - ASCII_ZERO;
      address++;
    }
    host <<= 8;
    host |= byte;
    if (*address == 0) {
      break;
    }
    address++;
  }
  return host;
}

static void 
AsciiToMac48 (const char *str, uint8_t addr[6])
{
  int i = 0;
  while (*str != 0 && i < 6) 
    {
      uint8_t byte = 0;
      while (*str != ASCII_COLON && *str != 0) 
	{
	  byte <<= 4;
	  char low = AsciiToLowCase (*str);
	  if (low >= ASCII_a) 
	    {
	      byte |= low - ASCII_a + 10;
	    } 
	  else 
	    {
	      byte |= low - ASCII_ZERO;
	    }
	  str++;
	}
      addr[i] = byte;
      i++;
      if (*str == 0) 
	{
	  break;
	}
      str++;
    }
}

static void
SetInetAddress (sockaddr *ad, uint32_t networkOrder)
{
  struct sockaddr_in *sin = (struct sockaddr_in*)ad;
  sin->sin_family = AF_INET;
  sin->sin_port = 0; // unused
  sin->sin_addr.s_addr = htonl (networkOrder);
}


static int
CreateTap (const char *mac_addr, const char *ip, 
	   const char *gw, const char *netmask)
{
  // opening the tun device usually requires root privs
  int tap = open ("/dev/net/tun", O_RDWR);
  if (tap == -1)
    {
      EXIT_ERROR ("Could not open /dev/net/tun", 1);
    }
  // now, crate a tap device.
  struct ifreq ifr;
  // make sure that the tap device will not send us the tun_pi header.
  ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
  ifr.ifr_name[0] = 0; // allow the kernel to pick a device name.
  int status = ioctl (tap, TUNSETIFF, (void *) &ifr);
  if (status == -1)
    {
      EXIT_ERROR ("Could not allocate a tap device", 2);
    }
  std::string tapDeviceName = (char *)ifr.ifr_name;
  LOG ("Allocated TAP device=" << tapDeviceName);

  // set its hardware address to something we know will be unique within the simulation
  ifr.ifr_hwaddr.sa_family = 1; // this is ARPHRD_ETHER from if_arp.h
  AsciiToMac48 (mac_addr, (uint8_t*)ifr.ifr_hwaddr.sa_data);
  status = ioctl (tap, SIOCSIFHWADDR, &ifr);
  if (status == -1)
    {
      EXIT_ERROR ("Could not set hardware address=" << mac_addr << " for=" << (char *)ifr.ifr_name, 3);
    }
  LOG ("device=" << (char *)ifr.ifr_name << " addr=" << mac_addr);


  // The ip address must be set using an AF_INET socket.
  int fd = socket (AF_INET, SOCK_DGRAM, 0);

  // set interface up.
  status = ioctl (fd, SIOCGIFFLAGS, &ifr);
  if (status == -1)
    {
      EXIT_ERROR ("Could not get flags for interface=" << (char *)ifr.ifr_name, 4);
    }
  ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
  status = ioctl (fd, SIOCSIFFLAGS, &ifr);
  if (status == -1)
    {
      EXIT_ERROR ("Could not bring interface " << (char *)ifr.ifr_name << " up", 5);
    }
  LOG ("device=" << (char *)ifr.ifr_name << " is up");


  // set its ip address.
  SetInetAddress (&ifr.ifr_addr, AsciiToIpv4 (ip));
  status = ioctl (fd, SIOCSIFADDR, &ifr);
  if (status == -1)
    {
      EXIT_ERROR ("Could not set ip address=" << ip << " for=" <<  (char *)ifr.ifr_name, 6);
    }
  LOG ("device=" << (char *)ifr.ifr_name << " addr=" << ip);

  // set its ip mask to be /32
  SetInetAddress (&ifr.ifr_netmask, 0xffffffff);
  status = ioctl (fd, SIOCSIFNETMASK, &ifr);
  if (status == -1)
    {
      EXIT_ERROR ("Could not set ip mask=" << netmask << " for=" <<  (char *)ifr.ifr_name, 7);
    }
  LOG ("device=" << (char *)ifr.ifr_name << " mask=" << netmask); 

  // add routing entry for gateway.
  struct rtentry rt;
  SetInetAddress (&rt.rt_dst, AsciiToIpv4 (gw));
  SetInetAddress (&rt.rt_genmask, 0xffffffff);
  rt.rt_flags = RTF_UP;
  rt.rt_metric = 2;
  rt.rt_dev = (char*)tapDeviceName.c_str ();
  status = ioctl (fd, SIOCADDRT, &rt);
  if (status == -1)
    {
      EXIT_ERROR ("Could not add routing table entry", 8);
    }
  LOG ("added routing table entry for gw.");

  // add routing entry for subnet through gateway.
  uint32_t network = AsciiToIpv4 (ip) & AsciiToIpv4 (netmask);
  SetInetAddress (&rt.rt_dst, network);
  SetInetAddress (&rt.rt_gateway, AsciiToIpv4 (gw));
  SetInetAddress (&rt.rt_genmask, AsciiToIpv4 (netmask));
  rt.rt_flags = RTF_UP | RTF_GATEWAY;
  rt.rt_metric = 2;
  rt.rt_dev = (char*)tapDeviceName.c_str ();
  status = ioctl (fd, SIOCADDRT, &rt);
  if (status == -1)
    {
      EXIT_ERROR ("Could not add routing table entry", 9);
    }
  LOG ("added routing table entry for subnet.");

  return tap;
}

static struct sockaddr_un
DecodeFromString (const char *path, socklen_t *len)
{
  sockaddr_un un;
  uint8_t *buffer = (uint8_t *)&un;
  std::istringstream iss;
  iss.str (path);
  uint8_t n = 0;
  while (!iss.bad () && !iss.eof () && !iss.fail ())
    {
      char c;
      iss.read (&c, 1);
      uint32_t tmp;
      iss >> std::hex >> tmp;
      //LOG (std::hex << tmp);
      buffer[n] = tmp;
      n++;
    }
  *len = n;
  return un;
}

static void
SendFd (const char *path, int fd)
{
  // send back configuration to caller.
  int sock = socket (PF_UNIX, SOCK_DGRAM, 0);
  if (sock == -1)
    {
      EXIT_ERROR ("Socket creation", 10);
    }
  LOG ("Socket Created");
  
  socklen_t local_len;
  struct sockaddr_un local = DecodeFromString (path, &local_len);
  LOG ("len=" << local_len);
  int status = connect (sock, (struct sockaddr*)&local, local_len);
  if (status == -1)
    {
      EXIT_ERROR ("Could not connect to caller", 11);
    }
  LOG ("Socket Connected");

  // we send a single byte whose content is meaningless.
  // we also return as ancillary data the tap file descriptor
  struct cmsghdr *cmsg;
  size_t msg_size = sizeof(int);
  char control[CMSG_SPACE(msg_size)];
  struct iovec iov;
  struct msghdr msg;
  char buffer = 0;
  iov.iov_base = &buffer;
  iov.iov_len = 1;
  msg.msg_name = 0;
  msg.msg_namelen = 0;
  msg.msg_iov = &iov;
  msg.msg_iovlen = 1;
  msg.msg_control = control;
  msg.msg_controllen = sizeof (control);
  msg.msg_flags = 0;

  cmsg = CMSG_FIRSTHDR(&msg);
  cmsg->cmsg_level = SOL_SOCKET;
  cmsg->cmsg_type = SCM_RIGHTS;
  cmsg->cmsg_len = CMSG_LEN(msg_size);
  msg.msg_controllen = cmsg->cmsg_len;

  int *fdptr = (int*) (CMSG_DATA(cmsg));
  *fdptr = fd;

  ssize_t len = sendmsg(sock, &msg, 0);
  if (len == -1)
    {
      EXIT_ERROR ("Could not send SCM_RIGHTS", 12);
    }
  LOG ("Sent SCM_RIGHTS");
}




int main (int argc, char *argv[])
{
  char *path = 0;
  char *mac_addr = 0;
  char *ip_addr = 0;
  char *ip_gw = 0;
  char *ip_netmask = 0;
  char *stop = 0;
  argv++;
  argc--;
  while (argc > 0)
    {
      CHECK_ARG("path", path);
      CHECK_ARG("mac-addr", mac_addr);
      CHECK_ARG("ip-addr", ip_addr);
      CHECK_ARG("ip-gw", ip_gw);
      CHECK_ARG("ip-netmask", ip_netmask);
      CHECK_ARG("stop", stop);
      argv++;
      argc--;
    }
  
  int tap = CreateTap (mac_addr, ip_addr, ip_gw, ip_netmask);

  if (stop)
    {
      while (1) {}
    }

  SendFd (path, tap);

  return 0;
}