diff --git a/drivers/console/CMakeLists.txt b/drivers/console/CMakeLists.txt index a193be2f3bb62..2c1005fdf02b9 100644 --- a/drivers/console/CMakeLists.txt +++ b/drivers/console/CMakeLists.txt @@ -6,4 +6,4 @@ zephyr_sources_if_kconfig(ipm_console_sender.c) zephyr_sources_if_kconfig(uart_pipe.c) zephyr_sources_if_kconfig(telnet_console.c) zephyr_sources_if_kconfig(xtensa_sim_console.c) -zephyr_sources_if_kconfig(native_posix_console.c) +zephyr_sources_if_kconfig(websocket_console.c) diff --git a/drivers/console/Kconfig b/drivers/console/Kconfig index ecba136a741ce..f8ca29f3b2802 100644 --- a/drivers/console/Kconfig +++ b/drivers/console/Kconfig @@ -224,4 +224,5 @@ config NATIVE_POSIX_CONSOLE_INIT_PRIORITY Device driver initialization priority. source "drivers/console/Kconfig.telnet" +source "drivers/console/Kconfig.ws" endif diff --git a/drivers/console/Kconfig.ws b/drivers/console/Kconfig.ws new file mode 100644 index 0000000000000..67dad14e595da --- /dev/null +++ b/drivers/console/Kconfig.ws @@ -0,0 +1,112 @@ +# Kconfig - console driver configuration options + +# +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig WEBSOCKET_CONSOLE + bool "Enable websocket console service" + default n + select NETWORKING + select NET_TCP + select HTTP_PARSER + select HTTP_SERVER + select WEBSOCKET + help + This option enables console over a websocket link. Currently, + it is basically just a redirection of the Zephyr console through + websocket. It nicely works along with another console driver (like + uart), twist being that it will take over the output only if a + successful connection to its HTTP service is done. + +if WEBSOCKET_CONSOLE + +config WEBSOCKET_CONSOLE_LINE_BUF_SIZE + int "WS console line buffer size" + default 128 + help + This option can be used to modify the size of the buffer storing + console output line, prior to sending it through the network. + Of course an output line can be longer than such size, it just + means sending it will start as soon as it reaches this size. + It really depends on what type of output is expected. + If there is a lot of short lines, then lower this value. If there + are longer lines, then raise this value. + +config WEBSOCKET_CONSOLE_LINE_BUF_NUMBERS + int "WS console line buffers" + default 4 + help + This option can be used to modify the amount of line buffers the + driver can use. It really depends on how much output is meant to be + sent, depending on the system load etc. You can play on both + WEBSOCKET_CONSOLE_LINE_BUF_SIZE and this current option to get the + best possible buffer settings you need. + +config WEBSOCKET_CONSOLE_SEND_TIMEOUT + int "WS console line send timeout" + default 100 + help + This option can be used to modify the duration of the timer that kick + in when a line buffer is not empty but did not yet meet the line feed. + +config WEBSOCKET_CONSOLE_SEND_THRESHOLD + int "WS console line send threshold" + default 5 + help + This option can be used to modify the minimal amount of a line buffer + that can be sent by the WS server when nothing has happened for + a little while (see WEBSOCKET_CONSOLE_SEND_TIMEOUT) and when the line + buffer did not meet the line feed yet. + +config WEBSOCKET_CONSOLE_STACK_SIZE + int "WS console inner thread stack size" + default 1500 + help + This option helps to fine-tune WS console inner thread stack size. + +config WEBSOCKET_CONSOLE_PRIO + int "WS console inner thread priority" + default 7 + help + This option helps to fine-tune WS console inner thread priority. + +config SYS_LOG_WEBSOCKET_CONSOLE_LEVEL + int "WS console log level" + default 0 + depends on SYS_LOG + help + Sets log level for websocket console (for WS console dev only) + + Levels are: + + - 0 OFF, do not write + + - 1 ERROR, only write SYS_LOG_ERR + + - 2 WARNING, write SYS_LOG_WRN in addition to previous level + + - 3 INFO, write SYS_LOG_INF in addition to previous levels + + - 4 DEBUG, write SYS_LOG_DBG in addition to previous levels + +config WEBSOCKET_CONSOLE_DEBUG_DEEP + bool "Forward output to original console handler" + depends on UART_CONSOLE + default n + help + For WS console developers only, this will forward each output to + original console handler. So if by chance WS console seems silent, + at least things will be printed to original handler, usually + UART console. + +config WEBSOCKET_CONSOLE_INIT_PRIORITY + int "WS console init priority" + default 99 + help + WS console driver initialization priority. Note that WS works + on application level. Usually, you won't have to tweak this. + +endif diff --git a/drivers/console/websocket_console.c b/drivers/console/websocket_console.c new file mode 100644 index 0000000000000..7448a7d9ffae4 --- /dev/null +++ b/drivers/console/websocket_console.c @@ -0,0 +1,338 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/** + * @file + * @brief Websocket console + * + * + * Websocket console driver. The console is provided over + * a websocket connection. + */ + +#define SYS_LOG_LEVEL CONFIG_SYS_LOG_WEBSOCKET_CONSOLE_LEVEL +#define SYS_LOG_DOMAIN "ws/console" +#include + +#include +#include +#include + +#include +#include +#include +#include + +#define NVT_NUL 0 +#define NVT_LF 10 +#define NVT_CR 13 + +#define WS_CONSOLE_STACK_SIZE CONFIG_WEBSOCKET_CONSOLE_STACK_SIZE +#define WS_CONSOLE_PRIORITY CONFIG_WEBSOCKET_CONSOLE_PRIO +#define WS_CONSOLE_TIMEOUT K_MSEC(CONFIG_WEBSOCKET_CONSOLE_SEND_TIMEOUT) +#define WS_CONSOLE_LINES CONFIG_WEBSOCKET_CONSOLE_LINE_BUF_NUMBERS +#define WS_CONSOLE_LINE_SIZE CONFIG_WEBSOCKET_CONSOLE_LINE_BUF_SIZE +#define WS_CONSOLE_TIMEOUT K_MSEC(CONFIG_WEBSOCKET_CONSOLE_SEND_TIMEOUT) +#define WS_CONSOLE_THRESHOLD CONFIG_WEBSOCKET_CONSOLE_SEND_THRESHOLD + +#define WS_CONSOLE_MIN_MSG 2 + +/* These 2 structures below are used to store the console output + * before sending it to the client. This is done to keep some + * reactivity: the ring buffer is non-protected, if first line has + * not been sent yet, and if next line is reaching the same index in rb, + * the first one will be replaced. In a perfect world, this should + * not happen. However on a loaded system with a lot of debug output + * this is bound to happen eventualy, moreover if it does not have + * the luxury to bufferize as much as it wants to. Just raise + * CONFIG_WEBSOCKET_CONSOLE_LINE_BUF_NUMBERS if possible. + */ +struct line_buf { + char buf[WS_CONSOLE_LINE_SIZE]; + u16_t len; +}; + +struct line_buf_rb { + struct line_buf l_bufs[WS_CONSOLE_LINES]; + u16_t line_in; + u16_t line_out; +}; + +static struct line_buf_rb ws_rb; + +NET_STACK_DEFINE(WS_CONSOLE, ws_console_stack, + WS_CONSOLE_STACK_SIZE, WS_CONSOLE_STACK_SIZE); +static struct k_thread ws_thread_data; +static K_SEM_DEFINE(send_lock, 0, UINT_MAX); + +/* The timer is used to send non-lf terminated output that has + * been around for "tool long". This will prove to be useful + * to send the shell prompt for instance. + * ToDo: raise the time, incrementaly, when no output is coming + * so the timer will kick in less and less. + */ +static void ws_send_prematurely(struct k_timer *timer); +static K_TIMER_DEFINE(send_timer, ws_send_prematurely, NULL); +static int (*orig_printk_hook)(int); + +static struct k_fifo *avail_queue; +static struct k_fifo *input_queue; + +/* Websocket context that this console is related to */ +static struct http_ctx *ws_console; + +extern void __printk_hook_install(int (*fn)(int)); +extern void *__printk_get_hook(void); + +void ws_register_input(struct k_fifo *avail, struct k_fifo *lines, + u8_t (*completion)(char *str, u8_t len)) +{ + ARG_UNUSED(completion); + + avail_queue = avail; + input_queue = lines; +} + +static void ws_rb_init(void) +{ + int i; + + ws_rb.line_in = 0; + ws_rb.line_out = 0; + + for (i = 0; i < WS_CONSOLE_LINES; i++) { + ws_rb.l_bufs[i].len = 0; + } +} + +static void ws_end_client_connection(struct http_ctx *console) +{ + __printk_hook_install(orig_printk_hook); + orig_printk_hook = NULL; + + k_timer_stop(&send_timer); + + ws_send_msg(console, NULL, 0, WS_OPCODE_CLOSE, false, true, + NULL); + + ws_rb_init(); +} + +static void ws_rb_switch(void) +{ + ws_rb.line_in++; + + if (ws_rb.line_in == WS_CONSOLE_LINES) { + ws_rb.line_in = 0; + } + + ws_rb.l_bufs[ws_rb.line_in].len = 0; + + /* Unfortunately, we don't have enough line buffer, + * so we eat the next to be sent. + */ + if (ws_rb.line_in == ws_rb.line_out) { + ws_rb.line_out++; + if (ws_rb.line_out == WS_CONSOLE_LINES) { + ws_rb.line_out = 0; + } + } + + k_timer_start(&send_timer, WS_CONSOLE_TIMEOUT, WS_CONSOLE_TIMEOUT); + k_sem_give(&send_lock); +} + +static inline struct line_buf *ws_rb_get_line_out(void) +{ + u16_t out = ws_rb.line_out; + + ws_rb.line_out++; + if (ws_rb.line_out == WS_CONSOLE_LINES) { + ws_rb.line_out = 0; + } + + if (!ws_rb.l_bufs[out].len) { + return NULL; + } + + return &ws_rb.l_bufs[out]; +} + +static inline struct line_buf *ws_rb_get_line_in(void) +{ + return &ws_rb.l_bufs[ws_rb.line_in]; +} + +/* The actual printk hook */ +static int ws_console_out(int c) +{ + int key = irq_lock(); + struct line_buf *lb = ws_rb_get_line_in(); + bool yield = false; + + lb->buf[lb->len++] = (char)c; + + if (c == '\n' || lb->len == WS_CONSOLE_LINE_SIZE - 1) { + lb->buf[lb->len-1] = NVT_CR; + lb->buf[lb->len++] = NVT_LF; + ws_rb_switch(); + yield = true; + } + + irq_unlock(key); + +#ifdef CONFIG_WEBSOCKET_CONSOLE_DEBUG_DEEP + /* This is ugly, but if one wants to debug websocket console, it + * will also output the character to original console + */ + orig_printk_hook(c); +#endif + + if (yield) { + k_yield(); + } + + return c; +} + +static void ws_send_prematurely(struct k_timer *timer) +{ + struct line_buf *lb = ws_rb_get_line_in(); + + if (lb->len >= WS_CONSOLE_THRESHOLD) { + ws_rb_switch(); + } +} + +static inline void ws_handle_input(struct net_pkt *pkt) +{ + struct console_input *input; + u16_t len, offset, pos; + + len = net_pkt_appdatalen(pkt); + if (len > CONSOLE_MAX_LINE_LEN || len < WS_CONSOLE_MIN_MSG) { + return; + } + + if (!avail_queue || !input_queue) { + return; + } + + input = k_fifo_get(avail_queue, K_NO_WAIT); + if (!input) { + return; + } + + offset = net_pkt_get_len(pkt) - len; + net_frag_read(pkt->frags, offset, &pos, len, (u8_t *)input->line); + + /* The data from websocket does not contain \n or NUL, so insert + * it here. + */ + input->line[len] = NVT_NUL; + + /* LF/CR will be removed if only the line is not NUL terminated */ + if (input->line[len-1] != NVT_NUL) { + if (input->line[len-1] == NVT_LF) { + input->line[len-1] = NVT_NUL; + } + + if (input->line[len-2] == NVT_CR) { + input->line[len-2] = NVT_NUL; + } + } + + k_fifo_put(input_queue, input); +} + +/* The data is coming from outside system and going into zephyr */ +int ws_console_recv(struct http_ctx *ctx, struct net_pkt *pkt) +{ + if (ctx != ws_console) { + return -ENOENT; + } + + ws_handle_input(pkt); + + net_pkt_unref(pkt); + + return 0; +} + +/* This is for transferring data from zephyr to outside system */ +static bool ws_console_send(struct http_ctx *console) +{ + struct line_buf *lb = ws_rb_get_line_out(); + + if (lb) { + (void)ws_send_msg(console, (u8_t *)lb->buf, lb->len, + WS_OPCODE_DATA_TEXT, false, true, NULL); + + /* We reinitialize the line buffer */ + lb->len = 0; + } + + return true; +} + +/* WS console loop, used to send buffered output in the RB */ +static void ws_console_run(void) +{ + while (true) { + k_sem_take(&send_lock, K_FOREVER); + + if (!ws_console_send(ws_console)) { + ws_end_client_connection(ws_console); + } + } +} + +int ws_console_enable(struct http_ctx *ctx) +{ + orig_printk_hook = __printk_get_hook(); + __printk_hook_install(ws_console_out); + + k_timer_start(&send_timer, WS_CONSOLE_TIMEOUT, WS_CONSOLE_TIMEOUT); + + ws_console = ctx; + + return 0; +} + +int ws_console_disable(struct http_ctx *ctx) +{ + if (!ws_console) { + return 0; + } + + if (ws_console != ctx) { + return -ENOENT; + } + + ws_end_client_connection(ws_console); + + ws_console = NULL; + + return 0; +} + +static int ws_console_init(struct device *arg) +{ + k_thread_create(&ws_thread_data, ws_console_stack, + K_THREAD_STACK_SIZEOF(ws_console_stack), + (k_thread_entry_t)ws_console_run, + NULL, NULL, NULL, + K_PRIO_COOP(WS_CONSOLE_PRIORITY), 0, K_MSEC(10)); + + SYS_LOG_INF("Websocket console initialized"); + + return 0; +} + +/* Websocket console is initialized as an application directly, as it requires + * the whole network stack to be ready. + */ +SYS_INIT(ws_console_init, APPLICATION, CONFIG_WEBSOCKET_CONSOLE_INIT_PRIORITY); diff --git a/include/drivers/console/websocket_console.h b/include/drivers/console/websocket_console.h new file mode 100644 index 0000000000000..2ce36ac5e98b8 --- /dev/null +++ b/include/drivers/console/websocket_console.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __WS_CONSOLE_H__ +#define __WS_CONSOLE_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +/** @brief Register websocket input processing + * + * Input processing is started when string is received in WS server. + * Carriage return is translated to NULL making string always NULL + * terminated. Application before calling register function need to + * initialize two fifo queues mentioned below. + * + * @param avail k_fifo queue keeping available input slots + * @param lines k_fifo queue of entered lines which to be processed + * in the application code. + * @param completion callback for tab completion of entered commands + * + * @return N/A + */ +void ws_register_input(struct k_fifo *avail, struct k_fifo *lines, + u8_t (*completion)(char *str, u8_t len)); + +#ifdef __cplusplus +} +#endif + +#endif /* __WS_CONSOLE_H__ */ diff --git a/include/net/http_app.h b/include/net/http_app.h index 440c128c323b5..bea2a682ccb9d 100644 --- a/include/net/http_app.h +++ b/include/net/http_app.h @@ -430,6 +430,27 @@ struct http_ctx { u16_t url_len; } http; +#if defined(CONFIG_WEBSOCKET) + struct { + /** Pending data that is not yet ready for processing */ + struct net_pkt *pending; + + /** Amount of data that needs to be read still */ + u32_t data_waiting; + + /** Websocket connection masking value */ + u32_t masking_value; + + /** How many bytes we have read */ + u32_t data_read; + + /** Message type flag. Value is one of WS_FLAG_XXX flag values + * defined in weboscket.h + */ + u32_t msg_type_flag; + } websocket; +#endif /* CONFIG_WEBSOCKET */ + #if defined(CONFIG_NET_DEBUG_HTTP_CONN) sys_snode_t node; #endif /* CONFIG_HTTP_DEBUG_HTTP_CONN */ diff --git a/include/net/net_pkt.h b/include/net/net_pkt.h index 5a6e77b5909c7..33b28110e300f 100644 --- a/include/net/net_pkt.h +++ b/include/net/net_pkt.h @@ -66,6 +66,10 @@ struct net_pkt { /** @cond ignore */ +#if defined(CONFIG_NET_ROUTING) + struct net_if *orig_iface; /* Original network interface */ +#endif + u8_t *appdata; /* application data starts here */ u8_t *next_hdr; /* where is the next header */ @@ -181,6 +185,23 @@ static inline void net_pkt_set_iface(struct net_pkt *pkt, struct net_if *iface) pkt->lladdr_dst.type = iface->link_addr.type; } +static inline struct net_if *net_pkt_orig_iface(struct net_pkt *pkt) +{ +#if defined(CONFIG_NET_ROUTING) + return pkt->orig_iface; +#else + return pkt->iface; +#endif +} + +static inline void net_pkt_set_orig_iface(struct net_pkt *pkt, + struct net_if *iface) +{ +#if defined(CONFIG_NET_ROUTING) + pkt->orig_iface = iface; +#endif +} + static inline u8_t net_pkt_family(struct net_pkt *pkt) { return pkt->family; diff --git a/include/net/websocket.h b/include/net/websocket.h new file mode 100644 index 0000000000000..47cc0ba5f3f15 --- /dev/null +++ b/include/net/websocket.h @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __WEBSOCKET_H__ +#define __WEBSOCKET_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Websocket library + * @defgroup websocket Websocket Library + * @{ + */ + +/** Values for flag variable in HTTP receive callback */ +#define WS_FLAG_FINAL 0x00000001 +#define WS_FLAG_TEXT 0x00000002 +#define WS_FLAG_BINARY 0x00000004 +#define WS_FLAG_CLOSE 0x00000008 +#define WS_FLAG_PING 0x00000010 +#define WS_FLAG_PONG 0x00000011 + +enum ws_opcode { + WS_OPCODE_CONTINUE = 0x00, + WS_OPCODE_DATA_TEXT = 0x01, + WS_OPCODE_DATA_BINARY = 0x02, + WS_OPCODE_CLOSE = 0x08, + WS_OPCODE_PING = 0x09, + WS_OPCODE_PONG = 0x0A, +}; + +/** + * @brief Send websocket msg to peer. + * + * @details The function will automatically add websocket header to the + * message. + * + * @param ctx Websocket context. + * @param payload Websocket data to send. + * @param payload_len Length of the data to be sent. + * @param opcode Operation code (text, binary, ping, pong, close) + * @param mask Mask the data, see RFC 6455 for details + * @param final Is this final message for this message send. If final == false, + * then the first message must have valid opcode and subsequent messages must + * have opcode WS_OPCODE_CONTINUE. If final == true and this is the only + * message, then opcode should have proper opcode (text or binary) set. + * @param user_send_data User specific data to this connection. This is passed + * as a parameter to sent cb after the packet has been sent. + * + * @return 0 if ok, <0 if error. + */ +int ws_send_msg(struct http_ctx *ctx, u8_t *payload, size_t payload_len, + enum ws_opcode opcode, bool mask, bool final, + void *user_send_data); + +/** + * @brief Send message to client. + * + * @details The function will automatically add websocket header to the + * message. + * + * @param ctx Websocket context. + * @param payload Websocket data to send. + * @param payload_len Length of the data to be sent. + * @param opcode Operation code (text, binary, ping ,pong ,close) + * @param final Is this final message for this data send + * @param user_send_data User specific data to this connection. This is passed + * as a parameter to sent cb after the packet has been sent. + * + * @return 0 if ok, <0 if error. + */ +static inline int ws_send_msg_to_client(struct http_ctx *ctx, + u8_t *payload, + size_t payload_len, + enum ws_opcode opcode, + bool final, + void *user_send_data) +{ + return ws_send_msg(ctx, payload, payload_len, opcode, false, final, + user_send_data); +} + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __WS_H__ */ diff --git a/include/net/websocket_console.h b/include/net/websocket_console.h new file mode 100644 index 0000000000000..21d2c641ef992 --- /dev/null +++ b/include/net/websocket_console.h @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __WEBSOCKET_CONSOLE_H__ +#define __WEBSOCKET_CONSOLE_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Websocket console library + * @defgroup websocket Websocket Console Library + * @{ + */ + +/** + * @brief Enable websocket console. + * + * @details The console can be sent over websocket to browser. + * + * @param ctx HTTP context + * + * @return 0 if ok, <0 if error + */ +int ws_console_enable(struct http_ctx *ctx); + +/** + * @brief Disable websocket console. + * + * @param ctx HTTP context + * + * @return 0 if ok, <0 if error + */ +int ws_console_disable(struct http_ctx *ctx); + +/** + * @brief Receive data from outside system and feed it into Zephyr. + * + * @param ctx HTTP context + * @param pkt Network packet containing the received data. + * + * @return 0 if ok, <0 if error + */ +int ws_console_recv(struct http_ctx *ctx, struct net_pkt *pkt); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __WEBSOCKET_CONSOLE_H__ */ diff --git a/samples/net/rpl_border_router/CMakeLists.txt b/samples/net/rpl_border_router/CMakeLists.txt new file mode 100644 index 0000000000000..70fbd1e2368a4 --- /dev/null +++ b/samples/net/rpl_border_router/CMakeLists.txt @@ -0,0 +1,38 @@ +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) diff --git a/samples/net/rpl_border_router/README.rst b/samples/net/rpl_border_router/README.rst new file mode 100644 index 0000000000000..f9382265d9f31 --- /dev/null +++ b/samples/net/rpl_border_router/README.rst @@ -0,0 +1,177 @@ +.. _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 it 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 RPL border router in QEMU, as the +border router requires access to a real radio network technology like +IEEE 802.15.4 which is not available in QEMU. + +For testing purposes it is possible to 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 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 toward your host computer, then IPv4 support can be disabled +in 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 is 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 TI CC2520 card. See + https://wiki.zephyrproject.org/index.php?title=TI_CC2520 + for instructions for wiring etc. + +* FRDM-K64F with Freescale CR20A card. + http://www.nxp.com/products/developer-resources/hardware-development-tools/freedom-development-boards/freedom-development-board-for-mcr20a-wireless-transceiver:FRDM-CR20A + +You can build the application like this for CC2520: + +.. zephyr-app-commands:: + :zephyr-app: samples/net/rpl_border_router + :board: frdm_k64f + :conf: prj_frdm_k64f_cc2520.conf + :goals: build flash + :compact: + +and for CR20A like this: + +.. 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.conf b/samples/net/rpl_border_router/prj.conf new file mode 100644 index 0000000000000..0b2e7cc9db6e2 --- /dev/null +++ b/samples/net/rpl_border_router/prj.conf @@ -0,0 +1,74 @@ +# Note that this configuration file does not provide usable system. +# It is only used to verify the compilation of the application. + +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_TCP=y +CONFIG_NET_UDP=y +CONFIG_RANDOM_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_LOG=y +CONFIG_INIT_STACKS=y +CONFIG_NET_SHELL=y +CONFIG_NET_STATISTICS=y +CONFIG_NET_MAX_CONTEXTS=8 +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=128 +CONFIG_NET_PKT_TX_COUNT=128 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5 + +# IPv6 neighbors/routes count +CONFIG_NET_IPV6_MAX_NEIGHBORS=8 + +# RPL and routing options +CONFIG_NET_RPL=y +CONFIG_NET_ROUTE=y +CONFIG_NET_ROUTING=y +# This is used as a prefix when creating DAG. +CONFIG_NET_RPL_PREFIX="fd77:d528:ae6a:6df6::1/64" + +# HTTP(S) admin interface +CONFIG_HTTP=y +CONFIG_HTTPS=n +CONFIG_HTTP_APP=y +CONFIG_NET_APP_SERVER=y +CONFIG_HTTP_SERVER=y +# Firefox sends lot of fields so this needs to be quite large +CONFIG_HTTP_HEADERS=20 +CONFIG_HTTP_SERVER_NUM_URLS=8 +CONFIG_WEBSOCKET=y + +# 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=30000 + +# Logging +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=1 + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_HTTP=y +CONFIG_NET_DEBUG_HTTP_CONN=y +CONFIG_NET_DEBUG_RPL=y +CONFIG_NET_DEBUG_ROUTE=y + +# 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" diff --git a/samples/net/rpl_border_router/prj_frdm_k64f_cc2520.conf b/samples/net/rpl_border_router/prj_frdm_k64f_cc2520.conf new file mode 100644 index 0000000000000..cb6ceb2ef84b1 --- /dev/null +++ b/samples/net/rpl_border_router/prj_frdm_k64f_cc2520.conf @@ -0,0 +1,111 @@ +# 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_NET_LOG=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=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=128 +CONFIG_NET_PKT_TX_COUNT=128 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 + +# 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=128 + +# 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_HTTPS=n +CONFIG_HTTP_SERVER=y +CONFIG_HTTP_SERVER_NUM_URLS=8 +CONFIG_WEBSOCKET=y +CONFIG_WEBSOCKET_CONSOLE=y + +# Allow two concurrent incoming connections +CONFIG_NET_TCP_BACKLOG_SIZE=2 +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_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL=0 + +# Debugging +CONFIG_NET_DEBUG_NET_PKT=y +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_RPL=n +CONFIG_NET_DEBUG_ROUTE=n +CONFIG_NET_DEBUG_APP=y +CONFIG_NET_DEBUG_IPV6=n +CONFIG_NET_DEBUG_IPV6_NBR_CACHE=n +CONFIG_NET_DEBUG_CONTEXT=n +CONFIG_NET_DEBUG_TCP=n +CONFIG_NET_DEBUG_CONN=n +CONFIG_NET_DEBUG_HTTP=y +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 + +# 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=26 +CONFIG_NET_APP_IEEE802154_DEV_NAME="cc2520" +CONFIG_IEEE802154_CC2520=y + +# IPv6 compression is needed for IEEE 802.15.4 +CONFIG_NET_6LO=y +CONFIG_NET_6LO_CONTEXT=y + +# CC2520 options +CONFIG_IEEE802154_CC2520_AUTO_ACK=y +CONFIG_IEEE802154_CC2520_SPI_DRV_NAME="SPI_0" +CONFIG_IEEE802154_CC2520_SPI_FREQ=4000000 +CONFIG_IEEE802154_CC2520_SPI_SLAVE=0 +CONFIG_GPIO=y +CONFIG_SPI=y +CONFIG_SPI_0=y diff --git a/samples/net/rpl_border_router/prj_frdm_k64f_cc2520_tls.conf b/samples/net/rpl_border_router/prj_frdm_k64f_cc2520_tls.conf new file mode 100644 index 0000000000000..aad83f5ed2d53 --- /dev/null +++ b/samples/net/rpl_border_router/prj_frdm_k64f_cc2520_tls.conf @@ -0,0 +1,110 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_TCP=y +CONFIG_NET_UDP=y +CONFIG_RANDOM_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_LOG=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=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=128 +CONFIG_NET_PKT_TX_COUNT=128 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 + +# 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=128 + +# 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. +CONFIG_NET_RPL_PREFIX="fde3:2cda:3eea:4d14::1/64" + +# HTTP(S) admin and websocket interface support +CONFIG_HTTP=y +CONFIG_HTTPS=y +CONFIG_HTTP_APP=y +CONFIG_NET_APP_SERVER=y +CONFIG_HTTP_SERVER=y +# Firefox sends lot of fields to this needs to be quite large +CONFIG_HTTP_HEADERS=20 +CONFIG_HTTP_SERVER_NUM_URLS=8 +CONFIG_WEBSOCKET=y + +# 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=28500 + +# Logging +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL=0 + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_RPL=n +CONFIG_NET_DEBUG_ROUTE=n +CONFIG_NET_DEBUG_APP=y +CONFIG_NET_DEBUG_HTTP=y +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_IF=n +CONFIG_NET_DEBUG_CORE=n +CONFIG_NET_DEBUG_NET_PKT=y +CONFIG_NET_DEBUG_CONN=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" + +# Allow one concurrent connection. This is mandatory for HTTPS. +# You can increase the limit for plain HTTP. +CONFIG_NET_TCP_BACKLOG_SIZE=1 +CONFIG_NET_APP_SERVER_NUM_CONN=1 + +# 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=15 +CONFIG_NET_APP_IEEE802154_DEV_NAME="cc2520" +CONFIG_IEEE802154_CC2520=y + +# IPv6 compression is needed for IEEE 802.15.4 +CONFIG_NET_6LO=y +CONFIG_NET_6LO_CONTEXT=y + +# CC2520 options +CONFIG_IEEE802154_CC2520_AUTO_ACK=y +CONFIG_IEEE802154_CC2520_SPI_DRV_NAME="SPI_0" +CONFIG_IEEE802154_CC2520_SPI_FREQ=4000000 +CONFIG_IEEE802154_CC2520_SPI_SLAVE=0 +CONFIG_GPIO=y +CONFIG_SPI=y +CONFIG_SPI_0=y 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..43f44ec9627ab --- /dev/null +++ b/samples/net/rpl_border_router/prj_frdm_k64f_mcr20a.conf @@ -0,0 +1,79 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_TCP=y +CONFIG_RANDOM_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_LOG=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=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=128 +CONFIG_NET_PKT_TX_COUNT=128 +CONFIG_NET_BUF_RX_COUNT=128 +CONFIG_NET_BUF_TX_COUNT=128 + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=4 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=5 + +# IPv6 neighbors/routes count +CONFIG_NET_IPV6_MAX_NEIGHBORS=64 + +# 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. +CONFIG_NET_RPL_PREFIX="fd53:8eca:d389:5959::1/64" + +# HTTP(S) admin interface +CONFIG_HTTP_SERVER=y +CONFIG_HTTPS=y +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_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=1 + +# Debugging +CONFIG_NET_DEBUG_HTTP=y +CONFIG_NET_DEBUG_HTTP_CONN=y +CONFIG_NET_DEBUG_RPL=y +CONFIG_NET_DEBUG_ROUTE=y +CONFIG_SYS_LOG_IEEE802154_DRIVER_LEVEL=0 + +# 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" + +# IPv6 compression is needed for IEEE 802.15.4 +CONFIG_NET_6LO=y +CONFIG_NET_6LO_CONTEXT=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=15 +CONFIG_NET_APP_IEEE802154_DEV_NAME="mcr20a" +CONFIG_IEEE802154_MCR20A=y + +# MCR20A board options +CONFIG_GPIO=y +CONFIG_SPI=y +CONFIG_SPI_0=y diff --git a/samples/net/rpl_border_router/prj_qemu_x86.conf b/samples/net/rpl_border_router/prj_qemu_x86.conf new file mode 100644 index 0000000000000..a961a731b0369 --- /dev/null +++ b/samples/net/rpl_border_router/prj_qemu_x86.conf @@ -0,0 +1,45 @@ +CONFIG_NETWORKING=y +CONFIG_NET_TCP=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_NET_LOG=y +CONFIG_NET_SLIP_TAP=y +CONFIG_INIT_STACKS=y + +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=16 +CONFIG_NET_BUF_TX_COUNT=16 + +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=2 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +CONFIG_NET_MAX_CONTEXTS=16 + +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_SYS_LOG_NET_LEVEL=2 +#CONFIG_NET_DEBUG_HTTP=y + +CONFIG_HTTP_SERVER=y + +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y + +CONFIG_HTTPS=y +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" + +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +CONFIG_NET_SHELL=y +CONFIG_NET_STATISTICS=y +#CONFIG_NET_STATISTICS_PERIODIC_OUTPUT=y + +CONFIG_NET_MGMT=y +CONFIG_NET_MGMT_EVENT=y + +CONFIG_NET_RPL=y diff --git a/samples/net/rpl_border_router/sample.yaml b/samples/net/rpl_border_router/sample.yaml new file mode 100644 index 0000000000000..7976d64c5e41a --- /dev/null +++ b/samples/net/rpl_border_router/sample.yaml @@ -0,0 +1,9 @@ +sample: + name: RPL Border Router +tests: +- test: + build_only: true + # Currently do a compile test for QEMU only because we do not have + # a device with 802.15.4 and ethernet support. + platform_whitelist: qemu_x86 + tags: net rpl 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..e8bf549db81dd --- /dev/null +++ b/samples/net/rpl_border_router/src/br-info.schema.json.py @@ -0,0 +1,292 @@ +#!/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" } + ] + } + }, + + "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" : { + "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" + } + } + } + }, + + "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" + } + ] + }, + { + "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", + "IPv6 address" : "fe80::212:4b00:0:3", + "Link address" : "00:12:4B:00:00:00:00:03" + } + + ] + }, + { + "0x20009a30": [ + ] + } + ] + + }, 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..bb95b2ae0e5e6 --- /dev/null +++ b/samples/net/rpl_border_router/src/br.js @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +var connected; +var first_run; +var ws; +var interfaces; +var header_added; +var column; + +function init() { + ws = new WebSocket(location.origin.replace("http", "ws") + "/ws"); + + interfaces = {}; + first_run = "true"; + connected = "false"; + changeConnectText(); + + 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"); + print_neighbors(info.neighbors); + } + } catch(err) { + } + + try { + if (info.routes.length > 0) { + output("Route information received"); + print_routes(info.routes); + } + } 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) { + header_added = false; + + for (const property in iface) { + var table_id = "iface_table_" + property; + + //output("iface " + property); + + table_create(document.getElementById("iface"), table_id); + + print_iface_settings(iface, property, table_id); + } +} + +function print_iface_settings(iface, 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) { + print_iface_data(property, iface[property][value], table_id, + head_row, row, column++); + } + + header_added = true; +} + +function print_iface_data(iface_id, setting, table_id, head_row, + row, column) { + for (const key in setting) { + if (!header_added) { + table_column_header(table_id, head_row, key, column); + } + + 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 " + table_id + " " + addresses + " to column " + column); + table_add_data(table_id, row, addresses, column, "wrappable"); + } else { + //output("insert " + table_id + " " + setting[key].toString() + " to column " + column); + table_add_data(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 print_neighbors(config) { + var table_id = "neighbors_table"; + header_added = false; + table_create(document.getElementById("neighbors"), table_id); + + for (var i = 0; i < config.length; i++) { + print_neighbor_iface_data(config[i], table_id); + } +} + +function print_neighbor_iface_data(iface, table_id) { + for (const value in iface) { + //output("iface " + value.toString()); + print_neighbor_data(iface[value], table_id); + } +} + +function print_neighbor_data(property, table_id) { + for (var i = 0; i < property.length; i++) { + print_table_values(property[i], table_id); + } +} + +function print_routes(config) { + var table_id = "routes_table"; + header_added = false; + table_create(document.getElementById("routes"), table_id); + + for (var i = 0; i < config.length; i++) { + print_route_iface_data(config[i], table_id); + } +} + +function print_route_iface_data(iface, table_id) { + for (const value in iface) { + //output("iface " + value.toString()); + print_route_data(iface[value], table_id); + } +} + +function print_route_data(property, table_id) { + for (var i = 0; i < property.length; i++) { + print_table_values(property[i], table_id); + } +} + +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); + } + + //output("insert " + table_id + " " + property[value] + " to column " + column); + table_add_data(table_id, row, property[value], column++); + } + + header_added = true; +} + +function select_default_tab() { + configuration(); +} + +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; + } +} 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..9fa566a97ffd0 --- /dev/null +++ b/samples/net/rpl_border_router/src/config.h @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* 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); +#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..d204e7039cd44 --- /dev/null +++ b/samples/net/rpl_border_router/src/http.c @@ -0,0 +1,1439 @@ +/* + * 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 "../../../subsys/net/ip/ipv6.h" +#include "../../../subsys/net/ip/route.h" +#include "../../../subsys/net/ip/rpl.h" +#include "../../../subsys/net/ip/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; + int msg_count; + int iface_count; + int nbr_count; + int route_count; + int failure; +}; + +static struct { + struct net_if *iface; + struct sockaddr auth_addr; + bool auth_ok; +} rpl; + +static int http_serve_index_html(struct http_ctx *ctx); + +#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 */ + +/* + * 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) +{ + int ret; + + ret = http_add_header(ctx, header, NULL); + if (ret < 0) { + NET_ERR("Cannot add HTTP header (%d)", ret); + return ret; + } + + ret = http_add_header(ctx, HTTP_CRLF, NULL); + if (ret < 0) { + return ret; + } + + ret = http_send_chunk(ctx, payload, payload_len, 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) +{ + 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)); +} + +int http_response_401(struct http_ctx *ctx) +{ + return http_response(ctx, HTTP_401_STATUS_US, "", 0); +} + +static int http_basic_auth(struct http_ctx *ctx, + enum http_connection_type type) +{ + const char auth_str[] = HTTP_CRLF "Authorization: Basic "; + int ret = 0; + char *ptr; + + 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); + 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); + 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); + goto close; + } + + ret = http_response_401(ctx); + goto close; + } + + ret = http_response_401(ctx); + +close: + http_close(ctx); + return ret; +} + +static int http_serve_index_html(struct http_ctx *ctx) +{ + 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)); +} + +static int http_serve_style_css(struct http_ctx *ctx) +{ + 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)); +} + +static int http_serve_br_js(struct http_ctx *ctx) +{ + 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)); +} + +static int http_serve_favicon_ico(struct http_ctx *ctx) +{ + 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)); +} + +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"; + } + + 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, 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, 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->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->l2 == &NET_L2_GET_NAME(IEEE802154)) { + return "IEEE 802.15.4"; + } +#endif + +#ifdef CONFIG_NET_L2_ETHERNET + if (iface->l2 == &NET_L2_GET_NAME(ETHERNET)) { + return "Ethernet"; + } +#endif + +#ifdef CONFIG_NET_L2_DUMMY + if (iface->l2 == &NET_L2_GET_NAME(DUMMY)) { + return "Dummy"; + } +#endif + +#ifdef CONFIG_NET_L2_BT + if (iface->l2 == &NET_L2_GET_NAME(BLUETOOTH)) { + return "Bluetooth"; + } +#endif + +#ifdef CONFIG_NET_L2_OFFLOAD + if (iface->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->link_addr.addr, + iface->link_addr.len)); + if (ret < 0) { + goto out; + } + + ret = append_and_send_data(data, false, "{\"MTU\":\"%d\"},", + iface->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) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface_count = 0; + data.msg_count = 0; + + 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) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.msg_count = 0; + + 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 setup RPL configuration"); + goto out; + } + + ret = append_and_send_data(&data, true, "]}"); + if (ret < 0) { + goto out; + } + + return ret; + +out: + return ret; +} + +static void nbr_cb(struct net_nbr *nbr, void *user_data) +{ + struct user_data *data = user_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 int send_ipv6_neighbors(struct http_ctx *ctx) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface = NULL; + data.msg_count = 0; + + ret = append_and_send_data(&data, false, "{\"neighbors\":["); + if (ret < 0) { + goto out; + } + + data.nbr_count = 0; + data.iface_count = 0; + + net_ipv6_nbr_foreach(nbr_cb, &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 void route_cb(struct net_route_entry *entry, void *user_data) +{ + struct user_data *data = user_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 iface_cb_for_routes(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; + } + + data->iface = iface; + data->route_count = 0; + + net_route_foreach(route_cb, 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 int send_ipv6_routes(struct http_ctx *ctx) +{ + struct user_data data; + int ret; + + data.ctx = ctx; + data.iface_count = 0; + data.msg_count = 0; + + ret = append_and_send_data(&data, false, "{\"routes\":["); + if (ret < 0) { + goto out; + } + + net_if_foreach(iface_cb_for_routes, &data); + + 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) +{ + int ret; + + ret = send_iface_configuration(ctx); + if (ret < 0) { + NET_ERR("Cannot send interface configuration (%d)", ret); + } + + ret = send_rpl_configuration(ctx); + if (ret < 0) { + NET_ERR("Cannot send RPL configuration (%d)", ret); + } + + ret = send_ipv6_neighbors(ctx); + if (ret < 0) { + NET_ERR("Cannot send neighbor information (%d)", ret); + } + + ret = send_ipv6_routes(ctx); + if (ret < 0) { + NET_ERR("Cannot send route information (%d)", ret); + } +} + +static void http_connected(struct http_ctx *ctx, + enum http_connection_type type, + void *user_data) +{ + char url[32]; + int len = min(sizeof(url), ctx->http.url_len); + + 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 (0 && (!rpl.auth_ok || !check_addr(ctx))) { + rpl.auth_ok = false; + http_basic_auth(ctx, type); + return; + } + + if (type == HTTP_CONNECTION) { + if (strncmp(ctx->http.url, "/", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/index.html", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/br.js", + ctx->http.url_len) == 0) { + http_serve_br_js(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/style.css", + ctx->http.url_len) == 0) { + http_serve_style_css(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/favicon.ico", + ctx->http.url_len) == 0) { + http_serve_favicon_ico(ctx); + http_close(ctx); + return; + } + } else if (type == WS_CONNECTION) { + if (strncmp(ctx->http.url, "/ws", + ctx->http.url_len) == 0) { + ws_send_info(ctx); + return; + } + } +} + +static void http_received(struct http_ctx *ctx, + struct net_pkt *pkt, + int status, + u32_t flags, + void *user_data) +{ + if (!status) { + NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); + + 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) +{ + 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); + } + + return HTTP_VERDICT_DROP; +} + +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.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.family = AF_INET6; + net_sin6(&addr)->sin6_port = htons(ZEPHYR_PORT); + + ret = net_ipaddr_parse(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); + + rpl.iface = iface; +} + +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..e69651b4ee5f7 --- /dev/null +++ b/samples/net/rpl_border_router/src/index.html @@ -0,0 +1,57 @@ + + + + + + + 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..0e4b453287e55 --- /dev/null +++ b/samples/net/rpl_border_router/src/main.c @@ -0,0 +1,56 @@ +/* + * 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_default(); + if (!mgmt_iface) { + NET_INFO("No management network interface found."); + } else { + start_http_server(mgmt_iface); + } + + setup_rpl(iface, CONFIG_NET_RPL_PREFIX); +} 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..d350661a26b70 --- /dev/null +++ b/samples/net/rpl_border_router/src/shell.c @@ -0,0 +1,42 @@ +/* + * 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; +} + +static struct shell_cmd br_commands[] = { + /* Keep the commands in alphabetical order */ + { "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..735a82853bae5 --- /dev/null +++ b/samples/net/rpl_border_router/src/style.css @@ -0,0 +1,105 @@ +h1 { + margin-left: 20px; +} + +table, th, td { + border-collapse: collapse; +} + +th, td { + padding: 15px; +} + +th { + text-align: left; +} + +td.wrappable { + word-wrap: break-word; + white-space: normal; +} + +.box { + background-color: white; + color: black; + border-radius: 5px; + padding: 10px; + font-size: 150%; +} + +body { + margin: 40px; +} + +.sidebar { + grid-area: sidebar; +} + +.content { + grid-area: content; + height: 100%; + overflow: auto; +} + +.header { + grid-area: header; + text-align: center; +} + +.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"; +} + +.header, .footer { + background-color: #999; +} + +/* Style the tab */ +div.tab { + overflow: hidden; + border: 1px solid #ccc; + background-color: #f1f1f1; +} + +/* 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; +} + +/* Change background color of buttons on hover */ +div.tab button:hover { + background-color: #ddd; +} + +/* Create an active/current tablink class */ +div.tab button.active { + background-color: #ccc; +} + +/* Style the tab content */ +.tabcontent { + display: none; + padding: 6px 12px; + border: 1px solid #ccc; + border-top: none; +} diff --git a/samples/net/ws_console/CMakeLists.txt b/samples/net/ws_console/CMakeLists.txt new file mode 100644 index 0000000000000..e4f7d8261bc07 --- /dev/null +++ b/samples/net/ws_console/CMakeLists.txt @@ -0,0 +1,37 @@ +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 + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + --gzip + ) +endforeach() + +target_link_libraries_ifdef(CONFIG_MBEDTLS app mbedTLS) diff --git a/samples/net/ws_console/README.rst b/samples/net/ws_console/README.rst new file mode 100644 index 0000000000000..05f9bdf20454f --- /dev/null +++ b/samples/net/ws_console/README.rst @@ -0,0 +1,40 @@ +.. _websocket-console-sample: + +Websocket Console +################# + +Overview +******** + +The websocket-console sample application for Zephyr implements a console +over a websocket. The websocket-console sample application listens for incoming +IPv4 or IPv6 HTTP(S) requests and provides Zephyr console to the browser over +a websocket. + +The source code for this sample application can be found at: +:file:`samples/net/ws_console`. + +Requirements +************ + +- :ref:`networking_with_qemu` + +Building and Running +******************** + +There are multiple ways to use this application. One of the most common +usage scenario is to run websocket-console application inside QEMU. This is +described in :ref:`networking_with_qemu`. + +Build ws_console sample application like this: + +.. zephyr-app-commands:: + :zephyr-app: samples/net/ws_console + :board: qemu_x86 + :goals: run + :compact: + +The default make BOARD configuration for this sample is ``qemu_x86``. + +Connect to the console from your browser using these URLs http://[2001:db8::1] +or http://192.0.2.1 diff --git a/samples/net/ws_console/prj.conf b/samples/net/ws_console/prj.conf new file mode 100644 index 0000000000000..4496a6a4dc27a --- /dev/null +++ b/samples/net/ws_console/prj.conf @@ -0,0 +1,58 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_UDP=n +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_NET_MAX_CONTEXTS=8 +CONFIG_NET_SHELL=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=32 +CONFIG_NET_BUF_TX_COUNT=32 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +# Network application settings +CONFIG_NET_APP=y +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +# HTTP & Websocket options +CONFIG_WEBSOCKET=y +CONFIG_WEBSOCKET_CONSOLE=y +CONFIG_HTTP=y +CONFIG_HTTPS=n +CONFIG_HTTP_SERVER=y +# How many URLs we are serving +CONFIG_HTTP_SERVER_NUM_URLS=5 + +# base64 support needed by websocket +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" + +# Logging +CONFIG_NET_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=2 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_NET_STATISTICS=y +CONFIG_PRINTK=y + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_HTTP=n +CONFIG_NET_DEBUG_APP=n +CONFIG_NET_DEBUG_NET_PKT=y diff --git a/samples/net/ws_console/prj_tls.conf b/samples/net/ws_console/prj_tls.conf new file mode 100644 index 0000000000000..f8b1bd5980037 --- /dev/null +++ b/samples/net/ws_console/prj_tls.conf @@ -0,0 +1,60 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_UDP=n +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_NET_MAX_CONTEXTS=8 +CONFIG_NET_SHELL=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=64 +CONFIG_NET_PKT_TX_COUNT=64 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=64 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +# Network application settings +CONFIG_NET_APP=y +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +# HTTP & Websocket options +CONFIG_WEBSOCKET=y +CONFIG_WEBSOCKET_CONSOLE=y +CONFIG_HTTP=y +CONFIG_HTTPS=y +CONFIG_HTTP_SERVER=y +# How many URLs we are serving +CONFIG_HTTP_SERVER_NUM_URLS=5 + +# 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=30000 + +# Logging +CONFIG_NET_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=2 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_NET_STATISTICS=y +CONFIG_PRINTK=y + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_HTTP=n +CONFIG_NET_DEBUG_APP=n +CONFIG_NET_DEBUG_NET_PKT=y diff --git a/samples/net/ws_console/sample.yaml b/samples/net/ws_console/sample.yaml new file mode 100644 index 0000000000000..5865dc412d438 --- /dev/null +++ b/samples/net/ws_console/sample.yaml @@ -0,0 +1,7 @@ +sample: + name: Websocket console +tests: +- test: + build_only: true + platform_whitelist: qemu_x86 + tags: net websocket diff --git a/samples/net/ws_console/src/common.h b/samples/net/ws_console/src/common.h new file mode 100644 index 0000000000000..44132ba3843e7 --- /dev/null +++ b/samples/net/ws_console/src/common.h @@ -0,0 +1,12 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define MAX_DBG_PRINT 64 + +void start_ws_console(void); +void stop_ws_console(void); + +void quit(void); diff --git a/samples/net/ws_console/src/config.h b/samples/net/ws_console/src/config.h new file mode 100644 index 0000000000000..475ed0221bcdf --- /dev/null +++ b/samples/net/ws_console/src/config.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* 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 + +#endif diff --git a/samples/net/ws_console/src/echo-apps-cert.der b/samples/net/ws_console/src/echo-apps-cert.der new file mode 100644 index 0000000000000..bfcb335e31c8c Binary files /dev/null and b/samples/net/ws_console/src/echo-apps-cert.der differ diff --git a/samples/net/ws_console/src/echo-apps-key.der b/samples/net/ws_console/src/echo-apps-key.der new file mode 100644 index 0000000000000..5a4d67372ea41 Binary files /dev/null and b/samples/net/ws_console/src/echo-apps-key.der differ diff --git a/samples/net/ws_console/src/favicon.ico b/samples/net/ws_console/src/favicon.ico new file mode 100644 index 0000000000000..6916039458a6e Binary files /dev/null and b/samples/net/ws_console/src/favicon.ico differ diff --git a/samples/net/ws_console/src/index.html b/samples/net/ws_console/src/index.html new file mode 100644 index 0000000000000..d7fd5167d4f9e --- /dev/null +++ b/samples/net/ws_console/src/index.html @@ -0,0 +1,137 @@ + + + + + + + Zephyr WS Console + + + + + +
+
+
+

