From 4fb07a852dd32fb578850ab672de899e46536267 Mon Sep 17 00:00:00 2001
From: Kaspar Schleiser <kaspar@schleiser.de>
Date: Wed, 30 Mar 2016 12:28:39 +0200
Subject: [PATCH] sys: net: add UHCP protocol code and gnrc client
 implementation

---
 Makefile.dep                                  |   6 +
 sys/Makefile                                  |   8 +
 sys/auto_init/auto_init.c                     |   5 +
 sys/include/net/uhcp.h                        | 179 ++++++++++++++++++
 sys/net/application_layer/uhcp/Makefile       |   3 +
 sys/net/application_layer/uhcp/uhcp.c         | 111 +++++++++++
 sys/net/application_layer/uhcp/uhcpc.c        |  75 ++++++++
 sys/net/gnrc/application_layer/uhcpc/Makefile |   3 +
 .../gnrc/application_layer/uhcpc/gnrc_uhcpc.c | 132 +++++++++++++
 9 files changed, 522 insertions(+)
 create mode 100644 sys/include/net/uhcp.h
 create mode 100644 sys/net/application_layer/uhcp/Makefile
 create mode 100644 sys/net/application_layer/uhcp/uhcp.c
 create mode 100644 sys/net/application_layer/uhcp/uhcpc.c
 create mode 100644 sys/net/gnrc/application_layer/uhcpc/Makefile
 create mode 100644 sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c

diff --git a/Makefile.dep b/Makefile.dep
index 283a83df2d..d2b09b295e 100644
--- a/Makefile.dep
+++ b/Makefile.dep
@@ -25,6 +25,12 @@ ifneq (,$(filter netdev2_ieee802154,$(USEMODULE)))
   USEMODULE += ieee802154
 endif
 
+ifneq (,$(filter gnrc_uhcpc,$(USEMODULE)))
+    USEMODULE += uhcpc
+    USEMODULE += gnrc_conn_udp
+    USEMODULE += fmt
+endif
+
 ifneq (,$(filter gnrc_%,$(filter-out gnrc_netapi gnrc_netreg gnrc_netif% gnrc_pktbuf,$(USEMODULE))))
   USEMODULE += gnrc
 endif
diff --git a/sys/Makefile b/sys/Makefile
index eaea7e3815..c6ffeb132a 100644
--- a/sys/Makefile
+++ b/sys/Makefile
@@ -78,6 +78,14 @@ ifneq (,$(filter hamming256,$(USEMODULE)))
     DIRS += ecc/hamming256
 endif
 
+ifneq (,$(filter uhcpc,$(USEMODULE)))
+    DIRS += net/application_layer/uhcp
+endif
+
+ifneq (,$(filter gnrc_uhcpc,$(USEMODULE)))
+    DIRS += net/gnrc/application_layer/uhcpc
+endif
+
 ifneq (,$(filter netopt,$(USEMODULE)))
     DIRS += net/crosslayer/netopt
 endif
diff --git a/sys/auto_init/auto_init.c b/sys/auto_init/auto_init.c
index 9981271385..d9ca369481 100644
--- a/sys/auto_init/auto_init.c
+++ b/sys/auto_init/auto_init.c
@@ -213,6 +213,11 @@ void auto_init(void)
     gnrc_ipv6_netif_init_by_dev();
 #endif
 
+#ifdef MODULE_GNRC_UHCPC
+    extern void auto_init_gnrc_uhcpc(void);
+    auto_init_gnrc_uhcpc();
+#endif
+
 /* initialize sensors and actuators */
 #ifdef MODULE_AUTO_INIT_SAUL
     DEBUG("auto_init SAUL\n");
