Thursday, May 13, 2010

IPv6 address assignment & bind() issues

IPv6 address assignment & bind() issues

The C program given below assigns an IPv6 address to a specified network interface.

#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <net/if.h>

#define IPV6_BINARY_LEN 128

int32_t set_ip_addr(const char *dev, const char *ip, int32_t prefix_len);

int main(int argc, char *argv[])
{
        struct sockaddr_in6 sa;
        int32_t size;
        int32_t sd;
        int32_t ret;

        if (argc != 4) {
                fprintf(stderr, "Usage: %s <dev> <ip address> <prefix Length>.\n",
                        argv[0]);
                goto out1;
        }

        ret = set_ip_addr(argv[1], argv[2], atoi(argv[3]));
        if (ret == -1) {
                fprintf(stderr, "Unable to set IP address.\n");
                goto out1;
        }

        sd = socket(AF_INET6, SOCK_DGRAM, 0);
        if (sd == -1) {
                perror("socket");
                goto out1;
        }

        memset(&sa, 0, sizeof(sa));
        sa.sin6_family = AF_INET6;
        sa.sin6_port = htons(49153);
        sa.sin6_scope_id = 0;
        ret = inet_pton(AF_INET6, argv[2], sa.sin6_addr.s6_addr);
        if (ret != 1) {
                perror("inet_pton");
                goto out2;
        }

        ret = bind(sd, (struct sockaddr *)&sa, sizeof(sa));
        if (ret == -1) {
                perror("bind");
                goto out2;
        }

        printf("Bind success!\n");

        close(sd);

        exit(0);
out2:
        close(sd);
out1:
        exit(1);
}

struct in6_ifreq {
        struct in6_addr ifr6_addr;
        uint32_t        ifr6_prefixlen;
        int32_t         ifr6_ifindex;
};

int32_t set_ip_addr(const char *dev, const char *ip, int32_t prefix_len)
{
        struct in6_ifreq in6_ifreq;
        struct ifreq ifreq;
        uint8_t ip_bin[IPV6_BINARY_LEN];
        int32_t sd;
        int32_t ret;

        if ((dev == NULL) || (ip == NULL) || (prefix_len < 0)) {
                fprintf(stderr, "%s: Invalid arguments.\n", __func__);
                goto out1;
        }

        ret = inet_pton(AF_INET6, ip, ip_bin);
        if (ret != 1) {
                perror("inet_pton");
                goto out1;
        }

        sd = socket(AF_INET6, SOCK_DGRAM, 0);
        if (sd == -1) {
                perror("socket");
                goto out1;
        }

        memset(&ifreq, 0, sizeof(ifreq));
        strncpy(ifreq.ifr_name, dev, IFNAMSIZ - 1);
        ret = ioctl(sd, SIOCGIFINDEX, &ifreq);
        if (ret == -1) {
                perror("ioctl");
                goto out2;
        }

        memset(&in6_ifreq, 0, sizeof(in6_ifreq));
        ret = inet_pton(AF_INET6, ip, &in6_ifreq.ifr6_addr.s6_addr);
        if (ret != 1) {
                perror("inet_pton");
                goto out2;
        }

        in6_ifreq.ifr6_ifindex = ifreq.ifr_ifindex;
        in6_ifreq.ifr6_prefixlen = prefix_len;

        ret = ioctl(sd, SIOCSIFADDR, &in6_ifreq);
        if (ret == -1) {
                perror("ioctl");
                goto out2;
        }

        close(sd);

        return 0;
out2:
        close(sd);
out1:
        return -1;
}

Compile and execute the program as shown below:

$ gcc -Wall set_ipv6_addr.c -oset_ipv6_addr
$ ./set_ipv6_addr eth0 2001:db8:0:145::243 64

The bind() system call in the above program fails and errno is set to EADDRNOTAVAIL.

To solve this issue (a workaround rather than a correct fix :-() follow the instructions given below:

  1. Open the file net/ipv6/addrconf.c in the Linux kernel.
  2. In the function ipv6_add_addr(), replace the following line
ifa->flags = flags | IFA_F_TENTATIVE;

with

ifa->flags = flags | IFA_F_NODAD;

Recompile the kernel and execute the previously listed program after booting into the new kernel.

HTML generated by org-mode 7.3 in emacs 23

3 comments:

  1. Hi,

    I am seeing the same bind() failed with EADDRNOTAVAIL problem. Did u find any solution other than rebuilding the kernel???

    thanks in advance
    Lakshmi

    ReplyDelete
  2. You can disable DAD (and thus fix this problem) without patching the kernel. You just need to set net.ipv6.conf.INTERFACE.accept_dad to 0 in sysctl.

    For example, before bringing up eth0, run:

    echo 0 > /proc/sys/net/ipv6/conf/eth0/accept_dad

    If you're using Debian, you can put this in your interfaces file as a "pre-up" command.

    ReplyDelete
  3. Hi, I am facing same issue, but with the above hack(echo 0 > /proc/sys/net/ipv6/conf/eth0/accept_dad) also I am facing the same bind error issue. Can you please help?I am using linux kernel.

    ReplyDelete