+
+
+
+
+
+ + + + + +
+ + + + + +
+
+
+ + diff --git a/samples/net/ws_console/src/main.c b/samples/net/ws_console/src/main.c new file mode 100644 index 0000000000000..c0ccb6a05fae8 --- /dev/null +++ b/samples/net/ws_console/src/main.c @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "ws-console" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include +#include + +#include "common.h" + +static struct k_sem quit_lock; + +void quit(void) +{ + k_sem_give(&quit_lock); +} + +static inline int init_app(void) +{ + k_sem_init(&quit_lock, 0, UINT_MAX); + + return 0; +} + +void main(void) +{ + init_app(); + + start_ws_console(); + + k_sem_take(&quit_lock, K_FOREVER); + + stop_ws_console(); +} diff --git a/samples/net/ws_console/src/style.css b/samples/net/ws_console/src/style.css new file mode 100644 index 0000000000000..021a027c39f26 --- /dev/null +++ b/samples/net/ws_console/src/style.css @@ -0,0 +1,54 @@ +body { + background-color: black; + color: white; +} + +table, th, td { + border: 1px solid black; + border-collapse: collapse; +} + +th, td { + padding: 5px; +} + +th { + text-align: left; +} + +div.container { +} + +div.textcontainer { + font-family: monospace !important; + font-size: 150%; +} + +div.zconsole { + white-space: pre-wrap !important; + scroll-behavior: auto; + border: 1px solid gray; + padding: 5px; + width: 95%; + height: 70%; + overflow: auto; +} + +div.output { + scroll-behavior: auto; + border: 1px solid gray; + padding: 5px; + margin-top: 20px; + width: 95%; + height: 10%; + overflow: auto; +} + +div.inputbar { + margin-top: 5px; + width: 100%; +} + +.zconsole em { + color: red; +} diff --git a/samples/net/ws_console/src/ws_console.c b/samples/net/ws_console/src/ws_console.c new file mode 100644 index 0000000000000..f92c1e9fbef46 --- /dev/null +++ b/samples/net/ws_console/src/ws_console.c @@ -0,0 +1,469 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "ws-console" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +/* Printing debugs from this module looks funny in console so do not + * do it unless you are debugging this module. + */ +#define EXTRA_DEBUG 0 + +#include +#include +#include + +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +static struct http_ctx http_ctx; +static struct http_server_urls http_urls; + +#define MAX_URL_LEN 128 +#define SEND_TIMEOUT K_SECONDS(10) + +/* Note that both tcp and udp can share the same pool but in this + * example the UDP context and TCP context have separate pools. + */ +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) +NET_PKT_TX_SLAB_DEFINE(echo_tx_tcp, 15); +NET_PKT_DATA_POOL_DEFINE(echo_data_tcp, 30); + +static struct k_mem_slab *tx_tcp_slab(void) +{ + return &echo_tx_tcp; +} + +static struct net_buf_pool *data_tcp_pool(void) +{ + return &echo_data_tcp; +} +#else +#define tx_tcp_slab NULL +#define data_tcp_pool NULL +#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ + +/* The result buf size is set to large enough so that we can receive max size + * buf back. Note that mbedtls needs also be configured to have equal size + * value for its buffer size. See MBEDTLS_SSL_MAX_CONTENT_LEN option in TLS + * config file. + */ +#define RESULT_BUF_SIZE 1500 +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 8192 +#endif /* CONFIG_NET_APP_TLS_STACK_SIZE */ + +#define APP_BANNER "Run TLS ws-console" +#define INSTANCE_INFO "Zephyr TLS ws-console #1" + +/* Note that each net_app context needs its own stack as there will be + * a separate thread needed. + */ +NET_STACK_DEFINE(WS_TLS_CONSOLE, ws_tls_console_stack, + CONFIG_NET_APP_TLS_STACK_SIZE, CONFIG_NET_APP_TLS_STACK_SIZE); + +#define RX_FIFO_DEPTH 4 +K_MEM_POOL_DEFINE(ssl_pool, 4, 64, RX_FIFO_DEPTH, 4); +#endif /* CONFIG_HTTPS */ + +#if defined(CONFIG_HTTPS) +/* 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; +} +#endif /* CONFIG_HTTPS */ + +#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_200_OK_CSS \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/css\r\n" \ + "Transfer-Encoding: chunked\r\n" + +#define HTML_HEADER "" \ + "Zephyr websocket console" \ + "