diff --git a/sys/include/net/uhcp.h b/sys/include/net/uhcp.h
new file mode 100644
index 0000000000..1f8c21d358
--- /dev/null
+++ b/sys/include/net/uhcp.h
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+/**
+ * @defgroup    net_uhcp UHCP
+ * @ingroup     net
+ * @brief       Provides UHCP (micro host configuration protocol)
+ * @{
+ *
+ * @file
+ * @brief   UHCP header
+ *
+ * @author  Kaspar Schleiser <kaspar@schleiser.de>
+ */
+
+#ifndef UHCP_H
+#define UHCP_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include <arpa/inet.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/** @brief UHCP magic number */
+#define UHCP_MAGIC  (0x55484350) /* "UHCP" in hex */
+
+/** @brief UHCP version of this header */
+#define UHCP_VER    (0)
+
+/** @brief UHCP port number */
+#define UHCP_PORT       (12345U)
+
+/** @brief UHCP port number (as string for e.g., getaddrinfo() service arg */
+#define UHCP_PORT_STR   "12345"
+
+/** @brief Enum containing possible UHCP packet types */
+typedef enum {
+    UHCP_REQ,               /**< packet is a request packet */
+    UHCP_PUSH               /**< packet is a push / answer packet */
+} uhcp_type_t;
+
+/**
+ * @brief UHCP packet header struct
+ */
+typedef struct __attribute__((packed)) {
+    uint32_t uhcp_magic;    /**< always contains UHCP in hex */
+    uint8_t ver_type;       /**< four bits version number, four bits
+                                 packet type (see uchp_type_t) */
+} uhcp_hdr_t;
+
+/**
+ * @brief struct for request packets
+ *
+ * @extends uhcp_hdr_t
+ */
+typedef struct __attribute__((packed)) {
+    uhcp_hdr_t hdr;         /**< member holding parent type */
+    uint8_t prefix_len;     /**< contains the requested prefix length */
+} uhcp_req_t;
+
+/**
+ * @brief struct for push packets
+ *
+ * @extends uhcp_hdr_t
+ */
+typedef struct __attribute__((packed)) {
+    uhcp_hdr_t hdr;         /**< member holding parent type */
+    uint8_t prefix_len;     /**< contains the prefix length of assigned
+                                 prefix */
+    uint8_t prefix[];       /**< contains the assigned prefix */
+} uhcp_push_t;
+
+/** @brief typedef for interface handle */
+typedef unsigned uhcp_iface_t;
+
+/**
+ * @brief handle incoming UDP packet
+ *
+ * This function should be called by UHCP server/client network code for every
+ * incoming UDP packet destined to UCHP_PORT.
+ *
+ * @param[in]   buf     buffer containing UDP packet
+ * @param[in]   len     length of @c buf
+ * @param[in]   src     ptr to IPv6 source address
+ * @param[in]   port    source port of packet
+ * @param[in]   iface   interface number of incoming packet
+ */
+void uhcp_handle_udp(uint8_t *buf, size_t len, uint8_t *src, uint16_t port, uhcp_iface_t iface);
+
+/**
+ * @brief handle incoming UHCP request packet
+ *
+ * This function will be called by uhcp_handle_udp() for incoming request
+ * packet.
+ *
+ * @internal
+ *
+ * @param[in]   req     ptr to UHCP request header
+ * @param[in]   src     ptr to IPv6 source address
+ * @param[in]   port    source port of packet
+ * @param[in]   iface   number of interface the packet came in
+ */
+void uhcp_handle_req(uhcp_req_t *req, uint8_t *src, uint16_t port, uhcp_iface_t iface);
+
+/**
+ * @brief handle incoming UHCP push packet
+ *
+ * This function will be called by uhcp_handle_udp() for incoming push
+ * packet.
+ *
+ * @internal
+ *
+ * @param[in]   req     ptr to UHCP push header
+ * @param[in]   src     ptr to IPv6 source address
+ * @param[in]   port    source port of packet
+ * @param[in]   iface   number of interface the packet came in
+ */
+void uhcp_handle_push(uhcp_push_t *req, uint8_t *src, uint16_t port, uhcp_iface_t iface);
+
+/**
+ * @brief handle incoming prefix (as parsed from push packet)
+ *
+ * Supposed to be implemented by UHCP client implementations.
+ *
+ * The function might be called with an already configured prefix. In that
+ * case, the lifetime *MUST* be updated.
+ *
+ * If the function is called with a different prefix than before, the old
+ * prefix *MUST* be considered obsolete.
+ *
+ * @param[in]   prefix      ptr to assigned prefix
+ * @param[in]   prefix_len  length of assigned prefix
+ * @param[in]   lifetime    lifetime of prefix
+ * @param[in]   src         ptr to IPv6 source address
+ * @param[in]   iface       number of interface the packet came in
+ */
+void uhcp_handle_prefix(uint8_t *prefix, uint8_t prefix_len, uint16_t lifetime, uint8_t *src, uhcp_iface_t iface);
+
+/**
+ * @brief function to set constant values in UHCP header
+ *
+ * @internal
+ *
+ * @param[out]  hdr     hdr to set up
+ * @param[in]   type    type of packet (request or push)
+ */
+static inline void uhcp_hdr_set(uhcp_hdr_t *hdr, uhcp_type_t type)
+{
+    hdr->uhcp_magic = htonl(UHCP_MAGIC);
+    hdr->ver_type = (UHCP_VER << 4) | (type & 0xF);
+}
+
+/**
+ * @brief UDP send function used by UHCP client / server
+ *
+ * Supposed to be implemented by UHCP clients.
+ *
+ * @param[in]   buf         buffer to send
+ * @param[in]   len         length of buf
+ * @param[in]   dst         ptr to IPv6 destination address
+ * @param[in]   dst_port    destination port
+ * @param[in]   dst_iface   interface number of destination interface
+ */
+int udp_sendto(uint8_t *buf, size_t len, uint8_t *dst, uint16_t dst_port, uhcp_iface_t dst_iface);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* UHCP_H */
+/** @} */
diff --git a/sys/net/application_layer/uhcp/Makefile b/sys/net/application_layer/uhcp/Makefile
new file mode 100644
index 0000000000..5bf81d28db
--- /dev/null
+++ b/sys/net/application_layer/uhcp/Makefile
@@ -0,0 +1,3 @@
+MODULE=uhcpc
+CFLAGS += -DUHCP_CLIENT
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/application_layer/uhcp/uhcp.c b/sys/net/application_layer/uhcp/uhcp.c
new file mode 100644
index 0000000000..6f4ea7b162
--- /dev/null
+++ b/sys/net/application_layer/uhcp/uhcp.c
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+#include <stdio.h>
+
+#include <arpa/inet.h>
+#include <netinet/in.h>
+#include <string.h>
+
+#include "net/uhcp.h"
+
+void uhcp_handle_udp(uint8_t *buf, size_t len, uint8_t *src, uint16_t port, uhcp_iface_t iface)
+{
+    char addr_str[INET6_ADDRSTRLEN];
+    inet_ntop(AF_INET6, src, addr_str, INET6_ADDRSTRLEN);
+    printf("got packet from %s port %u\n", addr_str, (unsigned)port);
+
+    if (len < sizeof(uhcp_req_t)) {
+        puts("error: packet too small.");
+        return;
+    }
+
+    uhcp_hdr_t *hdr = (uhcp_hdr_t *)buf;
+
+    if (! (ntohl(hdr->uhcp_magic) == UHCP_MAGIC)) {
+        puts("error: wrong magic number.");
+        return;
+    }
+
+    unsigned ver, type;
+    ver = hdr->ver_type >> 4;
+    type = hdr->ver_type & 0xF;
+
+    if (ver != UHCP_VER) {
+        puts("error: wrong protocol version.");
+    }
+
+    switch(type) {
+#ifdef UHCP_SERVER
+        case UHCP_REQ:
+            if (len < sizeof(uhcp_req_t)) {
+                puts("error: request too small\n");
+            }
+            else {
+                uhcp_handle_req((uhcp_req_t*)hdr, src, port, iface);
+            }
+            break;
+#endif
+#ifdef UHCP_CLIENT
+        case UHCP_PUSH:
+            {
+                uhcp_push_t *push = (uhcp_push_t*)hdr;
+                if ((len < sizeof(uhcp_push_t))
+                    || (len < (sizeof(uhcp_push_t) + (push->prefix_len >> 3)))
+                   ) {
+                    puts("error: request too small\n");
+                }
+                else {
+                    uhcp_handle_push(push, src, port, iface);
+                }
+                break;
+            }
+#endif
+        default:
+             puts("error: unexpected type\n");
+    }
+}
+
+#ifdef UHCP_SERVER
+extern char _prefix[16];
+extern unsigned _prefix_len;
+void uhcp_handle_req(uhcp_req_t *req, uint8_t *src, uint16_t port, uhcp_iface_t iface)
+{
+    size_t prefix_bytes = (_prefix_len + 7)>>3;
+    uint8_t packet[sizeof(uhcp_push_t) + prefix_bytes];
+
+    uhcp_push_t *reply = (uhcp_push_t *)packet;
+    uhcp_hdr_set(&reply->hdr, UHCP_PUSH);
+
+    reply->prefix_len = _prefix_len;
+    memcpy(reply->prefix, _prefix, prefix_bytes);
+
+    int res = udp_sendto(packet, sizeof(packet), src, port, iface);
+    if (res == -1) {
+        printf("uhcp_handle_req(): udp_sendto() res=%i\n", res);
+    }
+}
+#endif /* UHCP_SERVER */
+
+#ifdef UHCP_CLIENT
+void uhcp_handle_push(uhcp_push_t *req, uint8_t *src, uint16_t port, uhcp_iface_t iface)
+{
+    char addr_str[INET6_ADDRSTRLEN];
+    char prefix_str[INET6_ADDRSTRLEN];
+    inet_ntop(AF_INET6, src, addr_str, INET6_ADDRSTRLEN);
+    uint8_t prefix[16];
+    size_t prefix_bytes = (req->prefix_len + 7)>>3;
+    memset(prefix + 16 - prefix_bytes, '\0', 16 - prefix_bytes);
+    memcpy(prefix, req->prefix, prefix_bytes);
+
+    inet_ntop(AF_INET6, prefix, prefix_str, INET6_ADDRSTRLEN);
+
+    printf("uhcp: push from %s:%u prefix=%s/%u\n", addr_str, (unsigned)port, prefix_str, req->prefix_len);
+    uhcp_handle_prefix(prefix, req->prefix_len, 0xFFFF, src, iface);
+}
+#endif
diff --git a/sys/net/application_layer/uhcp/uhcpc.c b/sys/net/application_layer/uhcp/uhcpc.c
new file mode 100644
index 0000000000..bc1b32652d
--- /dev/null
+++ b/sys/net/application_layer/uhcp/uhcpc.c
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+#include "net/uhcp.h"
+
+#include "net/af.h"
+#include "net/conn/udp.h"
+#include "net/ipv6/addr.h"
+#include "xtimer.h"
+
+static void _timeout(void *arg) {
+    kernel_pid_t pid = *(kernel_pid_t*)arg;
+    msg_t msg;
+    msg_send_int(&msg, pid);
+    msg_send_int(&msg, pid);
+    msg_send_int(&msg, pid);
+}
+
+/**
+ * @brief Request prefix from uhcp server
+ *
+ * Never returns.
+ * Calls @c uhcp_handle_prefix() when a prefix or prefix change is received.
+ *
+ * @param[in]   iface   interface to request prefix on
+ */
+void uhcp_client(uhcp_iface_t iface)
+{
+    ipv6_addr_t target;
+    ipv6_addr_from_str(&target, "ff15::abcd");
+
+    /* prepare UHCP header */
+    uhcp_req_t req;
+    uhcp_hdr_set(&req.hdr, UHCP_REQ);
+    req.prefix_len = 64;
+
+    /* create listening socket */
+    ipv6_addr_t zero = {{0}};
+    conn_udp_t conn;
+    int res = conn_udp_create(&conn, &zero, 16, AF_INET6, UHCP_PORT);
+
+    uint8_t srv_addr[16];
+    size_t srv_addr_len;
+    uint16_t srv_port;
+    uint8_t buf[sizeof(uhcp_push_t) + 16];
+
+    kernel_pid_t pid = thread_getpid();
+    xtimer_t timeout;
+    timeout.callback = _timeout;
+    timeout.arg = &pid;
+
+    while(1) {
+        xtimer_set(&timeout, 10U*SEC_IN_USEC);
+        puts("uhcp_client(): sending REQ...");
+        conn_udp_sendto(&req, sizeof(uhcp_req_t), NULL, 0, &target, 16, AF_INET6 , 12345, 12345);
+        res = conn_udp_recvfrom(&conn, buf, sizeof(buf), srv_addr, &srv_addr_len, &srv_port);
+        if (res > 0) {
+            xtimer_remove(&timeout);
+            uhcp_handle_udp(buf, res, srv_addr, srv_port, iface);
+            xtimer_sleep(60);
+        }
+        else {
+            msg_t msg;
+            msg_try_receive(&msg);
+            msg_try_receive(&msg);
+            msg_try_receive(&msg);
+            puts("uhcp_client(): timeout waiting for reply");
+        }
+    }
+}
diff --git a/sys/net/gnrc/application_layer/uhcpc/Makefile b/sys/net/gnrc/application_layer/uhcpc/Makefile
new file mode 100644
index 0000000000..8f29e62611
--- /dev/null
+++ b/sys/net/gnrc/application_layer/uhcpc/Makefile
@@ -0,0 +1,3 @@
+MODULE = gnrc_uhcpc
+
+include $(RIOTBASE)/Makefile.base
diff --git a/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c b/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c
new file mode 100644
index 0000000000..bd4c016be8
--- /dev/null
+++ b/sys/net/gnrc/application_layer/uhcpc/gnrc_uhcpc.c
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 Kaspar Schleiser <kaspar@schleiser.de>
+ *
+ * This file is subject to the terms and conditions of the GNU Lesser
+ * General Public License v2.1. See the file LICENSE in the top level
+ * directory for more details.
+ */
+
+#include "net/fib.h"
+#include "net/gnrc/ipv6.h"
+#include "net/gnrc/ipv6/nc.h"
+#include "net/gnrc/ipv6/netif.h"
+#include "net/gnrc/netapi.h"
+#include "net/gnrc/netif.h"
+#include "net/ipv6/addr.h"
+#include "net/netdev2.h"
+#include "net/netopt.h"
+
+#include "net/uhcp.h"
+#include "log.h"
+#include "fmt.h"
+
+static kernel_pid_t gnrc_border_interface;
+static kernel_pid_t gnrc_wireless_interface;
+
+static void set_interface_roles(void)
+{
+    kernel_pid_t ifs[GNRC_NETIF_NUMOF];
+    size_t numof = gnrc_netif_get(ifs);
+    uint16_t dev_type;
+
+    for (size_t i = 0; i < numof && i < GNRC_NETIF_NUMOF; i++) {
+        kernel_pid_t dev = ifs[i];
+        int res = gnrc_netapi_get(dev, NETOPT_DEVICE_TYPE, 0, &dev_type, sizeof(dev_type));
+        if (res <= 0) {
+            dev_type = NETDEV2_TYPE_UNKNOWN;
+        }
+        if ((!gnrc_border_interface) && (dev_type == NETDEV2_TYPE_ETHERNET)) {
+            ipv6_addr_t addr, defroute;
+            gnrc_border_interface = dev;
+
+            ipv6_addr_from_str(&addr, "fe80::2");
+            gnrc_ipv6_netif_add_addr(dev, &addr, 64,
+                                     GNRC_IPV6_NETIF_ADDR_FLAGS_UNICAST);
+
+            ipv6_addr_from_str(&defroute, "::");
+            ipv6_addr_from_str(&addr, "fe80::1");
+            fib_add_entry(&gnrc_ipv6_fib_table, dev, defroute.u8, 16,
+                    0x00, addr.u8, 16, 0,
+                    (uint32_t)FIB_LIFETIME_NO_EXPIRE);
+        }
+        else if ((!gnrc_wireless_interface) && (dev_type == NETDEV2_TYPE_UNKNOWN)) {
+            gnrc_wireless_interface = dev;
+        }
+
+        if (gnrc_border_interface && gnrc_wireless_interface) {
+            break;
+        }
+    }
+
+    LOG_INFO("uhcpc: Using %u as border interface and %u as wireless interface.\n", gnrc_border_interface, gnrc_wireless_interface);
+}
+
+static ipv6_addr_t _prefix;
+
+void uhcp_handle_prefix(uint8_t *prefix, uint8_t prefix_len, uint16_t lifetime, uint8_t *src, uhcp_iface_t iface)
+{
+    eui64_t iid;
+    if (!gnrc_wireless_interface) {
+        LOG_WARNING("uhcpc: uhcp_handle_prefix(): received prefix, but don't know any wireless interface\n");
+        return;
+    }
+
+    if (iface != gnrc_border_interface) {
+        LOG_WARNING("uhcpc: uhcp_handle_prefix(): received prefix from unexpected interface\n");
+        return;
+    }
+
+    if (gnrc_netapi_get(gnrc_wireless_interface, NETOPT_IPV6_IID, 0, &iid,
+                        sizeof(eui64_t)) >= 0) {
+        ipv6_addr_set_aiid((ipv6_addr_t*)prefix, iid.uint8);
+    }
+    else {
+        LOG_WARNING("uhcpc: uhcp_handle_prefix(): cannot get IID of wireless interface\n");
+        return;
+    }
+
+    if (ipv6_addr_equal(&_prefix, (ipv6_addr_t*)prefix)) {
+        LOG_WARNING("uhcpc: uhcp_handle_prefix(): got same prefix again\n");
+        return;
+    }
+
+    gnrc_ipv6_netif_add_addr(gnrc_wireless_interface, (ipv6_addr_t*)prefix, 64,
+                             GNRC_IPV6_NETIF_ADDR_FLAGS_UNICAST |
+                             GNRC_IPV6_NETIF_ADDR_FLAGS_NDP_AUTO);
+
+    gnrc_ipv6_netif_remove_addr(gnrc_wireless_interface, &_prefix);
+    print_str("uhcpc: uhcp_handle_prefix(): configured new prefix ");
+    ipv6_addr_print((ipv6_addr_t*)prefix);
+    puts("/64");
+
+    if (!ipv6_addr_is_unspecified(&_prefix)) {
+        gnrc_ipv6_netif_remove_addr(gnrc_wireless_interface, &_prefix);
+        print_str("uhcpc: uhcp_handle_prefix(): removed old prefix ");
+        ipv6_addr_print(&_prefix);
+        puts("/64");
+    }
+
+    memcpy(&_prefix, prefix, 16);
+}
+
+extern void uhcp_client(uhcp_iface_t iface);
+
+static char _uhcp_client_stack[THREAD_STACKSIZE_DEFAULT + THREAD_EXTRA_STACKSIZE_PRINTF];
+static msg_t _uhcp_msg_queue[4];
+
+static void* uhcp_client_thread(void *arg)
+{
+    msg_init_queue(_uhcp_msg_queue, sizeof(_uhcp_msg_queue)/sizeof(msg_t));
+    uhcp_client(gnrc_border_interface);
+    return NULL;
+}
+
+void auto_init_gnrc_uhcpc(void)
+{
+    set_interface_roles();
+
+    /* initiate uhcp client */
+    thread_create(_uhcp_client_stack, sizeof(_uhcp_client_stack),
+            THREAD_PRIORITY_MAIN - 1, THREAD_CREATE_STACKTEST,
+            uhcp_client_thread, NULL, "uhcp");
+}
-- 
GitLab