Imported Upstream version 1.38
[platform/upstream/connman.git] / gdhcp / common.c
index 784844b..1d667d1 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  DHCP library with GLib integration
  *
- *  Copyright (C) 2007-2010  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2007-2013  Intel Corporation. All rights reserved.
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
 #include <config.h>
 #endif
 
+#include <stdio.h>
+#include <stdlib.h>
 #include <errno.h>
 #include <unistd.h>
+#include <sys/ioctl.h>
 #include <stdint.h>
 #include <string.h>
 #include <endian.h>
+#include <net/if_arp.h>
+#include <linux/if.h>
 #include <netpacket/packet.h>
 #include <net/ethernet.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
 
 #include "gdhcp.h"
 #include "common.h"
+#include "../src/connman.h"
 
 static const DHCPOption client_options[] = {
        { OPTION_IP,                    0x01 }, /* subnet-mask */
        { OPTION_IP | OPTION_LIST,      0x03 }, /* routers */
        { OPTION_IP | OPTION_LIST,      0x06 }, /* domain-name-servers */
+       { OPTION_STRING,                0x0c }, /* hostname */
        { OPTION_STRING,                0x0f }, /* domain-name */
+       { OPTION_U16,                   0x1a }, /* mtu */
        { OPTION_IP | OPTION_LIST,      0x2a }, /* ntp-servers */
        { OPTION_U32,                   0x33 }, /* dhcp-lease-time */
        /* Options below will not be exposed to user */
        { OPTION_IP,                    0x32 }, /* requested-ip */
        { OPTION_U8,                    0x35 }, /* message-type */
+       { OPTION_U32,                   0x36 }, /* server-id */
        { OPTION_U16,                   0x39 }, /* max-size */
        { OPTION_STRING,                0x3c }, /* vendor */
        { OPTION_STRING,                0x3d }, /* client-id */
+       { OPTION_STRING,                0xfc }, /* UNOFFICIAL proxy-pac */
        { OPTION_UNKNOWN,               0x00 },
 };
 
@@ -135,6 +147,82 @@ int dhcp_end_option(uint8_t *optionptr)
        return i;
 }
 