" \ + "
Zephyr websocket console

\r\n" + +#define HTML_FOOTER "\r\n" + +static int http_response(struct http_ctx *ctx, const char *header, + const char *payload, size_t payload_len) +{ + char content_length[6]; + int ret; + + ret = http_add_header(ctx, header, NULL); + if (ret < 0) { + NET_ERR("Cannot add HTTP header (%d)", ret); + return ret; + } + + ret = snprintk(content_length, sizeof(content_length), "%zd", + payload_len); + if (ret <= 0 || ret >= sizeof(content_length)) { + ret = -ENOMEM; + return ret; + } + + ret = http_add_header_field(ctx, "Content-Length", content_length, + NULL); + if (ret < 0) { + NET_ERR("Cannot add Content-Length HTTP header (%d)", ret); + return ret; + } + + ret = http_add_header(ctx, HTTP_CRLF, NULL); + if (ret < 0) { + return ret; + } + + ret = http_send_chunk(ctx, payload, payload_len, 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) +{ + 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)); +} + +static int http_serve_index_html(struct http_ctx *ctx) +{ + static const char index_html[] = { +#include "index.html.gz.inc" + }; + + NET_DBG("Sending index.html (%zd bytes) to client", + sizeof(index_html)); + + return http_response(ctx, HTTP_STATUS_200_OK_GZ, index_html, + sizeof(index_html)); +} + +static int http_serve_style_css(struct http_ctx *ctx) +{ + static const char style_css_gz[] = { +#include "style.css.gz.inc" + }; + + 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)); +} + +static int http_serve_favicon_ico(struct http_ctx *ctx) +{ + static const char favicon_ico_gz[] = { +#include "favicon.ico.gz.inc" + }; + + 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)); +} + +static void ws_connected(struct http_ctx *ctx, + enum http_connection_type type, + void *user_data) +{ + char url[32]; + int len = min(sizeof(url), ctx->http.url_len); + + memcpy(url, ctx->http.url, len); + url[len] = '\0'; + + NET_DBG("%s connect attempt URL %s", + type == HTTP_CONNECTION ? "HTTP" : "WS", url); + + if (type == HTTP_CONNECTION) { + if (strncmp(ctx->http.url, "/index.html", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/style.css", + ctx->http.url_len) == 0) { + http_serve_style_css(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/favicon.ico", + ctx->http.url_len) == 0) { + http_serve_favicon_ico(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx); + http_close(ctx); + return; + } + + } else if (type == WS_CONNECTION) { + if (strncmp(ctx->http.url, "/console", + ctx->http.url_len) == 0) { + ws_console_enable(ctx); + return; + } + } + + /* Give 404 error for all the other URLs we do not want to handle + * right now. + */ + http_response_soft_404(ctx); + http_close(ctx); +} + +static void ws_received(struct http_ctx *ctx, + struct net_pkt *pkt, + int status, + u32_t flags, + void *user_data) +{ + if (!status) { + int ret; + +#if EXTRA_DEBUG + NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); +#endif + + ret = ws_console_recv(ctx, pkt); + if (ret < 0) { + goto out; + } + } else { + NET_ERR("Receive error (%d)", status); + + goto out; + } + + return; + +out: + if (pkt) { + net_pkt_unref(pkt); + } +} + +static void ws_sent(struct http_ctx *ctx, + int status, + void *user_data_send, + void *user_data) +{ +#if EXTRA_DEBUG + NET_DBG("Data sent status %d", status); +#endif +} + +static void ws_closed(struct http_ctx *ctx, + int status, + void *user_data) +{ + NET_DBG("Connection %p closed", ctx); + + ws_console_disable(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) +{ + 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); + } + + return HTTP_VERDICT_DROP; +} + +void start_ws_console(void) +{ + 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 + * default port 80 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 to desired + * value. If the protocol family in sockaddr is set to AF_UNSPEC, + * then both IPv4 and IPv6 socket is 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 both IPv4 and IPv6 */ + addr.sa_family = AF_UNSPEC; + + 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, &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, "/favicon.ico", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/console", HTTP_URL_WEBSOCKET); + + ret = http_server_init(&http_ctx, &http_urls, server_addr, + result, sizeof(result), + "Zephyr WS console", NULL); + if (ret < 0) { + NET_ERR("Cannot init web server (%d)", ret); + return; + } + + http_set_cb(&http_ctx, ws_connected, ws_received, ws_sent, ws_closed); + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) + net_app_set_net_pkt_pool(&http_ctx.app_ctx, tx_tcp_slab, + data_tcp_pool); +#endif + +#if defined(CONFIG_HTTPS) + ret = http_server_set_tls(&http_ctx, + APP_BANNER, + INSTANCE_INFO, + strlen(INSTANCE_INFO), + setup_cert, + NULL, + &ssl_pool, + ws_tls_console_stack, + K_THREAD_STACK_SIZEOF(ws_tls_console_stack)); + if (ret < 0) { + NET_ERR("Cannot enable TLS support (%d)", ret); + } +#endif + + http_server_enable(&http_ctx); +} + +void stop_ws_console(void) +{ + http_server_disable(&http_ctx); + http_release(&http_ctx); +} diff --git a/samples/net/ws_echo_server/CMakeLists.txt b/samples/net/ws_echo_server/CMakeLists.txt new file mode 100644 index 0000000000000..d2b23c8f442b7 --- /dev/null +++ b/samples/net/ws_echo_server/CMakeLists.txt @@ -0,0 +1,27 @@ +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 + index.html + ) + generate_inc_file_for_target( + app + src/${inc_file} + ${gen_dir}/${inc_file}.inc + ) +endforeach() + +generate_inc_file_for_target(app src/ws.js ${gen_dir}/ws.js.gz.inc --gzip) + +target_link_libraries_ifdef(CONFIG_MBEDTLS app mbedTLS) diff --git a/samples/net/ws_echo_server/README.rst b/samples/net/ws_echo_server/README.rst new file mode 100644 index 0000000000000..ac63b8da4ec6f --- /dev/null +++ b/samples/net/ws_echo_server/README.rst @@ -0,0 +1,36 @@ +.. _websocket-server-sample: + +Websocket Server +################ + +Overview +******** + +The websocket-server sample application for Zephyr implements a websocket +server. The websocket-server listens for incoming IPv4 or IPv6 HTTP(S) +requests. + +The source code for this sample application can be found at: +:file:`samples/net/ws_server`. + +Requirements +************ + +- :ref:`networking_with_qemu` + +Building and Running +******************** + +There are multiple ways to use this application. One of the most common +usage scenario is to run websocket-server application inside QEMU. This is +described in :ref:`networking_with_qemu`. + +Build websocket-server sample application like this: + +.. zephyr-app-commands:: + :zephyr-app: samples/net/ws_echo_server + :board: qemu_x86 + :goals: run + :compact: + +The default make BOARD configuration for this sample is ``qemu_x86``. diff --git a/samples/net/ws_echo_server/prj.conf b/samples/net/ws_echo_server/prj.conf new file mode 100644 index 0000000000000..4d9c41b355c63 --- /dev/null +++ b/samples/net/ws_echo_server/prj.conf @@ -0,0 +1,56 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_UDP=n +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_NET_MAX_CONTEXTS=8 +CONFIG_NET_SHELL=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=16 +CONFIG_NET_PKT_TX_COUNT=16 +CONFIG_NET_BUF_RX_COUNT=16 +CONFIG_NET_BUF_TX_COUNT=16 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +# Network application settings +CONFIG_NET_APP=y +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +# HTTP & Websocket options +CONFIG_WEBSOCKET=y +CONFIG_HTTP=y +CONFIG_HTTPS=n +CONFIG_HTTP_SERVER=y +# How many URLs we are serving +CONFIG_HTTP_SERVER_NUM_URLS=5 + +# base64 support needed by websocket +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" + +# Logging +CONFIG_NET_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=4 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_NET_STATISTICS=y + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_HTTP=y +CONFIG_NET_DEBUG_APP=n +CONFIG_NET_DEBUG_NET_PKT=y diff --git a/samples/net/ws_echo_server/prj_tls.conf b/samples/net/ws_echo_server/prj_tls.conf new file mode 100644 index 0000000000000..f73bef7b9759b --- /dev/null +++ b/samples/net/ws_echo_server/prj_tls.conf @@ -0,0 +1,59 @@ +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_UDP=n +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_NET_MAX_CONTEXTS=8 +CONFIG_NET_SHELL=y + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=64 +CONFIG_NET_PKT_TX_COUNT=64 +CONFIG_NET_BUF_RX_COUNT=64 +CONFIG_NET_BUF_TX_COUNT=64 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +# Network application settings +CONFIG_NET_APP=y +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" + +# HTTP & Websocket options +CONFIG_WEBSOCKET=y +CONFIG_HTTP=y +CONFIG_HTTPS=y +CONFIG_HTTP_SERVER=y +# How many URLs we are serving +CONFIG_HTTP_SERVER_NUM_URLS=5 + +# 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=30000 + +# Logging +CONFIG_NET_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=2 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_NET_STATISTICS=y +CONFIG_PRINTK=y + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=y +CONFIG_NET_DEBUG_HTTP=n +CONFIG_NET_DEBUG_APP=n +CONFIG_NET_DEBUG_NET_PKT=y diff --git a/samples/net/ws_echo_server/sample.yaml b/samples/net/ws_echo_server/sample.yaml new file mode 100644 index 0000000000000..1556a1a6576bf --- /dev/null +++ b/samples/net/ws_echo_server/sample.yaml @@ -0,0 +1,7 @@ +sample: + name: Websocket echo-server +tests: +- test: + build_only: true + platform_whitelist: qemu_x86 qemu_cortex_m3 + tags: net websocket diff --git a/samples/net/ws_echo_server/src/Makefile b/samples/net/ws_echo_server/src/Makefile new file mode 100644 index 0000000000000..900b44acafbe4 --- /dev/null +++ b/samples/net/ws_echo_server/src/Makefile @@ -0,0 +1,24 @@ +# +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +# List of files that are used to generate .h file that can be included +# into .c file. +generate_inc_file += \ + echo-apps-cert.der \ + echo-apps-key.der + +generate_inc_file += \ + index.html + +generate_inc_gz_file += \ + ws.js + +include $(ZEPHYR_BASE)/scripts/Makefile.gen + +include $(ZEPHYR_BASE)/samples/net/common/Makefile.common + +obj-y += main.o +obj-y += ws.o diff --git a/samples/net/ws_echo_server/src/common.h b/samples/net/ws_echo_server/src/common.h new file mode 100644 index 0000000000000..3274700f9d316 --- /dev/null +++ b/samples/net/ws_echo_server/src/common.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define MAX_DBG_PRINT 64 + +void start_server(void); +void stop_server(void); + +struct net_pkt *build_reply_pkt(const char *name, + struct net_app_ctx *ctx, + struct net_pkt *pkt); +void pkt_sent(struct net_app_ctx *ctx, int status, + void *token, void *user_data); +void panic(const char *msg); +void quit(void); diff --git a/samples/net/ws_echo_server/src/config.h b/samples/net/ws_echo_server/src/config.h new file mode 100644 index 0000000000000..475ed0221bcdf --- /dev/null +++ b/samples/net/ws_echo_server/src/config.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef _CONFIG_H_ +#define _CONFIG_H_ + +/* 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 + +#endif diff --git a/samples/net/ws_echo_server/src/echo-apps-cert.der b/samples/net/ws_echo_server/src/echo-apps-cert.der new file mode 100644 index 0000000000000..bfcb335e31c8c Binary files /dev/null and b/samples/net/ws_echo_server/src/echo-apps-cert.der differ diff --git a/samples/net/ws_echo_server/src/echo-apps-key.der b/samples/net/ws_echo_server/src/echo-apps-key.der new file mode 100644 index 0000000000000..5a4d67372ea41 Binary files /dev/null and b/samples/net/ws_echo_server/src/echo-apps-key.der differ diff --git a/samples/net/ws_echo_server/src/index.html b/samples/net/ws_echo_server/src/index.html new file mode 100644 index 0000000000000..da6482eab4b9c --- /dev/null +++ b/samples/net/ws_echo_server/src/index.html @@ -0,0 +1,20 @@ + + + + + Zephyr HTTP websocket server sample + + + +
+
+

