diff --git a/samples/net/rpl-node/0001-Added-CoAP-support-for-Sparrow-Border-Router.patch b/samples/net/rpl-node/0001-Added-CoAP-support-for-Sparrow-Border-Router.patch deleted file mode 100644 index 9d2481c68fac9..0000000000000 --- a/samples/net/rpl-node/0001-Added-CoAP-support-for-Sparrow-Border-Router.patch +++ /dev/null @@ -1,272 +0,0 @@ -From cb38f06f84e8d3c1e021265d40a21bc16b5a8a44 Mon Sep 17 00:00:00 2001 -From: Ravi kumar Veeramally -Date: Fri, 9 Jun 2017 13:08:52 +0300 -Subject: [PATCH] Added CoAP support for Sparrow Border Router - -Sparrow border router communicates with only TLV messages. But -there are other implementations which does not support TLV. -Support added with generic CoAP based commands to retrieve -RPL and IPv6 information with CoAP commands. Also supported -LED's control methods. - -Signed-off-by: Ravi kumar Veeramally ---- - examples/sparrow/wsdemoserver.py | 4 +- - examples/sparrow/wspcoap.py | 81 +++++++++++++++++++++++++++ - examples/sparrow/wspnodes.py | 38 +++++++------ - examples/sparrow/www/index.html | 12 ++++ - products/sparrow-border-router/project-conf.h | 3 + - tools/sparrow/deviceserver.py | 1 + - 6 files changed, 120 insertions(+), 19 deletions(-) - create mode 100644 examples/sparrow/wspcoap.py - -diff --git a/examples/sparrow/wsdemoserver.py b/examples/sparrow/wsdemoserver.py -index 8db1db7..67127fa 100755 ---- a/examples/sparrow/wsdemoserver.py -+++ b/examples/sparrow/wsdemoserver.py -@@ -40,7 +40,7 @@ DEBUG = 0 - - import sys, subprocess, thread, string, tlvlib, socket, binascii - from SimpleWebSocketServer import WebSocket, SimpleWebSocketServer --import json, deviceserver, struct, wspserial, wsptlvs, wspnodes -+import json, deviceserver, struct, wspserial, wsptlvs, wspnodes, wspcoap - import httpd - - # Some global vaiables -@@ -304,5 +304,5 @@ if __name__ == "__main__": - print "Starting demo server" - setup_state() - server = SimpleWebSocketServer('', 8001, DemoSocket) -- plugins = plugins + [wspserial.SerialCommands(), wsptlvs.TLVCommands(), wspnodes.NodeCommands()] -+ plugins = plugins + [wspserial.SerialCommands(), wsptlvs.TLVCommands(), wspnodes.NodeCommands(), wspcoap.CoAPCommands()] - server.serveforever() -diff --git a/examples/sparrow/wspcoap.py b/examples/sparrow/wspcoap.py -new file mode 100644 -index 0000000..a1b2899 ---- /dev/null -+++ b/examples/sparrow/wspcoap.py -@@ -0,0 +1,81 @@ -+import wsplugin, thread, subprocess, json, re, os.path -+ -+def coap_is_supported(): -+ return os.path.isfile("/home/rveerama/src/libcoap-develop/examples/coap-client") -+ -+def coap_get(ws, uri): -+ ws.stop = False -+ p=subprocess.Popen(["/home/rveerama/src/libcoap-develop/examples/coap-client", "-m", "get", uri], -+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) -+ data = "" -+ try: -+ while not ws.stop: -+ line = p.stdout.readline() -+ if line == '': break -+ data = data + line -+ except Exception as e: -+ print e -+ print "CoAP Unexpected error:", sys.exc_info()[0] -+ p.terminate() -+ return data -+ -+# This assumes that data is ascii! -+def coap_put(ws, uri, data): -+ ws.stop = False -+ p=subprocess.Popen(["/home/rveerama/src/libcoap-develop/examples/coap-client", "-e" + data, -+ "-m", "put", uri], -+ stdout=subprocess.PIPE, stderr=subprocess.PIPE) -+ line = "" -+ try: -+ while not ws.stop: -+ line = p.stdout.readline() -+ if line == '': break -+ print line -+ except Exception as e: -+ print e -+ print "CoAP Unexpected error:", sys.exc_info()[0] -+ p.terminate() -+ return line -+ -+def coap_check(ws): -+ if not coap_is_supported(): -+ print "Error: can not find libcoap at /home/rveerama/src/libcoap-develop/examples/coap-client" -+ ws.sendMessage(json.dumps({"error":"Can not find libcoap. Please install libcoap in the server and try again."})) -+ return False -+ return True -+ -+# The plugin class -+class CoAPCommands(wsplugin.DemoPlugin): -+ -+ def get_commands(self): -+ return ["coapled", "coaptemp"] -+ -+ def handle_command(self, wsdemo, cmds): -+ if cmds[0] == "coapled": -+ if coap_check(wsdemo): -+ ip = cmds[1] -+ led = cmds[2] -+ on = cmds[3] -+ thread.start_new_thread(coapled, (wsdemo, ip, led, on)) -+ return True -+ elif cmds[0] == "coaptemp": -+ if coap_check(wsdemo): -+ ip = cmds[1] -+ thread.start_new_thread(coaptemp, (wsdemo, ip)) -+ return True -+ return False -+ -+# Toggle a LED on a Yanzi IoT-U10 node (or other node with LEDs) -+def coapled(ws, ip, led, on): -+ coap_put(ws, "coap://[" + ip + "]/led/" + led, on) -+ -+# Read the temperature from a Yanzi IoT-U10 node -+def coaptemp(ws, ip): -+ temperature = coap_get(ws, "coap://[" + ip + "]/temperature") -+ print "\t",temperature -+ # get the temperature from the coap response -+ m = re.search("Temperature .+: (.+)", temperature) -+ if m: -+ ws.sendMessage(json.dumps({"temp":m.group(1),"address":ip})) -+ else: -+ ws.sendMessage(json.dumps({"error":"Failed to fetch temperature via CoAP"})) -diff --git a/examples/sparrow/wspnodes.py b/examples/sparrow/wspnodes.py -index 3f5dc13..abfd1ae 100644 ---- a/examples/sparrow/wspnodes.py -+++ b/examples/sparrow/wspnodes.py -@@ -28,6 +28,7 @@ - # - - import socket, binascii, wsplugin, tlvlib, thread, deviceserver, json, struct, urllib2, time -+import wspcoap - - RANK_DIVISOR = 128.0 - -@@ -102,37 +103,40 @@ class NodeCommands(wsplugin.DemoPlugin): - node = 2 - nodes[endfix] = 1 - for device in devs: -- rpl = device.nstats_rpl -- parent = None -+ rpl = wspcoap.coap_get(ws, "coap://[" + device.address + "]/rpl-info") - if rpl is None: - rank = "unknown" - else: -- rank = rpl.dag_rank() / RANK_DIVISOR -- parent = rpl.parent_as_string() -+ parent1 = rpl.split('\n', 4)[1] -+ rank1 = rpl.split('\n', 4)[2] -+ rank = rank1.split('-', 2)[1], -+ parent = parent1.split('-', 2)[1] - addr = socket.inet_pton(socket.AF_INET6, device.address) - endfix = binascii.hexlify(addr[-4:]) - # First we add the parent as level - second round we will - # change this to "level" instead -- topology["nodes"] = topology["nodes"] + [{"id":node, "label":"N" + str(node), "title":"Rank " + str(rank) + "
" + device.address, -- "level":-1, "parent":parent, "address":endfix}] -+ topology["nodes"] = topology["nodes"] + [{"id":node, "label":"N" + str(node), -+ "title":"Rank " + str(rank) + "
" + device.address, -+ "level":-1, "parent":str(parent), "address":endfix}] - nodes[endfix] = node - node = node + 1 - -- changed = True -- i = 0 - edges = [] -- while changed and i < 10: -- changed = False -+ i = 0 -+ while i < 3: - i = i + 1 - for n in topology["nodes"]: - if n["level"] is -1: -- p = topology["nodes"][nodes[n["parent"]] - 1] -- # add an edge -- edges = edges + [{"from":p["id"],"to":n["id"]}] -- if p["level"] is not -1: -- n["level"] = p["level"] + 1 -- #print "level should be ", p["level"] + 1 -- changed = True -+ parent = socket.inet_pton(socket.AF_INET6, n["parent"]) -+ endfix = binascii.hexlify(parent[-4:]) -+ for k in topology["nodes"]: -+ if k["address"] == endfix: -+ # add an edge -+ edges = edges + [{"from":k["id"],"to":n["id"]}] -+ if k["level"] is not -1: -+ n["level"] = k["level"] + 1 -+ break -+ - topology["edges"] = edges - print "Sending json: ",json.dumps({'topology':topology}) - ws.sendMessage(json.dumps({'topology':topology})) -diff --git a/examples/sparrow/www/index.html b/examples/sparrow/www/index.html -index 685fb64..631f597 100644 ---- a/examples/sparrow/www/index.html -+++ b/examples/sparrow/www/index.html -@@ -292,6 +292,9 @@ function getTypeButtons(type, address) { - ' ' + - ' ' + - ''; -+ } else { -+ buttons = buttons + -+ ' '; - } - return buttons; - } -@@ -305,6 +308,14 @@ function temp_read(address) { - doSend("tlvtemp " + address); - } - -+function coap_led_control(address, led, val) { -+ doSend("coapled " + address + " " + led + " " + val); -+} -+ -+function coap_temp_read(address) { -+ doSend("coaptemp " + address); -+} -+ - function handleEvent(json) { - if (json.event.type == "discovery") { - addr = json.event.address.replace(new RegExp(":", 'g'), "\\:"); -@@ -563,6 +574,7 @@ function updateRSSI(rssi) { -
  • Javascript-surface-plot by Greg Ross (New BSD License) -
  • Vis.js for Network Topology Graphs (Apache 2.0 and MIT License) -
  • SimpleWebSocketServer - Python library by Dave Pallot (MIT License) -+
  • libcoap - a CoAP library by Olaf Bergmann (BSD License) - - - -diff --git a/products/sparrow-border-router/project-conf.h b/products/sparrow-border-router/project-conf.h -index 0921f13..1a8f32e 100644 ---- a/products/sparrow-border-router/project-conf.h -+++ b/products/sparrow-border-router/project-conf.h -@@ -88,6 +88,8 @@ - #undef WEBSERVER_CONF_CFS_CONNS - #define WEBSERVER_CONF_CFS_CONNS 2 - -+#define WEBSERVER 1 -+ - #define CMD_CONF_OUTPUT border_router_cmd_output - #define CMD_CONF_ERROR border_router_cmd_error - -@@ -100,6 +102,7 @@ - /* Configure DAO routes to have a lifetime of 30 x 60 seconds */ - #define RPL_CONF_DEFAULT_LIFETIME_UNIT 60 - #define RPL_CONF_DEFAULT_LIFETIME 30 -+#define RPL_CONF_WITH_DAO_ACK 1 - - #undef NBR_TABLE_CONF_MAX_NEIGHBORS - #define NBR_TABLE_CONF_MAX_NEIGHBORS 1000 -diff --git a/tools/sparrow/deviceserver.py b/tools/sparrow/deviceserver.py -index 754e872..3796ad5 100755 ---- a/tools/sparrow/deviceserver.py -+++ b/tools/sparrow/deviceserver.py -@@ -595,6 +595,7 @@ class DeviceServer: - - p = re.compile(" ([a-fA-F0-9:]+)(/| prefixlen )") - m = p.search(output) -+ return default_host - if m: - return m.group(1) - else: --- -2.11.0 - diff --git a/samples/net/rpl-node/src/main.c b/samples/net/rpl-node/src/main.c deleted file mode 100644 index ef904e3db5ce3..0000000000000 --- a/samples/net/rpl-node/src/main.c +++ /dev/null @@ -1,1158 +0,0 @@ -/* - * Copyright (c) 2017 Intel Corporation - * - * SPDX-License-Identifier: Apache-2.0 - */ - -#if 1 -#define SYS_LOG_DOMAIN "rpl-node" -#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG -#define NET_LOG_ENABLED 1 -#endif - -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include <6lo.h> -#include - -#include - -#include -#include - -#define MY_COAP_PORT 5683 - -#define ALL_NODES_LOCAL_COAP_MCAST \ - { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfd } } } - -#ifndef LED0_GPIO_CONTROLLER -#ifdef LED0_GPIO_PORT -#define LED0_GPIO_CONTROLLER LED0_GPIO_PORT -#else -#define LED0_GPIO_CONTROLLER "(fail)" -#define LED0_GPIO_PIN 0 -#endif -#endif - -#define LED_GPIO_NAME LED0_GPIO_CONTROLLER -#define LED_PIN LED0_GPIO_PIN - -#define RPL_MAX_REPLY 75 - -#define PKT_WAIT_TIME K_SECONDS(1) - -static struct net_context *context; -static struct device *led0; -static const u8_t plain_text_format; - -/* LED */ -static const char led_on[] = "LED0 ON"; -static const char led_off[] = "LED0 OFF"; -static const char led_toggle_on[] = "LED Toggle ON"; -static const char led_toggle_off[] = "LED Toggle OFF"; - -static bool fake_led; - -/* RPL */ -static const char rpl_parent[] = "RPL Parent:"; -static const char rpl_no_parent[] = "No parent yet"; - -static const char rpl_rank[] = "RPL Rank:"; -static const char rpl_no_rank[] = "No rank yet"; - -static const char rpl_link[] = "Link Metric:"; -static const char rpl_no_link[] = "No link metric yet"; - -static const char ipv6_no_nbr[] = "No IPv6 Neighbors"; - -static void get_from_ip_addr(struct coap_packet *cpkt, - struct sockaddr_in6 *from) -{ - struct net_udp_hdr hdr, *udp_hdr; - - udp_hdr = net_udp_get_hdr(cpkt->pkt, &hdr); - if (!udp_hdr) { - return; - } - - net_ipaddr_copy(&from->sin6_addr, &NET_IPV6_HDR(cpkt->pkt)->src); - from->sin6_port = udp_hdr->src_port; - from->sin6_family = AF_INET6; -} - -static void send_error_response(struct coap_resource *resource, - struct coap_packet *request, - struct sockaddr_in6 *from) -{ - struct net_context *context; - struct coap_packet response; - struct net_pkt *pkt; - struct net_buf *frag; - u16_t id; - int r; - - id = coap_header_get_id(request); - context = net_pkt_context(request->pkt); - - pkt = net_pkt_get_tx(context, PKT_WAIT_TIME); - if (!pkt) { - return; - } - - frag = net_pkt_get_data(context, PKT_WAIT_TIME); - if (!frag) { - net_pkt_unref(pkt); - return; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - 0, NULL, COAP_RESPONSE_CODE_BAD_REQUEST, id); - if (r < 0) { - net_pkt_unref(pkt); - return; - } - - r = net_context_sendto(pkt, (const struct sockaddr *)from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } -} - -static int well_known_core_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct coap_packet response; - struct sockaddr_in6 from; - struct net_pkt *pkt; - struct net_buf *frag; - int r; - - NET_DBG(""); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_well_known_core_get(resource, request, &response, pkt); - if (r < 0) { - net_pkt_unref(response.pkt); - send_error_response(resource, request, &from); - - return r; - } - - get_from_ip_addr(request, &from); - - r = net_context_sendto(response.pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(response.pkt); - } - - return r; -} - -static bool read_led(void) -{ - u32_t led = 0; - int r; - - if (!led0) { - return fake_led; - } - - r = gpio_pin_read(led0, LED_PIN, &led); - if (r < 0) { - return false; - } - - return led; -} - -static void write_led(bool led) -{ - if (!led0) { - fake_led = led; - return; - } - - gpio_pin_write(led0, LED_PIN, led); -} - -static int led_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - const char *str; - u16_t len, id; - int r; - - NET_DBG(""); - - id = coap_header_get_id(request); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - 0, NULL, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - if (read_led()) { - str = led_on; - len = sizeof(led_on); - } else { - str = led_off; - len = sizeof(led_off); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -static int led_post(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - const char *str; - u16_t offset; - u16_t len; - u16_t id; - u32_t led; - u8_t payload; - int r; - - NET_DBG(""); - - led = 0; - - frag = net_frag_skip(request->frag, request->offset, &offset, - request->hdr_len + request->opt_len); - if (!frag && offset == 0xffff) { - return -EINVAL; - } - - frag = net_frag_read_u8(frag, offset, &offset, &payload); - if (!frag && offset == 0xffff) { - printk("packet without payload, so toggle the led"); - - led = read_led(); - led = !led; - } else { - if (payload == 0x31) { - led = 1; - } - } - - write_led(led); - - id = coap_header_get_id(request); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - 0, NULL, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - if (led) { - str = led_toggle_on; - len = sizeof(led_toggle_on); - } else { - str = led_toggle_off; - len = sizeof(led_toggle_off); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -static int led_put(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - const char *str; - u16_t offset; - u16_t len; - u16_t id; - u32_t led; - u8_t payload; - int r; - - NET_DBG(""); - - led = 0; - - frag = net_frag_skip(request->frag, request->offset, &offset, - request->hdr_len + request->opt_len); - if (!frag && offset == 0xffff) { - return -EINVAL; - } - - frag = net_frag_read_u8(frag, offset, &offset, &payload); - if (!frag && offset == 0xffff) { - printk("packet without payload, so toggle the led"); - - led = read_led(); - led = !led; - } else { - if (payload == 0x31) { - led = 1; - } - } - - write_led(led); - - id = coap_header_get_id(request); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - 0, NULL, COAP_RESPONSE_CODE_CHANGED, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - if (led) { - str = led_on; - len = sizeof(led_on); - } else { - str = led_off; - len = sizeof(led_off); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -struct ipv6_nbr_str { - char str[(sizeof("xx:xx:xx:xx:xx:xx:xx:xx") * - CONFIG_NET_IPV6_MAX_NEIGHBORS) + - CONFIG_NET_IPV6_MAX_NEIGHBORS]; - u8_t len; -}; - -static void ipv6_nbr_cb(struct net_nbr *nbr, void *user_data) -{ - struct ipv6_nbr_str *nbr_str = user_data; - char temp[sizeof("xx:xx:xx:xx:xx:xx:xx:xx") + sizeof("\n")]; - - snprintk(temp, sizeof(temp), "%s\n", - net_sprint_ipv6_addr(&net_ipv6_nbr_data(nbr)->addr)); - - memcpy(nbr_str->str + nbr_str->len, temp, strlen(temp)); - nbr_str->len += strlen(temp); -} - -/* IPv6 Neighbors */ -static int ipv6_neighbors_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - struct ipv6_nbr_str nbr_str; - u8_t token[8]; - const char *str; - u16_t len, id; - u8_t tkl; - int r; - - NET_DBG(""); - - id = coap_header_get_id(request); - tkl = coap_header_get_token(request, (u8_t *)token); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - tkl, token, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_option(&response, COAP_OPTION_CONTENT_FORMAT, - &plain_text_format, - sizeof(plain_text_format)); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - nbr_str.len = 0; - net_ipv6_nbr_foreach(ipv6_nbr_cb, &nbr_str); - - if (nbr_str.len) { - str = nbr_str.str; - len = nbr_str.len; - } else { - str = ipv6_no_nbr; - len = strlen(ipv6_no_nbr); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -/* RPL Information */ -static int rpl_info_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_rpl_instance *rpl; - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - struct in6_addr *parent; - struct net_nbr *nbr; - const char *str; - u8_t token[8]; - u16_t len, id; - u16_t out_len; - u8_t tkl; - int r; - char out[RPL_MAX_REPLY]; - - NET_DBG(""); - - id = coap_header_get_id(request); - tkl = coap_header_get_token(request, (u8_t *)token); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - tkl, token, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_option(&response, COAP_OPTION_CONTENT_FORMAT, - &plain_text_format, - sizeof(plain_text_format)); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - rpl = net_rpl_get_default_instance(); - if (rpl && rpl->current_dag && rpl->current_dag->preferred_parent) { - nbr = net_rpl_get_nbr(rpl->current_dag->preferred_parent); - } else { - nbr = NULL; - } - - /* Write all RPL info in JSON format */ - snprintk(out, sizeof(out) - 1, "parent-"); - out_len = strlen(out); - - if (!rpl || !rpl->current_dag || !rpl->current_dag->preferred_parent) { - snprintk(&out[out_len], sizeof(out) - out_len - 1, "None"); - } else { - parent = net_rpl_get_parent_addr(net_pkt_iface(pkt), - rpl->current_dag->preferred_parent); - snprintk(&out[out_len], sizeof(out) - out_len - 1, "%s", - net_sprint_ipv6_addr(parent)); - } - - out_len = strlen(out); - - snprintk(&out[out_len], sizeof(out) - out_len - 1, "\nrank-"); - out_len = strlen(out); - - if (!rpl || !rpl->current_dag) { - snprintk(&out[out_len], sizeof(out) - out_len - 1, "inf"); - } else { - snprintk(&out[out_len], sizeof(out) - out_len - 1, "%u.%02u", - rpl->current_dag->rank / NET_RPL_MC_ETX_DIVISOR, - (100 * (rpl->current_dag->rank % - NET_RPL_MC_ETX_DIVISOR)) / - NET_RPL_MC_ETX_DIVISOR); - } - - out_len = strlen(out); - - snprintk(&out[out_len], sizeof(out) - out_len - 1, "\nlinkmetric-"); - out_len = strlen(out); - - if (!nbr) { - snprintk(&out[out_len], sizeof(out) - out_len - 1, "inf"); - } else { - snprintk(&out[out_len], sizeof(out) - out_len - 1, "%u.%02u", - (net_ipv6_nbr_data(nbr)->link_metric) / - NET_RPL_MC_ETX_DIVISOR, - (100 * ((net_ipv6_nbr_data(nbr)->link_metric) % - NET_RPL_MC_ETX_DIVISOR)) / - NET_RPL_MC_ETX_DIVISOR); - } - - out_len = strlen(out); - - str = out; - len = out_len; - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -/* RPL Parent */ -static int rpl_parent_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_rpl_instance *rpl; - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - struct in6_addr *parent; - const char *str; - u8_t token[8]; - u16_t len, id; - u8_t tkl; - int r; - char out[RPL_MAX_REPLY]; - - NET_DBG(""); - - id = coap_header_get_id(request); - tkl = coap_header_get_token(request, (u8_t *)token); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - tkl, token, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_option(&response, COAP_OPTION_CONTENT_FORMAT, - &plain_text_format, - sizeof(plain_text_format)); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - rpl = net_rpl_get_default_instance(); - if (!rpl || !rpl->current_dag || !rpl->current_dag->preferred_parent) { - str = rpl_no_parent; - len = sizeof(rpl_no_parent); - - } else { - parent = net_rpl_get_parent_addr(net_pkt_iface(pkt), - rpl->current_dag->preferred_parent); - snprintk(out, sizeof(out), "%s %s", rpl_parent, - net_sprint_ipv6_addr(parent)); - str = out; - len = strlen(out); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -/* RPL Rank */ -static int rpl_rank_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_rpl_instance *rpl; - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - const char *str; - u8_t token[8]; - u16_t len, id; - u8_t tkl; - int r; - char out[RPL_MAX_REPLY]; - - NET_DBG(""); - - id = coap_header_get_id(request); - tkl = coap_header_get_token(request, (u8_t *)token); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - tkl, token, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_option(&response, COAP_OPTION_CONTENT_FORMAT, - &plain_text_format, - sizeof(plain_text_format)); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - rpl = net_rpl_get_default_instance(); - if (!rpl || !rpl->current_dag) { - str = rpl_no_rank; - len = sizeof(rpl_no_rank); - - } else { - snprintk(out, sizeof(out), "%s %u.%02u", rpl_rank, - rpl->current_dag->rank / NET_RPL_MC_ETX_DIVISOR, - (100 * (rpl->current_dag->rank % - NET_RPL_MC_ETX_DIVISOR)) / - NET_RPL_MC_ETX_DIVISOR); - str = out; - len = strlen(out); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -/* RPL Link Metric */ -static int rpl_link_metric_get(struct coap_resource *resource, - struct coap_packet *request) -{ - struct net_rpl_instance *rpl; - struct net_pkt *pkt; - struct net_buf *frag; - struct sockaddr_in6 from; - struct coap_packet response; - struct net_nbr *nbr; - const char *str; - u8_t token[8]; - u16_t len, id; - u8_t tkl; - int r; - char out[RPL_MAX_REPLY]; - - NET_DBG(""); - - id = coap_header_get_id(request); - tkl = coap_header_get_token(request, (u8_t *)token); - - pkt = net_pkt_get_tx(context, K_FOREVER); - if (!pkt) { - return -ENOMEM; - } - - frag = net_pkt_get_data(context, K_FOREVER); - if (!frag) { - net_pkt_unref(pkt); - return -ENOMEM; - } - - net_pkt_frag_add(pkt, frag); - - r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, - tkl, token, COAP_RESPONSE_CODE_CONTENT, id); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_option(&response, COAP_OPTION_CONTENT_FORMAT, - &plain_text_format, - sizeof(plain_text_format)); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - rpl = net_rpl_get_default_instance(); - if (rpl && rpl->current_dag && - rpl->current_dag->preferred_parent) { - nbr = net_rpl_get_nbr(rpl->current_dag->preferred_parent); - if (nbr) { - snprintk(out, sizeof(out), "%s %u.%02u", rpl_link, - (net_ipv6_nbr_data(nbr)->link_metric) / - NET_RPL_MC_ETX_DIVISOR, - (100 * ((net_ipv6_nbr_data(nbr)->link_metric) % - NET_RPL_MC_ETX_DIVISOR)) / - NET_RPL_MC_ETX_DIVISOR); - - str = out; - len = strlen(out); - } else { - str = rpl_no_link; - len = sizeof(rpl_no_link); - } - } else { - str = rpl_no_link; - len = sizeof(rpl_no_link); - } - - r = coap_packet_append_payload_marker(&response); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - r = coap_packet_append_payload(&response, (u8_t *)str, len); - if (r < 0) { - net_pkt_unref(pkt); - return -EINVAL; - } - - get_from_ip_addr(request, &from); - r = net_context_sendto(pkt, (const struct sockaddr *)&from, - sizeof(struct sockaddr_in6), - NULL, 0, NULL, NULL); - if (r < 0) { - net_pkt_unref(pkt); - } - - return r; -} - -static const char * const led_default_path[] = { "led", "0", NULL }; -static const char * const led_default_attributes[] = { - "title=\"LED0: led=toggle ?len=0..\"", - "rt=\"Text\"", - NULL }; - -static const char * const ipv6_neighbors_default_path[] = { "ipv6", - "neighbors", - NULL }; -static const char * const ipv6_neighbors_default_attributes[] = { - "title=\"IPv6 Neighbors\"", - "rt=Text", - NULL }; - -static const char * const rpl_info_default_path[] = { "rpl-info", NULL }; -static const char * const rpl_info_default_attributes[] = { - "title=\"RPL Information\"", - "rt=Text", - NULL }; - -static const char * const rpl_parent_default_path[] = { "rpl-info", - "parent", - NULL }; -static const char * const rpl_parent_default_attributes[] = { - "title=\"RPL Parent\"", - "rt=Text", - NULL }; - -static const char * const rpl_rank_default_path[] = { "rpl-info", - "rank", - NULL }; -static const char * const rpl_rank_default_attributes[] = { - "title=\"RPL Rank\"", - "rt=Text", - NULL }; - -static const char * const rpl_link_default_path[] = { "rpl-info", - "link-metric", - NULL }; -static const char * const rpl_link_default_attributes[] = { - "title=\"RPL Link Metric\"", - "rt=Text", - NULL }; - -static struct coap_resource resources[] = { - { .get = well_known_core_get, - .post = NULL, - .put = NULL, - .path = COAP_WELL_KNOWN_CORE_PATH, - .user_data = NULL, - }, - { .get = led_get, - .post = led_post, - .put = led_put, - .path = led_default_path, - .user_data = &((struct coap_core_metadata) { - .attributes = led_default_attributes, - }), - }, - { .get = ipv6_neighbors_get, - .post = NULL, - .put = NULL, - .path = ipv6_neighbors_default_path, - .user_data = &((struct coap_core_metadata) { - .attributes = ipv6_neighbors_default_attributes, - }), - }, - { .get = rpl_info_get, - .post = NULL, - .put = NULL, - .path = rpl_info_default_path, - .user_data = &((struct coap_core_metadata) { - .attributes = rpl_info_default_attributes, - }), - }, - { .get = rpl_parent_get, - .post = NULL, - .put = NULL, - .path = rpl_parent_default_path, - .user_data = &((struct coap_core_metadata) { - .attributes = rpl_parent_default_attributes, - }), - }, - { .get = rpl_rank_get, - .post = NULL, - .put = NULL, - .path = rpl_rank_default_path, - .user_data = &((struct coap_core_metadata) { - .attributes = rpl_rank_default_attributes, - }), - }, - { .get = rpl_link_metric_get, - .post = NULL, - .put = NULL, - .path = rpl_link_default_path, - .user_data = &((struct coap_core_metadata) { - .attributes = rpl_link_default_attributes, - }), - }, - { }, -}; - -static void udp_receive(struct net_context *context, - struct net_pkt *pkt, - int status, - void *user_data) -{ - struct coap_packet request; - struct coap_option options[16] = { 0 }; - u8_t opt_num = 16; - int r; - - r = coap_packet_parse(&request, pkt, options, opt_num); - if (r < 0) { - NET_ERR("Invalid data received (%d)\n", r); - goto end; - } - - r = coap_handle_request(&request, resources, options, opt_num); - if (r < 0) { - NET_ERR("No handler for such request (%d)\n", r); - } - -end: - net_pkt_unref(pkt); -} - -static bool join_coap_multicast_group(void) -{ - static struct sockaddr_in6 mcast_addr = { - .sin6_family = AF_INET6, - .sin6_addr = ALL_NODES_LOCAL_COAP_MCAST, - .sin6_port = htons(MY_COAP_PORT) }; - struct net_if_mcast_addr *mcast; - struct net_if *iface; - - iface = net_if_get_default(); - if (!iface) { - NET_ERR("Could not get te default interface\n"); - return false; - } - - mcast = net_if_ipv6_maddr_add(iface, &mcast_addr.sin6_addr); - if (!mcast) { - NET_ERR("Could not add multicast address to interface\n"); - return false; - } - - return true; -} - -static void init_app(void) -{ - static struct sockaddr_in6 any_addr = { - .sin6_family = AF_INET6, - .sin6_addr = IN6ADDR_ANY_INIT, - .sin6_port = htons(MY_COAP_PORT) }; - int r; - - led0 = device_get_binding(LED_GPIO_NAME); - if (led0) { - gpio_pin_configure(led0, LED_PIN, GPIO_DIR_OUT); - } else { - NET_WARN("Failed to bind '%s'" - "fake_led will provide dummpy replies", - LED_GPIO_NAME); - } - - if (!join_coap_multicast_group()) { - NET_ERR("Could not join CoAP multicast group"); - return; - } - - r = net_context_get(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &context); - if (r) { - NET_ERR("Could not get an UDP context"); - return; - } - - r = net_context_bind(context, (struct sockaddr *) &any_addr, - sizeof(any_addr)); - if (r) { - NET_ERR("Could not bind the context"); - return; - } - - r = net_context_recv(context, udp_receive, 0, NULL); - if (r) { - NET_ERR("Could not receive in the context"); - } -} - -void main(void) -{ - NET_DBG("Start Demo"); - - init_app(); -} diff --git a/samples/net/rpl_border_router/CMakeLists.txt b/samples/net/rpl_border_router/CMakeLists.txt new file mode 100644 index 0000000000000..35d6446eae582 --- /dev/null +++ b/samples/net/rpl_border_router/CMakeLists.txt @@ -0,0 +1,39 @@ +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(NONE) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +include($ENV{ZEPHYR_BASE}/samples/net/common/common.cmake) + +set(gen_dir ${ZEPHYR_BINARY_DIR}/include/generated/) + +# List of files that are used to generate .h file that can be included +# into .c file. +foreach(inc_file + echo-apps-cert.der + echo-apps-key.der + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + ) +endforeach() + +foreach(inc_file + index.html + style.css + favicon.ico + br.js + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.gz.inc + --gzip + ) +endforeach() + +target_link_libraries_ifdef(CONFIG_MBEDTLS app mbedTLS) +target_include_directories(app PRIVATE $ENV{ZEPHYR_BASE}/subsys/net/ip) diff --git a/samples/net/rpl_border_router/README.rst b/samples/net/rpl_border_router/README.rst new file mode 100644 index 0000000000000..4a83f5cb75e12 --- /dev/null +++ b/samples/net/rpl_border_router/README.rst @@ -0,0 +1,164 @@ +.. _rpl-border-router-sample: + +RPL Border Router +################# + +Overview +******** + +The RPL border router sample application for Zephyr provides a HTTP(S) server +and net shell for management purposes. Typically border router would be used to +connect to IEEE 802.15.4 network but Bluetooth IPSP network functionality is +also possible. + +The source code for this sample application can be found at: +:file:`samples/net/rpl_border_router`. + +Requirements +************ + +- Real device like Freedom Board (FRDM-K64F) with MCR20A IEEE 802.15.4 support. +- Linux machine with web browser and the screen terminal emulator (optional). +- Ethernet access for management purposes (optional). + +Note that there is no support for running an RPL border router in QEMU, as the +border router requires access to a real radio network technology such as +IEEE 802.15.4, which is not available in QEMU. + +For testing purposes you can compile the RPL border router for QEMU and do some +testing with the web UI. But with QEMU, it is not possible to connect to RPL +network and get information about the RPL nodes. + +Building and Running +******************** + +By default, the integrated HTTP server is configured to listen at port 80. +If you want to modify the HTTP server config options, please check +the configuration settings in :file:`samples/net/rpl_border_router/src/main.c` +file and also in the :file:`samples/net/rpl_border_router/src/config.h` file. + +This sample code supports both static and dynamic (DHCPv4) IP addresses that +can be defined in the project configuration file: + +.. code-block:: console + + CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" + CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +Note that the IPv4 address is only used for connection to the integrated web +server that provides an admin web page for management purposes. The web server +can also be connected using IPv6 address. If you do not have a web management +network interface for your host computer, then IPv4 support can be disabled +in the configuration file by setting :option:`CONFIG_NET_IPV4` to "n". +The RPL router uses only IPv6 when routing the network traffic. + +Note that the default project configuration file +:file:`samples/net/rpl_border_router/prj.conf` does not currently provide +a working system as there are no boards that would provide suitable network +interface support. The prj.conf file is provided only to compile test the +border router sample application. + +It is possible to use the border router application with these boards and +add-on cards: + +* `FRDM-K64F with Freescale CR20A card `_ + +You can build the application like this for CR20A: + +.. zephyr-app-commands:: + :zephyr-app: samples/net/rpl_border_router + :board: frdm_k64f + :conf: prj_frdm_k64f_mcr20a.conf + :goals: build flash + :compact: + +By default, the RPL border router application enables net shell support and +provides some useful commands for debugging and viewing the network status. + +The **br repair** command will cause the RPL network to re-configure itself. + +.. code-block:: console + + shell> br repair + [rpl-br/shell] [INF] br_repair: Starting global repair... + +The **net rpl** command first prints out static compile time configuration +settings. Then it prints information about runtime configuration of the system. + +.. code-block:: console + + shell> net rpl + RPL Configuration + ================= + RPL mode : mesh + Used objective function : MRHOF + Used routing metric : none + Mode of operation (MOP) : Storing, no mcast (MOP2) + Send probes to nodes : disabled + Max instances : 1 + Max DAG / instance : 2 + Min hop rank increment : 256 + Initial link metric : 2 + RPL preference value : 0 + DAG grounded by default : no + Default instance id : 30 (0x1e) + Insert Hop-by-hop option : yes + Specify DAG when sending DAO : yes + DIO min interval : 12 (4096 ms) + DIO doublings interval : 8 + DIO redundancy value : 10 + DAO sending timer value : 4 sec + DAO max retransmissions : 4 + Node expecting DAO ack : yes + Send DIS periodically : yes + DIS interval : 60 sec + Default route lifetime unit : 65535 sec + Default route lifetime : 255 + + Runtime status + ============== + Default instance (id 30) : 0xa80081e0 (active) + Instance DAGs : + [ 1]* fde3:2cda:3eea:4d14::1 prefix fde3:2cda:3eea:4d14::/64 rank 256/65535 ver 255 flags GJ parent 0x00000000 + + No parents found. + +The **net nbr** command prints information about currently found IPv6 neighbor +nodes. In this example there are two leaf nodes that are part of this RPL +network. + +.. code-block:: console + + shell> net nbr + Neighbor Flags Interface State Remain Link Address + [ 1] 0xa80065e0 1/0/1/0 0xa8007140 reachable 2920 00:12:4B:00:00:00:00:01 fe80::212:4b00:0:1 + [ 2] 0xa8006660 1/0/1/0 0xa8007140 stale 0 00:12:4B:00:00:00:00:03 fe80::212:4b00:0:3 + +The **nbr route** command prints information about currently found IPv6 routes. +In this example all the nodes are directly connected to this RPL border router +root node. + +.. code-block:: console + + shell> net route + IPv6 routes for interface 0xa8007140 + ==================================== + IPv6 prefix : fde3:2cda:3eea:4d14::212:4b00:0:3/128 + neighbor : 0xa80065e0 + link addr : 00:12:4B:00:00:00:00:03 + IPv6 prefix : fde3:2cda:3eea:4d14::212:4b00:0:1/128 + neighbor : 0xa8006660 + link addr : 00:12:4B:00:00:00:00:01 + +The IEEE 802.15.4 shell support is enabled by default, so the **ieee15_4** +command can be used to change the IEEE 802.15.4 network parameters such as +used channel or PAN id, if needed. + +.. code-block:: console + + shell> ieee15_4 set_chan 15 + Channel 15 set + +The border router sample application provides integrated HTTP(S) server. +Currently the admin support is very rudimentary but you can try it by connecting +to http://192.0.2.1 or http://[2001:db8::1] using web browser. diff --git a/samples/net/rpl_border_router/prj_frdm_k64f_mcr20a.conf b/samples/net/rpl_border_router/prj_frdm_k64f_mcr20a.conf new file mode 100644 index 0000000000000..7410ad6ff3f4b --- /dev/null +++ b/samples/net/rpl_border_router/prj_frdm_k64f_mcr20a.conf @@ -0,0 +1,136 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_TCP=y +CONFIG_NET_UDP=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_NET_SHELL=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_MAX_CONTEXTS=16 +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +CONFIG_NET_DHCPV4=n + +CONFIG_NET_RX_STACK_SIZE=2048 + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=80 +CONFIG_NET_PKT_TX_COUNT=80 +CONFIG_NET_BUF_RX_COUNT=80 +CONFIG_NET_BUF_TX_COUNT=80 + +# Number of IPv6 Network Interfaces +CONFIG_NET_IF_MAX_IPV6_COUNT=2 + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5 + +# IPv6 neighbors/routes count. This determines how many nodes +# our RPL network can have. +CONFIG_NET_IPV6_MAX_NEIGHBORS=25 + +# RPL and routing options +CONFIG_NET_RPL=y +CONFIG_NET_ROUTE=y +CONFIG_NET_ROUTING=y +CONFIG_NET_RPL_L2_IEEE802154=y +# This is used as a prefix when creating DAG. You can pick your own +# prefix here, better not to use this exact value. +CONFIG_NET_RPL_PREFIX="fde3:2cda:3eea:4d14::1/64" + +# HTTP(S) admin and websocket interface support +CONFIG_HTTP=y +CONFIG_HTTP_SERVER=y +CONFIG_HTTP_SERVER_NUM_URLS=8 +CONFIG_WEBSOCKET=y +CONFIG_WEBSOCKET_CONSOLE=n + +# Allow two concurrent incoming connections +CONFIG_NET_TCP_BACKLOG_SIZE=1 +CONFIG_NET_APP_SERVER_NUM_CONN=2 + +# Crypto support +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" +CONFIG_MBEDTLS_ENABLE_HEAP=y +CONFIG_MBEDTLS_HEAP_SIZE=12000 + +# Logging +CONFIG_NET_LOG=y +CONFIG_INIT_STACKS=y +CONFIG_PRINTK=y +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=4 +#CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL=2 + +# Debugging +CONFIG_NET_DEBUG_NET_PKT=n +CONFIG_NET_DEBUG_WEBSOCKET=n +CONFIG_NET_DEBUG_RPL=n +CONFIG_NET_DEBUG_ROUTE=n +CONFIG_NET_DEBUG_APP=n +CONFIG_NET_DEBUG_IPV6=n +CONFIG_NET_DEBUG_L2_ETHERNET=n +CONFIG_NET_DEBUG_IPV6_NBR_CACHE=n +CONFIG_NET_DEBUG_CONTEXT=n +CONFIG_NET_DEBUG_TCP=n +CONFIG_NET_DEBUG_CORE=n +CONFIG_NET_DEBUG_CONN=n +CONFIG_NET_DEBUG_IF=n +CONFIG_NET_DEBUG_HTTP=n +CONFIG_MBEDTLS_DEBUG=n + +# Network application settings +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +# This is the ethernet interface setting for admin purposes +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +# Set a proper hostname for the router +CONFIG_NET_HOSTNAME_ENABLE=y + +# mDNS support is activated. After this one can connect to this device +# by zephyr.local name +CONFIG_MDNS_RESPONDER=y + +# IPv6 compression is needed for IEEE 802.15.4 +CONFIG_NET_6LO=y +CONFIG_NET_6LO_CONTEXT=n + +#COAP +CONFIG_COAP=y + +#MGMT +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y +CONFIG_NET_MGMT_EVENT_INFO=y +CONFIG_NET_MGMT_EVENT_STACK_SIZE=2048 +CONFIG_NET_MGMT_EVENT_QUEUE_SIZE=20 + +#JSON +CONFIG_JSON_LIBRARY=y + +CONFIG_NET_DEFAULT_IF_ETHERNET=y + +# IEEE 802.15.4 options +CONFIG_NET_L2_IEEE802154_SHELL=y +CONFIG_NET_L2_IEEE802154=y +CONFIG_NET_L2_IEEE802154_FRAGMENT=y +CONFIG_NET_APP_IEEE802154_CHANNEL=18 + +# IEEE MCR20A 802.15.4 options +CONFIG_NET_APP_IEEE802154_DEV_NAME="mcr20a" +CONFIG_IEEE802154_MCR20A=y +CONFIG_SPI=y + +#For TLS support enable options below +# HTTP(S) admin and websocket interface support +#CONFIG_HTTPS=y +# Firefox sends lot of fields to this needs to be quite large +#CONFIG_HTTP_HEADERS=20 +#CONFIG_MBEDTLS_HEAP_SIZE=28500 diff --git a/samples/net/rpl_border_router/sample.yaml b/samples/net/rpl_border_router/sample.yaml new file mode 100644 index 0000000000000..657931eb7447f --- /dev/null +++ b/samples/net/rpl_border_router/sample.yaml @@ -0,0 +1,12 @@ +common: + harness: net + tags: net rpl ieee802154 ethernet + platform_whitelist: frdm_k64f +sample: + description: Can be used to form RPL network. Needs + to be run in real device. This cannot be run in + QEMU. + name: RPL Border Router demo application +tests: + test_frdm_k64f_mcr20a: + extra_args: CONF_FILE="prj_frdm_k64f_mcr20a.conf" diff --git a/samples/net/rpl_border_router/src/br-info.schema.json.py b/samples/net/rpl_border_router/src/br-info.schema.json.py new file mode 100755 index 0000000000000..cb4361950273b --- /dev/null +++ b/samples/net/rpl_border_router/src/br-info.schema.json.py @@ -0,0 +1,353 @@ +#!/usr/bin/python3 + +from jsonschema import validate + +schema = { + "$schema": "http://json-schema.org/schema#", + "description": "Schema for border router information", + "title": "Border Router Information", + "type": "object", + "properties": { + "information": { + "type": "object", + "oneOf": [ + { "$ref": "#/definitions/interface_configuration" }, + { "$ref": "#/definitions/rpl_configuration" }, + { "$ref": "#/definitions/neighbors" }, + { "$ref": "#/definitions/routes" }, + { "$ref": "#/definitions/topology" } + ] + } + }, + + "definitions": { + "interface_configuration": { + "title": "Network Interface Configuration options", + "type": "array", + "items": { + "type": "object", + "items" : { + "type": "array", + "values": { + "oneOf": [ + { "$ref": "#/unicast/ipv6" }, + { "$ref": "#/unicast/ipv4" }, + { "$ref": "#/multicast/mcast_ipv6" }, + { "$ref": "#/multicast/mcast_ipv4" } + ] + } + } + } + }, + + "rpl_configuration": { + "title": "RPL Configuration options", + "type": "array", + "items": { + "type": "object", + "items" : { + "RPL mode" : "string", + "Objective function" : "string", + "Routing metric" : "string", + "Mode of operation" : "string", + "Send probes to nodes" : "string", + "Max instances" : "string", + "Max DAG / instance" : "string", + "Min hop rank increment" : "string", + "Initial link metric" : "string", + "RPL preference value" : "string", + "DAG grounded by default" : "string", + "Default instance id" : "string", + "Insert hop-by-hop option" : "string", + "Specify DAG when sending DAO" : "string", + "DIO min interval" : "string", + "DIO doublings interval" : "string", + "DIO redundancy value" : "string", + "DAO send timer" : "string", + "DAO max retransmissions" : "string", + "DAO ack expected" : "string", + "DIS send periodically" : "string", + "DIS interval" : "string", + "Default route lifetime unit" : "string", + "Default route lifetime" : "string", + "Multicast MOP3 route lifetime" : "string" + } + } + }, + + "neighbors": { + "title": "Neighbor information", + "type": "array", + "items": { + "type": "object", + "items" : { + "Operation" : "string", + "IPv6 address": "string", + "Link address": "string", + "Is router": "string", + "Link metric": "string", + } + } + }, + + "routes": { + "title": "Network routes", + "type": "array", + "items": { + "type": "object", + "items" : { + "Operation" : "string", + "IPv6 prefix": "string", + "IPv6 address": "string", + "Link address": "string" + } + } + }, + + "topology": { + "type": "object", + "properties": { + "nodes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "integer" + }, + "label": { + "type": "string" + }, + "title": { + "type": "string" + } + } + } + }, + "edges": { + "type": "array", + "items": { + "type": "object", + "properties": { + "from": { + "type": "integer" + }, + "to": { + "type": "integer" + } + } + } + } + } + } + }, + + "unicast": { + "ipv6": { + "title": "IPv6 addresses", + "type": "array", + "items": { + "state": "string", + "type": "string", + "lifetime": "string" + } + }, + + "ipv4": { + "title": "IPv4 addresses", + "type": "array", + "items": { + "state": "string", + "type": "string", + "lifetime": "string" + } + } + }, + + "multicast": { + "mcast_ipv6": { + "title": "IPv6 addresses", + "type": "array", + "items": { + "address": "string" + } + }, + + "mcast_ipv4": { + "title": "IPv4 addresses", + "type": "array", + "items": { + "value": "string" + } + } + } +} + +validate( + { + "interface_configuration": [ + { + "0x20009000": [ + { + "Type" : "Ethernet" + }, + { + "Link address" : "00:04:9F:2A:00:01" + }, + { + "MTU" : "1500" + }, + { + "IPv6" : [ + { + "fe80::204:9fff:fe2a:1" : { + "state": "preferred", + "type": "autoconf", + "lifetime": "infinite" + } + }, + { + "2001:db8::1" : { + "state": "preferred", + "type": "manual", + "lifetime": "infinite" + } + }, + ] + }, + { + "IPv4" : [ + { + "192.0.2.1" : { + "state": "preferred", + "type": "autoconf", + "lifetime": "infinite" + } + } + ] + }, + { + "IPv6 multicast" : + [ + "ff02::1", + "ff02::1:ff2a:1", + "ff02::1:ff00:1" + ] + }, + { + "IPv4 multicast" : + [ + "224.0.0.251" + ] + }, + { + "IPv4 gateway" : "0.0.0.0" + }, + { + "IPv4 netmask" : "255.255.255.0" + }, + ] + }, + { + "0x20009a30": [ + { "Type" : "IEEE 802.15.4" } + ] + } + ], + + "neighbors": [ + { + "0x20009000": [ + { + "IPv6 address": "2001:db8::1", + "Link address": "00:11:22:33:44:55:66:77", + "Is router": "true", + "Link metric": "0" + }, + { + "IPv6 address": "2001:db8::2", + "Link address": "77:11:22:33:44:55:66:00", + "Is router": "false", + "Link metric": "1" + }, + { + "Operation" : "delete", + "IPv6 address": "2001:db8::3" + } + ] + }, + { + "0x20009a30": [] + } + ], + + "rpl_configuration": [ + { "RPL mode" : "mesh" }, + { "Objective function" : "MRHOF" }, + { "Routing metric" : "none" }, + { "Mode of operation" : "MOP2" }, + { "Send probes to nodes" : "false" }, + { "Max instances" : "1" }, + { "Max DAG / instance" : "2" }, + { "Min hop rank increment" : "256" }, + { "Initial link metric" : "2" }, + { "RPL preference value" : "0" }, + { "DAG grounded by default" : "false" }, + { "Default instance id" : "42" }, + { "Insert hop-by-hop option" : "true" }, + { "Specify DAG when sending DAO" : "true" }, + { "DIO min interval" : "12" }, + { "DIO doublings interval" : "8" }, + { "DIO redundancy value" : "10" }, + { "DAO send timer" : "4" }, + { "DAO max retransmissions" : "4" }, + { "DAO ack expected" : "true" }, + { "DIS send periodically" : "true" }, + { "DIS interval" : "60" }, + { "Default route lifetime unit" : "65535" }, + { "Default route lifetime" : "255" }, + { "Multicast MOP3 route lifetime" : "0" } + ], + + "routes": [ + { + "0x20009000": [ + { + "IPv6 prefix" : "fde3:2cda:3eea:4d14:212:4b00:0:2/128", + "IPv6 address" : "fe80::212:4b00:0:2", + "Link address" : "00:12:4B:00:00:00:00:02" + }, + { + "Operation" : "delete", + "IPv6 prefix" : "fde3:2cda:3eea:4d14:212:4b00:0:3/128" + } + + ] + }, + { + "0x20009a30": [ + ] + } + ], + + "topology" : { + "nodes": [ + { + "id": 1, + "label": "N1", + "title": "Node 1" + }, + { + "id": 2, + "label": "N2", + "title": "Node 2" + } + ], + "edges": [ + { + "from": 1, + "to": 2 + } + ] + } + }, schema) diff --git a/samples/net/rpl_border_router/src/br.js b/samples/net/rpl_border_router/src/br.js new file mode 100644 index 0000000000000..91ae383e85466 --- /dev/null +++ b/samples/net/rpl_border_router/src/br.js @@ -0,0 +1,500 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +var connected; +var first_run; +var ws; +var interfaces; +var column; +var iface_table_id; +var neighbors_table_id; +var routes_table_id; + +function init() { + ws = new WebSocket(location.origin.replace("http", "ws") + "/ws"); + + interfaces = {}; + first_run = "true"; + connected = "false"; + changeConnectText(); + createIfaceTable(); + createNeighborTable(); + createRouteTable(); + + ws.onopen = function() { + output("Connection opened"); + connected = "true"; + changeConnectText(); + }; + + ws.onmessage = function(e) { + //output("Data received: " + e.data); + var info = JSON.parse(e.data); + + try { + if (info.interface_configuration.length > 0) { + output("Network interface configuration received"); + print_iface_configuration(info.interface_configuration); + } + } catch(err) { + } + + try { + if (info.rpl_configuration.length > 0) { + output("RPL configuration received"); + print_rpl_configuration(info.rpl_configuration); + } + } catch(err) { + } + + try { + if (info.neighbors.length > 0) { + output("Neighbor information received"); + add_neighbors(info.neighbors); + } + } catch(err) { + } + + try { + if (info.routes.length > 0) { + output("Route information received"); + add_routes(info.routes); + } + } catch(err) { + } + + try { + if (info.topology.nodes.length > 0) { + output("Topology information received"); + draw_topology(info.topology); + } + } catch(err) { + } + + if (first_run == "true") { + first_run = "false"; + select_default_tab(); + } + }; + + ws.onclose = function() { + output("Connection closed"); + connected = "false"; + changeConnectText(); + }; + + ws.onerror = function(e) { + output("Data error (see console)"); + console.log(e) + }; +} + +function print_iface_configuration(config) { + for (var i = 0; i < config.length; i++) { + print_iface(config[i]); + } +} + +function print_iface(iface) { + + for (const property in iface) { + var table_id = "iface_table_" + property; + + //output("iface " + property); + + print_iface_settings(iface, property); + } +} + +function print_iface_settings(iface, property) { + var row = table_insert_row(iface_table_id); + + column = 0; + + for (const value in property) { + print_iface_data(property, iface[property][value], row, column++); + } +} + +function print_iface_data(iface_id, setting, row, column) { + for (const key in setting) { + if (key == "Type") { + interfaces[iface_id] = setting[key]; + } + + if (key == "IPv6" || key == "IPv4") { + /* TODO: Print detailed IPv6 and IPv4 data */ + var addresses = ""; + //output(key + " has " + setting[key].length + " addresses"); + + for (var i = 0; i < setting[key].length; i++) { + for (const address in setting[key][i]) { + addresses += address + " "; + } + } + + //output("insert " + iface_table_id + " " + addresses + " to column " + column); + table_add_data(iface_table_id, row, addresses, column, "wrappable"); + } else { + //output("insert " + iface_table_id + " " + setting[key].toString() + " to column " + column); + table_add_data(iface_table_id, row, setting[key], column); + } + } +} + +function print_rpl_configuration(config) { + var table_id = "rpl_table"; + table_create(document.getElementById("rpl"), table_id); + + for (var i = 0; i < config.length; i++) { + print_rpl_data(config[i], table_id); + } +} + +function print_rpl_data(property, table_id) { + for (const value in property) { + //output(value + "=" + property[value]); + print_rpl(value, property[value], table_id); + } +} + +function print_rpl(label, property, table_id) { + var row = table_insert_row(table_id); + + table_add_data(table_id, row, label, 0); + table_add_data(table_id, row, property, 1); +} + +function add_neighbors(config) { + for (var i = 0; i < config.length; i++) { + add_neighbor_iface_data(config[i]); + } +} + +function add_neighbor_iface_data(iface) { + for (const value in iface) { + //output("value " + value.toString()); + if (interfaces[value] == "IEEE 802.15.4") { + add_neighbor_data(iface, iface[value]); + } + } +} + +function toogle(b) { + var req = new Object(); + var details = new Object(); + var jsonString; + + details.command = "toggle"; + details.ipv6_addr = b.getAttribute("IPv6_address"); + + req.coap = details; + + jsonString = JSON.stringify(req); + //output("request " + jsonString); + + if (connected == "true") { + ws.send(jsonString); + } +} + +function add_neighbor_data(iface, property) { + for (var i = 0; i < property.length; i++) { + //output("property " + property[i].toString()); + if (property[i]["Operation"] == "delete") { + delete_neighbor(property[i]["IPv6 address"]); + } else { + var neighbor_row = table_insert_row(neighbors_table_id); + var value = property[i]["IPv6 address"]; + var toggle; + + toggle = table_add_button(neighbors_table_id, neighbor_row, 'Toggle', 'Resources', '', "toggle(this);"); + toggle.setAttribute("IPv6_address", value); + + neighbor_row.setAttribute("IPv6_address", value); + + table_add_data(neighbors_table_id, neighbor_row, value, 'IP address', ''); + } + } +} + +function delete_neighbor(ipv6_addr) { + var table = document.getElementById(neighbors_table_id); + + for (var i = 0, row; row = table.rows[i]; i++) { + if (row.getAttribute("IPv6_address") == ipv6_addr) { + output("deleting " + ipv6_addr); + table.deleteRow(i); + return; + } + } +} + +function add_routes(config) { + for (var i = 0; i < config.length; i++) { + add_route_iface_data(config[i]); + } +} + +function add_route_iface_data(iface) { + for (const value in iface) { + //output("iface " + value.toString()); + add_route_data(iface[value]); + } +} + +function add_route_data(property) { + for (var i = 0; i < property.length; i++) { + if (property[i]["Operation"] == "delete") { + delete_route(property[i]["IPv6 prefix"]); + } else { + var route_row = table_insert_row(routes_table_id); + + table_add_data(routes_table_id, route_row, property[i]["Link address"], 'Link address', ''); + table_add_data(routes_table_id, route_row, property[i]["IPv6 prefix"], 'IPv6 prefix', ''); + route_row.setAttribute("IPv6_prefix", property[i]["IPv6 prefix"]); + } + } +} + +function delete_route(ipv6_prefix) { + var table = document.getElementById(routes_table_id); + + for (var i = 0, row; row = table.rows[i]; i++) { + if (row.getAttribute("IPv6_prefix") == ipv6_prefix) { + output("deleting " + ipv6_prefix); + table.deleteRow(i); + return; + } + } +} + +function print_table_values(property, table_id) { + var row = table_insert_row(table_id); + var head_row; + var thead; + + column = 0; + + if (!header_added) { + var table = document.getElementById(table_id); + thead = table.createTHead(); + head_row = thead.insertRow(); + } + + for (const value in property) { + if (!header_added) { + table_column_header(table_id, head_row, value, column); + } + + table_add_data(table_id, row, property[value], column++); + } + + header_added = true; +} + +function select_default_tab() { + configuration(); +} + +function draw_topology(topology) { + var network; + var options = { interaction: { hover: true } }; + + var container = document.getElementById('topology'); + container.innerHTML = ""; + + network = new vis.Network(container, topology, options); +} + +function configuration() { + document.getElementById("interface_open").click(); +} + +function rpl() { + document.getElementById("rpl_open").click(); +} + +function neighbors() { + document.getElementById("neighbors_open").click(); +} + +function routes() { + document.getElementById("routes_open").click(); +} + +function zconsole() { + document.getElementById("zconsole_open").click(); +} + +function openTab(evt, tabName) { + var i, tabcontent, tablinks; + + tabcontent = document.getElementsByClassName("tabcontent"); + for (i = 0; i < tabcontent.length; i++) { + tabcontent[i].style.display = "none"; + } + + tablinks = document.getElementsByClassName("tablinks"); + for (i = 0; i < tablinks.length; i++) { + tablinks[i].className = tablinks[i].className.replace(" active", ""); + } + + document.getElementById(tabName).style.display = "block"; + evt.currentTarget.className += " active"; +} + +function onSubmit() { + var input = document.getElementById("input"); + ws.send(input.value); + input.value = ""; + input.focus(); +} + +function wsConnect() { + if (connected == "false") { + location.reload(); + } +} + +function changeConnectText() { + if (connected == "false") { + document.getElementById("connect_button").innerText= "Connect"; + } else { + document.getElementById("connect_button").innerText= "Close"; + } +} + +function onConnectClick() { + if (connected == "true") { + ws.close(); + connected = "false"; + changeConnectText(); + } else { + changeConnectText(); + wsConnect(); + } +} + +function escape(str) { + return str.replace(/&/, "&").replace(//, ">").replace(/"/, """); // " +} + +function output(str) { + var log = document.getElementById("output"); + log.innerHTML += escape(str) + "
    "; +} + +function table_create(parent, table_id) { + var tbl = document.createElement("table"); + tbl.setAttribute("id", table_id); + parent.appendChild(tbl); +} + +function table_header(table_id, label) { + var escaped_label = escape(label); + var table = document.getElementById(table_id); + var table_head = table.tHead; + + if (!table_head) { + table_head = table.createTHead(); + } + + var text_label = document.createTextNode(escaped_label); + + table_head.appendChild(escaped_label); + table.appendChild(table_head); +} + +function table_column_header(table_id, head_row, label, column) { + var escaped_label = escape(label); + var text_label = document.createTextNode(escaped_label); + var cell_th = head_row.insertCell(column); + + cell_th.appendChild(text_label); +} + +function table_insert_row(table_id) { + var table = document.getElementById(table_id); + return row = table.insertRow(); +} + +function table_add_data(table_id, row, value, column, cell_class) { + var escaped_value = escape(value); + var table = document.getElementById(table_id); + var cell_value = row.insertCell(column); + var text_value = document.createTextNode(escaped_value); + + cell_value.appendChild(text_value); + + if (cell_class) { + cell_value.className = cell_class; + } +} + +function table_add_button(table_id, row, value, column, cell_class, on_click) { + var escaped_value = escape(value); + var table = document.getElementById(table_id); + var cell_value = row.insertCell(column); + var button = document.createElement('input'); + + button.setAttribute('type', 'button'); + button.setAttribute('value', value); + + cell_value.appendChild(button); + + if (cell_class) { + cell_value.className = cell_class; + } + + if (on_click) { + button.setAttribute("onclick", on_click); + } + + return button; +} + +function createNeighborTable() { + var table; + + neighbors_table_id = "neighbors_table"; + table_create(document.getElementById("neighbors"), neighbors_table_id); + table = document.getElementById(neighbors_table_id); + + thead = table.createTHead(); + head_row = thead.insertRow(); + table_column_header(neighbors_table_id, head_row, 'IP address', 0); + table_column_header(neighbors_table_id, head_row, 'Resources', 1); +} + +function createRouteTable() { + var table; + + routes_table_id = "routes_table"; + table_create(document.getElementById("routes"), routes_table_id); + table = document.getElementById(routes_table_id); + + thead = table.createTHead(); + head_row = thead.insertRow(); + table_column_header(routes_table_id, head_row, 'IPv6 prefix', 0); + table_column_header(routes_table_id, head_row, 'Link address', 1); +} + +function createIfaceTable() { + var table; + + iface_table_id = "iface_table"; + table_create(document.getElementById("iface"), iface_table_id); + table = document.getElementById(iface_table_id); + + thead = table.createTHead(); + head_row = thead.insertRow(); + table_column_header(iface_table_id, head_row, 'Type', 0); + table_column_header(iface_table_id, head_row, 'MTU', 1); + table_column_header(iface_table_id, head_row, 'Link Address', 2); + table_column_header(iface_table_id, head_row, 'IPv6', 3); +} diff --git a/samples/net/rpl_border_router/src/coap.c b/samples/net/rpl_border_router/src/coap.c new file mode 100644 index 0000000000000..34f239184bec3 --- /dev/null +++ b/samples/net/rpl_border_router/src/coap.c @@ -0,0 +1,632 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "rpl-br/coap" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include +#include + +#include "rpl.h" +#include "net_private.h" + +#include "config.h" + +#define MY_COAP_PORT 0xC0AB +#define PEER_COAP_PORT htons(0x1633) /* 5683 */ + +#define PKT_WAIT_TIME K_SECONDS(1) +#define RESPONSE_TIME K_SECONDS(3) + +#define MAX_COAP_REQUEST_ATTEMPTS 5 +#define MAX_COAP_REQUESTS 20 + +struct coap_request { + struct k_delayed_work timer; /* Timer to retransmit messages */ + struct sockaddr_in6 peer; /* Peer CoAP server socket address */ + u16_t id; /* Message id sent */ + u8_t code; /* Expecting reply code */ + u8_t count; /* Number of trails */ + bool used; /* Entry used or not */ + enum coap_request_type type; + coap_reply_cb_t cb; + void *user_data; +}; + +static struct net_context *coap; +static struct coap_request requests[MAX_COAP_REQUESTS]; + +static const char * const led_uri_path[] = { "led", NULL }; +static const char * const rpl_obs_path[] = { "rpl-obs", NULL }; + +static void get_from_ip_addr(struct coap_packet *cpkt, + struct sockaddr_in6 *from) +{ + struct net_udp_hdr hdr, *udp_hdr; + + udp_hdr = net_udp_get_hdr(cpkt->pkt, &hdr); + if (!udp_hdr) { + return; + } + + net_ipaddr_copy(&from->sin6_addr, &NET_IPV6_HDR(cpkt->pkt)->src); + from->sin6_port = udp_hdr->src_port; + from->sin6_family = AF_INET6; +} + +static struct coap_request * +get_coap_request_by_type(const struct sockaddr_in6 *peer, + enum coap_request_type type) +{ + u8_t i; + + for (i = 0; i < MAX_COAP_REQUESTS; i++) { + if (!requests[i].used) { + continue; + } + + if (requests[i].type == type && + net_ipv6_addr_cmp(&requests[i].peer.sin6_addr, + &peer->sin6_addr)) { + return &requests[i]; + } + } + + return NULL; +} + +static struct coap_request * +get_coap_request_by_id(const struct sockaddr_in6 *peer, u16_t id) +{ + u8_t i; + + for (i = 0; i < MAX_COAP_REQUESTS; i++) { + if (!requests[i].used) { + continue; + } + + if (requests[i].id == id && + net_ipv6_addr_cmp(&requests[i].peer.sin6_addr, + &peer->sin6_addr)) { + return &requests[i]; + } + } + + return NULL; +} + +static struct coap_request * +get_coap_request_by_addr(const struct in6_addr *peer) +{ + u8_t i; + + for (i = 0; i < MAX_COAP_REQUESTS; i++) { + if (!requests[i].used) { + continue; + } + + if (net_ipv6_addr_cmp(&requests[i].peer.sin6_addr, peer)) { + return &requests[i]; + } + } + + return NULL; +} + +static struct coap_request *get_free_coap_request(void) +{ + int i; + + for (i = 0; i < MAX_COAP_REQUESTS; i++) { + if (!requests[i].used) { + return &requests[i]; + } + } + + return NULL; +} + +static void clear_coap_request(struct coap_request *request) +{ + if (!request->used) { + return; + } + + request->type = COAP_REQ_NONE; + request->id = 0; + request->code = 0; + request->count = 0; + request->used = false; + request->cb = NULL; + request->user_data = NULL; + + k_delayed_work_cancel(&request->timer); +} + +static bool toggle_led(const struct sockaddr_in6 *peer, u16_t id) +{ + struct net_pkt *pkt; + struct net_buf *frag; + struct coap_packet request; + const char * const *p; + int r; + + pkt = net_pkt_get_tx(coap, PKT_WAIT_TIME); + if (!pkt) { + NET_ERR("Ran out of network packets"); + return false; + } + + frag = net_pkt_get_data(coap, PKT_WAIT_TIME); + if (!frag) { + NET_ERR("Ran out of network buffers"); + goto end; + } + + net_pkt_frag_add(pkt, frag); + + r = coap_packet_init(&request, pkt, 1, COAP_TYPE_NON_CON, + 0, NULL, COAP_METHOD_POST, id); + if (r < 0) { + NET_ERR("Failed to initialize CoAP packet"); + goto end; + } + + for (p = led_uri_path; p && *p; p++) { + r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH, + *p, strlen(*p)); + if (r < 0) { + NET_ERR("Unable add option to request.\n"); + goto end; + } + } + + r = net_context_sendto(pkt, (const struct sockaddr *)peer, + sizeof(struct sockaddr_in6), + NULL, 0, NULL, NULL); + if (r < 0) { + NET_ERR("Cannot send data to peer (%d)", r); + goto end; + } + + return true; + +end: + net_pkt_unref(pkt); + return false; +} + +static bool set_rpl_observer(const struct sockaddr_in6 *peer, u16_t id) +{ + struct net_pkt *pkt; + struct net_buf *frag; + struct coap_packet request; + const char * const *p; + int r; + + pkt = net_pkt_get_tx(coap, PKT_WAIT_TIME); + if (!pkt) { + NET_ERR("Ran out of network packets"); + return false; + } + + frag = net_pkt_get_data(coap, PKT_WAIT_TIME); + if (!frag) { + NET_ERR("Ran out of network buffers"); + goto end; + } + + net_pkt_frag_add(pkt, frag); + + r = coap_packet_init(&request, pkt, 1, COAP_TYPE_CON, + 8, coap_next_token(), + COAP_METHOD_GET, id); + if (r < 0) { + NET_ERR("Failed to initialize CoAP packet"); + goto end; + } + + r = coap_append_option_int(&request, COAP_OPTION_OBSERVE, 0); + if (r < 0) { + NET_ERR("Unable add option to request"); + goto end; + } + + for (p = rpl_obs_path; p && *p; p++) { + r = coap_packet_append_option(&request, COAP_OPTION_URI_PATH, + *p, strlen(*p)); + if (r < 0) { + NET_ERR("Unable add option to request"); + goto end; + } + } + + r = net_context_sendto(pkt, (const struct sockaddr *)peer, + sizeof(struct sockaddr_in6), + NULL, 0, NULL, NULL); + if (r < 0) { + NET_ERR("Cannot send data to peer (%d)", r); + goto end; + } + + return true; + +end: + net_pkt_unref(pkt); + return false; +} + +static void request_timeout(struct k_work *work) +{ + struct coap_request *request = CONTAINER_OF(work, + struct coap_request, + timer); + + if (request->type != COAP_REQ_RPL_OBS) { + return; + } + + /* Check if number of CoAP requests to this peer reached max or not. */ + if (request->count >= MAX_COAP_REQUEST_ATTEMPTS) { + clear_coap_request(request); + return; + } + + request->count++; + + set_rpl_observer(&request->peer, request->id); + + k_delayed_work_submit(&request->timer, RESPONSE_TIME); +} + +static void add_nbr_to_topology(struct in6_addr *nbr) +{ + if (topology.nodes[0].used) { + return; + } + + topology.nodes[0].id = 1; + topology.nodes[0].used = true; + snprintk(topology.nodes[0].label, sizeof(topology.nodes[0].label), + "NBR"); + net_ipaddr_copy(&topology.nodes[0].addr, nbr); +} + +static void add_node_to_topology(struct in6_addr *node) +{ + u8_t i; + + /* BR takes 'id : 1', so node's id starts from 2 */ + for (i = 1; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { + if (topology.nodes[i].used) { + continue; + } + + topology.nodes[i].id = i + 1; + topology.nodes[i].used = true; + + snprintk(topology.nodes[i].label, + sizeof(topology.nodes[i].label), + "N%d", topology.nodes[i].id); + + net_ipaddr_copy(&topology.nodes[i].addr, node); + break; + } +} + +static void update_node_topology(struct in6_addr *node, + struct in6_addr *parent, + u16_t rank) +{ + u8_t i; + + for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { + if (!topology.nodes[i].used) { + continue; + } + + if (!net_ipv6_addr_cmp(&topology.nodes[i].addr, + node)) { + continue; + } + + topology.nodes[i].rank = rank; + net_ipaddr_copy(&topology.nodes[i].parent, parent); + break; + } +} + +static void remove_node_from_topology(struct in6_addr *node) +{ + u8_t i; + + for (i = 1; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { + if (!topology.nodes[i].used) { + continue; + } + + if (!net_ipv6_addr_cmp(&topology.nodes[i].addr, + node)) { + continue; + } + + topology.nodes[i].used = false; + } +} + +#define COAP_REPLY_PARENT strlen("parent-") +#define COAP_REPLY_RANK strlen("rank-") +#define COAP_REPLY_NONE strlen("None") +#define COAP_REPLY_RANK_VALUE 6 + +#define MAX_PAYLOAD_SIZE (NET_IPV6_ADDR_LEN + \ + COAP_REPLY_PARENT + \ + COAP_REPLY_RANK + \ + COAP_REPLY_RANK_VALUE) + +static void node_obs_reply(struct coap_packet *response, void *user_data) +{ + char payload[MAX_PAYLOAD_SIZE]; + char parent_str[NET_IPV6_ADDR_LEN + 1]; + char rank_str[COAP_REPLY_RANK_VALUE + 1]; + struct sockaddr_in6 from; + struct in6_addr parent; + struct net_buf *frag; + u16_t offset; + u16_t len; + u16_t rank; + u8_t i; + + frag = coap_packet_get_payload(response, &offset, &len); + if (!frag && offset == 0xffff) { + NET_ERR("Error while getting payload"); + return; + } + + if (!len) { + NET_ERR("Invalid response"); + return; + } + + frag = net_frag_read(frag, offset, &offset, len, (u8_t *) payload); + if (!frag && offset == 0xffff) { + return; + } + + if (strncmp(payload, "parent-", COAP_REPLY_PARENT)) { + return; + } + + if (!strncmp(payload + COAP_REPLY_PARENT, "None", COAP_REPLY_NONE)) { + return; + } + + i = 0; + offset = COAP_REPLY_PARENT; + while (payload[offset] != '\n') { + parent_str[i++] = payload[offset++]; + } + + parent_str[i] = '\0'; + + i++; + if (strncmp(payload + COAP_REPLY_PARENT + i, "rank-", + COAP_REPLY_RANK)) { + return; + } + + if (!strncmp(payload + COAP_REPLY_PARENT + i, "None", + COAP_REPLY_NONE)) { + return; + } + + offset = COAP_REPLY_PARENT + i + COAP_REPLY_RANK; + i = 0; + while (offset < len) { + rank_str[i++] = payload[offset++]; + } + + rank_str[i] = '\0'; + + if (net_addr_pton(AF_INET6, parent_str, &parent) < 0) { + NET_ERR("Failed to convert parent address"); + return; + } + + get_from_ip_addr(response, &from); + rank = atoi(rank_str); + + update_node_topology(&from.sin6_addr, &parent, rank); +} + +static void pkt_receive(struct net_context *context, + struct net_pkt *pkt, + int status, + void *user_data) +{ + struct coap_option options[4] = { 0 }; + struct coap_packet response; + struct coap_request *coap_req; + struct sockaddr_in6 from; + u16_t id; + u8_t type; + u8_t code; + u8_t opt_num = 4; + int r; + + r = coap_packet_parse(&response, pkt, options, opt_num); + if (r < 0) { + NET_ERR("Invalid data received (%d)\n", r); + net_pkt_unref(pkt); + return; + } + + type = coap_header_get_type(&response); + code = coap_header_get_code(&response); + id = coap_header_get_id(&response); + get_from_ip_addr(&response, &from); + + if (type != COAP_TYPE_ACK) { + NET_ERR("Invalid response, type %d", type); + net_pkt_unref(pkt); + return; + } + + NET_DBG("Received %d bytes coap payload", + net_pkt_appdatalen(pkt) - response.hdr_len - response.opt_len); + + coap_req = get_coap_request_by_id(&from, id); + if (!coap_req) { + net_pkt_unref(pkt); + return; + } + + if (code != coap_req->code) { + NET_ERR("Invalid response, code %d", code); + goto end; + } + + if (coap_req->type == COAP_REQ_RPL_OBS) { + node_obs_reply(&response, coap_req->user_data); + } + + if (coap_req->cb) { + coap_req->cb(&response, coap_req->user_data); + } + +end: + clear_coap_request(coap_req); + net_pkt_unref(pkt); +} + +void coap_remove_node_from_topology(struct in6_addr *peer) +{ + struct coap_request *request; + + request = get_coap_request_by_addr(peer); + if (request) { + clear_coap_request(request); + } + + remove_node_from_topology(peer); +} + +void coap_send_request(struct in6_addr *peer_addr, + enum coap_request_type type, + coap_reply_cb_t cb, + void *user_data) +{ + struct sockaddr_in6 peer; + struct coap_request *request; + + peer.sin6_family = AF_INET6; + peer.sin6_port = PEER_COAP_PORT; + net_ipaddr_copy(&peer.sin6_addr, peer_addr); + + if (type == COAP_REQ_TOGGLE_LED) { + toggle_led(&peer, coap_next_id()); + return; + } + + if (type != COAP_REQ_RPL_OBS) { + return; + } + + /* Check for request has been sent already or not. If request has been + * sent already then timer will run until number of requests reach + * MAX_COAP_REQUEST_ATTEMPTS. + */ + request = get_coap_request_by_type(&peer, type); + if (request) { + return; + } + + request = get_free_coap_request(); + if (!request) { + NET_ERR("Failed to get free coap request"); + return; + } + + request->peer.sin6_family = peer.sin6_family; + request->peer.sin6_port = peer.sin6_port; + net_ipaddr_copy(&request->peer.sin6_addr, &peer.sin6_addr); + + request->id = coap_next_id(); + request->count = 1; + request->type = type; + request->cb = cb; + request->user_data = user_data; + request->used = true; + request->code = COAP_RESPONSE_CODE_CONTENT; + + add_node_to_topology(peer_addr); + set_rpl_observer(&request->peer, request->id); + + k_delayed_work_init(&request->timer, request_timeout); + k_delayed_work_submit(&request->timer, RESPONSE_TIME); +} + +int coap_init(void) +{ + struct net_if *iface = NULL; + + static struct sockaddr_in6 my_addr = { + .sin6_family = AF_INET6, + .sin6_port = htons(MY_COAP_PORT) + }; + u8_t i; + int r; + + iface = net_if_get_ieee802154(); + if (!iface) { + NET_ERR("No IEEE 802.15.4 network interface found."); + return -EINVAL; + } + + for (i = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + if (iface->config.ip.ipv6->unicast[i].is_used) { + break; + } + } + + if (i >= NET_IF_MAX_IPV6_ADDR) { + return -EINVAL; + } + + net_ipaddr_copy(&my_addr.sin6_addr, + &iface->config.ip.ipv6->unicast[i].address.in6_addr); + + add_nbr_to_topology(&my_addr.sin6_addr); + + r = net_context_get(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &coap); + if (r < 0) { + NET_ERR("Could not get UDP context"); + return r; + } + + r = net_context_bind(coap, (struct sockaddr *) &my_addr, + sizeof(my_addr)); + if (r < 0) { + NET_ERR("Could not bind to the context"); + return r; + } + + r = net_context_recv(coap, pkt_receive, 0, NULL); + if (r < 0) { + NET_ERR("Could not set recv callback in the context"); + return r; + } + + return 0; +} diff --git a/samples/net/rpl_border_router/src/config.h b/samples/net/rpl_border_router/src/config.h new file mode 100644 index 0000000000000..aa72e8c20f35b --- /dev/null +++ b/samples/net/rpl_border_router/src/config.h @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +#include + +/* The startup time needs to be longish if DHCP is enabled as setting + * DHCP up takes some time. + */ +#define APP_STARTUP_TIME K_SECONDS(20) + +#ifdef CONFIG_NET_APP_SETTINGS +#ifdef CONFIG_NET_IPV6 +#define ZEPHYR_ADDR CONFIG_NET_APP_MY_IPV6_ADDR +#else +#define ZEPHYR_ADDR CONFIG_NET_APP_MY_IPV4_ADDR +#endif +#else +#ifdef CONFIG_NET_IPV6 +#define ZEPHYR_ADDR "2001:db8::1" +#else +#define ZEPHYR_ADDR "192.0.2.1" +#endif +#endif + +#ifndef ZEPHYR_PORT +#define ZEPHYR_PORT 8080 +#endif + +#define HTTP_TITLE "Zephyr Border Router" + +#define HTTP_AUTH_URL "/auth" +#define HTTP_AUTH_TYPE "Basic" + +/* HTTP Basic Auth, see https://tools.ietf.org/html/rfc7617 */ +#define HTTP_AUTH_REALM "Zephyr" +#define HTTP_AUTH_USERNAME "zephyr" +#define HTTP_AUTH_PASSWORD "zephyr" + +/* If you do not need HTTP support, then it is possible to disable it */ +#if defined(CONFIG_WEBSOCKET) +void start_http_server(struct net_if *iface); +#else +#define start_http_server(...) +#endif + +bool setup_rpl(struct net_if *iface, const char *addr_prefix); + +enum coap_request_type { + COAP_REQ_NONE = 0, + COAP_REQ_TOGGLE_LED = 1, /* Toggle the LED */ + COAP_REQ_RPL_OBS = 2, /* Register OBS */ +}; + +struct network_topology { + struct { + bool used; + u16_t id; + u16_t rank; + char label[4]; + struct in6_addr addr; + struct in6_addr parent; + } nodes[CONFIG_NET_IPV6_MAX_NEIGHBORS]; + + struct { + u16_t from; + u16_t to; + bool used; + } edges[CONFIG_NET_IPV6_MAX_NEIGHBORS]; +}; + +struct network_topology topology; + +typedef void (*coap_reply_cb_t)(struct coap_packet *response, void *user_data); + +int coap_init(void); +void coap_send_request(struct in6_addr *peer_addr, + enum coap_request_type type, + coap_reply_cb_t cb, + void *user_data); + +void coap_remove_node_from_topology(struct in6_addr *peer); +#endif diff --git a/samples/net/rpl_border_router/src/echo-apps-cert.der b/samples/net/rpl_border_router/src/echo-apps-cert.der new file mode 100644 index 0000000000000..bfcb335e31c8c Binary files /dev/null and b/samples/net/rpl_border_router/src/echo-apps-cert.der differ diff --git a/samples/net/rpl_border_router/src/echo-apps-key.der b/samples/net/rpl_border_router/src/echo-apps-key.der new file mode 100644 index 0000000000000..5a4d67372ea41 Binary files /dev/null and b/samples/net/rpl_border_router/src/echo-apps-key.der differ diff --git a/samples/net/rpl_border_router/src/favicon.ico b/samples/net/rpl_border_router/src/favicon.ico new file mode 100644 index 0000000000000..33988a2b4eac1 Binary files /dev/null and b/samples/net/rpl_border_router/src/favicon.ico differ diff --git a/samples/net/rpl_border_router/src/http.c b/samples/net/rpl_border_router/src/http.c new file mode 100644 index 0000000000000..02c829515f591 --- /dev/null +++ b/samples/net/rpl_border_router/src/http.c @@ -0,0 +1,2005 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "rpl-br/http" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include + +/* For Basic auth, we need base64 decoder which can be found + * in mbedtls library. + */ +#include + +#if defined(CONFIG_MBEDTLS) +#if !defined(CONFIG_MBEDTLS_CFG_FILE) +#include "mbedtls/config.h" +#else +#include CONFIG_MBEDTLS_CFG_FILE +#endif +#endif + +#include +#include +#include +#include +#include + +#include "ipv6.h" +#include "route.h" +#include "rpl.h" +#include "net_private.h" + +#include "config.h" + +#define HTTP_CRLF "\r\n" +#define MAX_BUF_LEN 128 +#define ALLOC_TIMEOUT 100 + +struct user_data { + char buf[MAX_BUF_LEN]; + struct http_ctx *ctx; + struct net_if *iface; + const struct sockaddr *dst; + int msg_count; + int iface_count; + int nbr_count; + int route_count; + int failure; +}; + +static struct { + struct sockaddr auth_addr; + bool auth_ok; +} rpl; + +static const struct sockaddr *ws_dst; + +static int http_serve_index_html(struct http_ctx *ctx, + const struct sockaddr *dst); + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) +NET_PKT_TX_SLAB_DEFINE(http_srv_tx, 64); +NET_PKT_DATA_POOL_DEFINE(http_srv_data, 64); + +static struct k_mem_slab *tx_slab(void) +{ + return &http_srv_tx; +} + +static struct net_buf_pool *data_pool(void) +{ + return &http_srv_data; +} +#else +#if defined(CONFIG_NET_L2_BLUETOOTH) +#error "TCP connections over Bluetooth need CONFIG_NET_CONTEXT_NET_PKT_POOL "\ + "defined." +#endif /* CONFIG_NET_L2_BLUETOOTH */ + +#define tx_slab NULL +#define data_pool NULL +#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ + +/* Note that this should fit largest compressed file (br.js) */ +#define RESULT_BUF_SIZE 2500 +static u8_t result[RESULT_BUF_SIZE]; + +#if defined(CONFIG_HTTPS) +#if !defined(CONFIG_NET_APP_TLS_STACK_SIZE) +#define CONFIG_NET_APP_TLS_STACK_SIZE 8196 +#endif /* CONFIG_NET_APP_TLS_STACK_SIZE */ + +#define APP_BANNER "Zephyr HTTP Server for border router" +#define INSTANCE_INFO "Zephyr border router example #1" + +/* Note that each HTTPS context needs its own stack as there will be + * a separate thread for each HTTPS context. + */ +NET_STACK_DEFINE(HTTPS, https_stack, CONFIG_NET_APP_TLS_STACK_SIZE, + CONFIG_NET_APP_TLS_STACK_SIZE); + +#define RX_FIFO_DEPTH 4 +K_MEM_POOL_DEFINE(ssl_rx_pool, 4, 64, RX_FIFO_DEPTH, 4); + +#else /* CONFIG_HTTPS */ +#define APP_BANNER "Zephyr HTTP server for border router" +#endif /* CONFIG_HTTPS */ + +static K_SEM_DEFINE(ws_reply, 0, UINT_MAX); + +/* + * Note that the http_ctx and http_server_urls are quite large so be + * careful if those are allocated from stack. + */ +static struct http_ctx http_ctx; +static struct http_server_urls http_urls; + +#define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/html\r\n" \ + "Transfer-Encoding: chunked\r\n" + +#define HTTP_STATUS_200_OK_GZ "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/html\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "Content-Encoding: gzip\r\n" + +#define HTTP_STATUS_200_OK_GZ_CSS \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/css\r\n" \ + "Transfer-Encoding: chunked\r\n" \ + "Content-Encoding: gzip\r\n" + +#define HTTP_STATUS_301_RE "HTTP/1.1 301 Redirect\r\n" \ + "Content-Type: text/html\r\n" \ + "Location: /index.html\r\n" \ + "\r\n" + +#define HTTP_401_STATUS_US "HTTP/1.1 401 Unauthorized status\r\n" \ + "WWW-Authenticate: Basic realm=" \ + "\""HTTP_AUTH_REALM"\"\r\n\r\n" + +#define HTML_HEADER "" \ + "Zephyr RPL Border Router" \ + "

    " \ + "
    Zephyr RPL Border Router
    " \ + "

    " + +#define HTML_FOOTER "\r\n" + +#if defined(CONFIG_HTTPS) +static bool check_file_size(const char *file, size_t size) +{ + if (size > MBEDTLS_SSL_MAX_CONTENT_LEN) { + NET_ERR("The MBEDTLS_SSL_MAX_CONTENT_LEN (%d) is too small.", + MBEDTLS_SSL_MAX_CONTENT_LEN); + NET_ERR("Cannot send %s (len %zd)", file, size); + + return false; + } + + if (size > RESULT_BUF_SIZE) { + NET_ERR("The RESULT_BUF_SIZE (%d) is too small.", + RESULT_BUF_SIZE); + NET_ERR("Cannot send %s (len %zd)", file, size); + + return false; + } + + return true; +} + +/* Load the certificates and private RSA key. */ + +static const char echo_apps_cert_der[] = { +#include "echo-apps-cert.der.inc" +}; + +static const char echo_apps_key_der[] = { +#include "echo-apps-key.der.inc" +}; + +static int setup_cert(struct net_app_ctx *ctx, + mbedtls_x509_crt *cert, + mbedtls_pk_context *pkey) +{ + int ret; + + ret = mbedtls_x509_crt_parse(cert, echo_apps_cert_der, + sizeof(echo_apps_cert_der)); + if (ret != 0) { + NET_ERR("mbedtls_x509_crt_parse returned %d", ret); + return ret; + } + + ret = mbedtls_pk_parse_key(pkey, echo_apps_key_der, + sizeof(echo_apps_key_der), + NULL, 0); + if (ret != 0) { + NET_ERR("mbedtls_pk_parse_key returned %d", ret); + return ret; + } + + return 0; +} +#else /* CONFIG_HTTPS */ +static bool check_file_size(const char *file, size_t size) +{ + return true; +} +#endif /* CONFIG_HTTPS */ + +static int http_response(struct http_ctx *ctx, const char *header, + const char *payload, size_t payload_len, + const struct sockaddr *dst) +{ + int ret; + + ret = http_add_header(ctx, header, dst, NULL); + if (ret < 0) { + NET_ERR("Cannot add HTTP header (%d)", ret); + return ret; + } + + ret = http_add_header(ctx, HTTP_CRLF, dst, NULL); + if (ret < 0) { + return ret; + } + + ret = http_send_chunk(ctx, payload, payload_len, dst, NULL); + if (ret < 0) { + NET_ERR("Cannot send data to peer (%d)", ret); + return ret; + } + + return http_send_flush(ctx, NULL); +} + +static int http_response_soft_404(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + static const char *not_found = + HTML_HEADER + "

    404 Not Found

    " + HTML_FOOTER; + + return http_response(ctx, HTTP_STATUS_200_OK, not_found, + strlen(not_found), dst); +} + +int http_response_401(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + return http_response(ctx, HTTP_401_STATUS_US, "", 0, dst); +} + +static int http_basic_auth(struct http_ctx *ctx, + enum http_connection_type type, + const struct sockaddr *dst) +{ + const char auth_str[] = HTTP_CRLF "Authorization: Basic "; + int ret = 0; + char *ptr; + + NET_DBG(""); + + ptr = strstr(ctx->http.field_values[0].key, auth_str); + if (ptr) { + char output[sizeof(HTTP_AUTH_USERNAME) + + sizeof(":") + + sizeof(HTTP_AUTH_PASSWORD)]; + size_t olen, ilen, alen; + char *end, *colon; + int ret; + + memset(output, 0, sizeof(output)); + + end = strstr(ptr + 2, HTTP_CRLF); + if (!end) { + ret = http_response_401(ctx, dst); + goto close; + } + + alen = sizeof(auth_str) - 1; + ilen = end - (ptr + alen); + + ret = mbedtls_base64_decode(output, sizeof(output) - 1, + &olen, ptr + alen, ilen); + if (ret) { + ret = http_response_401(ctx, dst); + goto close; + } + + colon = memchr(output, ':', olen); + + if (colon && colon > output && colon < (output + olen) && + memcmp(output, HTTP_AUTH_USERNAME, colon - output) == 0 && + memcmp(colon + 1, HTTP_AUTH_PASSWORD, + output + olen - colon) == 0) { + rpl.auth_ok = true; + memcpy(&rpl.auth_addr, + &ctx->app_ctx.default_ctx->remote, + sizeof(struct sockaddr)); + if (type == WS_CONNECTION) { + return 0; + } + + ret = http_serve_index_html(ctx, dst); + goto close; + } + + ret = http_response_401(ctx, dst); + goto close; + } + + ret = http_response_401(ctx, dst); + +close: + http_close(ctx); + return ret; +} + +static int http_serve_index_html(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + static const char index_html_gz[] = { +#include "index.html.gz.inc" + }; + + check_file_size("index.html", sizeof(index_html_gz)); + + NET_DBG("Sending index.html (%zd bytes) to client", + sizeof(index_html_gz)); + return http_response(ctx, HTTP_STATUS_200_OK_GZ, index_html_gz, + sizeof(index_html_gz), dst); +} + +static int http_serve_style_css(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + static const char style_css_gz[] = { +#include "style.css.gz.inc" + }; + + check_file_size("style.css", sizeof(style_css_gz)); + + NET_DBG("Sending style.css (%zd bytes) to client", + sizeof(style_css_gz)); + return http_response(ctx, HTTP_STATUS_200_OK_GZ_CSS, + style_css_gz, + sizeof(style_css_gz), + dst); +} + +static int http_serve_br_js(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + static const char br_js_gz[] = { +#include "br.js.gz.inc" + }; + + check_file_size("br.js", sizeof(br_js_gz)); + + NET_DBG("Sending br.js (%zd bytes) to client", + sizeof(br_js_gz)); + return http_response(ctx, HTTP_STATUS_200_OK_GZ_CSS, + br_js_gz, + sizeof(br_js_gz), dst); +} + +static int http_serve_favicon_ico(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + static const char favicon_ico_gz[] = { +#include "favicon.ico.gz.inc" + }; + + check_file_size("favicon.ico", sizeof(favicon_ico_gz)); + + NET_DBG("Sending favicon.ico (%zd bytes) to client", + sizeof(favicon_ico_gz)); + return http_response(ctx, HTTP_STATUS_200_OK_GZ, favicon_ico_gz, + sizeof(favicon_ico_gz), dst); +} + +static bool check_addr(struct http_ctx *ctx) +{ +#if defined(CONFIG_NET_IPV4) + if (ctx->app_ctx.ipv6.ctx->remote.sa_family == AF_INET6) { + return memcmp( + &net_sin6(&rpl.auth_addr)->sin6_addr, + &net_sin6(&ctx->app_ctx.ipv6.ctx->remote)->sin6_addr, + sizeof(struct in6_addr)) == 0; + } +#endif +#if defined(CONFIG_NET_IPV4) + if (ctx->app_ctx.ipv4.ctx->remote.sa_family == AF_INET) { + return memcmp( + &net_sin(&rpl.auth_addr)->sin_addr, + &net_sin(&ctx->app_ctx.ipv4.ctx->remote)->sin_addr, + sizeof(struct in_addr)) == 0; + } +#endif + + return false; +} + +static const char *addrtype2str(enum net_addr_type addr_type) +{ + switch (addr_type) { + case NET_ADDR_ANY: + return "unknown"; + case NET_ADDR_AUTOCONF: + return "autoconf"; + case NET_ADDR_DHCP: + return "DHCP"; + case NET_ADDR_MANUAL: + return "manual"; + case NET_ADDR_OVERRIDABLE: + return "overridable"; + } + + return "invalid"; +} + +static const char *addrstate2str(enum net_addr_state addr_state) +{ + switch (addr_state) { + case NET_ADDR_ANY_STATE: + return "unknown"; + case NET_ADDR_TENTATIVE: + return "tentative"; + case NET_ADDR_PREFERRED: + return "preferred"; + case NET_ADDR_DEPRECATED: + return "deprecated"; + } + + return "invalid"; +} + +static int append_and_send_data(struct user_data *data, + bool final, + const char *fmt, ...) +{ + enum ws_opcode opcode = WS_OPCODE_CONTINUE; + int ret, pos, len; + va_list ap; + + va_start(ap, fmt); + pos = vsnprintk(data->buf, MAX_BUF_LEN, fmt, ap); + va_end(ap); + + len = strlen(data->buf); + + if (final) { + if (data->msg_count == 0) { + opcode = WS_OPCODE_DATA_TEXT; + } + + ret = ws_send_msg_to_client(data->ctx, data->buf, len, + opcode, final, data->dst, NULL); + if (ret < 0) { + NET_DBG("Could not send %d bytes data to client", len); + goto out; + } else { + NET_DBG("Sent %d bytes to client", len); + } + + data->msg_count = 0; + + return ret; + } + + if (data->msg_count == 0) { + opcode = WS_OPCODE_DATA_TEXT; + } + + ret = ws_send_msg_to_client(data->ctx, data->buf, len, + opcode, final, data->dst, NULL); + if (ret < 0) { + NET_DBG("Could not send %d bytes data to client", len); + goto out; + } else { + NET_DBG("Sent %d bytes to client", len); + } + + data->msg_count++; + +out: + return ret; +} + +static void append_unicast_addr(struct net_if *iface, struct user_data *data) +{ + char addr[NET_IPV6_ADDR_LEN], lifetime[10]; + struct net_if_addr *unicast; + int i, ret = 0; + int printed, count; + + for (i = 0, printed = 0, count = 0; i < NET_IF_MAX_IPV6_ADDR; i++) { + unicast = &iface->config.ip.ipv6->unicast[i]; + + if (!unicast->is_used) { + continue; + } + + net_addr_ntop(AF_INET6, &unicast->address.in6_addr, addr, + NET_IPV6_ADDR_LEN); + + if (!printed) { + ret = append_and_send_data(data, false, + "{\"IPv6\":["); + if (ret < 0) { + goto out; + } + } + + if (unicast->is_infinite) { + snprintk(lifetime, sizeof(lifetime), "%s", "infinite"); + } else { + snprintk(lifetime, sizeof(lifetime), "%d", + k_delayed_work_remaining_get( + &unicast->lifetime)); + } + + ret = append_and_send_data(data, false, + "%s{\"%s\":{" + "\"State\":\"%s\"," + "\"Type\":\"%s\"," + "\"Lifetime\":\"%s\"" + "}}", + count > 0 ? "," : "", addr, + addrstate2str(unicast->addr_state), + addrtype2str(unicast->addr_type), + lifetime); + if (ret < 0) { + goto out; + } + + count++; + printed++; + } + + if (printed > 0) { + ret = append_and_send_data(data, false, "]}"); + if (ret < 0) { + goto out; + } + } + +out: + if (ret < 0) { + NET_ERR("Out of mem"); + } +} + +static const char *iface2str(struct net_if *iface) +{ +#ifdef CONFIG_NET_L2_IEEE802154 + if (iface->if_dev->l2 == &NET_L2_GET_NAME(IEEE802154)) { + return "IEEE 802.15.4"; + } +#endif + +#ifdef CONFIG_NET_L2_ETHERNET + if (iface->if_dev->l2 == &NET_L2_GET_NAME(ETHERNET)) { + return "Ethernet"; + } +#endif + +#ifdef CONFIG_NET_L2_DUMMY + if (iface->if_dev->l2 == &NET_L2_GET_NAME(DUMMY)) { + return "Dummy"; + } +#endif + +#ifdef CONFIG_NET_L2_BT + if (iface->if_dev->l2 == &NET_L2_GET_NAME(BLUETOOTH)) { + return "Bluetooth"; + } +#endif + +#ifdef CONFIG_NET_L2_OFFLOAD + if (iface->if_dev->l2 == &NET_L2_GET_NAME(OFFLOAD_IP)) { + return "IP Offload"; + } +#endif + + return "unknown"; +} + +static void iface_cb(struct net_if *iface, void *user_data) +{ + struct user_data *data = user_data; + int ret; + + if (!net_if_is_up(iface)) { + NET_DBG("Interface %p is down", iface); + return; + } + + ret = append_and_send_data(data, false, "%s{\"%p\":[", + data->iface_count > 0 ? "," : "", iface); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(data, false, "{\"Type\":\"%s\"},", + iface2str(iface)); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(data, false, "{\"Link address\":\"%s\"},", + net_sprint_ll_addr(iface->if_dev->link_addr.addr, + iface->if_dev->link_addr.len)); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(data, false, "{\"MTU\":\"%d\"},", + iface->if_dev->mtu); + if (ret < 0) { + goto out; + } + + append_unicast_addr(iface, data); + + /* Add more data here.... */ + + data->iface_count++; + + ret = append_and_send_data(data, false, "]}"); + if (ret < 0) { + goto out; + } + +out: + if (ret < 0) { + NET_ERR("Out of mem"); + } +} + +static int send_iface_configuration(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface_count = 0; + data.msg_count = 0; + data.dst = dst; + + ret = append_and_send_data(&data, false, + "{\"interface_configuration\":["); + if (ret < 0) { + goto out; + } + + net_if_foreach(iface_cb, &data); + + ret = append_and_send_data(&data, true, "]}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + return ret; +} + +static const char *mode2str(enum net_rpl_mode mode) +{ + if (mode == NET_RPL_MODE_MESH) { + return "mesh"; + } else if (mode == NET_RPL_MODE_FEATHER) { + return "feather"; + } else if (mode == NET_RPL_MODE_LEAF) { + return "leaf"; + } + + return ""; +} + +static int _add_string(struct user_data *data, + const char *name, + const char *value, + bool first, + bool add_block) +{ + int ret; + + ret = append_and_send_data(data, false, "%s%s\"%s\":\"%s\"%s", + first ? "" : ",", + add_block ? "{" : "", + name, value, + add_block ? "}" : ""); + if (ret < 0) { + goto out; + } + +out: + return ret; +} + +static int _add_int(struct user_data *data, + const char *name, + int value, + bool first, + bool add_block) +{ + int ret; + + ret = append_and_send_data(data, false, "%s%s\"%s\":\"%d\"%s", + first ? "" : ",", + add_block ? "{" : "", + name, value, + add_block ? "}" : ""); + if (ret < 0) { + goto out; + } + +out: + return ret; +} + +static int add_string(struct user_data *data, + const char *name, + const char *value, + bool first) +{ + return _add_string(data, name, value, first, false); +} + +static int add_int(struct user_data *data, + const char *name, + int value, + bool first) +{ + return _add_int(data, name, value, first, false); +} + +static int add_string_block(struct user_data *data, + const char *name, + const char *value, + bool first) +{ + return _add_string(data, name, value, first, true); +} + +static int add_int_block(struct user_data *data, + const char *name, + int value, + bool first) +{ + return _add_int(data, name, value, first, true); +} + +static int add_rpl_config(struct user_data *data) +{ + int ret; + + ret = add_string_block(data, "RPL mode", mode2str(net_rpl_get_mode()), + true); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "Objective function", + IS_ENABLED(CONFIG_NET_RPL_MRHOF) ? "MRHOF" : + (IS_ENABLED(CONFIG_NET_RPL_OF0) ? "OF0" : + ""), false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "Routing metric", + IS_ENABLED(CONFIG_NET_RPL_MC_NONE) ? "none" : + (IS_ENABLED(CONFIG_NET_RPL_MC_ETX) ? + "estimated num of TX" : + (IS_ENABLED(CONFIG_NET_RPL_MC_ENERGY) ? + "energy based" : + "")), false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "Mode of operation", + IS_ENABLED(CONFIG_NET_RPL_MOP2) ? + "Storing, no mcast (MOP2)" : + (IS_ENABLED(CONFIG_NET_RPL_MOP3) ? + "Storing (MOP3)" : + ""), false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "Send probes to nodes", + IS_ENABLED(CONFIG_NET_RPL_PROBING) ? + "true" : "false", false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "Max instances", + CONFIG_NET_RPL_MAX_INSTANCES, + false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "Max DAG / instance", + CONFIG_NET_RPL_MAX_DAG_PER_INSTANCE, + false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "Min hop rank increment", + CONFIG_NET_RPL_MIN_HOP_RANK_INC, + false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "Initial link metric", + CONFIG_NET_RPL_INIT_LINK_METRIC, + false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "RPL preference value", + CONFIG_NET_RPL_PREFERENCE, false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "DAG grounded by default", + IS_ENABLED(CONFIG_NET_RPL_GROUNDED) ? + "true" : "false", false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "Default instance id", + CONFIG_NET_RPL_DEFAULT_INSTANCE, + false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "Insert hop-by-hop option", + IS_ENABLED(CONFIG_NET_RPL_INSERT_HBH_OPTION) ? + "true" : "false", false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "Specify DAG when sending DAO", + IS_ENABLED(CONFIG_NET_RPL_DAO_SPECIFY_DAG) ? + "true" : "false", false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "DIO min interval", + CONFIG_NET_RPL_DIO_INTERVAL_MIN, false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "DIO doublings interval", + CONFIG_NET_RPL_DIO_INTERVAL_DOUBLINGS, false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "DIO redundancy value", + CONFIG_NET_RPL_DIO_REDUNDANCY, + false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "DAO send timer", + CONFIG_NET_RPL_DAO_TIMER, false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "DAO max retransmissions", + CONFIG_NET_RPL_DAO_MAX_RETRANSMISSIONS, false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "DAO ack expected", + IS_ENABLED(CONFIG_NET_RPL_DAO_ACK) ? + "true" : "false", false); + if (ret < 0) { + goto out; + } + + ret = add_string_block(data, "DIS send periodically", + IS_ENABLED(CONFIG_NET_RPL_DIS_SEND) ? + "true" : "false", false); + if (ret < 0) { + goto out; + } + +#if defined(CONFIG_NET_RPL_DIS_SEND) + ret = add_int_block(data, "DIS interval", CONFIG_NET_RPL_DIS_INTERVAL, + false); + if (ret < 0) { + goto out; + } +#endif + + ret = add_int_block(data, "Default route lifetime unit", + CONFIG_NET_RPL_DEFAULT_LIFETIME_UNIT, false); + if (ret < 0) { + goto out; + } + + ret = add_int_block(data, "Default route lifetime", + CONFIG_NET_RPL_DEFAULT_LIFETIME, false); + if (ret < 0) { + goto out; + } + +#if defined(CONFIG_NET_RPL_MOP3) + ret = add_int_block(data, "Multicast MOP3 route lifetime", + CONFIG_NET_RPL_MCAST_LIFETIME, false); + if (ret < 0) { + goto out; + } +#endif + +out: + return ret; +} + +static int send_rpl_configuration(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.msg_count = 0; + data.dst = dst; + + ret = append_and_send_data(&data, false, "{\"rpl_configuration\":["); + if (ret < 0) { + goto out; + } + + ret = add_rpl_config(&data); + if (ret < 0) { + NET_ERR("Could not send RPL configuration"); + goto out; + } + + ret = append_and_send_data(&data, true, "]}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + return ret; +} + +static void append_nbr(struct net_nbr *nbr, struct user_data *data) +{ + int ret; + + if (data->iface != nbr->iface) { + ret = append_and_send_data(data, false, "%s{\"%p\":[", + data->iface_count > 0 ? "]}," : "", + nbr->iface); + if (ret < 0) { + goto out; + } + + data->iface_count++; + data->nbr_count = 0; + data->iface = nbr->iface; + } + + ret = append_and_send_data(data, false, "%s{", + data->nbr_count > 0 ? "," : ""); + if (ret < 0) { + goto out; + } + + ret = add_string(data, "Link address", + nbr->idx == NET_NBR_LLADDR_UNKNOWN ? "?" : + net_sprint_ll_addr( + net_nbr_get_lladdr(nbr->idx)->addr, + net_nbr_get_lladdr(nbr->idx)->len), + true); + if (ret < 0) { + goto out; + } + + ret = add_string(data, "IPv6 address", + net_sprint_ipv6_addr(&net_ipv6_nbr_data(nbr)->addr), + false); + if (ret < 0) { + goto out; + } + + ret = add_int(data, "Link metric", net_ipv6_nbr_data(nbr)->link_metric, + false); + if (ret < 0) { + goto out; + } + + ret = add_string(data, "Is router", + net_ipv6_nbr_data(nbr)->is_router ? "true" : "false", + false); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(data, false, "}"); + if (ret < 0) { + goto out; + } + + data->nbr_count++; + +out: + data->failure = ret; +} + +static void nbr_cb(struct net_nbr *nbr, void *user_data) +{ + struct user_data *data = user_data; + + append_nbr(nbr, data); +} + +static int send_ipv6_neighbors(struct http_ctx *ctx, + const struct sockaddr *dst, + struct net_nbr *nbr) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface = NULL; + data.msg_count = 0; + data.dst = dst; + + ret = append_and_send_data(&data, false, "{\"neighbors\":["); + if (ret < 0) { + goto out; + } + + data.nbr_count = 0; + data.iface_count = 0; + + if (!nbr) { + net_ipv6_nbr_foreach(nbr_cb, &data); + } else { + append_nbr(nbr, &data); + } + + if (data.failure < 0) { + ret = data.failure; + goto out; + } + + if (data.iface_count > 0) { + ret = append_and_send_data(&data, false, "]}"); + if (ret < 0) { + goto out; + } + } + + ret = append_and_send_data(&data, true, "]}"); + if (ret < 0) { + ret = -ENOMEM; + goto out; + } + + return ret; + +out: + NET_DBG("Cannot send neighbor information"); + + return ret; +} + +static int send_ipv6_neighbor_deletion(struct http_ctx *ctx, + const struct sockaddr *dst, + struct net_if *iface, + struct in6_addr *addr) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface = NULL; + data.dst = dst; + + ret = append_and_send_data(&data, false, "{\"neighbors\":["); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, "{\"%p\":[{", iface); + if (ret < 0) { + goto out; + } + + ret = add_string(&data, "Operation", "delete", true); + if (ret < 0) { + goto out; + } + + ret = add_string(&data, "IPv6 address", + net_sprint_ipv6_addr(addr), false); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, true, "}]}]}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + NET_DBG("Cannot send neighbor information"); + + return ret; +} + +static void append_route(struct net_route_entry *entry, struct user_data *data) +{ + struct net_route_nexthop *nexthop_route; + int ret = 0; + + if (entry->iface != data->iface) { + return; + } + + ret = append_and_send_data(data, false, + "%s{\"IPv6 prefix\":\"%s/%d\"", + data->route_count > 0 ? "," : "", + net_sprint_ipv6_addr(&entry->addr), + entry->prefix_len); + if (ret < 0) { + goto out; + } + + SYS_SLIST_FOR_EACH_CONTAINER(&entry->nexthop, nexthop_route, node) { + struct net_linkaddr_storage *lladdr; + + if (!nexthop_route->nbr) { + continue; + } + + if (nexthop_route->nbr->idx == NET_NBR_LLADDR_UNKNOWN) { + ret = add_string(data, "Link address", "unknown", + false); + } else { + lladdr = net_nbr_get_lladdr(nexthop_route->nbr->idx); + + ret = add_string(data, "Link address", + net_sprint_ll_addr(lladdr->addr, + lladdr->len), + false); + if (ret < 0) { + ret = -ENOMEM; + goto out; + } + } + } + + ret = append_and_send_data(data, false, "}"); + if (ret < 0) { + goto out; + } + + data->route_count++; + return; + +out: + data->failure = ret; +} + +static void route_cb(struct net_route_entry *entry, void *user_data) +{ + struct user_data *data = user_data; + + append_route(entry, data); +} + +static void append_route_iface(struct net_if *iface, + struct net_route_entry *route, + void *user_data) +{ + struct user_data *data = user_data; + int ret; + + if (!net_if_is_up(iface)) { + NET_DBG("Interface %p is down", iface); + return; + } + + ret = append_and_send_data(data, false, "%s{\"%p\":[", + data->iface_count > 0 ? "," : "", iface); + if (ret < 0) { + goto out; + } + + data->iface = iface; + data->route_count = 0; + + if (!route) { + net_route_foreach(route_cb, data); + } else { + append_route(route, data); + } + + data->iface_count++; + + ret = append_and_send_data(data, false, "]}"); + if (ret < 0) { + ret = -ENOMEM; + goto out; + } + +out: + if (ret < 0) { + NET_ERR("Out of mem"); + } +} + +static void iface_cb_for_routes(struct net_if *iface, void *user_data) +{ + append_route_iface(iface, NULL, user_data); +} + +static int send_ipv6_routes(struct http_ctx *ctx, + const struct sockaddr *dst, + struct net_if *iface, + struct net_route_entry *route) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface_count = 0; + data.msg_count = 0; + data.dst = dst; + + ret = append_and_send_data(&data, false, "{\"routes\":["); + if (ret < 0) { + goto out; + } + + if (!iface && !route) { + net_if_foreach(iface_cb_for_routes, &data); + } else { + append_route_iface(iface, route, &data); + } + + ret = append_and_send_data(&data, true, "]}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + return ret; +} + +static int send_ipv6_route_deletion(struct http_ctx *ctx, + const struct sockaddr *dst, + struct net_if *iface, + struct net_event_ipv6_route *info) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.dst = dst; + + ret = append_and_send_data(&data, false, "{\"routes\":["); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, "{\"%p\":[", iface); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, "{\"Operation\":\"delete\","); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, + "\"IPv6 prefix\":\"%s/%d\"", + net_sprint_ipv6_addr(&info->addr), + info->prefix_len); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, true, "}]}]}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + return ret; +} + +static void calculate_edges(void) +{ + u8_t i, j, k; + + k = 0; + + for (i = 1; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { + if (!topology.nodes[i].used) { + continue; + } + + for (j = 0; j < CONFIG_NET_IPV6_MAX_NEIGHBORS; j++) { + if (!topology.nodes[j].used) { + continue; + } + + if (!net_ipv6_addr_cmp(&topology.nodes[i].parent, + &topology.nodes[j].addr)) { + continue; + } + + topology.edges[k].from = topology.nodes[i].id; + topology.edges[k].to = topology.nodes[j].id; + topology.edges[k].used = true; + + k++; + break; + + } + } +} + +static int send_topology_information(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + struct user_data data; + u8_t i; + int ret; + + data.ctx = ctx; + data.iface_count = 0; + data.msg_count = 0; + data.dst = dst; + + calculate_edges(); + + ret = append_and_send_data(&data, false, "{\"topology\":{"); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, "\"nodes\":["); + if (ret < 0) { + goto out; + } + + for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { + if (!topology.nodes[i].used) { + continue; + } + + ret = append_and_send_data(&data, false, i == 0 ? "{" : ",{"); + if (ret < 0) { + goto out; + } + + ret = add_int(&data, "id", topology.nodes[i].id, true); + if (ret < 0) { + goto out; + } + + ret = add_string(&data, "label", + topology.nodes[i].label, false); + if (ret < 0) { + goto out; + } + + ret = add_string(&data, "title", + net_sprint_ipv6_addr(&topology.nodes[i].addr), + false); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, "}"); + if (ret < 0) { + goto out; + } + } + + ret = append_and_send_data(&data, false, "],\"edges\":["); + if (ret < 0) { + goto out; + } + + for (i = 0; i < CONFIG_NET_IPV6_MAX_NEIGHBORS; i++) { + if (!topology.edges[i].used) { + continue; + } + + ret = append_and_send_data(&data, false, i == 0 ? "{" : ",{"); + if (ret < 0) { + goto out; + } + + ret = add_int(&data, "from", topology.edges[i].from, true); + if (ret < 0) { + goto out; + } + + ret = add_int(&data, "to", topology.edges[i].to, false); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(&data, false, "}"); + if (ret < 0) { + goto out; + } + } + + ret = append_and_send_data(&data, true, "]}}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + return ret; +} + +static void ws_send_info(struct http_ctx *ctx, + const struct sockaddr *dst) +{ + int ret; + + ret = send_iface_configuration(ctx, dst); + if (ret < 0) { + NET_ERR("Cannot send interface configuration (%d)", ret); + } + + ret = send_rpl_configuration(ctx, dst); + if (ret < 0) { + NET_ERR("Cannot send RPL configuration (%d)", ret); + } + + ret = send_ipv6_neighbors(ctx, dst, NULL); + if (ret < 0) { + NET_ERR("Cannot send neighbor information (%d)", ret); + return; + } + + ret = send_ipv6_routes(ctx, dst, NULL, NULL); + if (ret < 0) { + NET_ERR("Cannot send route information (%d)", ret); + return; + } + + ret = send_topology_information(ctx, dst); + if (ret < 0) { + NET_ERR("Cannot send topology information (%d)", ret); + } +} + +struct ws_http_ctx { + struct http_ctx *ctx; + const struct sockaddr *dst; + bool data_set; +}; + +static struct ws_http_ctx ws_ctx; + +static void ws_serve_replies(void) +{ + ws_send_info(ws_ctx.ctx, ws_ctx.dst); + + ws_ctx.data_set = false; +} + +static void http_connected(struct http_ctx *ctx, + enum http_connection_type type, + const struct sockaddr *dst, + void *user_data) +{ + char url[32]; + int len = min(sizeof(url), ctx->http.url_len); + + NET_DBG(""); + + if (0 && (!rpl.auth_ok || !check_addr(ctx))) { + rpl.auth_ok = false; + http_basic_auth(ctx, type, dst); + return; + } + + memcpy(url, ctx->http.url, len); + url[len] = '\0'; + + NET_DBG("%s connect attempt URL %s", + type == HTTP_CONNECTION ? "HTTP" : + (type == WS_CONNECTION ? "WS" : ""), url); + + if (type == HTTP_CONNECTION) { + if (strncmp(ctx->http.url, "/", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx, dst); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/index.html", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx, dst); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/br.js", + ctx->http.url_len) == 0) { + http_serve_br_js(ctx, dst); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/style.css", + ctx->http.url_len) == 0) { + http_serve_style_css(ctx, dst); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/favicon.ico", + ctx->http.url_len) == 0) { + http_serve_favicon_ico(ctx, dst); + http_close(ctx); + return; + } + } else if (type == WS_CONNECTION) { + if (strncmp(ctx->http.url, "/ws", ctx->http.url_len) == 0) { + ws_ctx.ctx = ctx; + ws_ctx.dst = dst; + ws_dst = dst; + ws_ctx.data_set = true; + k_sem_give(&ws_reply); + } + } +} + +/** + * The sample coap JSON command from WebUI looks like this. + * {"coap":{"command":"led_on","ipv6_addr":"fe80::212:4b00:0:2"}} + */ + +#define JSON_COAP_PREFIX "{\"coap\":" +#define MAX_PAYLOAD_LEN 100 +struct coap_command { + const char *command; + const char *ipv6_addr; +}; + +struct rpl_coap_req { + struct coap_command coap; +}; + +static const struct json_obj_descr command_descr[] = { + JSON_OBJ_DESCR_PRIM(struct coap_command, command, JSON_TOK_STRING), + JSON_OBJ_DESCR_PRIM(struct coap_command, ipv6_addr, JSON_TOK_STRING), +}; + +static const struct json_obj_descr coap_descr[] = { + JSON_OBJ_DESCR_OBJECT(struct rpl_coap_req, coap, command_descr), +}; + +static void handle_coap_request(struct http_ctx *ctx, + struct net_pkt *pkt, + void *user_data) +{ + struct rpl_coap_req req; + struct net_buf *frag; + struct in6_addr peer_addr; + enum coap_request_type type; + char payload[MAX_PAYLOAD_LEN]; + u8_t *ptr; + u8_t len; + u16_t pos; + int ret; + + len = net_pkt_appdatalen(pkt); + if (len > MAX_PAYLOAD_LEN - 1) { + NET_ERR("Can't handle payload more than %d(%d)", + MAX_PAYLOAD_LEN, len); + } + + frag = pkt->frags; + ptr = net_pkt_appdata(pkt); + + pos = (u16_t)(ptr - frag->data); + + frag = net_frag_read(frag, pos, &pos, len, &payload[0]); + if (!frag && pos == 0xffff) { + NET_WARN("Failed to read payload"); + return; + } + + payload[len] = '\0'; + + ret = json_obj_parse((char *)payload, len, coap_descr, + ARRAY_SIZE(coap_descr), &req); + if (ret < 0) { + NET_ERR("Failed to parse JSON string %d", ret); + return; + } + + ret = net_addr_pton(AF_INET6, req.coap.ipv6_addr, &peer_addr); + if (ret < 0) { + NET_WARN("Invalid peer address %s", req.coap.ipv6_addr); + return; + } + + if (strcmp(req.coap.command, "toggle") == 0) { + type = COAP_REQ_TOGGLE_LED; + } else { + NET_WARN("Invalid coap command %s", req.coap.command); + return; + } + + coap_send_request(&peer_addr, type, NULL, NULL); + NET_DBG("Send CoAP request '%s'-'%s'", req.coap.command, + req.coap.ipv6_addr); +} + +static void http_received(struct http_ctx *ctx, + struct net_pkt *pkt, + int status, + u32_t flags, + const struct sockaddr *dst, + void *user_data) +{ + if (!status) { + NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); + + if (!strncmp((char *)net_pkt_appdata(pkt), JSON_COAP_PREFIX, + sizeof(JSON_COAP_PREFIX) - 1)) { + handle_coap_request(ctx, pkt, user_data); + } + + + if (pkt) { + net_pkt_unref(pkt); + } + } else { + NET_ERR("Receive error (%d)", status); + + if (pkt) { + net_pkt_unref(pkt); + } + } +} + +static void http_sent(struct http_ctx *ctx, + int status, + void *user_data_send, + void *user_data) +{ + NET_DBG("Data sent status %d", status); +} + +static void http_closed(struct http_ctx *ctx, + int status, + void *user_data) +{ + NET_DBG("Connection %p closed", ctx); +} + +static const char *get_string(int str_len, const char *str) +{ + static char buf[64]; + int len = min(str_len, sizeof(buf) - 1); + + memcpy(buf, str, len); + buf[len] = '\0'; + + return buf; +} + +static enum http_verdict default_handler(struct http_ctx *ctx, + enum http_connection_type type, + const struct sockaddr *dst) +{ + NET_DBG("No handler for %s URL %s", + type == HTTP_CONNECTION ? "HTTP" : "WS", + get_string(ctx->http.url_len, ctx->http.url)); + + if (type == HTTP_CONNECTION) { + http_response_soft_404(ctx, dst); + } + + return HTTP_VERDICT_DROP; +} + +static void coap_obs_cb(struct coap_packet *response, void *user_data) +{ + int ret; + + ret = send_topology_information(&http_ctx, ws_dst); + if (ret < 0) { + NET_ERR("Cannot send topology (%d)", ret); + } +} + +static struct net_mgmt_event_callback br_mgmt_cb; +static void mgmt_cb(struct net_mgmt_event_callback *cb, + u32_t mgmt_event, struct net_if *iface) +{ +#if !defined(CONFIG_NET_L2_IEEE802154) + NET_DBG("CONFIG_NET_L2_IEEE802154 not enabled"); + return; +#endif + struct net_if *iface_802154 = net_if_get_ieee802154(); + struct net_event_ipv6_route *route_info; + struct net_event_ipv6_nbr *nbr_info; + struct net_route_entry *route; + struct net_nbr *nbr; + int ret; + + if (iface_802154 != iface) { + return; + } + + if (!cb->info) { + return; + } + + if (mgmt_event == NET_EVENT_IPV6_NBR_ADD) { + nbr_info = (struct net_event_ipv6_nbr *)cb->info; + if (!nbr_info) { + NET_ERR("Invalid info received on event"); + return; + } + + nbr = net_ipv6_nbr_lookup(iface, &nbr_info->addr); + if (!nbr || !net_ipv6_nbr_data(nbr)) { + NET_ERR("Invalid neighbor data received"); + return; + } + + NET_DBG("NBR add %s", net_sprint_ipv6_addr(&nbr_info->addr)); + + ret = send_ipv6_neighbors(&http_ctx, ws_dst, nbr); + if (ret < 0) { + NET_ERR("Cannot send neighbor information (%d)", ret); + return; + } + } else if (mgmt_event == NET_EVENT_IPV6_NBR_DEL) { + nbr_info = (struct net_event_ipv6_nbr *)cb->info; + if (!nbr_info) { + NET_ERR("Invalid info received on event"); + return; + } + + NET_DBG("NBR del %s", net_sprint_ipv6_addr(&nbr_info->addr)); + + ret = send_ipv6_neighbor_deletion(&http_ctx, ws_dst, iface, + &nbr_info->addr); + if (ret < 0) { + NET_ERR("Cannot send neighbor information (%d)", ret); + return; + } + } else if (mgmt_event == NET_EVENT_IPV6_ROUTE_ADD) { + route_info = (struct net_event_ipv6_route *)cb->info; + if (!route_info) { + NET_ERR("Invalid info received on event"); + return; + } + + route = net_route_lookup(iface, &route_info->addr); + if (!route) { + NET_ERR("Invalid route entry received"); + return; + } + + NET_DBG("ROUTE add addr %s/%d", + net_sprint_ipv6_addr(&route_info->addr), + route_info->prefix_len); + { + NET_DBG("ROUTE add nexthop %s", + net_sprint_ipv6_addr(&route_info->nexthop)); + + } + + coap_send_request(&route_info->nexthop, + COAP_REQ_RPL_OBS, coap_obs_cb, NULL); + + ret = send_ipv6_routes(&http_ctx, ws_dst, iface, route); + if (ret < 0) { + NET_ERR("Cannot send route information (%d)", ret); + return; + } + } else if (mgmt_event == NET_EVENT_IPV6_ROUTE_DEL) { + route_info = (struct net_event_ipv6_route *)cb->info; + if (!route_info) { + NET_ERR("Invalid info received on event"); + return; + } + + NET_DBG("ROUTE del addr %s/%d", + net_sprint_ipv6_addr(&route_info->addr), + route_info->prefix_len); + { + NET_DBG("ROUTE del nexthop %s", + net_sprint_ipv6_addr(&route_info->nexthop)); + + } + + ret = send_ipv6_route_deletion(&http_ctx, ws_dst, iface, + route_info); + if (ret < 0) { + NET_ERR("Cannot send route information (%d)", ret); + return; + } + + coap_remove_node_from_topology(&route_info->nexthop); + + ret = send_topology_information(&http_ctx, ws_dst); + if (ret < 0) { + NET_ERR("Cannot send topology information (%d)", ret); + } + } +} + +#define WS_HTTP_STACK_SIZE 2500 +NET_STACK_DEFINE(WS_HTTP, ws_http_stack, WS_HTTP_STACK_SIZE, + WS_HTTP_STACK_SIZE); +static struct k_thread ws_http_thread_data; + +static void ws_http_thread(void) +{ + while (1) { + k_sem_take(&ws_reply, K_FOREVER); + + if (ws_ctx.data_set) { + ws_serve_replies(); + } + } +} + +void start_http_server(struct net_if *iface) +{ + struct sockaddr addr, *server_addr; + int ret; + + /* + * There are several options here for binding to local address. + * 1) The server address can be left empty in which case the + * library will bind to both IPv4 and IPv6 addresses and to + * port 80 which is the default, or 443 if TLS is enabled. + * 2) The server address can be partially filled, meaning that + * the address can be left to 0 and port can be set if a value + * other than 80 is desired. If the protocol family in sockaddr + * is set to AF_UNSPEC, then both IPv4 and IPv6 sockets are bound. + * 3) The address can be set to some real value. + */ +#define ADDR_OPTION 1 + +#if ADDR_OPTION == 1 + server_addr = NULL; + + ARG_UNUSED(addr); + +#elif ADDR_OPTION == 2 + /* Accept any local listening address */ + memset(&addr, 0, sizeof(addr)); + + net_sin(&addr)->sin_port = htons(ZEPHYR_PORT); + + /* In this example, listen only IPv4 */ + addr.sa_family = AF_INET; + + server_addr = &addr; + +#elif ADDR_OPTION == 3 + /* Set the bind address according to your configuration */ + memset(&addr, 0, sizeof(addr)); + + /* In this example, listen only IPv6 */ + addr.sa_family = AF_INET6; + net_sin6(&addr)->sin6_port = htons(ZEPHYR_PORT); + + ret = net_ipaddr_parse(ZEPHYR_ADDR, strlen(ZEPHYR_ADDR), &addr); + if (ret < 0) { + NET_ERR("Cannot set local address (%d)", ret); + panic(NULL); + } + + server_addr = &addr; + +#else + server_addr = NULL; + + ARG_UNUSED(addr); +#endif + + http_server_add_default(&http_urls, default_handler); + http_server_add_url(&http_urls, "/", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/index.html", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/style.css", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/br.js", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/favicon.ico", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/ws", HTTP_URL_WEBSOCKET); + + ret = http_server_init(&http_ctx, &http_urls, server_addr, + result, sizeof(result), + "Zephyr HTTP Server for border router", NULL); + if (ret < 0) { + NET_ERR("Cannot init web server (%d)", ret); + return; + } + + + http_set_cb(&http_ctx, http_connected, http_received, http_sent, + http_closed); + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) + net_app_set_net_pkt_pool(&http_ctx.app_ctx, tx_slab, data_pool); +#endif + +#if defined(CONFIG_NET_APP_TLS) + ret = http_server_set_tls(&http_ctx, + NULL, + INSTANCE_INFO, + strlen(INSTANCE_INFO), + setup_cert, + NULL, + &ssl_rx_pool, + https_stack, + K_THREAD_STACK_SIZEOF(https_stack)); + if (ret < 0) { + NET_ERR("Cannot enable TLS support (%d)", ret); + } +#endif + + http_server_enable(&http_ctx); + + net_mgmt_init_event_callback(&br_mgmt_cb, mgmt_cb, + NET_EVENT_IPV6_NBR_ADD | + NET_EVENT_IPV6_NBR_DEL | + NET_EVENT_IPV6_ROUTE_ADD | + NET_EVENT_IPV6_ROUTE_DEL); + net_mgmt_add_event_callback(&br_mgmt_cb); + + /* Run http(WS) replies in separate thread */ + k_sem_init(&ws_reply, 0, UINT_MAX); + + k_thread_create(&ws_http_thread_data, ws_http_stack, + K_THREAD_STACK_SIZEOF(ws_http_stack), + (k_thread_entry_t)ws_http_thread, NULL, NULL, NULL, + K_PRIO_COOP(10), 0, 0); +} + +void stop_http_server(void) +{ + http_server_disable(&http_ctx); + http_release(&http_ctx); +} diff --git a/samples/net/rpl_border_router/src/index.html b/samples/net/rpl_border_router/src/index.html new file mode 100644 index 0000000000000..aedd3b3282934 --- /dev/null +++ b/samples/net/rpl_border_router/src/index.html @@ -0,0 +1,62 @@ + + + + + + + + + Zephyr RPL Border Router + + + +
    +
    +

    Zephyr RPL Border Router

    +
    +
    +
    + + + + + +
    +
    +
    +
    +
    +
    +
    + + +
    + + diff --git a/samples/net/rpl_border_router/src/main.c b/samples/net/rpl_border_router/src/main.c new file mode 100644 index 0000000000000..5e7dd4691a93b --- /dev/null +++ b/samples/net/rpl_border_router/src/main.c @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "rpl-br/main" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include + +#include + +#include "config.h" + +/* Sets the network parameters */ + +void main(void) +{ + struct net_if *iface = NULL, *mgmt_iface = NULL; + + NET_DBG("RPL border router starting"); + +#if defined(CONFIG_NET_L2_IEEE802154) + iface = net_if_get_ieee802154(); + if (!iface) { + NET_INFO("No IEEE 802.15.4 network interface found."); + } +#else +#if defined(CONFIG_QEMU_TARGET) + /* This is just for testing purposes, the RPL network + * is not fully functional with QEMU. + */ + iface = net_if_get_default(); +#endif +#endif + + if (!iface) { + NET_INFO("Cannot continue because no suitable network " + "interface exists."); + return; + } + + mgmt_iface = net_if_get_first_by_type(&NET_L2_GET_NAME(ETHERNET)); + if (!mgmt_iface) { + NET_INFO("No management network interface found."); + } else { + start_http_server(mgmt_iface); + } + + setup_rpl(iface, CONFIG_NET_RPL_PREFIX); + +#if defined(CONFIG_COAP) + coap_init(); +#endif +} diff --git a/samples/net/rpl_border_router/src/rpl.c b/samples/net/rpl_border_router/src/rpl.c new file mode 100644 index 0000000000000..8bd8756c66403 --- /dev/null +++ b/samples/net/rpl_border_router/src/rpl.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "rpl-br/rpl" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include "../../../subsys/net/ip/rpl.h" + +#include "config.h" + +static struct { + struct in6_addr dag_id; + u8_t dag_init_version; + bool dag_has_version; + + struct in6_addr prefix; + u8_t prefix_len; +} rpl; + +static bool br_join_dag(struct net_rpl_dio *dio) +{ + static u8_t last_version; + + if (net_ipv6_addr_cmp(&dio->dag_id, &rpl.dag_id)) { + if (rpl.dag_init_version != dio->version && + last_version != dio->version) { + NET_DBG("Me root, DIO version %d instance %d", + dio->version, dio->instance_id); + last_version = dio->version; + } + + if (dio->version > NET_RPL_LOLLIPOP_CIRCULAR_REGION) { + rpl.dag_init_version = dio->version; + rpl.dag_has_version = true; + } +#if CONFIG_SYS_LOG_NET_LEVEL > 3 + } else { + char me[NET_IPV6_ADDR_LEN]; + char other[NET_IPV6_ADDR_LEN]; + + net_addr_ntop(AF_INET6, &dio->dag_id, other, + NET_IPV6_ADDR_LEN); + net_addr_ntop(AF_INET6, &rpl.dag_id, me, NET_IPV6_ADDR_LEN); + + NET_DBG("Other root %s, me %s, DIO version %d instance %d", + other, me, dio->version, dio->instance_id); +#endif + } + + return 0; +} + +bool setup_rpl(struct net_if *iface, const char *addr_prefix) +{ + char prefix[NET_IPV6_ADDR_LEN + 1]; + struct net_rpl_dag *dag; + char *slash; + bool ret; + + /* As the addr_prefix is Kconfig option we need to copy it to temporary + * buffer in order to be able to manipulate it. + */ + memset(prefix, 0, sizeof(prefix)); + memcpy(prefix, addr_prefix, min(strlen(addr_prefix), + NET_IPV6_ADDR_LEN)); + + slash = strstr(prefix, "/"); + if (!slash) { + rpl.prefix_len = 64; + } else { + *slash = '\0'; + rpl.prefix_len = atoi(slash + 1); + } + + if (rpl.prefix_len == 0) { + NET_ERR("Invalid prefix length %s", slash + 1); + return false; + } + + if (net_addr_pton(AF_INET6, prefix, &rpl.prefix) < 0) { + NET_ERR("Invalid IPv6 prefix %s", prefix); + return false; + } + + net_rpl_set_join_callback(br_join_dag); + + if (rpl.dag_has_version) { + net_rpl_lollipop_increment(&rpl.dag_init_version); + + dag = net_rpl_set_root_with_version(iface, + CONFIG_NET_RPL_DEFAULT_INSTANCE, &rpl.dag_id, + rpl.dag_init_version); + } else { + dag = net_rpl_set_root(iface, CONFIG_NET_RPL_DEFAULT_INSTANCE, + &rpl.prefix); + } + if (!dag) { + NET_ERR("Cannot set root node"); + return false; + } + + net_rpl_dag_set_grounded_status(dag, 1); + + ret = net_rpl_set_prefix(iface, dag, &rpl.prefix, rpl.prefix_len); + if (!ret) { + NET_ERR("Cannot set prefix %s/%d", prefix, rpl.prefix_len); + return false; + } + +#if CONFIG_SYS_LOG_NET_LEVEL > 3 + { + char out[NET_IPV6_ADDR_LEN]; + + if (net_addr_ntop(AF_INET6, &rpl.prefix, out, + NET_IPV6_ADDR_LEN)) { + if (rpl.dag_has_version) { + NET_DBG("New RPL dag %s/%d version %u created", + out, rpl.prefix_len, + rpl.dag_init_version); + } else { + NET_DBG("New RPL dag %s/%d created", out, + rpl.prefix_len); + } + } + } +#endif + + return true; +} diff --git a/samples/net/rpl_border_router/src/shell.c b/samples/net/rpl_border_router/src/shell.c new file mode 100644 index 0000000000000..23cbddbb2526c --- /dev/null +++ b/samples/net/rpl_border_router/src/shell.c @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "rpl-br/shell" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include "../../../subsys/net/ip/rpl.h" + +#include "config.h" + +#define BR_SHELL_MODULE "br" + +int br_repair(int argc, char *argv[]) +{ + ARG_UNUSED(argc); + ARG_UNUSED(argv); + + NET_INFO("Starting global repair..."); + + net_rpl_repair_root(CONFIG_NET_RPL_DEFAULT_INSTANCE); + + return 0; +} + +int coap_send(int argc, char *argv[]) +{ + struct in6_addr peer_addr; + enum coap_request_type type; + int r; + + if (argc != 3 || !argv[1] || !argv[2]) { + NET_INFO("Invalid arguments"); + return -EINVAL; + } + + r = net_addr_pton(AF_INET6, argv[2], &peer_addr); + if (r < 0) { + return -EINVAL; + } + + if (strcmp(argv[1], "toggle") == 0) { + type = COAP_REQ_TOGGLE_LED; + } else if (strcmp(argv[1], "rpl_obs") == 0) { + type = COAP_REQ_RPL_OBS; + } else { + NET_INFO("Invalid arguments"); + return -EINVAL; + } + + coap_send_request(&peer_addr, type, NULL, NULL); + + return 0; +} + +static struct shell_cmd br_commands[] = { + /* Keep the commands in alphabetical order */ + { "coap", coap_send, "\n\tSend CoAP commands to Node\n" + "toggle \n\tToggle the LED on Node\n" + "rpl_obs \n\tSet RPL observer on Node\n" + }, + { "repair", br_repair, + "\n\tGlobal repair RPL network" }, + { NULL, NULL, NULL } +}; + +SHELL_REGISTER(BR_SHELL_MODULE, br_commands); diff --git a/samples/net/rpl_border_router/src/style.css b/samples/net/rpl_border_router/src/style.css new file mode 100644 index 0000000000000..ca259d8871b03 --- /dev/null +++ b/samples/net/rpl_border_router/src/style.css @@ -0,0 +1,173 @@ +h1 { + margin-left: 20px; +} + +table, th, td { + border-collapse: collapse; +} + +th, td { + padding: 15px; +} + +th { + text-align: left; +} + +.tabcontent table { + border-collapse: collapse; + width: 100%; +} + +.tabcontent td, th { + border: 1px solid #dddddd; + text-align: left; + padding: 8px; +} + +.tabcontent tr:nth-child(even) { + background-color: #dddddd; +} + +/* Change background color on hover +.tabcontent tr:hover { + background-color:#f5f5f5 +} +*/ + +td.wrappable { + word-wrap: break-word; + white-space: normal; +} + +.box { + background-color: white; + color: black; + border-radius: 5px; + font-size: 18px; +} + +body { + margin: 40px; +} + +.sidebar { + grid-area: sidebar; +} + +.content { + grid-area: content; + height: 100%; + overflow: auto; +} + +.header { + grid-area: header; + text-align: center; + background-color: #ededed; +} + +.footer { + grid-area: footer; + scroll-behavior: auto; + font-size: 100%; + overflow: auto; +} + +.container { + display: grid; + grid-gap: 10px; + grid-template-columns: 15% 45% 40%; + grid-template-rows: 3% 90% 5%; + grid-template-areas: + "header header header" + "sidebar content content" + "footer footer footer"; +} + +.footer { + background-color: #999; +} + +/* Style the tab */ +div.tab { + overflow: hidden; + background-color: #444444; + border: 1px solid #444444; +} + +/* Style the buttons inside the tab */ +div.tab button { + background-color: inherit; + float: left; + border: none; + outline: none; + cursor: pointer; + padding: 14px 16px; + transition: 0.3s; + color: #b0b0b0; + font-size: 14px; +} + +/* Change background color of buttons on hover */ +div.tab button:hover { + background-color: #444444; + color:#fff; +} + +/* Create an active/current tablink class */ +div.tab button.active { + background-color: #444444; + color:#fff; +} + +/* Style the tab content */ +.tabcontent { + display: none; + padding: 6px 12px; + border: 1px solid #ccc; + border-top: none; +} + +#topology { + height: 600px; + border: 1px solid; +} + +.configuration { + color: #444444; + text-decoration: none; + font-weight: 500; + padding-left: 17px; +} + +.console { + color: #444444; + text-decoration: none; + font-weight: 500; + padding-left: 17px; +} + +.connectButton { + cursor: pointer; + width: 125px; + height: 35px; + border-radius: 30px; + text-align: center; + letter-spacing: .9px; + font-weight: bold; + line-height: 27px; + color: #ffffff; + background-color: #444444; + border: solid 2px #444444; + text-transform: uppercase; + font-size: 10px; +} + +.connectButton:hover { + outline:0px; +} + +.title { + color: #444444; +} diff --git a/samples/net/rpl-node/CMakeLists.txt b/samples/net/rpl_node/CMakeLists.txt similarity index 100% rename from samples/net/rpl-node/CMakeLists.txt rename to samples/net/rpl_node/CMakeLists.txt diff --git a/samples/net/rpl-node/README.rst b/samples/net/rpl_node/README.rst similarity index 51% rename from samples/net/rpl-node/README.rst rename to samples/net/rpl_node/README.rst index c37a15e681029..7400263c9732d 100644 --- a/samples/net/rpl-node/README.rst +++ b/samples/net/rpl_node/README.rst @@ -20,11 +20,7 @@ The sample exports the following resources through a CoAP server role: .. code-block:: none /led - /ipv6/neighbors - /rpl-info - /rpl-info/parent - /rpl-info/rank - /rpl-info/link-metric + /rpl-obs These resources allow you to toggle an on-board LED (if available) and build the RPL mesh network topology from node RPL information. @@ -32,33 +28,11 @@ the RPL mesh network topology from node RPL information. Building And Running ******************** -If you're using a Sparrow border router, follow the steps below to build and -run Sparrow BR. (Sparrow has its own TLV mechanism to build topology that -Zephyr doesn't support.) A patch is provided in the sample folder to to support -building topology with CoAP-based responses. +Running BR +========== -Running Sparrow BR -================== - -.. code-block:: console - - git clone https://github.com/sics-iot/sparrow.git - cd sparrow - git am 0001-Added-CoAP-support-for-Sparrow-Border-Router.patch - cd products/sparrow-border-router - sudo make connect-high PORT=/dev/ttyACM0 - -If your PC is using an http proxy, you should unset it for this sample. -Wait until the border router is up and running. The python script used below -will run a web-based UI. - -.. code-block:: console - - cd examples/sparrow - ./wsdemoserver.py - -Wait until you see "Connected" message on console. Unset proxy in browser -and open 127.0.0.1:8000. +Follow the instructions from :ref:`rpl-border-router-sample` to run Zephyr +RPL border router. Running RPL node ================ diff --git a/samples/net/rpl_node/boards/nrf52840_pca10056.conf b/samples/net/rpl_node/boards/nrf52840_pca10056.conf new file mode 100644 index 0000000000000..b7e4f6b78b432 --- /dev/null +++ b/samples/net/rpl_node/boards/nrf52840_pca10056.conf @@ -0,0 +1,4 @@ +CONFIG_IEEE802154_NRF5=y +CONFIG_IEEE802154_NRF5_DRV_NAME="IEEE802154_nrf5" +CONFIG_IEEE802154_NRF5_INIT_PRIO=80 +CONFIG_NET_APP_IEEE802154_DEV_NAME="IEEE802154_nrf5" diff --git a/samples/net/rpl_node/boards/quark_se_c1000_devboard.conf b/samples/net/rpl_node/boards/quark_se_c1000_devboard.conf new file mode 100644 index 0000000000000..4fe667cf3c5a8 --- /dev/null +++ b/samples/net/rpl_node/boards/quark_se_c1000_devboard.conf @@ -0,0 +1,11 @@ +CONFIG_ARC_INIT=n + +CONFIG_IEEE802154_CC2520=y + +CONFIG_NET_APP_IEEE802154_DEV_NAME="cc2520" + +CONFIG_IEEE802154_CC2520_RANDOM_MAC=n +CONFIG_IEEE802154_CC2520_MAC4=0x00 +CONFIG_IEEE802154_CC2520_MAC5=0x00 +CONFIG_IEEE802154_CC2520_MAC6=0x00 +CONFIG_IEEE802154_CC2520_MAC7=0x01 diff --git a/samples/net/rpl-node/prj_quark_se_c1000_devboard.conf b/samples/net/rpl_node/prj.conf similarity index 69% rename from samples/net/rpl-node/prj_quark_se_c1000_devboard.conf rename to samples/net/rpl_node/prj.conf index 7cf74350ddc0e..166f05b263c59 100644 --- a/samples/net/rpl-node/prj_quark_se_c1000_devboard.conf +++ b/samples/net/rpl_node/prj.conf @@ -1,5 +1,3 @@ -CONFIG_ARC_INIT=n - CONFIG_NETWORKING=y CONFIG_ENTROPY_GENERATOR=y @@ -30,7 +28,6 @@ CONFIG_NET_LOG=y CONFIG_SYS_LOG_SHOW_COLOR=y CONFIG_SYS_LOG_NET_LEVEL=4 CONFIG_INIT_STACKS=y -CONFIG_PRINTK=y CONFIG_NET_BUF_LOG=n CONFIG_SYS_LOG_NET_BUF_LEVEL=1 @@ -44,28 +41,24 @@ CONFIG_NET_DEBUG_COAP=y CONFIG_NET_RPL=y CONFIG_NET_RPL_PROBING=y -CONFIG_NET_RPL_STATS=y -CONFIG_NET_RPL_MIN_HOP_RANK_INC=128 +CONFIG_NET_ROUTE=y CONFIG_NET_L2_IEEE802154=y CONFIG_NET_L2_IEEE802154_FRAGMENT=y -CONFIG_IEEE802154_CC2520=y -CONFIG_IEEE802154_CC2520_AUTO_ACK=n # IP core debug -CONFIG_NET_DEBUG_CORE=y -CONFIG_NET_DEBUG_IPV6_NBR_CACHE=y -CONFIG_NET_DEBUG_IPV6=y -CONFIG_NET_DEBUG_NET_BUF=n +CONFIG_NET_DEBUG_CORE=n +CONFIG_NET_DEBUG_IPV6_NBR_CACHE=n +CONFIG_NET_DEBUG_IPV6=n CONFIG_NET_DEBUG_UTILS=n -CONFIG_NET_DEBUG_IF=y -CONFIG_NET_DEBUG_ICMPV6=y +CONFIG_NET_DEBUG_IF=n +CONFIG_NET_DEBUG_ICMPV6=n CONFIG_NET_DEBUG_CONN=n CONFIG_NET_DEBUG_UDP=n CONFIG_NET_DEBUG_CONTEXT=n -CONFIG_NET_DEBUG_ROUTE=y +CONFIG_NET_DEBUG_ROUTE=n CONFIG_NET_DEBUG_6LO=n -CONFIG_NET_DEBUG_RPL=y +CONFIG_NET_DEBUG_RPL=n CONFIG_NET_STATISTICS=n CONFIG_NET_STATISTICS_PERIODIC_OUTPUT=n @@ -79,10 +72,3 @@ CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL=0 CONFIG_NET_APP_SETTINGS=y CONFIG_NET_APP_IEEE802154_CHANNEL=18 -CONFIG_NET_APP_IEEE802154_DEV_NAME="cc2520" - -CONFIG_IEEE802154_CC2520_RANDOM_MAC=n -CONFIG_IEEE802154_CC2520_MAC4=0x00 -CONFIG_IEEE802154_CC2520_MAC5=0x00 -CONFIG_IEEE802154_CC2520_MAC6=0x00 -CONFIG_IEEE802154_CC2520_MAC7=0x03 diff --git a/samples/net/rpl-node/sample.yaml b/samples/net/rpl_node/sample.yaml similarity index 53% rename from samples/net/rpl-node/sample.yaml rename to samples/net/rpl_node/sample.yaml index 4a8d2367415ca..ad6f8c12939c8 100644 --- a/samples/net/rpl-node/sample.yaml +++ b/samples/net/rpl_node/sample.yaml @@ -1,14 +1,12 @@ common: harness: net - depends_on: ieee802154 tags: net rpl ieee802154 sample: description: Can be used to test RPL network. Needs to be run in real device. This cannot be run in QEMU. name: RPL node demo application + platforms: all tests: - test_quark_se_c1000_devboard: - extra_args: CONF_FILE="prj_quark_se_c1000_devboard.conf" - platform_whitelist: quark_se_c1000_devboard - filter: ASSERT == 0 + test: + platform_whitelist: quark_se_c1000_devboard nrf52840_pca10056 diff --git a/samples/net/rpl_node/src/main.c b/samples/net/rpl_node/src/main.c new file mode 100644 index 0000000000000..a8778d71fb001 --- /dev/null +++ b/samples/net/rpl_node/src/main.c @@ -0,0 +1,511 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "rpl-node" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include <6lo.h> +#include +#include + +#include + +#include +#include + +#define MY_COAP_PORT 5683 + +#define ALL_NODES_LOCAL_COAP_MCAST \ + { { { 0xff, 0x02, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xfd } } } + +#ifndef LED0_GPIO_CONTROLLER +#ifdef LED0_GPIO_PORT +#define LED0_GPIO_CONTROLLER LED0_GPIO_PORT +#else +#define LED0_GPIO_CONTROLLER "(fail)" +#define LED0_GPIO_PIN 0 +#endif +#endif + +#define LED_GPIO_NAME LED0_GPIO_CONTROLLER +#define LED_PIN LED0_GPIO_PIN + +#define RPL_MAX_REPLY 75 + +#define PKT_WAIT_TIME K_SECONDS(1) + +static struct net_context *context; +static struct device *led0; + +#define NUM_OBSERVERS 3 +#define NUM_PENDINGS 3 + +static struct coap_observer observers[NUM_OBSERVERS]; +static struct coap_pending pendings[NUM_PENDINGS]; + +static struct k_delayed_work retransmit_work; + +static void get_from_ip_addr(struct coap_packet *cpkt, + struct sockaddr_in6 *from) +{ + struct net_udp_hdr hdr, *udp_hdr; + + udp_hdr = net_udp_get_hdr(cpkt->pkt, &hdr); + if (!udp_hdr) { + return; + } + + net_ipaddr_copy(&from->sin6_addr, &NET_IPV6_HDR(cpkt->pkt)->src); + from->sin6_port = udp_hdr->src_port; + from->sin6_family = AF_INET6; +} + +static void send_error_response(struct coap_resource *resource, + struct coap_packet *request, + struct sockaddr_in6 *from) +{ + struct net_context *context; + struct coap_packet response; + struct net_pkt *pkt; + struct net_buf *frag; + u16_t id; + int r; + + id = coap_header_get_id(request); + context = net_pkt_context(request->pkt); + + pkt = net_pkt_get_tx(context, PKT_WAIT_TIME); + if (!pkt) { + return; + } + + frag = net_pkt_get_data(context, PKT_WAIT_TIME); + if (!frag) { + net_pkt_unref(pkt); + return; + } + + net_pkt_frag_add(pkt, frag); + + r = coap_packet_init(&response, pkt, 1, COAP_TYPE_ACK, + 0, NULL, COAP_RESPONSE_CODE_BAD_REQUEST, id); + if (r < 0) { + net_pkt_unref(pkt); + return; + } + + r = net_context_sendto(pkt, (const struct sockaddr *)from, + sizeof(struct sockaddr_in6), + NULL, 0, NULL, NULL); + if (r < 0) { + net_pkt_unref(pkt); + } +} + +static int well_known_core_get(struct coap_resource *resource, + struct coap_packet *request) +{ + struct coap_packet response; + struct sockaddr_in6 from; + struct net_pkt *pkt; + struct net_buf *frag; + int r; + + NET_DBG(""); + + pkt = net_pkt_get_tx(context, K_FOREVER); + if (!pkt) { + return -ENOMEM; + } + + frag = net_pkt_get_data(context, K_FOREVER); + if (!frag) { + net_pkt_unref(pkt); + return -ENOMEM; + } + + net_pkt_frag_add(pkt, frag); + + r = coap_well_known_core_get(resource, request, &response, pkt); + if (r < 0) { + net_pkt_unref(response.pkt); + send_error_response(resource, request, &from); + + return r; + } + + get_from_ip_addr(request, &from); + + r = net_context_sendto(response.pkt, (const struct sockaddr *)&from, + sizeof(struct sockaddr_in6), + NULL, 0, NULL, NULL); + if (r < 0) { + net_pkt_unref(response.pkt); + } + + return r; +} + +static void toggle_led(void) +{ + u32_t led = 0; + int r; + + if (!led0) { + return; + } + + r = gpio_pin_read(led0, LED_PIN, &led); + if (r < 0) { + return; + } + + gpio_pin_write(led0, LED_PIN, !led); +} + +static int led_post(struct coap_resource *resource, + struct coap_packet *request) +{ + NET_DBG(""); + + toggle_led(); + + return 0; +} + +static void retransmit_request(struct k_work *work) +{ + struct coap_pending *pending; + int r; + + pending = coap_pending_next_to_expire(pendings, NUM_PENDINGS); + if (!pending) { + return; + } + + r = net_context_sendto(pending->pkt, &pending->addr, + sizeof(struct sockaddr_in6), + NULL, 0, NULL, NULL); + if (r < 0) { + return; + } + + if (!coap_pending_cycle(pending)) { + coap_pending_clear(pending); + return; + } + + k_delayed_work_submit(&retransmit_work, pending->timeout); +} + +static int append_rpl_parent(struct coap_packet *response) +{ + struct net_rpl_instance *rpl; + struct in6_addr *parent; + char out[RPL_MAX_REPLY]; + u16_t out_len; + u8_t pos; + + rpl = net_rpl_get_default_instance(); + + pos = snprintk(out, sizeof(out), "parent-"); + out_len = pos; + + if (!rpl || !rpl->current_dag || !rpl->current_dag->preferred_parent) { + pos = snprintk(&out[out_len], sizeof(out), "None"); + out_len += pos; + + } else { + parent = net_rpl_get_parent_addr(net_pkt_iface(response->pkt), + rpl->current_dag->preferred_parent); + pos = snprintk(&out[out_len], sizeof(out), "%s", + net_sprint_ipv6_addr(parent)); + out_len += pos; + } + + pos = snprintk(&out[out_len], sizeof(out), "\nrank-"); + out_len += pos; + + if (!rpl || !rpl->current_dag) { + pos = snprintk(&out[out_len], sizeof(out), "None"); + out_len += pos; + } else { + pos = snprintk(&out[out_len], sizeof(out), "%u", + rpl->current_dag->rank); + out_len += pos; + } + + return coap_packet_append_payload(response, (u8_t *)out, out_len); +} + +static int send_notification_packet(const struct sockaddr *addr, + u16_t age, + socklen_t addrlen, + u16_t id, + const u8_t *token, + u8_t tkl, + bool is_response) +{ + struct coap_packet response; + struct coap_pending *pending; + struct net_pkt *pkt; + struct net_buf *frag; + u8_t type = COAP_TYPE_CON; + int r; + + if (is_response) { + type = COAP_TYPE_ACK; + } + + if (!is_response) { + id = coap_next_id(); + } + + pkt = net_pkt_get_tx(context, K_FOREVER); + frag = net_pkt_get_data(context, K_FOREVER); + + net_pkt_frag_add(pkt, frag); + + r = coap_packet_init(&response, pkt, 1, type, + tkl, (u8_t *)token, + COAP_RESPONSE_CODE_CONTENT, id); + if (r < 0) { + net_pkt_unref(pkt); + return -EINVAL; + } + + if (age >= 2) { + coap_append_option_int(&response, COAP_OPTION_OBSERVE, age); + } + + r = coap_packet_append_payload_marker(&response); + if (r) { + net_pkt_unref(pkt); + return -EINVAL; + } + + r = append_rpl_parent(&response); + if (r < 0) { + net_pkt_unref(pkt); + return -EINVAL; + } + + if (type == COAP_TYPE_CON) { + pending = coap_pending_next_unused(pendings, NUM_PENDINGS); + if (!pending) { + return -EINVAL; + } + + r = coap_pending_init(pending, &response, addr); + if (r) { + return -EINVAL; + } + + coap_pending_cycle(pending); + pending = coap_pending_next_to_expire(pendings, NUM_PENDINGS); + + k_delayed_work_submit(&retransmit_work, pending->timeout); + } + + return net_context_sendto(pkt, addr, addrlen, NULL, 0, NULL, NULL); +} + +/* RPL Observe Server */ +static int rpl_obs_get(struct coap_resource *resource, + struct coap_packet *request) +{ + struct coap_observer *observer; + struct sockaddr_in6 from; + u8_t token[8]; + u16_t id; + u8_t tkl; + bool observe = true; + + get_from_ip_addr(request, &from); + + if (!coap_request_is_observe(request)) { + observe = false; + goto done; + } + + observer = coap_find_observer_by_addr(observers, NUM_OBSERVERS, + (const struct sockaddr *)&from); + if (observer) { + goto done; + } + + observer = coap_observer_next_unused(observers, NUM_OBSERVERS); + if (!observer) { + return -ENOMEM; + } + + coap_observer_init(observer, request, (const struct sockaddr *)&from); + coap_register_observer(resource, observer); + +done: + id = coap_header_get_id(request); + tkl = coap_header_get_token(request, token); + + return send_notification_packet((const struct sockaddr *)&from, + observe ? resource->age : 0, + sizeof(struct sockaddr_in6), id, + token, tkl, true); +} + +static const char * const led_default_path[] = { "led", NULL }; +static const char * const led_default_attributes[] = { + "title=\"LED toggle\"", + "rt=\"Text\"", + NULL }; + +static const char * const rpl_obs_default_path[] = { "rpl-obs", + NULL }; +static const char * const rpl_obs_default_attributes[] = { + "title=\"RPL Observe\"", + "rt=Text", + NULL }; + +static struct coap_resource resources[] = { + { .get = well_known_core_get, + .post = NULL, + .put = NULL, + .path = COAP_WELL_KNOWN_CORE_PATH, + .user_data = NULL, + }, + { .get = NULL, + .post = led_post, + .put = NULL, + .path = led_default_path, + .user_data = &((struct coap_core_metadata) { + .attributes = led_default_attributes, + }), + }, + { .get = rpl_obs_get, + .post = NULL, + .put = NULL, + .path = rpl_obs_default_path, + .user_data = &((struct coap_core_metadata) { + .attributes = rpl_obs_default_attributes, + }), + }, + { }, +}; + +static void udp_receive(struct net_context *context, + struct net_pkt *pkt, + int status, + void *user_data) +{ + struct coap_packet request; + struct coap_option options[16] = { 0 }; + u8_t opt_num = 16; + int r; + + r = coap_packet_parse(&request, pkt, options, opt_num); + if (r < 0) { + NET_ERR("Invalid data received (%d)\n", r); + goto end; + } + + r = coap_handle_request(&request, resources, options, opt_num); + if (r < 0) { + NET_ERR("No handler for such request (%d)\n", r); + } + +end: + net_pkt_unref(pkt); +} + +static bool join_coap_multicast_group(void) +{ + static struct sockaddr_in6 mcast_addr = { + .sin6_family = AF_INET6, + .sin6_addr = ALL_NODES_LOCAL_COAP_MCAST, + .sin6_port = htons(MY_COAP_PORT) }; + struct net_if_mcast_addr *mcast; + struct net_if *iface; + + iface = net_if_get_default(); + if (!iface) { + NET_ERR("Could not get te default interface\n"); + return false; + } + + mcast = net_if_ipv6_maddr_add(iface, &mcast_addr.sin6_addr); + if (!mcast) { + NET_ERR("Could not add multicast address to interface\n"); + return false; + } + + return true; +} + +static void init_app(void) +{ + static struct sockaddr_in6 any_addr = { + .sin6_family = AF_INET6, + .sin6_addr = IN6ADDR_ANY_INIT, + .sin6_port = htons(MY_COAP_PORT) }; + int r; + + led0 = device_get_binding(LED_GPIO_NAME); + if (led0) { + gpio_pin_configure(led0, LED_PIN, GPIO_DIR_OUT); + } else { + NET_WARN("Failed to bind '%s'" + "fake_led will provide dummpy replies", + LED_GPIO_NAME); + } + + if (!join_coap_multicast_group()) { + NET_ERR("Could not join CoAP multicast group"); + return; + } + + r = net_context_get(AF_INET6, SOCK_DGRAM, IPPROTO_UDP, &context); + if (r) { + NET_ERR("Could not get an UDP context"); + return; + } + + r = net_context_bind(context, (struct sockaddr *) &any_addr, + sizeof(any_addr)); + if (r) { + NET_ERR("Could not bind the context"); + return; + } + + r = net_context_recv(context, udp_receive, 0, NULL); + if (r) { + NET_ERR("Could not receive in the context"); + } + + k_delayed_work_init(&retransmit_work, retransmit_request); +} + +void main(void) +{ + NET_DBG("Start Demo"); + + init_app(); +}