+uint8_t *dhcpv6_get_option(struct dhcpv6_packet *packet, uint16_t pkt_len,
+                       int code, uint16_t *option_len, int *option_count)
+{
+       int rem, count = 0;
+       uint8_t *optionptr, *found = NULL;
+       uint16_t opt_code, opt_len, len;
+
+       optionptr = packet->options;
+       rem = pkt_len - 1 - 3;
+
+       if (rem <= 0)
+               goto bad_packet;
+
+       while (1) {
+               opt_code = optionptr[0] << 8 | optionptr[1];
+               opt_len = len = optionptr[2] << 8 | optionptr[3];
+               len += 2 + 2; /* skip code and len */
+
+               if (len < 4)
+                       goto bad_packet;
+
+               rem -= len;
+               if (rem < 0)
+                       break;
+
+               if (opt_code == code) {
+                       if (option_len)
+                               *option_len = opt_len;
+                       found = optionptr + 2 + 2;
+                       count++;
+               }
+
+               if (rem == 0)
+                       break;
+
+               optionptr += len;
+       }
+
+       if (option_count)
+               *option_count = count;
+
+       return found;
+
+bad_packet:
+       if (option_len)
+               *option_len = 0;
+       if (option_count)
+               *option_count = 0;
+       return NULL;
+}
+
+uint8_t *dhcpv6_get_sub_option(unsigned char *option, uint16_t max_len,
+                       uint16_t *option_code, uint16_t *option_len)
+{
+       int rem;
+       uint16_t code, len;
+
+       rem = max_len - 2 - 2;
+
+       if (rem <= 0)
+               /* Bad option */
+               return NULL;
+
+       code = option[0] << 8 | option[1];
+       len = option[2] << 8 | option[3];
+
+       rem -= len;
+       if (rem < 0)
+               return NULL;
+
+       *option_code = code;
+       *option_len = len;
+
+       return &option[4];
+}
+
 /*
  * Add an option (supplied in binary form) to the options.
  * Option format: [code][len][data1][data2]..[dataLEN]
@@ -157,28 +245,88 @@ void dhcp_add_binary_option(struct dhcp_packet *packet, uint8_t *addopt)
        optionptr[end + len] = DHCP_END;
 }
 
-void dhcp_add_simple_option(struct dhcp_packet *packet, uint8_t code,
-                                                       uint32_t data)
+/*
+ * Add an option (supplied in binary form) to the options.
+ * Option format: [code][len][data1][data2]..[dataLEN]
+ */
+void dhcpv6_add_binary_option(struct dhcpv6_packet *packet, uint16_t max_len,
+                               uint16_t *pkt_len, uint8_t *addopt)
+{
+       unsigned len;
+       uint8_t *optionptr = packet->options;
+
+       len = 2 + 2 + (addopt[2] << 8 | addopt[3]);
+
+       /* end position + (option code/length + addopt length) */
+       if (*pkt_len + len >= max_len)
+               /* option did not fit into the packet */
+               return;
+
+       memcpy(optionptr + *pkt_len, addopt, len);
+       *pkt_len += len;
+}
+
+static GDHCPOptionType check_option(uint8_t code, uint8_t data_len)
 {
-       uint8_t option[6], len;
        GDHCPOptionType type = dhcp_get_code_type(code);
+       uint8_t len;
 
        if (type == OPTION_UNKNOWN)
+               return type;
+
+       len = dhcp_option_lengths[type & OPTION_TYPE_MASK];
+       if (len != data_len) {
+               printf("Invalid option len %d (expecting %d) for code 0x%x\n",
+                       data_len, len, code);
+               return OPTION_UNKNOWN;
+       }
+
+       return type;
+}
+
+void dhcp_add_option_uint32(struct dhcp_packet *packet, uint8_t code,
+                                                       uint32_t data)
+{
+       uint8_t option[6];
+
+       if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
                return;
 
        option[OPT_CODE] = code;
+       option[OPT_LEN] = sizeof(data);
+       put_be32(data, option + OPT_DATA);
 
-       len = dhcp_option_lengths[type & OPTION_TYPE_MASK];
-       option[OPT_LEN] = len;
+       dhcp_add_binary_option(packet, option);
+}
 
-#if __BYTE_ORDER == __BIG_ENDIAN
-       data <<= 8 * (4 - len);
-#endif
+void dhcp_add_option_uint16(struct dhcp_packet *packet, uint8_t code,
+                                                       uint16_t data)
+{
+       uint8_t option[6];
+
+       if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
+               return;
+
+       option[OPT_CODE] = code;
+       option[OPT_LEN] = sizeof(data);
+       put_be16(data, option + OPT_DATA);
 
-       dhcp_put_unaligned(data, (uint32_t *) &option[OPT_DATA]);
        dhcp_add_binary_option(packet, option);
+}
+
+void dhcp_add_option_uint8(struct dhcp_packet *packet, uint8_t code,
+                                                       uint8_t data)
+{
+       uint8_t option[6];
+
+       if (check_option(code, sizeof(data)) == OPTION_UNKNOWN)
+               return;
+
+       option[OPT_CODE] = code;
+       option[OPT_LEN] = sizeof(data);
+       option[OPT_DATA] = data;
 
-       return;
+       dhcp_add_binary_option(packet, option);
 }
 
 void dhcp_init_header(struct dhcp_packet *packet, char type)
@@ -199,35 +347,24 @@ void dhcp_init_header(struct dhcp_packet *packet, char type)
        packet->cookie = htonl(DHCP_MAGIC);
        packet->options[0] = DHCP_END;
 
-       dhcp_add_simple_option(packet, DHCP_MESSAGE_TYPE, type);
+       dhcp_add_option_uint8(packet, DHCP_MESSAGE_TYPE, type);
 }
 