Zephyr HTTP websocket server sample

+
+
+

HTTP connection ok.

+
+
+
+
+ + diff --git a/samples/net/ws_echo_server/src/main.c b/samples/net/ws_echo_server/src/main.c new file mode 100644 index 0000000000000..e008a1dd0a3b8 --- /dev/null +++ b/samples/net/ws_echo_server/src/main.c @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2016 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "ws-server" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include "common.h" + +/* 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) + +#define APP_BANNER "Run websocket server" + +static struct k_sem quit_lock; + +void panic(const char *msg) +{ + if (msg) { + NET_ERR("%s", msg); + } + + for (;;) { + k_sleep(K_FOREVER); + } +} + +void quit(void) +{ + k_sem_give(&quit_lock); +} + +struct net_pkt *build_reply_pkt(const char *name, + struct net_app_ctx *ctx, + struct net_pkt *pkt) +{ + struct net_pkt *reply_pkt; + struct net_buf *frag, *tmp; + int header_len = 0, recv_len, reply_len; + + NET_INFO("%s received %d bytes", name, net_pkt_appdatalen(pkt)); + + if (net_pkt_appdatalen(pkt) == 0) { + return NULL; + } + + reply_pkt = net_app_get_net_pkt(ctx, net_pkt_family(pkt), K_FOREVER); + + NET_ASSERT(reply_pkt); + NET_ASSERT(net_pkt_family(reply_pkt) == net_pkt_family(pkt)); + + recv_len = net_pkt_get_len(pkt); + + tmp = pkt->frags; + + /* If we have link layer headers, then get rid of them here. */ + if (recv_len != net_pkt_appdatalen(pkt)) { + /* First fragment will contain IP header so move the data + * down in order to get rid of it. + */ + header_len = net_pkt_appdata(pkt) - tmp->data; + + NET_ASSERT(header_len < CONFIG_NET_BUF_DATA_SIZE); + + /* After this pull, the tmp->data points directly to application + * data. + */ + net_buf_pull(tmp, header_len); + } + + net_pkt_set_appdatalen(reply_pkt, net_pkt_appdatalen(pkt)); + + while (tmp) { + frag = net_app_get_net_buf(ctx, reply_pkt, K_FOREVER); + + if (net_buf_headroom(tmp) == 0) { + /* If there is no link layer headers in the + * received fragment, then get rid of that also + * in the sending fragment. We end up here + * if MTU is larger than fragment size, this + * is typical for ethernet. + */ + net_buf_push(frag, net_buf_headroom(frag)); + + frag->len = 0; /* to make fragment empty */ + + /* Make sure to set the reserve so that + * in sending side we add the link layer + * header if needed. + */ + net_pkt_set_ll_reserve(reply_pkt, 0); + } + + NET_ASSERT_INFO(net_buf_tailroom(frag) >= tmp->len, + "tail %zd longer than len %d", + net_buf_tailroom(frag), tmp->len); + + memcpy(net_buf_add(frag, tmp->len), tmp->data, tmp->len); + + tmp = net_pkt_frag_del(pkt, NULL, tmp); + } + + reply_len = net_pkt_get_len(reply_pkt); + + NET_ASSERT_INFO((recv_len - header_len) == reply_len, + "Received %d bytes, sending %d bytes", + recv_len - header_len, reply_len); + + return reply_pkt; +} + +void pkt_sent(struct net_app_ctx *ctx, + int status, + void *user_data_send, + void *user_data) +{ + if (!status) { + NET_INFO("Sent %d bytes", POINTER_TO_UINT(user_data_send)); + } +} + +static inline int init_app(void) +{ + k_sem_init(&quit_lock, 0, UINT_MAX); + + return 0; +} + +void main(void) +{ + init_app(); + + if (IS_ENABLED(CONFIG_NET_TCP)) { + start_server(); + } + + k_sem_take(&quit_lock, K_FOREVER); + + NET_INFO("Stopping..."); + + if (IS_ENABLED(CONFIG_NET_TCP)) { + stop_server(); + } +} diff --git a/samples/net/ws_echo_server/src/ws.c b/samples/net/ws_echo_server/src/ws.c new file mode 100644 index 0000000000000..6dba4d611f2fd --- /dev/null +++ b/samples/net/ws_echo_server/src/ws.c @@ -0,0 +1,524 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if 1 +#define SYS_LOG_DOMAIN "ws-echo-server" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include "common.h" +#include "config.h" + +#define MAX_BUF_LEN 128 +#define MAX_URL_LEN 128 +#define SEND_TIMEOUT K_SECONDS(10) +#define ALLOC_TIMEOUT 100 + +static struct http_ctx http_ctx; +static struct http_server_urls http_urls; +static char buf[MAX_BUF_LEN]; +static int msg_count; + +/* Note that both tcp and udp can share the same pool but in this + * example the UDP context and TCP context have separate pools. + */ +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) +NET_PKT_TX_SLAB_DEFINE(echo_tx_tcp, 15); +NET_PKT_DATA_POOL_DEFINE(echo_data_tcp, 30); + +static struct k_mem_slab *tx_tcp_slab(void) +{ + return &echo_tx_tcp; +} + +static struct net_buf_pool *data_tcp_pool(void) +{ + return &echo_data_tcp; +} +#else +#define tx_tcp_slab NULL +#define data_tcp_pool NULL +#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ + +/* The result buf size is set to large enough so that we can receive max size + * buf back. Note that mbedtls needs also be configured to have equal size + * value for its buffer size. See MBEDTLS_SSL_MAX_CONTENT_LEN option in TLS + * config file. + */ +#define RESULT_BUF_SIZE 1500 +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 8192 +#endif /* CONFIG_NET_APP_TLS_STACK_SIZE */ + +#define APP_BANNER "Run TLS ws-server" +#define INSTANCE_INFO "Zephyr TLS ws-server #1" + +/* Note that each net_app context needs its own stack as there will be + * a separate thread needed. + */ +NET_STACK_DEFINE(WS_ECHO_SERVER, ws_tls_stack, + CONFIG_NET_APP_TLS_STACK_SIZE, CONFIG_NET_APP_TLS_STACK_SIZE); + +#define RX_FIFO_DEPTH 4 +K_MEM_POOL_DEFINE(ssl_pool, 4, 64, RX_FIFO_DEPTH, 4); +#endif /* CONFIG_HTTPS */ + +#if defined(CONFIG_HTTPS) +/* 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; +} +#endif /* CONFIG_HTTPS */ + +#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_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 HTML_HEADER "" \ + "Zephyr HTTP Server" \ + "

" \ + "
Zephyr HTTP websocket server

