Thursday, September 1, 2022

[SOLVED] "Cannot assign requested address" when trying to set TUN interface netmask

Issue

I'm trying to write a Linux userspace program that opens a TUN interface and assigned it an IPv4 address and a netmask. Assigning the IP address works fine but setting the netmask results in the error in the title (if perror is called right after). Here is a code snippet that showcases the problem:

int tun_open(char *tun_name)
{
  int tun_fd;
  if ((tun_fd = open("/dev/net/tun", O_RDWR)) == -1) {
    return -1;
  }

  struct ifreq ifr;
  memset(&ifr, 0, sizeof(ifr));

  ifr.ifr_flags = IFF_TUN | IFF_NO_PI;

  if (ioctl(tun_fd, TUNSETIFF, &ifr) == -1) {
    close(tun_fd);
    return -1;
  }

  strncpy(tun_name, ifr.ifr_name, IFNAMSIZ);

  return tun_fd;
}

int tun_assign_addr(int tun_fd,
                    char const *tun_name,
                    char const *tun_addr,
                    char const *tun_netmask)
{
  struct sockaddr_in addr;
  memset(&addr, 0, sizeof(addr));

  addr.sin_family = AF_INET;

  struct ifreq ifr;
  memset(&ifr, 0, sizeof(ifr));

  strncpy(ifr.ifr_name, tun_name, IFNAMSIZ);
  ifr.ifr_addr = *(struct sockaddr *)&addr;

  int s = socket(ifr.ifr_addr.sa_family, SOCK_DGRAM, 0);

  if (inet_pton(addr.sin_family, tun_addr, &addr.sin_addr) != 1)
    return -1;

  if (ioctl(s, SIOCSIFADDR, &ifr) == -1)
    return -1;

  if (inet_pton(addr.sin_family, tun_netmask, &addr.sin_addr) != 1)
    return -1;

  if (ioctl(s, SIOCSIFNETMASK, &ifr) == -1) {
    perror(">>> ERROR HERE <<<")
    return -1;
  }

  return 0;
}

int main()
{
  int tun_fd;
  char tun_name[IFNAMSIZ];
  assert(tun_open(tun_name) != -1);

  assert(tun_assign_addr(tun_fd, tun_name, "10.0.0.0", "255.255.255.0") != -1);
}

After a bit of digging in the kernel code (I'm on 5.14.14) it seems this must be because the bad_mask check in net/ipv4/devinet.c:1214. But if I manually run bad_mask from include/linux/inetdevice.h on my netmask it returns false as expected.

What is really going on here?


Solution

You're assigning the "content" of addr to ifr_addr before setting the address, for the ip like the netmask. Thus you're sending NULL to ioctl for the IP and then NULL for the MASK. The inet_pton touch only addr, which does not then change ifr.ifr_addr.

Here is the corrected code :

int tun_assign_addr(int tun_fd,
                    char const *tun_name,
                    char const *tun_addr,
                    char const *tun_netmask)
{
  struct sockaddr_in addr,mask;
  memset(&addr, 0, sizeof(addr));
  memset(&mask, 0, sizeof(mask));

  addr.sin_family = AF_INET;
  mask.sin_family = AF_INET;

  struct ifreq ifr;
  memset(&ifr, 0, sizeof(ifr));

  strncpy(ifr.ifr_name, tun_name, IFNAMSIZ);

  int s = socket(addr.sin_family, SOCK_DGRAM, 0);

  if (inet_pton(addr.sin_family, tun_addr, &addr.sin_addr) != 1)
    return -1;

  ifr.ifr_addr = *(struct sockaddr *)&addr;


  if (ioctl(s, SIOCSIFADDR, &ifr) == -1) {
    perror(">>> ERROR HERE ADD <<<");
    return -1;
  }


  if (inet_pton(mask.sin_family, tun_netmask, &mask.sin_addr) != 1)
    return -1;

  ifr.ifr_netmask = *(struct sockaddr *)&mask;

  if (ioctl(s, SIOCSIFNETMASK, &ifr) == -1) {
    perror(">>> ERROR HERE MSK <<<");
    return -1;

  }

  return 0;
}



Answered By - Zilog80
Answer Checked By - Willingham (WPSolving Volunteer)