-static gboolean check_vendor(uint8_t  *option_vendor, const char *vendor)
+void dhcpv6_init_header(struct dhcpv6_packet *packet, uint8_t type)
 {
-       uint8_t vendor_length = sizeof(vendor) - 1;
-
-       if (option_vendor[OPT_LEN - OPT_DATA] != vendor_length)
-               return FALSE;
-
-       if (memcmp(option_vendor, vendor, vendor_length) != 0)
-               return FALSE;
-
-       return TRUE;
-}
+       int id;
+       uint64_t rand;
 
-static void check_broken_vendor(struct dhcp_packet *packet)
-{
-       uint8_t *vendor;
+       memset(packet, 0, sizeof(*packet));
 
-       if (packet->op != BOOTREQUEST)
-               return;
+       packet->message = type;
 
-       vendor = dhcp_get_option(packet, DHCP_VENDOR);
-       if (vendor == NULL)
-               return;
+       __connman_util_get_random(&rand);
+       id = rand;
 
-       if (check_vendor(vendor, "MSFT 98") == TRUE)
-               packet->flags |= htons(BROADCAST_FLAG);
+       packet->transaction_id[0] = (id >> 16) & 0xff;
+       packet->transaction_id[1] = (id >> 8) & 0xff;
+       packet->transaction_id[2] = id & 0xff;
 }
 
 int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd)
@@ -243,7 +380,19 @@ int dhcp_recv_l3_packet(struct dhcp_packet *packet, int fd)
        if (packet->cookie != htonl(DHCP_MAGIC))
                return -EPROTO;
 
-       check_broken_vendor(packet);
+       return n;
+}
+
+int dhcpv6_recv_l3_packet(struct dhcpv6_packet **packet, unsigned char *buf,
+                       int buf_len, int fd)
+{
+       int n;
+
+       n = read(fd, buf, buf_len);
+       if (n < 0)
+               return -errno;
+
+       *packet = (struct dhcpv6_packet *)buf;
 
        return n;
 }
@@ -279,9 +428,102 @@ uint16_t dhcp_checksum(void *addr, int count)
        return ~sum;
 }
 