\r\n" + +#define HTML_FOOTER "\r\n" + +static int http_response(struct http_ctx *ctx, const char *header, + const char *payload, size_t payload_len) +{ + char content_length[6]; + int ret; + + ret = http_add_header(ctx, header, NULL); + if (ret < 0) { + NET_ERR("Cannot add HTTP header (%d)", ret); + return ret; + } + + ret = snprintk(content_length, sizeof(content_length), "%zd", + payload_len); + if (ret <= 0 || ret >= sizeof(content_length)) { + ret = -ENOMEM; + return ret; + } + + ret = http_add_header_field(ctx, "Content-Length", content_length, + NULL); + if (ret < 0) { + NET_ERR("Cannot add Content-Length HTTP header (%d)", ret); + return ret; + } + + ret = http_add_header(ctx, HTTP_CRLF, NULL); + if (ret < 0) { + return ret; + } + + ret = http_send_chunk(ctx, payload, payload_len, NULL); + if (ret < 0) { + NET_ERR("Cannot send data to peer (%d)", ret); + return ret; + } + + return http_send_flush(ctx, NULL); +} + +static int http_serve_index_html(struct http_ctx *ctx) +{ + static const char index_html[] = { +#include "index.html.inc" + }; + + NET_DBG("Sending index.html (%zd bytes) to client", + sizeof(index_html)); + + return http_response(ctx, HTTP_STATUS_200_OK, index_html, + sizeof(index_html)); +} + +static int http_serve_js(struct http_ctx *ctx) +{ + static const char js_gz[] = { +#include "ws.js.gz.inc" + }; + + NET_DBG("Sending ws.js (%zd bytes) to client", sizeof(js_gz)); + return http_response(ctx, HTTP_STATUS_200_OK_GZ_CSS, + js_gz, sizeof(js_gz)); +} + +static int http_response_soft_404(struct http_ctx *ctx) +{ + 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)); +} + +static int append_and_send_data(struct http_ctx *http_ctx, bool final, + const char *fmt, ...) +{ + enum ws_opcode opcode = WS_OPCODE_CONTINUE; + va_list ap; + size_t len; + int ret; + + va_start(ap, fmt); + vsnprintk(buf, MAX_BUF_LEN, fmt, ap); + va_end(ap); + + len = strlen(buf); + + if (final) { + if (msg_count == 0) { + opcode = WS_OPCODE_DATA_TEXT; + } + + ret = ws_send_msg_to_client(http_ctx, buf, len, + opcode, final, NULL); + if (ret < 0) { + NET_DBG("Could not send %zd bytes data to client", + len); + goto out; + } else { + NET_DBG("Sent %zd bytes to client", len); + } + + msg_count = 0; + + return ret; + } + + if (msg_count == 0) { + opcode = WS_OPCODE_DATA_TEXT; + } + + ret = ws_send_msg_to_client(http_ctx, buf, len, + opcode, final, NULL); + if (ret < 0) { + NET_DBG("Could not send %zd bytes data to client", len); + goto out; + } else { + NET_DBG("Sent %zd bytes to client", len); + } + + msg_count++; + +out: + return ret; +} + +static int ws_works(struct http_ctx *ctx) +{ + int ret = 0; + + NET_INFO("WS url called"); + + append_and_send_data(ctx, false, "connection"); + append_and_send_data(ctx, true, " established."); + + return ret; +} + +static void ws_connected(struct http_ctx *ctx, + enum http_connection_type type, + void *user_data) +{ + char url[32]; + int len = min(sizeof(url), ctx->http.url_len); + + memcpy(url, ctx->http.url, len); + url[len] = '\0'; + + NET_DBG("%s connect attempt URL %s", + type == HTTP_CONNECTION ? "HTTP" : "WS", url); + + if (type == HTTP_CONNECTION) { + if (strncmp(ctx->http.url, "/index.html", + sizeof("/index.html") - 1) == 0) { + http_serve_index_html(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/ws.js", + sizeof("/ws.js") - 1) == 0) { + http_serve_js(ctx); + http_close(ctx); + return; + } + + if (strncmp(ctx->http.url, "/", + ctx->http.url_len) == 0) { + http_serve_index_html(ctx); + http_close(ctx); + return; + } + + } else if (type == WS_CONNECTION) { + if (strncmp(ctx->http.url, "/ws", + sizeof("/ws") - 1) == 0) { + ws_works(ctx); + return; + } + } + + /* Give 404 error for all the other URLs we do not want to handle + * right now. + */ + http_response_soft_404(ctx); + http_close(ctx); +} + +static void ws_received(struct http_ctx *ctx, + struct net_pkt *pkt, + int status, + u32_t flags, + void *user_data) +{ + if (!status) { + struct net_buf *frag; + enum ws_opcode opcode; + int ret, hdr_len; + + NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); + + if (flags & WS_FLAG_BINARY) { + opcode = WS_OPCODE_DATA_BINARY; + } else { + opcode = WS_OPCODE_DATA_TEXT; + } + + hdr_len = net_pkt_get_len(pkt) - net_pkt_appdatalen(pkt); + + frag = pkt->frags; + while (frag) { + ret = ws_send_msg_to_client(ctx, frag->data + hdr_len, + frag->len - hdr_len, + opcode, + frag->frags ? true : false, + user_data); + if (ret < 0) { + NET_DBG("Cannot send ws data (%d bytes) " + "back (%d)", + frag->len - hdr_len, ret); + } else { + NET_DBG("Sent %d bytes to client", + frag->len - hdr_len); + } + + frag = frag->frags; + + /* Websocket header is found in first fragment so + * reset the value here. + */ + hdr_len = 0; + } + + http_send_flush(ctx, user_data); + + if (pkt) { + net_pkt_unref(pkt); + } + + } else { + NET_ERR("Receive error (%d)", status); + + if (pkt) { + net_pkt_unref(pkt); + } + } +} + +static void ws_sent(struct http_ctx *ctx, + int status, + void *user_data_send, + void *user_data) +{ + NET_DBG("Data sent status %d", status); +} + +static void ws_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) +{ + 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); + } + + return HTTP_VERDICT_DROP; +} + +void start_server(void) +{ + 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 + * default port 80 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 to desired + * value. If the protocol family in sockaddr is set to AF_UNSPEC, + * then both IPv4 and IPv6 socket is 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 both IPv4 and IPv6 */ + addr.sa_family = AF_UNSPEC; + + 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, &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, "/index.html", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/ws.js", HTTP_URL_STANDARD); + http_server_add_url(&http_urls, "/", 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 WS server", NULL); + if (ret < 0) { + NET_ERR("Cannot init web server (%d)", ret); + return; + } + + http_set_cb(&http_ctx, ws_connected, ws_received, ws_sent, ws_closed); + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) + net_app_set_net_pkt_pool(&http_ctx.app_ctx, tx_tcp_slab, data_tcp_pool); +#endif + +#if defined(CONFIG_NET_APP_TLS) + ret = http_server_set_tls(&http_ctx, + APP_BANNER, + INSTANCE_INFO, + strlen(INSTANCE_INFO), + setup_cert, + NULL, + &ssl_pool, + ws_tls_stack, + K_THREAD_STACK_SIZEOF(ws_tls_stack)); + if (ret < 0) { + NET_ERR("Cannot enable TLS support (%d)", ret); + } +#endif + + http_server_enable(&http_ctx); +} + +void stop_server(void) +{ + http_server_disable(&http_ctx); + http_release(&http_ctx); +} diff --git a/samples/net/ws_echo_server/src/ws.js b/samples/net/ws_echo_server/src/ws.js new file mode 100644 index 0000000000000..ed207177a8a11 --- /dev/null +++ b/samples/net/ws_echo_server/src/ws.js @@ -0,0 +1,45 @@ +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +var connected; +var first_run; +var ws; + +function init() { + ws = new WebSocket(location.origin.replace("http", "ws") + "/ws"); + + first_run = "true"; + connected = "false"; + + ws.onopen = function() { + output("Websocket connection opened"); + connected = "true"; + }; + + ws.onmessage = function(e) { + output("Websocket data received: " + e.data); + }; + + ws.onclose = function() { + output("Websocket connection closed"); + connected = "false"; + }; + + ws.onerror = function(e) { + output("Websocket data error (see console)"); + console.log(e) + }; +} + +function escape(str) { + return str.replace(/&/, "&").replace(//, ">").replace(/"/, """); // " +} + +function output(str) { + var log = document.getElementById("output"); + log.innerHTML += escape(str) + "
"; +} diff --git a/subsys/net/ip/Kconfig b/subsys/net/ip/Kconfig index cdb40b63618e3..2be33da6f0fc6 100644 --- a/subsys/net/ip/Kconfig +++ b/subsys/net/ip/Kconfig @@ -67,6 +67,14 @@ config NET_ROUTE default n default y if NET_IPV6_NBR_CACHE +config NET_ROUTING + bool "IP routing between interfaces" + default n + depends on NET_ROUTE + help + Allow IPv6 routing between different network interfaces and + technologies. + config NET_MAX_ROUTES int "Max number of routing entries stored." default NET_IPV6_MAX_NEIGHBORS diff --git a/subsys/net/ip/ipv6.c b/subsys/net/ip/ipv6.c index 978ea9d585f41..7002edc078a3a 100644 --- a/subsys/net/ip/ipv6.c +++ b/subsys/net/ip/ipv6.c @@ -228,8 +228,11 @@ static struct net_nbr *nbr_lookup(struct net_nbr_table *table, continue; } - if (nbr->iface == iface && - net_ipv6_addr_cmp(&net_ipv6_nbr_data(nbr)->addr, addr)) { + if (iface && nbr->iface != iface) { + continue; + } + + if (net_ipv6_addr_cmp(&net_ipv6_nbr_data(nbr)->addr, addr)) { return nbr; } } @@ -783,9 +786,11 @@ static struct net_pkt *update_ll_reserve(struct net_pkt *pkt, /* No need to do anything if we are forwarding the packet * as we already know everything about the destination of - * the packet. + * the packet, but only if both src and dest are using + * same technology meaning that link address length is the same. */ - if (net_pkt_forwarding(pkt)) { + if (net_pkt_forwarding(pkt) && + net_pkt_orig_iface(pkt) == net_pkt_iface(pkt)) { return pkt; } @@ -797,6 +802,24 @@ static struct net_pkt *update_ll_reserve(struct net_pkt *pkt, NET_DBG("Adjust reserve old %d new %d", net_pkt_ll_reserve(pkt), reserve); + /* Normally these debug prints are not needed so we do not print them + * always. If your packets get dropped for some reason by L2, then + * you can enable this block to see the IPv6 and LL addresses that + * are used. + */ + if (0) { + NET_DBG("ll src %s", + net_sprint_ll_addr(net_pkt_ll_src(pkt)->addr, + net_pkt_ll_src(pkt)->len)); + NET_DBG("ll dst %s", + net_sprint_ll_addr(net_pkt_ll_dst(pkt)->addr, + net_pkt_ll_dst(pkt)->len)); + NET_DBG("ip src %s", + net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->src)); + NET_DBG("ip dst %s", + net_sprint_ipv6_addr(&NET_IPV6_HDR(pkt)->dst)); + } + net_pkt_set_ll_reserve(pkt, reserve); orig_frag = pkt->frags; @@ -851,6 +874,58 @@ static struct net_pkt *update_ll_reserve(struct net_pkt *pkt, return pkt; } +static struct in6_addr *check_route(struct net_if *iface, + struct in6_addr *dst, + bool *try_route) +{ + struct in6_addr *nexthop = NULL; + struct net_route_entry *route; + struct net_if_router *router; + + route = net_route_lookup(iface, dst); + if (route) { + nexthop = net_route_get_nexthop(route); + + NET_DBG("Route %p nexthop %s", route, + nexthop ? net_sprint_ipv6_addr(nexthop) : ""); + + if (!nexthop) { + net_route_del(route); + + net_rpl_global_repair(route); + + NET_DBG("No route to host %s", + net_sprint_ipv6_addr(dst)); + + return NULL; + } + } else { + /* No specific route to this host, use the default + * route instead. + */ + router = net_if_ipv6_router_find_default(NULL, dst); + if (!router) { + NET_DBG("No default route to %s", + net_sprint_ipv6_addr(dst)); + + /* Try to send the packet anyway */ + nexthop = dst; + if (try_route) { + *try_route = true; + } + + return nexthop; + } + + nexthop = &router->address.in6_addr; + + NET_DBG("Router %p nexthop %s", router, + net_sprint_ipv6_addr(nexthop)); + } + + return nexthop; +} + struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt) { struct in6_addr *nexthop = NULL; @@ -920,7 +995,16 @@ struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt) return pkt; } - if (net_pkt_ll_dst(pkt)->addr || + /* If the IPv6 destination address is not link local, then try to get + * the next hop from routing table if we have multi interface routing + * enabled. The reason for this is that the neighbor cache will not + * contain public IPv6 address information so in that case we should + * not enter this branch. + */ + if ((net_pkt_ll_dst(pkt)->addr && + ((IS_ENABLED(CONFIG_NET_ROUTING) && + net_is_ipv6_ll_addr(&NET_IPV6_HDR(pkt)->dst)) || + !IS_ENABLED(CONFIG_NET_ROUTING))) || net_is_ipv6_addr_mcast(&NET_IPV6_HDR(pkt)->dst)) { /* Update RPL header */ if (net_rpl_update_header(pkt, &NET_IPV6_HDR(pkt)->dst) < 0) { @@ -939,41 +1023,17 @@ struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt) /* We need to figure out where the destination * host is located. */ - struct net_route_entry *route; - struct net_if_router *router; - - route = net_route_lookup(NULL, &NET_IPV6_HDR(pkt)->dst); - if (route) { - nexthop = net_route_get_nexthop(route); - if (!nexthop) { - net_route_del(route); - - net_rpl_global_repair(route); - - NET_DBG("No route to host %s", - net_sprint_ipv6_addr( - &NET_IPV6_HDR(pkt)->dst)); + bool try_route = false; - net_pkt_unref(pkt); - return NULL; - } - } else { - /* No specific route to this host, use the default - * route instead. - */ - router = net_if_ipv6_router_find_default(NULL, - &NET_IPV6_HDR(pkt)->dst); - if (!router) { - NET_DBG("No default route to %s", - net_sprint_ipv6_addr( - &NET_IPV6_HDR(pkt)->dst)); - - /* Try to send the packet anyway */ - nexthop = &NET_IPV6_HDR(pkt)->dst; - goto try_send; - } + nexthop = check_route(NULL, &NET_IPV6_HDR(pkt)->dst, + &try_route); + if (!nexthop) { + net_pkt_unref(pkt); + return NULL; + } - nexthop = &router->address.in6_addr; + if (try_route) { + goto try_send; } } @@ -988,6 +1048,8 @@ struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt) */ if (net_if_ipv6_addr_onlink(&iface, nexthop)) { net_pkt_set_iface(pkt, iface); + } else { + iface = net_pkt_iface(pkt); } /* If the above check returns null, we try to send @@ -996,11 +1058,10 @@ struct net_pkt *net_ipv6_prepare_for_send(struct net_pkt *pkt) } try_send: - nbr = nbr_lookup(&net_neighbor.table, net_pkt_iface(pkt), nexthop); + nbr = nbr_lookup(&net_neighbor.table, iface, nexthop); NET_DBG("Neighbor lookup %p (%d) iface %p addr %s state %s", nbr, - nbr ? nbr->idx : NET_NBR_LLADDR_UNKNOWN, - net_pkt_iface(pkt), + nbr ? nbr->idx : NET_NBR_LLADDR_UNKNOWN, iface, net_sprint_ipv6_addr(nexthop), nbr ? net_ipv6_nbr_state2str(net_ipv6_nbr_data(nbr)->state) : "-"); @@ -1244,13 +1305,34 @@ int net_ipv6_send_na(struct net_if *iface, const struct in6_addr *src, return -EINVAL; } +static void ns_routing_info(struct net_pkt *pkt, + struct in6_addr *nexthop, + struct in6_addr *tgt) +{ +#if defined(CONFIG_NET_DEBUG_IPV6) && (CONFIG_SYS_LOG_NET_LEVEL > 3) + char out[NET_IPV6_ADDR_LEN]; + + snprintk(out, sizeof(out), "%s", net_sprint_ipv6_addr(nexthop)); + + if (net_ipv6_addr_cmp(nexthop, tgt)) { + NET_DBG("Routing to %s iface %p", out, net_pkt_iface(pkt)); + } else { + NET_DBG("Routing to %s via %s iface %p", + net_sprint_ipv6_addr(tgt), out, net_pkt_iface(pkt)); + } +#endif +} + static enum net_verdict handle_ns_input(struct net_pkt *pkt) { u16_t total_len = net_pkt_get_len(pkt); struct net_icmpv6_nd_opt_hdr ndopthdr, *nd_opt_hdr; struct net_icmpv6_ns_hdr nshdr, *ns_hdr; struct net_if_addr *ifaddr; + struct in6_addr *tgt; + const struct in6_addr *src; u8_t flags = 0, prev_opt_len = 0; + bool routing = false; int ret; size_t left_len; @@ -1332,14 +1414,62 @@ static enum net_verdict handle_ns_input(struct net_pkt *pkt) nd_opt_hdr = net_icmpv6_get_nd_opt_hdr(pkt, &ndopthdr); } - ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), - &ns_hdr->tgt); + if (IS_ENABLED(CONFIG_NET_ROUTING)) { + ifaddr = net_if_ipv6_addr_lookup(&ns_hdr->tgt, NULL); + } else { + ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), + &ns_hdr->tgt); + } + if (!ifaddr) { + if (IS_ENABLED(CONFIG_NET_ROUTING)) { + struct in6_addr *nexthop; + + nexthop = check_route(NULL, &ns_hdr->tgt, NULL); + if (nexthop) { + ns_routing_info(pkt, nexthop, &ns_hdr->tgt); + + /* Note that the target is not the address of + * the "nethop" as that is a link-local address + * which is not routable. + */ + tgt = &ns_hdr->tgt; + + /* Source address must be one of our real + * interface address where the packet was + * received. + */ + src = net_if_ipv6_select_src_addr( + net_pkt_iface(pkt), + &NET_IPV6_HDR(pkt)->src); + if (!src) { + NET_DBG("No interface address for " + "dst %s iface %p", + net_sprint_ipv6_addr( + &NET_IPV6_HDR(pkt)->src), + net_pkt_iface(pkt)); + goto drop; + } + + routing = true; + goto nexthop_found; + } + } + NET_DBG("No such interface address %s", net_sprint_ipv6_addr(&ns_hdr->tgt)); goto drop; + } else { + tgt = &ifaddr->address.in6_addr; + + /* As we swap the addresses later, the source will correctly + * have our address. + */ + src = &NET_IPV6_HDR(pkt)->src; } +nexthop_found: + #if !defined(CONFIG_NET_IPV6_DAD) if (net_is_ipv6_addr_unspecified(&NET_IPV6_HDR(pkt)->src)) { goto drop; @@ -1392,12 +1522,26 @@ static enum net_verdict handle_ns_input(struct net_pkt *pkt) goto send_na; } + if (routing) { + /* No need to do NUD here when the target is being routed. */ + goto send_na; + } + /* Neighbor Unreachability Detection (NUD) */ - if (net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), - &NET_IPV6_HDR(pkt)->dst)) { + if (IS_ENABLED(CONFIG_NET_ROUTING)) { + ifaddr = net_if_ipv6_addr_lookup(&NET_IPV6_HDR(pkt)->dst, + NULL); + } else { + ifaddr = net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), + &NET_IPV6_HDR(pkt)->dst); + } + + if (ifaddr) { net_ipaddr_copy(&NET_IPV6_HDR(pkt)->dst, &NET_IPV6_HDR(pkt)->src); net_ipaddr_copy(&NET_IPV6_HDR(pkt)->src, &ns_hdr->tgt); + src = &NET_IPV6_HDR(pkt)->src; + tgt = &ifaddr->address.in6_addr; flags = NET_ICMPV6_NA_FLAG_SOLICITED | NET_ICMPV6_NA_FLAG_OVERRIDE; goto send_na; @@ -1408,9 +1552,9 @@ static enum net_verdict handle_ns_input(struct net_pkt *pkt) send_na: ret = net_ipv6_send_na(net_pkt_iface(pkt), - &NET_IPV6_HDR(pkt)->src, + src, &NET_IPV6_HDR(pkt)->dst, - &ifaddr->address.in6_addr, + tgt, flags); if (!ret) { net_pkt_unref(pkt); @@ -1441,6 +1585,15 @@ static void nd_reachable_timeout(struct k_work *work) return; } + if (net_rpl_get_interface() && nbr->iface == net_rpl_get_interface()) { + /* The address belongs to RPL network, no need to activate + * full neighbor reachable rules in this case. + * Mark the neighbor always reachable. + */ + data->state = NET_IPV6_NBR_STATE_REACHABLE; + return; + } + switch (data->state) { case NET_IPV6_NBR_STATE_STATIC: NET_ASSERT_INFO(false, "Static entry shall never timeout"); @@ -3767,6 +3920,111 @@ static inline bool is_upper_layer_protocol_header(u8_t proto) proto == IPPROTO_TCP); } +#if defined(CONFIG_NET_ROUTE) +static struct net_route_entry *add_route(struct net_if *iface, + struct in6_addr *addr, + u8_t prefix_len) +{ + struct net_route_entry *route; + + route = net_route_lookup(iface, addr); + if (route) { + return route; + } + + route = net_route_add(iface, addr, prefix_len, addr); + + NET_DBG("%s route to %s/%d iface %p", route ? "Add" : "Cannot add", + net_sprint_ipv6_addr(addr), prefix_len, iface); + + return route; +} +#endif /* CONFIG_NET_ROUTE */ + +static void no_route_info(struct net_pkt *pkt, + struct in6_addr *src, + struct in6_addr *dst) +{ +#if defined(CONFIG_NET_DEBUG_IPV6) && (CONFIG_SYS_LOG_NET_LEVEL > 3) + char out[NET_IPV6_ADDR_LEN]; + + snprintk(out, sizeof(out), "%s", net_sprint_ipv6_addr(dst)); + + NET_DBG("Will not route pkt %p ll src %s to dst %s between interfaces", + pkt, net_sprint_ipv6_addr(src), out); +#endif +} + +#if defined(CONFIG_NET_ROUTE) +static enum net_verdict route_ipv6_packet(struct net_pkt *pkt, + struct net_ipv6_hdr *hdr) +{ + struct net_route_entry *route; + struct in6_addr *nexthop; + bool found; + + /* Check if the packet can be routed */ + if (IS_ENABLED(CONFIG_NET_ROUTING)) { + found = net_route_get_info(NULL, &hdr->dst, &route, + &nexthop); + } else { + found = net_route_get_info(net_pkt_iface(pkt), + &hdr->dst, &route, &nexthop); + } + + if (found) { + int ret; + + if (IS_ENABLED(CONFIG_NET_ROUTING) && + (net_is_ipv6_ll_addr(&hdr->src) || + net_is_ipv6_ll_addr(&hdr->dst))) { + /* RFC 4291 ch 2.5.6 */ + no_route_info(pkt, &hdr->src, &hdr->dst); + goto drop; + } + + /* Used when detecting if the original link + * layer address length is changed or not. + */ + net_pkt_set_orig_iface(pkt, net_pkt_iface(pkt)); + + if (route) { + net_pkt_set_iface(pkt, route->iface); + } + + if (IS_ENABLED(CONFIG_NET_ROUTING) && + net_pkt_orig_iface(pkt) != net_pkt_iface(pkt)) { + /* If the route interface to destination is + * different than the original route, then add + * route to original source. + */ + NET_DBG("Route pkt %p from %p to %p", + pkt, net_pkt_orig_iface(pkt), + net_pkt_iface(pkt)); + + add_route(net_pkt_orig_iface(pkt), + &NET_IPV6_HDR(pkt)->src, 128); + } + + ret = net_route_packet(pkt, nexthop); + if (ret < 0) { + NET_DBG("Cannot re-route pkt %p via %s " + "at iface %p (%d)", + pkt, net_sprint_ipv6_addr(nexthop), + net_pkt_iface(pkt), ret); + } else { + return NET_OK; + } + } else { + NET_DBG("No route to %s pkt %p dropped", + net_sprint_ipv6_addr(&hdr->dst), pkt); + } + +drop: + return NET_DROP; +} +#endif /* CONFIG_NET_ROUTE */ + enum net_verdict net_ipv6_process_pkt(struct net_pkt *pkt) { struct net_ipv6_hdr *hdr = NET_IPV6_HDR(pkt); @@ -3807,32 +4065,30 @@ enum net_verdict net_ipv6_process_pkt(struct net_pkt *pkt) !net_is_ipv6_addr_mcast(&hdr->dst) && !net_is_ipv6_addr_loopback(&hdr->dst)) { #if defined(CONFIG_NET_ROUTE) - struct net_route_entry *route; - struct in6_addr *nexthop; - - /* Check if the packet can be routed */ - if (net_route_get_info(net_pkt_iface(pkt), &hdr->dst, &route, - &nexthop)) { - int ret; - - if (route) { - net_pkt_set_iface(pkt, route->iface); - } + enum net_verdict verdict; - ret = net_route_packet(pkt, nexthop); - if (ret < 0) { - NET_DBG("Cannot re-route pkt %p via %s (%d)", - pkt, net_sprint_ipv6_addr(nexthop), - ret); - } else { - return NET_OK; - } - } else + verdict = route_ipv6_packet(pkt, hdr); + if (verdict == NET_OK) { + return NET_OK; + } +#else /* CONFIG_NET_ROUTE */ + NET_DBG("IPv6 packet in pkt %p not for me", pkt); #endif /* CONFIG_NET_ROUTE */ - { - NET_DBG("IPv6 packet in pkt %p not for me", pkt); - } + net_stats_update_ipv6_drop(); + goto drop; + } + + /* If we receive a packet with ll source address fe80: and destination + * address is one of ours, and if the packet would cross interface + * boundary, then drop the packet. RFC 4291 ch 2.5.6 + */ + if (IS_ENABLED(CONFIG_NET_ROUTING) && + net_is_ipv6_ll_addr(&hdr->src) && + !net_is_ipv6_addr_mcast(&hdr->dst) && + !net_if_ipv6_addr_lookup_by_iface(net_pkt_iface(pkt), + &hdr->dst)) { + no_route_info(pkt, &hdr->src, &hdr->dst); net_stats_update_ipv6_drop(); goto drop; diff --git a/subsys/net/ip/net_context.c b/subsys/net/ip/net_context.c index 0916e7b7e1704..3cc735f51af5b 100644 --- a/subsys/net/ip/net_context.c +++ b/subsys/net/ip/net_context.c @@ -54,6 +54,8 @@ #define FIN_TIMEOUT K_SECONDS(1) +#define BUF_ALLOC_TIMEOUT MSEC(200) + /* Declares a wrapper function for a net_conn callback that refs the * context around the invocation (to protect it from premature * deletion). Long term would be nice to see this feature be part of @@ -586,6 +588,191 @@ static void queue_fin(struct net_context *ctx) } } +/* Split the packet into MSS size units */ +static int queue_pkt(struct net_context *ctx, struct net_pkt *pkt) +{ + size_t pkt_len = net_pkt_get_len(pkt); + struct net_buf *frag_to_send = NULL; + struct net_buf *frag_remaining = NULL; + struct net_buf *frag, *prev, *orig; + struct net_pkt *new_pkt; + int ret, new_len; + size_t mss; + + /* FIXME: use mss for sending length instead of the recv one */ + mss = net_tcp_get_recv_mss(ctx->tcp); + if (pkt_len <= mss) { + NET_DBG("[%p] Queueing %zd bytes pkt %p (tcp %p)", ctx, + pkt_len, pkt, ctx->tcp); + + net_pkt_set_appdatalen(pkt, pkt_len); + return net_tcp_queue_data(ctx, pkt); + } + + /* Large packet, split it into smaller pieces */ + + /* There should not be any protocol headers in fragments + * but if there is, then skip them when setting appdatalen + * value. + */ + NET_ASSERT_INFO((pkt_len - net_pkt_appdatalen(pkt)) <= pkt->frags->len, + "Header %zd is longer than first fragment %d", + pkt_len - net_pkt_appdatalen(pkt), pkt->frags->len); + + NET_DBG("[%p] splitting pkt %p to smaller %zd long pieces", ctx, + pkt, mss); + + /* For cloning pkt, we do not want to clone the fragments at this + * point. + */ + frag = pkt->frags; + pkt->frags = NULL; + + orig = frag; + new_len = 0; + prev = NULL; + + while (frag) { + int fit_len, appdata_len; + + new_len += frag->len; + + if (new_len <= mss) { + prev = frag; + frag = frag->frags; + + continue; + } + + fit_len = mss - (new_len - frag->len); + appdata_len = new_len - frag->len + fit_len; + + new_pkt = net_pkt_clone(pkt, BUF_ALLOC_TIMEOUT); + if (!new_pkt) { + NET_DBG("Cannot clone pkt %p", new_pkt); + + if (frag_to_send) { + net_buf_unref(frag_to_send); + } + + if (frag_remaining) { + net_buf_unref(frag_remaining); + } + + pkt->frags = frag; + return -ENOMEM; + } + + NET_DBG("[%p] cloned pkt %p %d bytes", ctx, new_pkt, + appdata_len); + + ret = net_pkt_split(new_pkt, frag, fit_len, + &frag_to_send, &frag_remaining, + BUF_ALLOC_TIMEOUT); + if (ret < 0) { + NET_DBG("Cannot split %p from offset %d", frag, + fit_len); + + net_pkt_unref(new_pkt); + pkt->frags = frag; + return -ENOMEM; + } + + NET_DBG("[%p] frag %p split to %p and %p", ctx, frag, + frag_to_send, frag_remaining); + + if (prev) { + prev->frags = frag_to_send; + } else { + new_pkt->frags = frag_to_send; + } + + frag_to_send->frags = NULL; + + new_pkt->frags = orig; + new_len = net_pkt_get_len(new_pkt); + + prev = NULL; + + frag_remaining->frags = frag->frags; + + frag->frags = NULL; + net_buf_unref(frag); + + /* Add room for the link layer header in fragment. */ + if (net_buf_headroom(frag_remaining) == 0) { + ret = net_pkt_insert(new_pkt, frag_remaining, + 0, pkt->ll_reserve, NULL, + BUF_ALLOC_TIMEOUT); + if (!ret) { + goto fail; + } + + net_buf_pull(frag_remaining, pkt->ll_reserve); + } + + orig = frag = frag_remaining; + + NET_DBG("[%p] frag %p headroom %zd", ctx, frag, + net_buf_headroom(frag)); + + /* Now send the new_pkt which contains max number of bytes that + * we can send. + */ + + /* Note that we do not need to set the appdata pointer as the + * TCP code does not use it for anything. + */ + net_pkt_set_appdatalen(new_pkt, appdata_len); + + NET_DBG("[%p] Queueing %d bytes for pkt %p (tcp %p)", + ctx, new_len, new_pkt, ctx->tcp); + + ret = net_tcp_queue_data(ctx, new_pkt); + if (ret < 0) { + NET_DBG("[%p] Cannot queue %p (%d)", + ctx, new_pkt, ret); + goto fail; + } + + new_len = 0; + } + + if (!frag_remaining) { + NET_DBG("[%p] All fragments sent for pkt %p", ctx, pkt); + return 0; + } + + pkt->frags = frag_remaining; + pkt_len = net_pkt_get_len(pkt); + + /* Gets rid of any zero length fragments */ + net_pkt_compact(pkt); + + if (net_buf_headroom(frag_remaining) == 0) { + ret = net_pkt_insert(pkt, frag_remaining, + 0, pkt->ll_reserve, NULL, + BUF_ALLOC_TIMEOUT); + if (!ret) { + return -ENOMEM; + } + + net_buf_pull(frag_remaining, pkt->ll_reserve); + } + + NET_DBG("[%p] Remaining %zd bytes in %p chain", + ctx, pkt_len, pkt); + + net_pkt_set_appdatalen(pkt, pkt_len); + + return net_tcp_queue_data(ctx, pkt); + +fail: + net_pkt_unref(new_pkt); + + /* Caller should delete the remaining frags */ + return ret; +} #endif /* CONFIG_NET_TCP */ int net_context_ref(struct net_context *context) @@ -2136,8 +2323,8 @@ static int sendto(struct net_pkt *pkt, #if defined(CONFIG_NET_TCP) if (net_context_get_ip_proto(context) == IPPROTO_TCP) { - net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt)); - ret = net_tcp_queue_data(context, pkt); + /* Split the data to be sent to MSS size chunks */ + ret = queue_pkt(context, pkt); } else #endif /* CONFIG_NET_TCP */ { diff --git a/subsys/net/ip/net_core.c b/subsys/net/ip/net_core.c index 6a430a5665037..9a8ddedb5dfae 100644 --- a/subsys/net/ip/net_core.c +++ b/subsys/net/ip/net_core.c @@ -347,6 +347,10 @@ int net_recv_data(struct net_if *iface, struct net_pkt *pkt) NET_DBG("fifo %p iface %p pkt %p len %zu", &rx_queue, iface, pkt, net_pkt_get_len(pkt)); + if (IS_ENABLED(CONFIG_NET_ROUTING)) { + net_pkt_set_orig_iface(pkt, iface); + } + net_pkt_set_iface(pkt, iface); k_fifo_put(&rx_queue, pkt); diff --git a/subsys/net/ip/route.c b/subsys/net/ip/route.c index b5c82f9405da4..210c3f7cf0a57 100644 --- a/subsys/net/ip/route.c +++ b/subsys/net/ip/route.c @@ -737,9 +737,9 @@ int net_route_packet(struct net_pkt *pkt, struct in6_addr *nexthop) struct net_linkaddr_storage *lladdr; struct net_nbr *nbr; - nbr = net_ipv6_nbr_lookup(net_pkt_iface(pkt), nexthop); + nbr = net_ipv6_nbr_lookup(NULL, nexthop); if (!nbr) { - NET_DBG("Cannot find %s neighbor.", + NET_DBG("Cannot find %s neighbor", net_sprint_ipv6_addr(nexthop)); return -ENOENT; } @@ -777,6 +777,8 @@ int net_route_packet(struct net_pkt *pkt, struct in6_addr *nexthop) net_pkt_ll_dst(pkt)->type = lladdr->type; net_pkt_ll_dst(pkt)->len = lladdr->len; + net_pkt_set_iface(pkt, nbr->iface); + return net_send_data(pkt); } diff --git a/subsys/net/ip/rpl.c b/subsys/net/ip/rpl.c index 5994fa8bf0dc1..d02821573beb2 100644 --- a/subsys/net/ip/rpl.c +++ b/subsys/net/ip/rpl.c @@ -1471,6 +1471,11 @@ bool net_rpl_set_prefix(struct net_if *iface, last_len ? "non-NULL" : "NULL"); if (last_len == 0) { check_prefix(iface, NULL, &dag->prefix_info); + + net_if_ipv6_prefix_add(iface, + &dag->prefix_info.prefix, + dag->prefix_info.length, + NET_IPV6_ND_INFINITE_LIFETIME); } else { check_prefix(iface, &last_prefix, &dag->prefix_info); } @@ -4229,6 +4234,11 @@ struct net_rpl_instance *net_rpl_get_default_instance(void) return rpl_default_instance; } +struct net_if *net_rpl_get_interface(void) +{ + return rpl_default_iface; +} + void net_rpl_init(void) { /* Note that link_cb needs to be static as it is added diff --git a/subsys/net/ip/rpl.h b/subsys/net/ip/rpl.h index e0adb6eb6f8c0..4c39c26f777f3 100644 --- a/subsys/net/ip/rpl.h +++ b/subsys/net/ip/rpl.h @@ -938,6 +938,12 @@ void net_rpl_global_repair(struct net_route_entry *route); */ bool net_rpl_repair_root(u8_t instance_id); +/** + * @brief Get the default RPL interface. + * + */ +struct net_if *net_rpl_get_interface(void); + /** * @brief Update RPL headers in IPv6 packet. * @@ -1037,6 +1043,10 @@ void net_rpl_init(void); #define net_rpl_init(...) #define net_rpl_global_repair(...) #define net_rpl_update_header(...) 0 +static inline struct net_if *net_rpl_get_interface(void) +{ + return NULL; +} #endif /* CONFIG_NET_RPL */ #ifdef __cplusplus diff --git a/subsys/net/lib/CMakeLists.txt b/subsys/net/lib/CMakeLists.txt index 9b5e9e01466da..992019dd12cb8 100644 --- a/subsys/net/lib/CMakeLists.txt +++ b/subsys/net/lib/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory_ifdef(CONFIG_DNS_RESOLVER dns) add_subdirectory_ifdef(CONFIG_MQTT_LIB mqtt) add_subdirectory_ifdef(CONFIG_NET_APP_SETTINGS app) add_subdirectory_ifdef(CONFIG_NET_SOCKETS sockets) +add_subdirectory_ifdef(CONFIG_WEBSOCKET websocket) if(CONFIG_HTTP_PARSER_URL OR CONFIG_HTTP_PARSER diff --git a/subsys/net/lib/Kconfig b/subsys/net/lib/Kconfig index ebe3fe5920c94..8bd37062d9a6f 100644 --- a/subsys/net/lib/Kconfig +++ b/subsys/net/lib/Kconfig @@ -18,6 +18,8 @@ source "subsys/net/lib/lwm2m/Kconfig" source "subsys/net/lib/sntp/Kconfig" +source "subsys/net/lib/websocket/Kconfig" + endmenu menu "Network Application Support" diff --git a/subsys/net/lib/http/Kconfig b/subsys/net/lib/http/Kconfig index b7c945c270c9a..8c76a364a0f1b 100644 --- a/subsys/net/lib/http/Kconfig +++ b/subsys/net/lib/http/Kconfig @@ -39,9 +39,11 @@ config HTTP_HEADERS int "HTTP header field max number of items" depends on HTTP_SERVER default 8 + default 20 if WEBSOCKET help Number of HTTP header field items that an HTTP server - application will handle + application will handle. If websocket is enabled, then the + default needs to be much bigger. config HTTPS bool "HTTPS support" diff --git a/subsys/net/lib/http/http_app.c b/subsys/net/lib/http/http_app.c index f3823d33d3cb4..bd47612d4c91f 100644 --- a/subsys/net/lib/http/http_app.c +++ b/subsys/net/lib/http/http_app.c @@ -66,6 +66,15 @@ int http_close(struct http_ctx *ctx) } #endif +#if defined(CONFIG_HTTP_SERVER) && defined(CONFIG_WEBSOCKET) + if (ctx->websocket.pending) { + net_pkt_unref(ctx->websocket.pending); + ctx->websocket.pending = NULL; + } + + ctx->websocket.data_waiting = 0; +#endif + return net_app_close(&ctx->app_ctx); } diff --git a/subsys/net/lib/http/http_app_server.c b/subsys/net/lib/http/http_app_server.c index b38e22717e240..9fdad9c9e86b6 100644 --- a/subsys/net/lib/http/http_app_server.c +++ b/subsys/net/lib/http/http_app_server.c @@ -25,6 +25,11 @@ #include #include +#if defined(CONFIG_WEBSOCKET) +#include +#include "../websocket/websocket_internal.h" +#endif + #define BUF_ALLOC_TIMEOUT 100 #define HTTP_DEFAULT_PORT 80 @@ -310,6 +315,10 @@ static inline void new_client(struct http_ctx *ctx, struct net_context *net_ctx; const char *type_str = "HTTP"; + if (type == WS_CONNECTION) { + type_str = "WS"; + } + net_ctx = get_server_ctx(app_ctx, ctx->addr); if (net_ctx) { NET_INFO("[%p] %s connection from %s (%p)", ctx, type_str, @@ -527,6 +536,17 @@ static void http_closed(struct net_app_ctx *app_ctx, if (ctx->cb.close) { ctx->cb.close(ctx, 0, ctx->user_data); } + +#if defined(CONFIG_WEBSOCKET) + if (ctx->websocket.pending) { + net_pkt_unref(ctx->websocket.pending); + ctx->websocket.pending = NULL; + } + + ctx->websocket.data_waiting = 0; +#endif + + ctx->http.field_values_ctr = 0; } static void http_received(struct net_app_ctx *app_ctx, @@ -568,9 +588,9 @@ static void http_received(struct net_app_ctx *app_ctx, if (ctx->state == HTTP_STATE_OPEN) { /* We have active websocket session and there is no longer * any HTTP traffic in the connection. Give the data to - * application to send. + * application. */ - goto http_only; + goto ws_only; } while (frag) { @@ -582,7 +602,7 @@ static void http_received(struct net_app_ctx *app_ctx, ctx->http.request_buf_len) { if (ctx->state == HTTP_STATE_HEADER_RECEIVED) { - goto http_ready; + goto ws_ready; } /* If the caller has not supplied a callback, then @@ -637,7 +657,7 @@ static void http_received(struct net_app_ctx *app_ctx, http_send_error(ctx, 400, HTTP_STATUS_400_BR, NULL, 0); } else { if (ctx->state == HTTP_STATE_HEADER_RECEIVED) { - goto http_ready; + goto ws_ready; } http_process_recv(ctx); @@ -651,17 +671,181 @@ static void http_received(struct net_app_ctx *app_ctx, return; -http_only: +ws_only: if (ctx->cb.recv) { +#if defined(CONFIG_WEBSOCKET) + u32_t msg_len, header_len = 0; + bool masked = true; + int ret; + + if (ctx->websocket.data_waiting == 0) { + ctx->websocket.masking_value = 0; + ctx->websocket.data_read = 0; + ctx->websocket.msg_type_flag = 0; + + if (ctx->websocket.pending) { + /* Append the pending data to current buffer */ + int orig_len; + + orig_len = net_pkt_appdatalen( + ctx->websocket.pending); + net_pkt_set_appdatalen( + ctx->websocket.pending, + orig_len + net_pkt_appdatalen(pkt)); + + net_pkt_frag_add(ctx->websocket.pending, + pkt->frags); + + pkt->frags = NULL; + net_pkt_unref(pkt); + + net_pkt_compact(ctx->websocket.pending); + } else { + ctx->websocket.pending = pkt; + } + + ret = ws_strip_header(ctx->websocket.pending, &masked, + &ctx->websocket.masking_value, + &msg_len, + &ctx->websocket.msg_type_flag, + &header_len); + if (ret < 0) { + /* Not enough bytes for a complete websocket + * header, continue reading data. + */ + NET_DBG("[%p] pending %zd bytes, waiting more", + ctx, + net_pkt_get_len(ctx->websocket.pending)); + return; + } + + if (ctx->websocket.msg_type_flag & WS_FLAG_CLOSE) { + NET_DBG("[%p] Close request from peer", ctx); + http_close(ctx); + return; + } + + if (ctx->websocket.msg_type_flag & WS_FLAG_PING) { + NET_DBG("[%p] Ping request from peer", ctx); + ws_send_msg(ctx, NULL, 0, WS_OPCODE_PONG, + false, true, NULL); + ctx->websocket.data_waiting = 0; + return; + } + + /* We have now received some data. It might not yet be + * the full data that is told by msg_len but we can + * already pass this data to caller. + */ + ctx->websocket.data_waiting = msg_len; + + if (net_pkt_get_len(ctx->websocket.pending) == + header_len) { + NET_DBG("[%p] waiting more data", ctx); + + /* We do not need the websocket header + * any more, so discard it. + */ + net_pkt_unref(ctx->websocket.pending); + ctx->websocket.pending = NULL; + return; + } + + /* If we have more data pending than the header len, + * then discard the header as we do not need that. + */ + net_buf_pull(ctx->websocket.pending->frags, + header_len); + + pkt = ctx->websocket.pending; + ctx->websocket.pending = NULL; + + net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt)); + net_pkt_set_appdata(pkt, pkt->frags->data); + } + + if (net_pkt_appdatalen(pkt) > ctx->websocket.data_waiting) { + /* Now we received more data which in practice means + * that we got the next websocket header. + */ + struct net_buf *hdr, *payload; + struct net_pkt *cloned; + + ret = net_pkt_split(pkt, pkt->frags, + ctx->websocket.data_waiting, + &payload, &hdr, + ctx->timeout); + if (ret < 0) { + net_pkt_unref(pkt); + return; + } + + net_pkt_frag_unref(pkt->frags); + pkt->frags = NULL; + + cloned = net_pkt_clone(pkt, ctx->timeout); + if (!cloned) { + net_pkt_unref(pkt); + return; + } + + pkt->frags = payload; + payload->frags = NULL; + + net_pkt_compact(pkt); + + cloned->frags = hdr; + + ctx->websocket.pending = cloned; + ctx->websocket.data_waiting = 0; + + net_pkt_set_appdatalen(pkt, net_pkt_get_len(pkt)); + net_pkt_set_appdata(pkt, pkt->frags->data); + + net_pkt_set_appdatalen(cloned, net_pkt_get_len(cloned)); + net_pkt_set_appdata(cloned, cloned->frags->data); + + NET_DBG("More data (%d bytes) received, pending it", + net_pkt_appdatalen(cloned)); + } else { + ctx->websocket.data_waiting -= net_pkt_appdatalen(pkt); + } + + if (ctx->websocket.data_waiting) { + NET_DBG("[%p] waiting still %u bytes", ctx, + ctx->websocket.data_waiting); + } else { + NET_DBG("[%p] All bytes received", ctx); + } + + NET_DBG("[%p] Pass data (%d) to application for processing", + ctx, net_pkt_appdatalen(pkt)); + + NET_DBG("[%p] Masked %s mask 0x%04x", ctx, + masked ? "yes" : "no", + ctx->websocket.masking_value); + + if (masked) { + /* Always deliver unmasked data to the application */ + ws_mask_pkt(pkt, + ctx->websocket.masking_value, + &ctx->websocket.data_read); + } + + ctx->cb.recv(ctx, pkt, 0, ctx->websocket.msg_type_flag, + ctx->user_data); +#else ctx->cb.recv(ctx, pkt, 0, 0, ctx->user_data); +#endif } return; -http_ready: +ws_ready: http_change_state(ctx, HTTP_STATE_OPEN); - url_connected(ctx, HTTP_CONNECT); + url_connected(ctx, WS_CONNECTION); net_pkt_unref(pkt); + ctx->http.field_values_ctr = 0; } #if defined(CONFIG_HTTPS) @@ -773,7 +957,11 @@ static int on_headers_complete(struct http_parser *parser) { ARG_UNUSED(parser); +#if defined(CONFIG_WEBSOCKET) + return ws_headers_complete(parser); +#else return 0; +#endif } static int init_http_parser(struct http_ctx *ctx) diff --git a/subsys/net/lib/websocket/CMakeLists.txt b/subsys/net/lib/websocket/CMakeLists.txt new file mode 100644 index 0000000000000..64d9cf14df364 --- /dev/null +++ b/subsys/net/lib/websocket/CMakeLists.txt @@ -0,0 +1,6 @@ +# base64 support is need from mbedtls +zephyr_include_directories(. $ENV{ZEPHYR_BASE}/ext/lib/crypto/mbedtls/include/) + +zephyr_library() + +zephyr_library_sources_ifdef(CONFIG_WEBSOCKET websocket.c) diff --git a/subsys/net/lib/websocket/Kconfig b/subsys/net/lib/websocket/Kconfig new file mode 100644 index 0000000000000..7e47b98af202e --- /dev/null +++ b/subsys/net/lib/websocket/Kconfig @@ -0,0 +1,22 @@ +# Copyright (c) 2017 Intel Corporation +# +# SPDX-License-Identifier: Apache-2.0 +# + +menuconfig WEBSOCKET + bool "Websocket support [EXPERIMENTAL]" + default n + depends on HTTP_APP + select NET_TCP + help + This option enables the websocket library. + +if WEBSOCKET + +config NET_DEBUG_WEBSOCKET + bool "Debug websocket library" + default n + help + Enables websocket library to output debug messages + +endif # WEBSOCKET diff --git a/subsys/net/lib/websocket/websocket.c b/subsys/net/lib/websocket/websocket.c new file mode 100644 index 0000000000000..495fb4f0e4295 --- /dev/null +++ b/subsys/net/lib/websocket/websocket.c @@ -0,0 +1,495 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_NET_DEBUG_WEBSOCKET) +#define SYS_LOG_DOMAIN "ws" +#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 + +#define BUF_ALLOC_TIMEOUT 100 + +#define HTTP_CRLF "\r\n" + +/* From RFC 6455 chapter 4.2.2 */ +#define WS_MAGIC "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + +static void ws_mask_payload(u8_t *payload, size_t payload_len, + u32_t masking_value) +{ + int i; + + for (i = 0; i < payload_len; i++) { + payload[i] ^= masking_value >> (8 * (3 - i % 4)); + } +} + +void ws_mask_pkt(struct net_pkt *pkt, u32_t masking_value, u32_t *data_read) +{ + struct net_buf *frag; + u16_t pos; + int i; + + frag = net_frag_get_pos(pkt, + net_pkt_get_len(pkt) - net_pkt_appdatalen(pkt), + &pos); + if (!frag) { + return; + } + + NET_ASSERT(net_pkt_appdata(pkt) == frag->data + pos); + + while (frag) { + for (i = pos; i < frag->len; i++, (*data_read)++) { + frag->data[i] ^= + masking_value >> (8 * (3 - (*data_read) % 4)); + } + + pos = 0; + frag = frag->frags; + } +} + +int ws_send_msg(struct http_ctx *ctx, u8_t *payload, size_t payload_len, + enum ws_opcode opcode, bool mask, bool final, + void *user_send_data) +{ + u8_t header[14], hdr_len = 2; + int ret; + + if (ctx->state != HTTP_STATE_OPEN) { + return -ENOTCONN; + } + + if (opcode != WS_OPCODE_DATA_TEXT && opcode != WS_OPCODE_DATA_BINARY && + opcode != WS_OPCODE_CONTINUE && opcode != WS_OPCODE_CLOSE && + opcode != WS_OPCODE_PING && opcode != WS_OPCODE_PONG) { + return -EINVAL; + } + + memset(header, 0, sizeof(header)); + + /* Is this the last packet? */ + header[0] = final ? BIT(7) : 0; + + /* Text, binary, ping, pong or close ? */ + header[0] |= opcode; + + /* Masking */ + header[1] = mask ? BIT(7) : 0; + + if (payload_len < 126) { + header[1] |= payload_len; + } else if (payload_len < 65536) { + header[1] |= 126; + header[2] = payload_len >> 8; + header[3] = payload_len; + hdr_len += 2; + } else { + header[1] |= 127; + header[2] = 0; + header[3] = 0; + header[4] = 0; + header[5] = 0; + header[6] = payload_len >> 24; + header[7] = payload_len >> 16; + header[8] = payload_len >> 8; + header[9] = payload_len; + hdr_len += 8; + } + + /* Add masking value if needed */ + if (mask) { + u32_t masking_value; + + masking_value = sys_rand32_get(); + + header[hdr_len++] |= masking_value >> 24; + header[hdr_len++] |= masking_value >> 16; + header[hdr_len++] |= masking_value >> 8; + header[hdr_len++] |= masking_value; + + ws_mask_payload(payload, payload_len, masking_value); + } + + ret = http_prepare_and_send(ctx, header, hdr_len, user_send_data); + if (ret < 0) { + NET_DBG("Cannot add ws header (%d)", ret); + goto quit; + } + + if (payload) { + ret = http_prepare_and_send(ctx, payload, payload_len, + user_send_data); + if (ret < 0) { + NET_DBG("Cannot send %zd bytes message (%d)", + payload_len, ret); + goto quit; + } + } + + ret = http_send_flush(ctx, user_send_data); + +quit: + return ret; +} + +int ws_strip_header(struct net_pkt *pkt, bool *masked, u32_t *mask_value, + u32_t *message_length, u32_t *message_type_flag, + u32_t *header_len) +{ + struct net_buf *frag; + u16_t value, pos, appdata_pos; + u8_t len; /* message length byte */ + u8_t len_len; /* length of the length field in header */ + + frag = net_frag_read_be16(pkt->frags, 0, &pos, &value); + if (!frag && pos == 0xffff) { + return -ENOMSG; + } + + if (value & 0x8000) { + *message_type_flag |= WS_FLAG_FINAL; + } + + switch (value & 0x0f00) { + case 0x0100: + *message_type_flag |= WS_FLAG_TEXT; + break; + case 0x0200: + *message_type_flag |= WS_FLAG_BINARY; + break; + case 0x0800: + *message_type_flag |= WS_FLAG_CLOSE; + break; + case 0x0900: + *message_type_flag |= WS_FLAG_PING; + break; + case 0x0A00: + *message_type_flag |= WS_FLAG_PONG; + break; + } + + len = value & 0x007f; + if (len < 126) { + len_len = 0; + *message_length = len; + } else if (len == 126) { + u16_t msg_len; + + len_len = 2; + + frag = net_frag_read_be16(frag, pos, &pos, &msg_len); + if (!frag && pos == 0xffff) { + return -ENOMSG; + } + + *message_length = msg_len; + } else { + len_len = 4; + + frag = net_frag_read_be32(frag, pos, &pos, message_length); + if (!frag && pos == 0xffff) { + return -ENOMSG; + } + } + + if (value & 0x0080) { + *masked = true; + appdata_pos = 0; + + frag = net_frag_read_be32(frag, pos, &pos, mask_value); + if (!frag && pos == 0xffff) { + return -ENOMSG; + } + } else { + *masked = false; + appdata_pos = len_len; + } + + frag = net_frag_get_pos(pkt, pos + appdata_pos, &pos); + if (!frag && pos == 0xffff) { + return -ENOMSG; + } + + /* Minimum websocket header is 6 bytes, header length might be + * bigger depending on length field len. + */ + *header_len = 6 + len_len; + + return 0; +} + +static bool field_contains(const char *field, int field_len, + const char *str, int str_len) +{ + bool found = false; + char c, skip; + + c = *str++; + if (c == '\0') { + return false; + } + + str_len--; + + do { + do { + skip = *field++; + field_len--; + if (skip == '\0' || field_len == 0) { + return false; + } + } while (skip != c); + + if (field_len < str_len) { + return false; + } + + if (strncasecmp(field, str, str_len) == 0) { + found = true; + break; + } + + } while (field_len >= str_len); + + return found; +} + +static bool check_ws_headers(struct http_ctx *ctx, struct http_parser *parser, + int *ws_sec_key, int *host, int *subprotocol) +{ + int i, count, connection = -1; + int ws_sec_version = -1; + + if (!parser->upgrade || parser->method != HTTP_GET || + parser->http_major != 1 || parser->http_minor != 1) { + return false; + } + + for (i = 0, count = 0; i < ctx->http.field_values_ctr; i++) { + if (ctx->http.field_values[i].key_len == 0) { + continue; + } + + if (strncasecmp(ctx->http.field_values[i].key, + "Sec-WebSocket-Key", + sizeof("Sec-WebSocket-Key") - 1) == 0) { + *ws_sec_key = i; + continue; + } + + if (strncasecmp(ctx->http.field_values[i].key, + "Sec-WebSocket-Version", + sizeof("Sec-WebSocket-Version") - 1) == 0) { + if (strncmp(ctx->http.field_values[i].value, + "13", sizeof("13") - 1) == 0) { + ws_sec_version = i; + } + + continue; + } + + if (strncasecmp(ctx->http.field_values[i].key, + "Connection", sizeof("Connection") - 1) == 0) { + if (field_contains( + ctx->http.field_values[i].value, + ctx->http.field_values[i].value_len, + "Upgrade", sizeof("Upgrade") - 1)) { + connection = i; + } + + continue; + } + + if (strncasecmp(ctx->http.field_values[i].key, "Host", + sizeof("Host") - 1) == 0) { + *host = i; + continue; + } + + if (strncasecmp(ctx->http.field_values[i].key, + "Sec-WebSocket-Protocol", + sizeof("Sec-WebSocket-Protocol") - 1) == 0) { + *subprotocol = i; + continue; + } + } + + if (connection >= 0 && *ws_sec_key >= 0 && ws_sec_version >= 0 && + *host >= 0) { + return true; + } + + return false; +} + +static struct net_pkt *prepare_reply(struct http_ctx *ctx, + int ws_sec_key, int host, int subprotocol) +{ + char key_accept[32 + sizeof(WS_MAGIC) - 1]; + char accept[20]; + struct net_pkt *pkt; + char tmp[64]; + int ret; + size_t olen; + + pkt = net_app_get_net_pkt(&ctx->app_ctx, AF_UNSPEC, ctx->timeout); + if (!pkt) { + return NULL; + } + + snprintk(tmp, sizeof(tmp), "HTTP/1.1 101 OK\r\n"); + if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) { + goto fail; + } + + snprintk(tmp, sizeof(tmp), "User-Agent: %s\r\n", ZEPHYR_USER_AGENT); + if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) { + goto fail; + } + + snprintk(tmp, sizeof(tmp), "Upgrade: websocket\r\n"); + if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) { + goto fail; + } + + snprintk(tmp, sizeof(tmp), "Connection: Upgrade\r\n"); + if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) { + goto fail; + } + + olen = min(sizeof(key_accept), + ctx->http.field_values[ws_sec_key].value_len); + strncpy(key_accept, ctx->http.field_values[ws_sec_key].value, olen); + + olen = min(sizeof(key_accept) - + ctx->http.field_values[ws_sec_key].value_len, + sizeof(WS_MAGIC) - 1); + strncpy(key_accept + ctx->http.field_values[ws_sec_key].value_len, + WS_MAGIC, olen); + + olen = ctx->http.field_values[ws_sec_key].value_len + + sizeof(WS_MAGIC) - 1; + + mbedtls_sha1(key_accept, olen, accept); + + snprintk(tmp, sizeof(tmp), "Sec-WebSocket-Accept: "); + + ret = mbedtls_base64_encode(tmp + sizeof("Sec-WebSocket-Accept: ") - 1, + sizeof(tmp) - + (sizeof("Sec-WebSocket-Accept: ") - 1), + &olen, accept, sizeof(accept)); + if (ret) { + if (ret == MBEDTLS_ERR_BASE64_BUFFER_TOO_SMALL) { + NET_DBG("[%p] Too short buffer olen %zd", ctx, olen); + } + + goto fail; + } + + snprintk(tmp + sizeof("Sec-WebSocket-Accept: ") - 1 + olen, + sizeof(tmp) - (sizeof("Sec-WebSocket-Accept: ") - 1) - olen, + "\r\n\r\n"); + + if (!net_pkt_append_all(pkt, strlen(tmp), (u8_t *)tmp, ctx->timeout)) { + goto fail; + } + + return pkt; + +fail: + net_pkt_unref(pkt); + return NULL; +} + +int ws_headers_complete(struct http_parser *parser) +{ + struct http_ctx *ctx = parser->data; + int ws_sec_key = -1, host = -1, subprotocol = -1; + + if (check_ws_headers(ctx, parser, &ws_sec_key, &host, + &subprotocol)) { + struct net_pkt *pkt; + struct http_root_url *url; + int ret; + + url = http_url_find(ctx, HTTP_URL_WEBSOCKET); + if (!url) { + url = http_url_find(ctx, HTTP_URL_STANDARD); + if (url) { + /* Normal HTTP URL was found */ + return 0; + } + + /* If there is no URL to serve this websocket + * request, then just bail out. + */ + if (!ctx->http.urls) { + NET_DBG("[%p] No URL handlers found", ctx); + return 0; + } + + url = &ctx->http.urls->default_url; + if (url && url->is_used && + ctx->http.urls->default_cb) { + ret = ctx->http.urls->default_cb(ctx, + WS_CONNECTION); + if (ret == HTTP_VERDICT_ACCEPT) { + goto accept; + } + } + + if (url->flags == HTTP_URL_WEBSOCKET) { + goto fail; + } + } + + if (url->flags != HTTP_URL_WEBSOCKET) { + return 0; + } + +accept: + NET_DBG("[%p] ws header %d fields found", ctx, + ctx->http.field_values_ctr + 1); + + pkt = prepare_reply(ctx, ws_sec_key, host, subprotocol); + if (!pkt) { + goto fail; + } + + net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags)); + + ret = net_app_send_pkt(&ctx->app_ctx, pkt, NULL, 0, 0, + INT_TO_POINTER(K_FOREVER)); + if (ret) { + goto fail; + } + + http_change_state(ctx, HTTP_STATE_HEADER_RECEIVED); + + /* We do not expect any HTTP data after this */ + return 2; + +fail: + http_change_state(ctx, HTTP_STATE_CLOSED); + } + + return 0; +} diff --git a/subsys/net/lib/websocket/websocket_internal.h b/subsys/net/lib/websocket/websocket_internal.h new file mode 100644 index 0000000000000..b4ecb069f71a5 --- /dev/null +++ b/subsys/net/lib/websocket/websocket_internal.h @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef __WEBSOCKET_INTERNAL_H__ +#define __WEBSOCKET_INTERNAL_H__ + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Strip websocket header from the packet. + * + * @details The function will remove websocket header from the network packet. + * + * @param pkt Received network packet + * @param masked The mask status of the message is returned. + * @param mask_value The mask value of the message is returned. + * @param message_length Total length of the message from websocket header. + * @param message_type_flag Type of the websocket message (WS_FLAG_xxx value) + * @param header_len Length of the websocket header is returned to caller. + * + * @return 0 if ok, <0 if error + */ +int ws_strip_header(struct net_pkt *pkt, bool *masked, u32_t *mask_value, + u32_t *message_length, u32_t *message_type_flag, + u32_t *header_len); + +/** + * @brief Mask or unmask a websocket message if needed + * + * @details The function will either add or remove the masking from the data. + * + * @param pkt Network packet to process + * @param masking_value The mask value to use. + * @param data_read How many bytes we have read. This is modified by this + * function. + */ +void ws_mask_pkt(struct net_pkt *pkt, u32_t masking_value, u32_t *data_read); + +/** + * @brief This is called by HTTP server after all the HTTP headers have been + * received. + * + * @details The function will check if this is a valid websocket connection + * or not. + * + * @param parser HTTP parser instance + * + * @return 0 if ok, 1 if there is no body, 2 if HTTP connection is to be + * upgraded to websocket one + */ +int ws_headers_complete(struct http_parser *parser); + +#ifdef __cplusplus +} +#endif + +/** + * @} + */ + +#endif /* __WS_H__ */ diff --git a/subsys/shell/shell.c b/subsys/shell/shell.c index 212177b1816ec..48750f4475a41 100644 --- a/subsys/shell/shell.c +++ b/subsys/shell/shell.c @@ -24,6 +24,9 @@ #ifdef CONFIG_TELNET_CONSOLE #include #endif +#ifdef CONFIG_WEBSOCKET_CONSOLE +#include +#endif #include @@ -545,6 +548,9 @@ void shell_init(const char *str) #ifdef CONFIG_TELNET_CONSOLE telnet_register_input(&avail_queue, &cmds_queue, completion); #endif +#ifdef CONFIG_WEBSOCKET_CONSOLE + ws_register_input(&avail_queue, &cmds_queue, completion); +#endif } /** @brief Optionally register an app default cmd handler. diff --git a/tests/net/websocket/CMakeLists.txt b/tests/net/websocket/CMakeLists.txt new file mode 100644 index 0000000000000..6eba191b8017b --- /dev/null +++ b/tests/net/websocket/CMakeLists.txt @@ -0,0 +1,21 @@ +include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE) +project(NONE) + +target_include_directories(app PRIVATE $ENV{ZEPHYR_BASE}/subsys/net/ip) +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) + +# 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} + ${ZEPHYR_BINARY_DIR}/include/generated/${inc_file}.inc + ) +endforeach() + +target_link_libraries_ifdef(CONFIG_MBEDTLS app mbedTLS) diff --git a/tests/net/websocket/prj.conf b/tests/net/websocket/prj.conf new file mode 100644 index 0000000000000..6824795333d75 --- /dev/null +++ b/tests/net/websocket/prj.conf @@ -0,0 +1,74 @@ +# Testing infra +CONFIG_ZTEST=y +CONFIG_ZTEST_STACKSIZE=2048 +CONFIG_NET_TEST=y +CONFIG_NET_LOOPBACK=y +CONFIG_MAIN_STACK_SIZE=2048 + +# Generic IP stack options and features +CONFIG_NETWORKING=y +CONFIG_NET_UDP=n +CONFIG_NET_TCP=y +CONFIG_NET_IPV6=y +CONFIG_NET_IPV4=y +#CONFIG_NET_DHCPV4=y +CONFIG_ENTROPY_GENERATOR=y +CONFIG_TEST_RANDOM_GENERATOR=y +CONFIG_INIT_STACKS=y +CONFIG_NET_MAX_CONTEXTS=8 +CONFIG_NET_SHELL=y +CONFIG_NET_IPV6_ND=n +CONFIG_NET_IPV6_MLD=n +CONFIG_NET_IPV6_DAD=n + +# Number of network buffers +CONFIG_NET_PKT_RX_COUNT=32 +CONFIG_NET_PKT_TX_COUNT=32 +CONFIG_NET_BUF_RX_COUNT=32 +CONFIG_NET_BUF_TX_COUNT=32 +CONFIG_NET_CONTEXT_NET_PKT_POOL=y + +# IPv6 address counts +CONFIG_NET_IF_UNICAST_IPV6_ADDR_COUNT=3 +CONFIG_NET_IF_MCAST_IPV6_ADDR_COUNT=4 + +# Network application settings +CONFIG_NET_APP=y +CONFIG_NET_APP_SETTINGS=y +CONFIG_NET_APP_NEED_IPV6=y +CONFIG_NET_APP_NEED_IPV4=y +CONFIG_NET_APP_MY_IPV6_ADDR="2001:db8::1" +CONFIG_NET_APP_MY_IPV4_ADDR="192.0.2.1" +CONFIG_NET_APP_INIT_TIMEOUT=1 +CONFIG_NET_APP_CLIENT=y + +# HTTP & Websocket options +CONFIG_WEBSOCKET=y +CONFIG_HTTP=y +CONFIG_HTTPS=n +CONFIG_HTTP_SERVER=y +# How many URLs we are serving +CONFIG_HTTP_SERVER_NUM_URLS=5 + +# base64 support needed by websocket +CONFIG_MBEDTLS=y +CONFIG_MBEDTLS_BUILTIN=y +CONFIG_MBEDTLS_CFG_FILE="config-mini-tls1_2.h" + +# Logging +CONFIG_NET_LOG=y +CONFIG_SYS_LOG_NET_LEVEL=2 +CONFIG_SYS_LOG_SHOW_COLOR=y +CONFIG_NET_STATISTICS=y + +# Debugging +CONFIG_NET_DEBUG_WEBSOCKET=n +CONFIG_NET_DEBUG_HTTP=n +CONFIG_NET_DEBUG_APP=n +CONFIG_NET_DEBUG_NET_PKT=n +CONFIG_NET_DEBUG_CORE=n +CONFIG_NET_DEBUG_CONTEXT=n +CONFIG_NET_DEBUG_CONN=n +CONFIG_NET_DEBUG_TCP=n +CONFIG_NET_DEBUG_IF=n +CONFIG_NET_DEBUG_IPV6=n diff --git a/tests/net/websocket/src/echo-apps-cert.der b/tests/net/websocket/src/echo-apps-cert.der new file mode 100644 index 0000000000000..bfcb335e31c8c Binary files /dev/null and b/tests/net/websocket/src/echo-apps-cert.der differ diff --git a/tests/net/websocket/src/echo-apps-key.der b/tests/net/websocket/src/echo-apps-key.der new file mode 100644 index 0000000000000..5a4d67372ea41 Binary files /dev/null and b/tests/net/websocket/src/echo-apps-key.der differ diff --git a/tests/net/websocket/src/main.c b/tests/net/websocket/src/main.c new file mode 100644 index 0000000000000..fb2af41e222f0 --- /dev/null +++ b/tests/net/websocket/src/main.c @@ -0,0 +1,595 @@ +/* main.c - Application main entry point */ + +/* + * Copyright (c) 2017 Intel Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +/* In this websocket test, we create a websocket server which starts + * to listen connections. Then we start to send data to it and verify that + * we get proper data back. + */ + +#include + +#include +#include +#include + +static struct net_app_ctx app_ctx_v6; +static struct net_app_ctx app_ctx_v4; + +#if defined(CONFIG_NET_DEBUG_WEBSOCKET) +#define DBG(fmt, ...) printk(fmt, ##__VA_ARGS__) +#define NET_LOG_ENABLED 1 +#else +#define DBG(fmt, ...) +#endif + +#include "../../../subsys/net/ip/net_private.h" + +/* + * GET /ws HTTP/1.1 + * Upgrade: websocket + * Connection: Upgrade + * Host: 2001:db8::1 + * Origin: http://2001:db8::1 + * Sec-WebSocket-Key: 8VMFeU0j8bImbjyjPVHSQg== + * Sec-WebSocket-Version: 13 + */ +static char http_msg[] = { + 0x47, 0x45, 0x54, 0x20, 0x2f, 0x77, 0x73, 0x20, + 0x48, 0x54, 0x54, 0x50, 0x2f, 0x31, 0x2e, 0x31, + 0x0d, 0x0a, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, + 0x65, 0x3a, 0x20, 0x77, 0x65, 0x62, 0x73, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x0d, 0x0a, 0x43, 0x6f, + 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, + 0x3a, 0x20, 0x55, 0x70, 0x67, 0x72, 0x61, 0x64, + 0x65, 0x0d, 0x0a, 0x48, 0x6f, 0x73, 0x74, 0x3a, + 0x20, 0x32, 0x30, 0x30, 0x31, 0x3a, 0x64, 0x62, + 0x38, 0x3a, 0x3a, 0x31, 0x0d, 0x0a, 0x4f, 0x72, + 0x69, 0x67, 0x69, 0x6e, 0x3a, 0x20, 0x68, 0x74, + 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x32, 0x30, 0x30, + 0x31, 0x3a, 0x64, 0x62, 0x38, 0x3a, 0x3a, 0x31, + 0x0d, 0x0a, 0x53, 0x65, 0x63, 0x2d, 0x57, 0x65, + 0x62, 0x53, 0x6f, 0x63, 0x6b, 0x65, 0x74, 0x2d, + 0x4b, 0x65, 0x79, 0x3a, 0x20, 0x38, 0x56, 0x4d, + 0x46, 0x65, 0x55, 0x30, 0x6a, 0x38, 0x62, 0x49, + 0x6d, 0x62, 0x6a, 0x79, 0x6a, 0x50, 0x56, 0x48, + 0x53, 0x51, 0x67, 0x3d, 0x3d, 0x0d, 0x0a, 0x53, + 0x65, 0x63, 0x2d, 0x57, 0x65, 0x62, 0x53, 0x6f, + 0x63, 0x6b, 0x65, 0x74, 0x2d, 0x56, 0x65, 0x72, + 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x20, 0x31, 0x33, + 0x0d, 0x0a, 0x0d, 0x0a, +}; + +/* WebSocket: + * FIN: true + * Reserved: 0x00 + * Opcode: Text (1) + * Mask: True + * Payload len: 7 + * Masking key: d1ffa558 + * Masked payload: 0x99, 0x9a, 0xc9, 0x34, 0xbe, 0xd3, 0x85 + * Payload: "Hello, " + */ +/* This array is not modified */ +static const u8_t ws_test_msg_orig[] = { + 0x81, 0x87, 0xd1, 0xff, 0xa5, 0x58, 0x99, 0x9a, + 0xc9, 0x34, 0xbe, 0xd3, 0x85, +}; + +/* We are manipulating this array in the test */ +static u8_t ws_test_msg[sizeof(ws_test_msg_orig)]; + +static u32_t mask_value = 0xd1ffa558; +static struct sockaddr server_addr; +static s32_t timeout = K_MSEC(100); +static int bytes_received; +static bool failure; +static struct k_sem wait_data; +static struct k_sem progress; +extern struct http_ctx *ws_ctx; + +#define hdr_len 6 + +static u8_t ws_unmasked_msg[sizeof(ws_test_msg) - hdr_len]; +static int total_data_len = sizeof(ws_unmasked_msg); + +#define WAIT_TIME K_SECONDS(1) + +void test_websocket_init_server(void); +void test_websocket_cleanup_server(void); + +static void ws_mask_payload(u8_t *payload, size_t payload_len, + u32_t masking_value) +{ + int i; + + for (i = 0; i < payload_len; i++) { + payload[i] ^= masking_value >> (8 * (3 - i % 4)); + } +} + +static void recv_cb(struct net_app_ctx *ctx, + struct net_pkt *pkt, + int status, + void *user_data) +{ + int ret; + size_t len; + + if (!pkt) { + return; + } + + len = net_pkt_appdatalen(pkt); + + /* The pkt will contain websocket header because we are bypassing + * any websocket message parsing here. So we need to skip the websocket + * header here. The return header length is 2 bytes here only if + * the returned message < 127 bytes long. + */ + net_buf_pull(pkt->frags, 2); + net_pkt_set_appdata(pkt, net_pkt_appdata(pkt) + 2); + + len -= 2; + net_pkt_set_appdatalen(pkt, len); + + DBG("Received %zd bytes\n", len); + + /* Note that we can only use net_pkt_appdata() here because we + * know that the data fits in first fragment. + */ + ret = memcmp(ws_unmasked_msg + bytes_received, + net_pkt_appdata(pkt), len); + if (ret != 0) { + net_hexdump("recv", net_pkt_appdata(pkt), + net_pkt_appdatalen(pkt)); + net_hexdump("sent", ws_unmasked_msg, sizeof(ws_unmasked_msg)); + + failure = true; + + zassert_equal(ret, 0, "Received data does not match"); + + goto out; + } + + bytes_received += len; + failure = false; + + if (total_data_len == bytes_received) { + bytes_received = 0; + } + +out: + k_sem_give(&wait_data); + k_sem_give(&progress); + + net_pkt_unref(pkt); +} + +void test_init(void) +{ + /* The semaphore is there to wait the data to be received. */ + k_sem_init(&wait_data, 0, UINT_MAX); + + k_sem_init(&progress, 0, UINT_MAX); + + memcpy(ws_unmasked_msg, ws_test_msg_orig + hdr_len, + sizeof(ws_unmasked_msg)); + + ws_mask_payload(ws_unmasked_msg, sizeof(ws_unmasked_msg), mask_value); +} + +static void test_connect(struct net_app_ctx *ctx) +{ + int ret; + + ret = net_app_connect(ctx, K_FOREVER); + zassert_equal(ret, 0, "websocket client connect"); +} + +static void test_close(struct net_app_ctx *ctx) +{ + int ret; + + ret = net_app_close(ctx); + zassert_equal(ret, 0, "websocket client close"); +} + +/* The chunk_size tells how many bytes at a time to send. + * This is not the same as HTTP chunk! + */ +static void test_send_recv(int chunk_size, struct net_app_ctx *ctx) +{ + int i, j, ret; + + DBG("Sending %d bytes at a time\n", chunk_size); + + for (i = 0; i < sizeof(ws_test_msg); i += chunk_size) { + for (j = 0; + IS_ENABLED(CONFIG_NET_DEBUG_WEBSOCKET) && j < chunk_size; + j++) { + if ((i + chunk_size) >= sizeof(ws_test_msg)) { + break; + } + + if ((i + j) < hdr_len) { + DBG("[%d] = 0x%02x\n", i + j, + ws_test_msg[i + j]); + } else { + DBG("[%d] = 0x%02x -> \"%c\"\n", i + j, + ws_test_msg[i + j], + ws_unmasked_msg[i + j - hdr_len]); + } + } + + if ((i + chunk_size) >= sizeof(ws_test_msg)) { + chunk_size = sizeof(ws_test_msg) - i; + } + + ret = net_app_send_buf(ctx, &ws_test_msg[i], + chunk_size, + &server_addr, + sizeof(struct sockaddr_in6), + timeout, + NULL); + if (ret != 0) { + DBG("Cannot send %d byte(s) (%d)\n", ret, chunk_size); + + zassert_equal(ret, 0, "websocket IPv6 client ws send"); + } + + /* Make sure the receiving side gets the data now */ + k_yield(); + } +} + +static void test_send_multi_msg(struct net_app_ctx *ctx) +{ + u8_t ws_big_msg[sizeof(ws_test_msg) * 2]; + int i, j, ret, chunk_size = 4; + + k_sem_take(&progress, K_FOREVER); + + memcpy(ws_test_msg, ws_test_msg_orig, sizeof(ws_test_msg)); + bytes_received = 0; + + ws_ctx->websocket.data_waiting = 0; + if (ws_ctx->websocket.pending) { + net_pkt_unref(ws_ctx->websocket.pending); + ws_ctx->websocket.pending = NULL; + } + + memcpy(ws_big_msg, ws_test_msg, sizeof(ws_test_msg)); + memcpy(ws_big_msg + sizeof(ws_test_msg), ws_test_msg, + sizeof(ws_test_msg)); + + for (i = 0; i < sizeof(ws_big_msg); i += chunk_size) { + for (j = 0; + IS_ENABLED(CONFIG_NET_DEBUG_WEBSOCKET) && j < chunk_size; + j++) { + int first_msg = 0; + + if ((i + chunk_size) >= sizeof(ws_big_msg)) { + break; + } + + if (i > sizeof(ws_test_msg)) { + first_msg = sizeof(ws_test_msg); + } + + if (i + j + first_msg < hdr_len + first_msg) { + DBG("[%d] = 0x%02x\n", i + j, + ws_big_msg[i + j + first_msg]); + } else { + if (i + j + first_msg < + sizeof(ws_test_msg) + first_msg) { + DBG("[%d] = 0x%02x -> \"%c\"\n", i + j, + ws_big_msg[i + j + first_msg], + ws_unmasked_msg[i + j - hdr_len + + first_msg]); + } + } + } + + if ((i + chunk_size) >= sizeof(ws_big_msg)) { + chunk_size = sizeof(ws_big_msg) - i; + } + + ret = net_app_send_buf(ctx, &ws_big_msg[i], + chunk_size, + &server_addr, + sizeof(struct sockaddr_in6), + timeout, + NULL); + if (ret != 0) { + DBG("Cannot send %d byte(s) (%d)\n", ret, chunk_size); + + zassert_equal(ret, 0, "websocket IPv6 client ws send"); + } + + /* Make sure the receiving side gets the data now */ + k_yield(); + } +} + +/* Start to send raw data and do not use websocket client API for this so + * that we can send garbage data if needed. + */ +void test_v6_init(void) +{ + int ret; + + ret = net_ipaddr_parse(CONFIG_NET_APP_MY_IPV6_ADDR, + strlen(CONFIG_NET_APP_MY_IPV6_ADDR), + &server_addr); + zassert_equal(ret, 1, "cannot parse server address"); + + ret = net_app_init_tcp_client(&app_ctx_v6, + NULL, + NULL, + CONFIG_NET_APP_MY_IPV6_ADDR, + 80, + 0, + NULL); + zassert_equal(ret, 0, "websocket IPv6 client init"); + + net_app_set_cb(&app_ctx_v6, NULL, recv_cb, NULL, NULL); +} + +void test_v6_connect(void) +{ + test_connect(&app_ctx_v6); + + k_sem_give(&progress); +} + +void test_v6_close(void) +{ + test_close(&app_ctx_v6); +} + +static void test_v6_send_recv(int chunk_size) +{ + static int header_sent; + int ret; + + if (!header_sent) { + ret = net_app_send_buf(&app_ctx_v6, http_msg, + sizeof(http_msg) - 1, + &server_addr, + sizeof(struct sockaddr_in6), + timeout, NULL); + if (ret != 0) { + DBG("Cannot send byte (%d)\n", ret); + + zassert_equal(ret, 0, + "websocket IPv6 client http send"); + } + + header_sent = true; + } + + test_send_recv(chunk_size, &app_ctx_v6); +} + +void test_v6_send_recv_n(int chunk_size) +{ + k_sem_take(&progress, K_FOREVER); + + /* Make sure we have a fresh start before running this specific test */ + memcpy(ws_test_msg, ws_test_msg_orig, sizeof(ws_test_msg)); + bytes_received = 0; + + ws_ctx->websocket.data_waiting = 0; + if (ws_ctx->websocket.pending) { + net_pkt_unref(ws_ctx->websocket.pending); + ws_ctx->websocket.pending = NULL; + } + + test_v6_send_recv(chunk_size); + + if (k_sem_take(&wait_data, WAIT_TIME)) { + zassert_true(false, "Timeout while waiting data"); + } + + zassert_false(failure, "Send test failed"); +} + +void test_v6_send_recv_1(void) +{ + test_v6_send_recv_n(1); +} + +void test_v6_send_recv_2(void) +{ + test_v6_send_recv_n(2); +} + +void test_v6_send_recv_3(void) +{ + test_v6_send_recv_n(3); +} + +void test_v6_send_recv_4(void) +{ + test_v6_send_recv_n(4); +} + +void test_v6_send_recv_5(void) +{ + test_v6_send_recv_n(5); +} + +void test_v6_send_recv_6(void) +{ + test_v6_send_recv_n(6); +} + +void test_v6_send_recv_7(void) +{ + test_v6_send_recv_n(7); +} + +void test_v6_send_multi_msg(void) +{ + test_send_multi_msg(&app_ctx_v6); +} + +/* Start to send raw data and do not use websocket client API for this so + * that we can send garbage data if needed. + */ +void test_v4_init(void) +{ + int ret; + + ret = net_ipaddr_parse(CONFIG_NET_APP_MY_IPV4_ADDR, + strlen(CONFIG_NET_APP_MY_IPV4_ADDR), + &server_addr); + zassert_equal(ret, 1, "cannot parse server address"); + + ret = net_app_init_tcp_client(&app_ctx_v4, + NULL, + NULL, + CONFIG_NET_APP_MY_IPV4_ADDR, + 80, + 0, + NULL); + zassert_equal(ret, 0, "websocket IPv4 client init"); + + net_app_set_cb(&app_ctx_v4, NULL, recv_cb, NULL, NULL); +} + +void test_v4_connect(void) +{ + test_connect(&app_ctx_v4); + + k_sem_give(&progress); +} + +void test_v4_close(void) +{ + test_close(&app_ctx_v4); +} + +static void test_v4_send_recv(int chunk_size) +{ + static int header_sent; + int ret; + + if (!header_sent) { + ret = net_app_send_buf(&app_ctx_v4, http_msg, + sizeof(http_msg) - 1, + &server_addr, + sizeof(struct sockaddr_in6), + timeout, NULL); + if (ret != 0) { + DBG("Cannot send byte (%d)\n", ret); + + zassert_equal(ret, 0, + "websocket IPv4 client http send"); + } + + header_sent = true; + } + + test_send_recv(chunk_size, &app_ctx_v4); +} + +void test_v4_send_recv_n(int chunk_size) +{ + k_sem_take(&progress, K_FOREVER); + + /* Make sure we have a fresh start before running this specific test */ + memcpy(ws_test_msg, ws_test_msg_orig, sizeof(ws_test_msg)); + bytes_received = 0; + + ws_ctx->websocket.data_waiting = 0; + if (ws_ctx->websocket.pending) { + net_pkt_unref(ws_ctx->websocket.pending); + ws_ctx->websocket.pending = NULL; + } + + test_v4_send_recv(chunk_size); + + if (k_sem_take(&wait_data, WAIT_TIME)) { + zassert_true(false, "Timeout while waiting data"); + } + + zassert_false(failure, "Send test failed"); +} + +void test_v4_send_recv_1(void) +{ + test_v4_send_recv_n(1); +} + +void test_v4_send_recv_2(void) +{ + test_v4_send_recv_n(2); +} + +void test_v4_send_recv_3(void) +{ + test_v4_send_recv_n(3); +} + +void test_v4_send_recv_4(void) +{ + test_v4_send_recv_n(4); +} + +void test_v4_send_recv_5(void) +{ + test_v4_send_recv_n(5); +} + +void test_v4_send_recv_6(void) +{ + test_v4_send_recv_n(6); +} + +void test_v4_send_recv_7(void) +{ + test_v4_send_recv_n(7); +} + +void test_v4_send_multi_msg(void) +{ + test_send_multi_msg(&app_ctx_v4); +} + +void test_main(void) +{ + ztest_test_suite(websocket, + ztest_unit_test(test_websocket_init_server), + ztest_unit_test(test_init), + ztest_unit_test(test_v6_init), + ztest_unit_test(test_v6_connect), + ztest_unit_test(test_v6_send_recv_1), + ztest_unit_test(test_v6_send_recv_2), + ztest_unit_test(test_v6_send_recv_3), + ztest_unit_test(test_v6_send_recv_4), + ztest_unit_test(test_v6_send_recv_5), + ztest_unit_test(test_v6_send_recv_6), + ztest_unit_test(test_v6_send_recv_7), + ztest_unit_test(test_v6_send_multi_msg), + ztest_unit_test(test_v6_close), + ztest_unit_test(test_v4_init), + ztest_unit_test(test_v4_connect), + ztest_unit_test(test_v4_send_recv_1), + ztest_unit_test(test_v4_send_recv_2), + ztest_unit_test(test_v4_send_recv_3), + ztest_unit_test(test_v4_send_recv_4), + ztest_unit_test(test_v4_send_recv_5), + ztest_unit_test(test_v4_send_recv_6), + ztest_unit_test(test_v4_send_recv_7), + ztest_unit_test(test_v4_send_multi_msg), + ztest_unit_test(test_v4_close), + ztest_unit_test(test_websocket_cleanup_server)); + + ztest_run_test_suite(websocket); +} diff --git a/tests/net/websocket/src/server.c b/tests/net/websocket/src/server.c new file mode 100644 index 0000000000000..a176cbc3b947f --- /dev/null +++ b/tests/net/websocket/src/server.c @@ -0,0 +1,364 @@ +/* + * Copyright (c) 2017 Intel Corporation. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#if defined(CONFIG_NET_DEBUG_WEBSOCKET) +#define SYS_LOG_DOMAIN "test-ws-server" +#define NET_SYS_LOG_LEVEL SYS_LOG_LEVEL_DEBUG +#define NET_LOG_ENABLED 1 +#endif + +#include +#include +#include + +#include +#include +#include + +#include + +#include "../../../subsys/net/ip/net_private.h" + +#define MAX_BUF_LEN 128 +#define MAX_URL_LEN 128 +#define SEND_TIMEOUT K_SECONDS(10) +#define ALLOC_TIMEOUT 100 + +struct http_ctx *ws_ctx; +static struct http_ctx http_ctx; +static struct http_server_urls http_urls; + +/* Note that both tcp and udp can share the same pool but in this + * example the UDP context and TCP context have separate pools. + */ +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) +NET_PKT_TX_SLAB_DEFINE(echo_tx_tcp, 15); +NET_PKT_DATA_POOL_DEFINE(echo_data_tcp, 30); + +static struct k_mem_slab *tx_tcp_slab(void) +{ + return &echo_tx_tcp; +} + +static struct net_buf_pool *data_tcp_pool(void) +{ + return &echo_data_tcp; +} +#else +#define tx_tcp_slab NULL +#define data_tcp_pool NULL +#endif /* CONFIG_NET_CONTEXT_NET_PKT_POOL */ + +/* The result buf size is set to large enough so that we can receive max size + * buf back. Note that mbedtls needs also be configured to have equal size + * value for its buffer size. See MBEDTLS_SSL_MAX_CONTENT_LEN option in TLS + * config file. + */ +#define RESULT_BUF_SIZE 1500 +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 8192 +#endif /* CONFIG_NET_APP_TLS_STACK_SIZE */ + +#define APP_BANNER "Run TLS ws-server" +#define INSTANCE_INFO "Zephyr TLS ws-server #1" + +/* Note that each net_app context needs its own stack as there will be + * a separate thread needed. + */ +NET_STACK_DEFINE(WS_ECHO_SERVER, ws_tls_stack, + CONFIG_NET_APP_TLS_STACK_SIZE, CONFIG_NET_APP_TLS_STACK_SIZE); + +#define RX_FIFO_DEPTH 4 +K_MEM_POOL_DEFINE(ssl_pool, 4, 64, RX_FIFO_DEPTH, 4); +#endif /* CONFIG_HTTPS */ + +#if defined(CONFIG_HTTPS) +/* 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; +} +#endif /* CONFIG_HTTPS */ + +#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_CSS \ + "HTTP/1.1 200 OK\r\n" \ + "Content-Type: text/css\r\n" \ + "Transfer-Encoding: chunked\r\n" + +#define HTML_HEADER "" \ + "Zephyr HTTP Server" \ + "

