sd-dhcp6-client: Add basic DHCPv6 option handling
authorPatrik Flykt <patrik.flykt@linux.intel.com>
Thu, 19 Jun 2014 12:39:20 +0000 (15:39 +0300)
committerPatrik Flykt <patrik.flykt@linux.intel.com>
Thu, 19 Jun 2014 12:44:43 +0000 (15:44 +0300)
Add option appending and parsing. DHCPv6 options are not aligned, thus
the option handling code must be able to handle options starting at
any byte boundary.

Add a test case for the basic option handling.

Makefile.am
src/libsystemd-network/dhcp6-internal.h
src/libsystemd-network/dhcp6-option.c [new file with mode: 0644]
src/libsystemd-network/test-dhcp6-client.c

index 2926e98..82f187d 100644 (file)
@@ -2601,7 +2601,8 @@ libsystemd_network_la_SOURCES = \
        src/libsystemd-network/sd-dhcp6-client.c \
        src/libsystemd-network/sd-icmp6-nd.c \
        src/libsystemd-network/dhcp6-internal.h \
-       src/libsystemd-network/dhcp6-network.c
+       src/libsystemd-network/dhcp6-network.c \
+       src/libsystemd-network/dhcp6-option.c
 
 libsystemd_network_la_LIBADD = \
        libudev-internal.la \
@@ -2664,6 +2665,7 @@ test_icmp6_rs_LDADD = \
 
 test_dhcp6_client_SOURCES = \
        src/systemd/sd-dhcp6-client.h \
+       src/libsystemd-network/dhcp6-internal.h \
        src/libsystemd-network/test-dhcp6-client.c
 
 test_dhcp6_client_LDADD = \