+#define IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT \
+       { { { 0xff,0x02,0,0,0,0,0,0,0,0,0,0,0,0x1,0,0x2 } } } /* ff02::1:2 */
+static const struct in6_addr in6addr_all_dhcp_relay_agents_and_servers_mc =
+       IN6ADDR_ALL_DHCP_RELAY_AGENTS_AND_SERVERS_MC_INIT;
+
+int dhcpv6_send_packet(int index, struct dhcpv6_packet *dhcp_pkt, int len)
+{
+       struct msghdr m;
+       struct iovec v;
+       struct in6_pktinfo *pktinfo;
+       struct cmsghdr *cmsg;
+       int fd, ret, opt = 1;
+       struct sockaddr_in6 src;
+       struct sockaddr_in6 dst;
+       void *control_buf;
+       size_t control_buf_len;
+
+       fd = socket(PF_INET6, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+       if (fd < 0)
+               return -errno;
+
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
+
+       memset(&src, 0, sizeof(src));
+       src.sin6_family = AF_INET6;
+       src.sin6_port = htons(DHCPV6_CLIENT_PORT);
+
+       if (bind(fd, (struct sockaddr *) &src, sizeof(src)) <0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
+
+       memset(&dst, 0, sizeof(dst));
+       dst.sin6_family = AF_INET6;
+       dst.sin6_port = htons(DHCPV6_SERVER_PORT);
+
+       dst.sin6_addr = in6addr_all_dhcp_relay_agents_and_servers_mc;
+
+       control_buf_len = CMSG_SPACE(sizeof(struct in6_pktinfo));
+       control_buf = g_try_malloc0(control_buf_len);
+       if (!control_buf) {
+               close(fd);
+               return -ENOMEM;
+       }
+
+       memset(&m, 0, sizeof(m));
+       memset(&v, 0, sizeof(v));
+
+       m.msg_name = &dst;
+       m.msg_namelen = sizeof(dst);
+
+       v.iov_base = (char *)dhcp_pkt;
+       v.iov_len = len;
+       m.msg_iov = &v;
+       m.msg_iovlen = 1;
+
+       m.msg_control = control_buf;
+       m.msg_controllen = control_buf_len;
+       cmsg = CMSG_FIRSTHDR(&m);
+       cmsg->cmsg_level = IPPROTO_IPV6;
+       cmsg->cmsg_type = IPV6_PKTINFO;
+       cmsg->cmsg_len = CMSG_LEN(sizeof(*pktinfo));
+
+       pktinfo = (struct in6_pktinfo *)CMSG_DATA(cmsg);
+       memset(pktinfo, 0, sizeof(*pktinfo));
+       pktinfo->ipi6_ifindex = index;
+       m.msg_controllen = cmsg->cmsg_len;
+
+       ret = sendmsg(fd, &m, 0);
+       if (ret < 0) {
+               char *msg = "DHCPv6 msg send failed";
+
+               if (errno == EADDRNOTAVAIL) {
+                       char *str = g_strdup_printf("%s (index %d)",
+                                       msg, index);
+                       perror(str);
+                       g_free(str);
+               } else
+                       perror(msg);
+       }
+
+       g_free(control_buf);
+       close(fd);
+
+       return ret;
+}
+
 int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
-               uint32_t source_ip, int source_port, uint32_t dest_ip,
-                       int dest_port, const uint8_t *dest_arp, int ifindex)
+                       uint32_t source_ip, int source_port,
+                       uint32_t dest_ip, int dest_port,
+                       const uint8_t *dest_arp, int ifindex, bool bcast)
 {
        struct sockaddr_ll dest;
        struct ip_udp_dhcp_packet packet;
@@ -294,10 +536,13 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
                                offsetof(struct ip_udp_dhcp_packet, udp),
        };
 
-       fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+       fd = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
        if (fd < 0)
                return -errno;
 
+       if (bcast)
+               dhcp_pkt->flags |= htons(BROADCAST_FLAG);
+
        memset(&dest, 0, sizeof(dest));
        memset(&packet, 0, sizeof(packet));
        packet.data = *dhcp_pkt;
@@ -308,8 +553,9 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
        dest.sll_halen = 6;
        memcpy(dest.sll_addr, dest_arp, 6);
        if (bind(fd, (struct sockaddr *)&dest, sizeof(dest)) < 0) {
+               int err = errno;
                close(fd);
-               return -errno;
+               return -err;
        }
 
        packet.ip.protocol = IPPROTO_UDP;
@@ -336,8 +582,11 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
         */
        n = sendto(fd, &packet, IP_UPD_DHCP_SIZE, 0,
                        (struct sockaddr *) &dest, sizeof(dest));
-       if (n < 0)
-               return -errno;
+       if (n < 0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
 
        close(fd);
 
@@ -346,7 +595,8 @@ int dhcp_send_raw_packet(struct dhcp_packet *dhcp_pkt,
 
 int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
                                uint32_t source_ip, int source_port,
-                               uint32_t dest_ip, int dest_port)
+                               uint32_t dest_ip, int dest_port,
+                               const char *interface)
 {
        struct sockaddr_in client;
        int fd, n, opt = 1;
@@ -356,62 +606,165 @@ int dhcp_send_kernel_packet(struct dhcp_packet *dhcp_pkt,
                                        EXTEND_FOR_BUGGY_SERVERS,
        };
 
-       fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+       fd = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
        if (fd < 0)
                return -errno;
 
-       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+       if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
+                               interface, strlen(interface) + 1) < 0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
+
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
 
        memset(&client, 0, sizeof(client));
        client.sin_family = AF_INET;
        client.sin_port = htons(source_port);