" \ + "
Zephyr HTTP websocket server

\r\n" + +#define HTML_FOOTER "\r\n" + +static int ws_works(struct http_ctx *ctx) +{ + NET_INFO("WS url called"); + + return 0; +} + +static void ws_connected(struct http_ctx *ctx, + enum http_connection_type type, + void *user_data) +{ + char url[32]; + int len = min(sizeof(url), ctx->http.url_len); + + memcpy(url, ctx->http.url, len); + url[len] = '\0'; + + NET_DBG("%s connect attempt URL %s", + type == HTTP_CONNECTION ? "HTTP" : "WS", url); + + if (type == HTTP_CONNECTION) { + return; + + } else if (type == WS_CONNECTION) { + if (strncmp(ctx->http.url, "/ws", + sizeof("/ws") - 1) == 0) { + ws_works(ctx); + return; + } + } +} + +static void ws_received(struct http_ctx *ctx, + struct net_pkt *pkt, + int status, + u32_t flags, + void *user_data) +{ + if (!status) { + struct net_buf *frag; + enum ws_opcode opcode; + int ret, hdr_len; + + NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt)); + + if (flags & WS_FLAG_BINARY) { + opcode = WS_OPCODE_DATA_BINARY; + } else { + opcode = WS_OPCODE_DATA_TEXT; + } + + hdr_len = net_pkt_get_len(pkt) - net_pkt_appdatalen(pkt); + + frag = pkt->frags; + while (frag) { + net_hexdump("server recv", net_pkt_appdata(pkt), + net_pkt_appdatalen(pkt)); + + ret = ws_send_msg_to_client(ctx, frag->data + hdr_len, + frag->len - hdr_len, + opcode, + frag->frags ? true : false, + user_data); + if (ret < 0) { + NET_DBG("Cannot send ws data (%d bytes) " + "back (%d)", + frag->len - hdr_len, ret); + } else { + NET_DBG("Sent %d bytes to client", + frag->len - hdr_len); + } + + frag = frag->frags; + + /* Websocket header is found in first fragment so + * reset the value here. + */ + hdr_len = 0; + } + + http_send_flush(ctx, user_data); + + if (pkt) { + net_pkt_unref(pkt); + } + + } else { + NET_ERR("Receive error (%d)", status); + + if (pkt) { + net_pkt_unref(pkt); + } + } +} + +static void ws_sent(struct http_ctx *ctx, + int status, + void *user_data_send, + void *user_data) +{ + NET_DBG("Data sent status %d", status); +} + +static void ws_closed(struct http_ctx *ctx, + int status, + void *user_data) +{ + NET_DBG("Connection %p closed", ctx); +} + +#if defined(CONFIG_NET_DEBUG_WEBSOCKET) && (NET_SYS_LOG_LEVEL > 3) +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; +} +#endif + +static enum http_verdict default_handler(struct http_ctx *ctx, + enum http_connection_type type) +{ + NET_DBG("No handler for %s URL %s", + type == HTTP_CONNECTION ? "HTTP" : "WS", + get_string(ctx->http.url_len, ctx->http.url)); + + return HTTP_VERDICT_DROP; +} + +void test_websocket_init_server(void) +{ + 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 + * default port 80 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 to desired + * value. If the protocol family in sockaddr is set to AF_UNSPEC, + * then both IPv4 and IPv6 socket is 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 both IPv4 and IPv6 */ + addr.sa_family = AF_UNSPEC; + + 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, &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, "/ws", 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 WS server", NULL); + if (ret < 0) { + NET_ERR("Cannot init web server (%d)", ret); + return; + } + + http_set_cb(&http_ctx, ws_connected, ws_received, ws_sent, ws_closed); + +#if defined(CONFIG_NET_CONTEXT_NET_PKT_POOL) + net_app_set_net_pkt_pool(&http_ctx.app_ctx, tx_tcp_slab, data_tcp_pool); +#endif + +#if defined(CONFIG_NET_APP_TLS) + ret = http_server_set_tls(&http_ctx, + APP_BANNER, + INSTANCE_INFO, + strlen(INSTANCE_INFO), + setup_cert, + NULL, + &ssl_pool, + ws_tls_stack, + K_THREAD_STACK_SIZEOF(ws_tls_stack)); + if (ret < 0) { + NET_ERR("Cannot enable TLS support (%d)", ret); + } +#endif + + http_server_enable(&http_ctx); + + ws_ctx = &http_ctx; +} + +void test_websocket_cleanup_server(void) +{ + http_server_disable(&http_ctx); + http_release(&http_ctx); +} diff --git a/tests/net/websocket/testcase.yaml b/tests/net/websocket/testcase.yaml new file mode 100644 index 0000000000000..9666b4dff176e --- /dev/null +++ b/tests/net/websocket/testcase.yaml @@ -0,0 +1,5 @@ +tests: + - test: + build_only: true + min_ram: 32 + tags: net http websocket