Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Assign ipv6 address using ioctl

I'm trying to assign an IPv6 address to an interface using ioctl, but in vain. Here's the code I used:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>             
#include <net/if.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define IFNAME "eth1"
#define HOST "fec2::22"
#define ifreq_offsetof(x)  offsetof(struct ifreq, x)

int main(int argc, char **argv) {
  struct ifreq ifr;
  struct sockaddr_in6 sai;
  int sockfd;                     /* socket fd we use to manipulate stuff with */
  int selector;
  unsigned char mask;

  char *p;

  /* Create a channel to the NET kernel. */
  sockfd = socket(AF_INET6, SOCK_DGRAM, 0);
  if (sockfd == -1) {
    printf("Bad fd\n");
    return -1;
  }

  /* get interface name */
  strncpy(ifr.ifr_name, IFNAME, IFNAMSIZ);

  memset(&sai, 0, sizeof(struct sockaddr));
  sai.sin6_family = AF_INET6;
  sai.sin6_port = 0;

  //if(inet_aton(HOST, &sai.sin_addr.s_addr) == 0) {
  if(inet_pton(AF_INET6, HOST, (void *)&sai.sin6_addr) <= 0) {
    //&((struct sockaddr_in*)&sa)->sin_addr
    printf("Bad address\n");
    return -1;
  }

  p = (char *) &sai;
  memcpy( (((char *)&ifr + ifreq_offsetof(ifr_addr) )),
          p, sizeof(struct sockaddr));

  int ret = ioctl(sockfd, SIOCSIFADDR, &ifr);
  printf("ret: %d\terrno: %d\n", ret, errno);
  ioctl(sockfd, SIOCGIFFLAGS, &ifr);
  printf("ret: %d\terrno: %d\n", ret, errno);

  ifr.ifr_flags |= IFF_UP | IFF_RUNNING;
  // ifr.ifr_flags &= ~selector;  // unset something

  ioctl(sockfd, SIOCSIFFLAGS, &ifr);
  printf("ret: %d\terrno: %d\n", ret, errno);
  close(sockfd);
  return 0;
}

The ioctl calls fail saying ENODEV. When the family of the socket is changed to sockfd = socket(AF_INET, SOCK_DGRAM, 0);, the calls fail again saying EINVAL.

I was able to assign an IPv4 address to the interface with the above code by using sockaddr_in in lieu of sockaddr_in6.

Is it not possible to assign IPv6 address using ioctl?

like image 336
Maddy Avatar asked Nov 23 '11 10:11

Maddy


2 Answers

Drawing inspiration from the linux implementation of 'ifconfig' command, I was able to set the IPv6 address on an interface. Here's the code for it:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stddef.h>             
#include <net/if.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/sockios.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#if __GLIBC__ >=2 && __GLIBC_MINOR >= 1
#include <netpacket/packet.h>
#include <net/ethernet.h>
#else
#include <asm/types.h>
#include <linux/if_ether.h>
#endif

#define IFNAME "eth0"
#define HOST "fec2::22"
#define ifreq_offsetof(x)  offsetof(struct ifreq, x)

struct in6_ifreq {
    struct in6_addr ifr6_addr;
    __u32 ifr6_prefixlen;
    unsigned int ifr6_ifindex;
};

int main(int argc, char **argv) {

    struct ifreq ifr;
    struct sockaddr_in6 sai;
    int sockfd;                     
    struct in6_ifreq ifr6;

    sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);
    if (sockfd == -1) {
          printf("Bad fd\n");
          return -1;
    }

    /* get interface name */
    strncpy(ifr.ifr_name, IFNAME, IFNAMSIZ);

    memset(&sai, 0, sizeof(struct sockaddr));
    sai.sin6_family = AF_INET6;
    sai.sin6_port = 0;

    if(inet_pton(AF_INET6, HOST, (void *)&sai.sin6_addr) <= 0) {
        printf("Bad address\n");
        return -1;
    }

    memcpy((char *) &ifr6.ifr6_addr, (char *) &sai.sin6_addr,
               sizeof(struct in6_addr));

    if (ioctl(sockfd, SIOGIFINDEX, &ifr) < 0) {
        perror("SIOGIFINDEX");
    }
    ifr6.ifr6_ifindex = ifr.ifr_ifindex;
    ifr6.ifr6_prefixlen = 64;
    if (ioctl(sockfd, SIOCSIFADDR, &ifr6) < 0) {
        perror("SIOCSIFADDR");
    }

    ifr.ifr_flags |= IFF_UP | IFF_RUNNING;

    int ret = ioctl(sockfd, SIOCSIFFLAGS, &ifr);
    printf("ret: %d\terrno: %d\n", ret, errno);

    close(sockfd);
    return 0;
}
like image 59
Maddy Avatar answered Nov 03 '22 01:11

Maddy


Based off of @maddy's answer I made a more compact version that is a little easier to adapt. The trick is the struct in6_ifreq structure that has to be passed to the ioctl.

#include <stdint.h>
#include <string.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <netinet/in.h>

#define IFNAME "eth0"
#define HOST   "2001::22"

struct in6_ifreq {
    struct in6_addr addr;
    uint32_t        prefixlen;
    unsigned int    ifindex;
};

int main(int argc, char **argv) {

    struct ifreq ifr;
    struct in6_ifreq ifr6;
    int sockfd;
    int err;

    // Create IPv6 socket to perform the ioctl operations on
    sockfd = socket(AF_INET6, SOCK_DGRAM, IPPROTO_IP);

    // Copy the interface name to the ifreq struct
    strncpy(ifr.ifr_name, IFNAME, IFNAMSIZ);
    // Get the ifrindex of the interface
    err = ioctl(sockfd, SIOGIFINDEX, &ifr);

    // Prepare the in6_ifreq struct and set the address to the interface
    inet_pton(AF_INET6, HOST, &ifr6.addr);
    ifr6.ifindex = ifr.ifr_ifindex;
    ifr6.prefixlen = 64;
    err = ioctl(sockfd, SIOCSIFADDR, &ifr6);

    close(sockfd);
    return 0;
}

I left off all error checking for readability but the commands should be checked for errors.

like image 4
Brad Avatar answered Nov 03 '22 01:11

Brad