-       client.sin_addr.s_addr = source_ip;
+       client.sin_addr.s_addr = htonl(source_ip);
        if (bind(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
+               int err = errno;
                close(fd);
-               return -errno;
+               return -err;
        }
 
        memset(&client, 0, sizeof(client));
        client.sin_family = AF_INET;
        client.sin_port = htons(dest_port);
-       client.sin_addr.s_addr = dest_ip;
+       client.sin_addr.s_addr = htonl(dest_ip);
        if (connect(fd, (struct sockaddr *) &client, sizeof(client)) < 0) {
+               int err = errno;
                close(fd);
-               return -errno;
+               return -err;
        }
 
        n = write(fd, dhcp_pkt, DHCP_SIZE);
+       if (n < 0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
 
        close(fd);
 
-       if (n < 0)
-               return -errno;
-
        return n;
 }
 
-int dhcp_l3_socket(int port, const char *interface)
+int dhcp_l3_socket(int port, const char *interface, int family)
 {
-       int fd, opt = 1;
-       struct sockaddr_in addr;
+       int fd, opt = 1, len;
+       struct sockaddr_in addr4;
+       struct sockaddr_in6 addr6;
+       struct sockaddr *addr;
 
-       fd = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+       fd = socket(family, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_UDP);
+       if (fd < 0)
+               return -errno;
 
-       setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
+       if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+               int err = errno;
+               close(fd);
+               return -err;
+       }
 
        if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE,
                                interface, strlen(interface) + 1) < 0) {
+               int err = errno;
                close(fd);
-               return -1;
+               return -err;
+       }
+
+       if (family == AF_INET) {
+               memset(&addr4, 0, sizeof(addr4));
+               addr4.sin_family = family;
+               addr4.sin_port = htons(port);
+               addr = (struct sockaddr *)&addr4;
+               len = sizeof(addr4);
+       } else if (family == AF_INET6) {
+               memset(&addr6, 0, sizeof(addr6));
+               addr6.sin6_family = family;
+               addr6.sin6_port = htons(port);
+               addr = (struct sockaddr *)&addr6;
+               len = sizeof(addr6);
+       } else {
+               close(fd);
+               return -EINVAL;
        }
 
-       memset(&addr, 0, sizeof(addr));
-       addr.sin_family = AF_INET;
-       addr.sin_port = htons(port);
-       if (bind(fd, (struct sockaddr *)&addr, sizeof(addr)) != 0) {
+       if (bind(fd, addr, len) != 0) {
                close(fd);
                return -1;
        }
 
        return fd;
 }
+
+char *get_interface_name(int index)
+{
+       struct ifreq ifr;
+       int sk, err;
+
+       if (index < 0)
+               return NULL;
+
+       sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+       if (sk < 0) {
+               perror("Open socket error");
+               return NULL;
+       }
+
+       memset(&ifr, 0, sizeof(ifr));
+       ifr.ifr_ifindex = index;
+
+       err = ioctl(sk, SIOCGIFNAME, &ifr);
+       if (err < 0) {
+               perror("Get interface name error");
+               close(sk);
+               return NULL;
+       }
+
+       close(sk);
+
+       return g_strdup(ifr.ifr_name);
+}
+
+bool interface_is_up(int index)
+{
+       int sk, err;
+       struct ifreq ifr;
+       bool ret = false;
+
+       sk = socket(PF_INET, SOCK_DGRAM | SOCK_CLOEXEC, 0);
+       if (sk < 0) {
+               perror("Open socket error");
+               return false;
+       }
+
+       memset(&ifr, 0, sizeof(ifr));
+       ifr.ifr_ifindex = index;
+
+       err = ioctl(sk, SIOCGIFNAME, &ifr);
+       if (err < 0) {
+               perror("Get interface name error");
+               goto done;
+       }
+
+       err = ioctl(sk, SIOCGIFFLAGS, &ifr);
+       if (err < 0) {
+               perror("Get interface flags error");
+               goto done;
+       }
+
+       if (ifr.ifr_flags & IFF_UP)
+               ret = true;
+
+done:
+       close(sk);
+
+       return ret;
+}