index 1cdb912..30b624d 100644 (file)
@@ -59,3 +59,9 @@ typedef struct DHCP6IA DHCP6IA;
 
 int dhcp_network_icmp6_bind_router_solicitation(int index);
 int dhcp_network_icmp6_send_router_solicitation(int s, const struct ether_addr *ether_addr);
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+                        size_t optlen, const void *optval);
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia);
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+                       size_t *optlen, uint8_t **optvalue);
diff --git a/src/libsystemd-network/dhcp6-option.c b/src/libsystemd-network/dhcp6-option.c
new file mode 100644 (file)
index 0000000..cc4d261
--- /dev/null
@@ -0,0 +1,150 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright (C) 2014 Intel Corporation. All rights reserved.
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <netinet/in.h>
+#include <errno.h>
+#include <string.h>
+
+#include "sparse-endian.h"
+#include "util.h"
+
+#include "dhcp6-internal.h"
+#include "dhcp6-protocol.h"
+
+#define DHCP6_OPTION_HDR_LEN                    4
+#define DHCP6_OPTION_IA_NA_LEN                  12
+#define DHCP6_OPTION_IA_TA_LEN                  4
+#define DHCP6_OPTION_IAADDR_LEN                 24
+
+static int option_append_hdr(uint8_t **buf, size_t *buflen, uint16_t optcode,
+                             size_t optlen) {
+        assert_return(buf, -EINVAL);
+        assert_return(*buf, -EINVAL);
+        assert_return(buflen, -EINVAL);
+
+        if (optlen > 0xffff || *buflen < optlen + DHCP6_OPTION_HDR_LEN)
+                return -ENOBUFS;
+
+        (*buf)[0] = optcode >> 8;
+        (*buf)[1] = optcode & 0xff;
+        (*buf)[2] = optlen >> 8;
+        (*buf)[3] = optlen & 0xff;
+
+        *buf += DHCP6_OPTION_HDR_LEN;
+        *buflen -= DHCP6_OPTION_HDR_LEN;
+
+        return 0;
+}
+
+int dhcp6_option_append(uint8_t **buf, size_t *buflen, uint16_t code,
+                        size_t optlen, const void *optval) {
+        int r;
+
+        assert_return(optval, -EINVAL);
+
+        r = option_append_hdr(buf, buflen, code, optlen);
+        if (r < 0)
+                return r;
+
+        memcpy(*buf, optval, optlen);
+
+        *buf += optlen;
+        *buflen -= optlen;
+
+        return 0;
+}
+
+int dhcp6_option_append_ia(uint8_t **buf, size_t *buflen, DHCP6IA *ia) {
+        uint16_t len;
+        uint8_t *ia_hdr;
+        size_t ia_buflen, ia_addrlen = 0;
+        DHCP6Address *addr;
+        int r;
+
+        assert_return(buf && *buf && buflen && ia, -EINVAL);
+
+        switch (ia->type) {
+        case DHCP6_OPTION_IA_NA:
+                len = DHCP6_OPTION_IA_NA_LEN;
+                break;
+
+        case DHCP6_OPTION_IA_TA:
+                len = DHCP6_OPTION_IA_TA_LEN;
+                break;
+
+        default:
+                return -EINVAL;
+        }
+
+        if (*buflen < len)
+                return -ENOBUFS;
+
+        ia_hdr = *buf;
+        ia_buflen = *buflen;
+
+        *buf += DHCP6_OPTION_HDR_LEN;
+        *buflen -= DHCP6_OPTION_HDR_LEN;
+
+        memcpy(*buf, &ia->id, len);
+
+        *buf += len;
+        *buflen -= len;
+
+        LIST_FOREACH(addresses, addr, ia->addresses) {
+                r = option_append_hdr(buf, buflen, DHCP6_OPTION_IAADDR,
+                                      DHCP6_OPTION_IAADDR_LEN);
+                if (r < 0)
+                        return r;
+
+                memcpy(*buf, &addr->address, DHCP6_OPTION_IAADDR_LEN);
+
+                *buf += DHCP6_OPTION_IAADDR_LEN;
+                *buflen -= DHCP6_OPTION_IAADDR_LEN;
+
+                ia_addrlen += DHCP6_OPTION_HDR_LEN + DHCP6_OPTION_IAADDR_LEN;
+        }
+
+        r = option_append_hdr(&ia_hdr, &ia_buflen, ia->type, len + ia_addrlen);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+int dhcp6_option_parse(uint8_t **buf, size_t *buflen, uint16_t *optcode,
+                       size_t *optlen, uint8_t **optvalue) {
+        assert_return(buf && buflen && optcode && optlen && optvalue, -EINVAL);
+
+        if (*buflen == 0)
+                return -ENOMSG;
+
+        *optcode = (*buf)[0] << 8 | (*buf)[1];
+        *optlen = (*buf)[2] << 8 | (*buf)[3];
+
+        if (*optlen > *buflen - 4)
+                return -ENOBUFS;
+
+        *optvalue = &(*buf)[4];
+        *buflen -= (*optlen + 4);
+        (*buf) += (*optlen + 4);
+
+        return 0;
+}
index b2dc8ba..b52f407 100644 (file)
@@ -28,6 +28,7 @@
 
 #include "sd-dhcp6-client.h"
 #include "dhcp6-protocol.h"
+#include "dhcp6-internal.h"
 
 static struct ether_addr mac_addr = {
         .ether_addr_octet = {'A', 'B', 'C', '1', '2', '3'}
@@ -61,6 +62,72 @@ static int test_client_basic(sd_event *e) {
         return 0;
 }
 
+static int test_option(sd_event *e) {
+        uint8_t packet[] = {
+                'F', 'O', 'O',
+                0x00, DHCP6_OPTION_ORO, 0x00, 0x07,
+                'A', 'B', 'C', 'D', 'E', 'F', 'G',
+                0x00, DHCP6_OPTION_VENDOR_CLASS, 0x00, 0x09,
+                '1', '2', '3', '4', '5', '6', '7', '8', '9',
+                'B', 'A', 'R',
+        };
+        uint8_t result[] = {
+                'F', 'O', 'O',
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+                'B', 'A', 'R',
+        };
+        uint16_t optcode;
+        size_t optlen;
+        uint8_t *optval, *buf, *out;
+        size_t zero = 0, pos = 3;
+        size_t buflen = sizeof(packet), outlen = sizeof(result);
+
+        if (verbose)
+                printf("* %s\n", __FUNCTION__);
+
+        assert_se(buflen == outlen);
+
+        assert_se(dhcp6_option_parse(&buf, &zero, &optcode, &optlen,
+                                     &optval) == -ENOMSG);
+
+        buflen -= 3;
+        buf = &packet[3];
+        outlen -= 3;
+        out = &result[3];
+
+        assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen,
+                                     &optval) >= 0);
+        pos += 4 + optlen;
+        assert_se(buf == &packet[pos]);
+        assert_se(optcode == DHCP6_OPTION_ORO);
+        assert_se(optlen == 7);
+        assert_se(buflen + pos == sizeof(packet));
+
+        assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen,
+                                      optval) >= 0);
+        assert_se(out == &result[pos]);
+        assert_se(*out == 0x00);
+
+        assert_se(dhcp6_option_parse(&buf, &buflen, &optcode, &optlen,
+                                     &optval) >= 0);
+        pos += 4 + optlen;
+        assert_se(buf == &packet[pos]);
+        assert_se(optcode == DHCP6_OPTION_VENDOR_CLASS);
+        assert_se(optlen == 9);
+        assert_se(buflen + pos == sizeof(packet));
+
+        assert_se(dhcp6_option_append(&out, &outlen, optcode, optlen,
+                                      optval) >= 0);
+        assert_se(out == &result[pos]);
+        assert_se(*out == 'B');
+
+        assert_se(memcmp(packet, result, sizeof(packet)) == 0);
+
+        return 0;
+}
+
 int main(int argc, char *argv[]) {
         _cleanup_event_unref_ sd_event *e;
 
@@ -71,6 +138,9 @@ int main(int argc, char *argv[]) {
         log_open();
 
         test_client_basic(e);
+        test_option(e);
+
+        assert_se(!sd_event_unref(e));
 
         return 0;
 }