From 1741da954d9e95c695bf8db2a02a73f6bce48db0 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 8 Nov 2017 14:46:01 +0200 Subject: [PATCH 01/11] net: tcp: Split sent packet into MSS size pieces Instead of trying to send a packet that is larger than MSS, split it into suitable pieces and queue the pieces individually. Jira: ZEP-1998 Signed-off-by: Jukka Rissanen --- subsys/net/ip/net_context.c | 191 +++++++++++++++++++++++++++++++++++- 1 file changed, 189 insertions(+), 2 deletions(-) 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 */ { From 410adc002c443e020c4911095dfdbb246451c18e Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Sun, 20 Aug 2017 23:15:07 +0300 Subject: [PATCH 02/11] net: websocket: Initial support for server websocket This commit creates a websocket library that can be used by applications. The websocket library implements currently only server role and it uses services provided by net-app API. The library supports TLS if enabled in configuration file. This also adds websocket calls to HTTP app server if websocket connection is established. Signed-off-by: Jukka Rissanen --- include/net/http_app.h | 21 + include/net/websocket.h | 98 ++++ subsys/net/lib/CMakeLists.txt | 1 + subsys/net/lib/Kconfig | 2 + subsys/net/lib/http/Kconfig | 4 +- subsys/net/lib/http/http_app.c | 9 + subsys/net/lib/http/http_app_server.c | 202 ++++++- subsys/net/lib/websocket/CMakeLists.txt | 6 + subsys/net/lib/websocket/Kconfig | 22 + subsys/net/lib/websocket/websocket.c | 495 ++++++++++++++++++ subsys/net/lib/websocket/websocket_internal.h | 69 +++ 11 files changed, 921 insertions(+), 8 deletions(-) create mode 100644 include/net/websocket.h create mode 100644 subsys/net/lib/websocket/CMakeLists.txt create mode 100644 subsys/net/lib/websocket/Kconfig create mode 100644 subsys/net/lib/websocket/websocket.c create mode 100644 subsys/net/lib/websocket/websocket_internal.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/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/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__ */ From 9d3fd16dd89e65b7a1ac95bedf785845405cf6d8 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Sat, 26 Aug 2017 17:45:42 +0300 Subject: [PATCH 03/11] samples: net: websocket echo-server application This is a http(s) server that supports also websocket. It sends back any data sent to it over a websocket. Signed-off-by: Jukka Rissanen --- samples/net/ws_echo_server/CMakeLists.txt | 27 + samples/net/ws_echo_server/README.rst | 36 ++ samples/net/ws_echo_server/prj.conf | 56 ++ samples/net/ws_echo_server/prj_tls.conf | 59 ++ samples/net/ws_echo_server/sample.yaml | 7 + samples/net/ws_echo_server/src/Makefile | 24 + samples/net/ws_echo_server/src/common.h | 18 + samples/net/ws_echo_server/src/config.h | 33 ++ .../net/ws_echo_server/src/echo-apps-cert.der | Bin 0 -> 767 bytes .../net/ws_echo_server/src/echo-apps-key.der | Bin 0 -> 1218 bytes samples/net/ws_echo_server/src/index.html | 20 + samples/net/ws_echo_server/src/main.c | 161 ++++++ samples/net/ws_echo_server/src/ws.c | 524 ++++++++++++++++++ samples/net/ws_echo_server/src/ws.js | 45 ++ 14 files changed, 1010 insertions(+) create mode 100644 samples/net/ws_echo_server/CMakeLists.txt create mode 100644 samples/net/ws_echo_server/README.rst create mode 100644 samples/net/ws_echo_server/prj.conf create mode 100644 samples/net/ws_echo_server/prj_tls.conf create mode 100644 samples/net/ws_echo_server/sample.yaml create mode 100644 samples/net/ws_echo_server/src/Makefile create mode 100644 samples/net/ws_echo_server/src/common.h create mode 100644 samples/net/ws_echo_server/src/config.h create mode 100644 samples/net/ws_echo_server/src/echo-apps-cert.der create mode 100644 samples/net/ws_echo_server/src/echo-apps-key.der create mode 100644 samples/net/ws_echo_server/src/index.html create mode 100644 samples/net/ws_echo_server/src/main.c create mode 100644 samples/net/ws_echo_server/src/ws.c create mode 100644 samples/net/ws_echo_server/src/ws.js 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 0000000000000000000000000000000000000000..bfcb335e31c8c37fd5c964276c42a3554abc3f4e GIT binary patch literal 767 zcmXqLV)|{+#Q1mtGZP~d6DPwv0r`WU3|LlA&)ap-DdR6;hMk(GhDiIJZH z=nO8VCPqevV+_^27sZ{kS1zxd!{1vz@zQs9)6L?K?!L`s{JC+`NsopH^5?fNiT_~s zYX3vyA1jYOyCT`$q&%G?~a`7n%!BhJR8NxJ8LeAXMj8@Z}=^MXr+0N72oA?D7SXK(^cx;@x^i)my z(tRQdLffsY{O=sUs>_nL`zz1ck4Bc)1848L{c-s}-C57(WQW}Pc-Q0S^$(^seJXV> z`k(h(@=aU)(3QD6qd7+wwtn5CeIu6c$fmF-&;}Dq9gkyFl=eiRu;X}cf0k(j@>)) zviDuwwgnHgTeUi?cV#qa|IX8EcwRM~bF02W-`hPi@~2*nwpEi<6R~VsvBJr1b>w9C z=I@pJi?_zcR{Tp^^LD$O*V@A~EolM$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a4d67372ea41873b1c69e5e9371f6f9d2c5a4bd GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0LB1&4bc}v zYpJJsoDYq6k<#}^HM1Au-R*4w`LUA8NPyrU&$pys@HXnd;WPND#pcu*i-IND8FX-Y z?8a!x@6H;j@V5aqk^j?mZUVXnnkuZ%BEKsi!E!hvHR{@L-DjdJ88{gZMA30Lv~4DZ z*2ccUZ#?d=lspAiPOVdci_{}AX>uo%v^uOK=n$^;p9`jL({sug5z4-C09Gk9RLt5b zTP7))O<$p=xyviE4-fzZsSzwlv6-dHd}pP;6d)3}J9YgF3t-AMV@@HKpnBz{CM^S?O`maE}K1B+DL;kFThAp!#d009Dm0RaH7 z0UN?WjiWr2jsEeC*@o6{wYkmT#(VLVd8DRNY9H7lcyumSAs^->(9OQQ2?gklP=v-L`Gx}l}Epd9hrmzrWPB^HgY2; zPe4Raxg5}uhi0bmA2T-mx#s85P@au1X1#k-Aozb#I!LTKGTvnxtemE5>_qMcl?C!j z#SDE>AF8y#xd(^;D<~3x>O7vYf$#m);|U+hoAc_SeGMv2ZJY+*hf(xP2JocW!N5Gh>(fq?+ts}+mI(T~AV*HlNM#ec4c%-zD89*-5W zoj3kXL$XrmvJT)MNZtpI|8%|ly(cPqz-9@rTLkUAoS)`Hqn^ieJ#j$+4frJ&sg!>B1V-0 zfq+K~=0jzRp|HDkq&((1 z4^sTRXxZnW?SQnSc0LySo5cU}$nFBvF(&`13#hnd=8Im5cx&cpoOA&>{Ra-O67MCI z&{4EyJ}s*>fq2TSrW{4Sogwp8<_}h%jHv>FfdJOeMWN~48AK`JI_MJJDU z8=_kU!4}(t3vFMBFF}{=yak@NV(~@!ROfCqUOUOBjX|tSjTWBzA{$rPt$=l?X&Xg< zGCkVbG5nX724gmcghG9WqLN(tyh?m2u) + + + + 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) + "
"; +} From 7f262cdf9153ff4d4e33f93cbf1f28405f4b2a1a Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 15 Nov 2017 12:45:28 +0200 Subject: [PATCH 04/11] tests: net: websocket: Add tests for websocket This tests websocket by creating a websocket support http server and sending data to it and verifying the returned data is the same. Signed-off-by: Jukka Rissanen --- tests/net/websocket/CMakeLists.txt | 21 + tests/net/websocket/prj.conf | 74 +++ tests/net/websocket/src/echo-apps-cert.der | Bin 0 -> 767 bytes tests/net/websocket/src/echo-apps-key.der | Bin 0 -> 1218 bytes tests/net/websocket/src/main.c | 595 +++++++++++++++++++++ tests/net/websocket/src/server.c | 364 +++++++++++++ tests/net/websocket/testcase.yaml | 5 + 7 files changed, 1059 insertions(+) create mode 100644 tests/net/websocket/CMakeLists.txt create mode 100644 tests/net/websocket/prj.conf create mode 100644 tests/net/websocket/src/echo-apps-cert.der create mode 100644 tests/net/websocket/src/echo-apps-key.der create mode 100644 tests/net/websocket/src/main.c create mode 100644 tests/net/websocket/src/server.c create mode 100644 tests/net/websocket/testcase.yaml 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 0000000000000000000000000000000000000000..bfcb335e31c8c37fd5c964276c42a3554abc3f4e GIT binary patch literal 767 zcmXqLV)|{+#Q1mtGZP~d6DPwv0r`WU3|LlA&)ap-DdR6;hMk(GhDiIJZH z=nO8VCPqevV+_^27sZ{kS1zxd!{1vz@zQs9)6L?K?!L`s{JC+`NsopH^5?fNiT_~s zYX3vyA1jYOyCT`$q&%G?~a`7n%!BhJR8NxJ8LeAXMj8@Z}=^MXr+0N72oA?D7SXK(^cx;@x^i)my z(tRQdLffsY{O=sUs>_nL`zz1ck4Bc)1848L{c-s}-C57(WQW}Pc-Q0S^$(^seJXV> z`k(h(@=aU)(3QD6qd7+wwtn5CeIu6c$fmF-&;}Dq9gkyFl=eiRu;X}cf0k(j@>)) zviDuwwgnHgTeUi?cV#qa|IX8EcwRM~bF02W-`hPi@~2*nwpEi<6R~VsvBJr1b>w9C z=I@pJi?_zcR{Tp^^LD$O*V@A~EolM$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a4d67372ea41873b1c69e5e9371f6f9d2c5a4bd GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0LB1&4bc}v zYpJJsoDYq6k<#}^HM1Au-R*4w`LUA8NPyrU&$pys@HXnd;WPND#pcu*i-IND8FX-Y z?8a!x@6H;j@V5aqk^j?mZUVXnnkuZ%BEKsi!E!hvHR{@L-DjdJ88{gZMA30Lv~4DZ z*2ccUZ#?d=lspAiPOVdci_{}AX>uo%v^uOK=n$^;p9`jL({sug5z4-C09Gk9RLt5b zTP7))O<$p=xyviE4-fzZsSzwlv6-dHd}pP;6d)3}J9YgF3t-AMV@@HKpnBz{CM^S?O`maE}K1B+DL;kFThAp!#d009Dm0RaH7 z0UN?WjiWr2jsEeC*@o6{wYkmT#(VLVd8DRNY9H7lcyumSAs^->(9OQQ2?gklP=v-L`Gx}l}Epd9hrmzrWPB^HgY2; zPe4Raxg5}uhi0bmA2T-mx#s85P@au1X1#k-Aozb#I!LTKGTvnxtemE5>_qMcl?C!j z#SDE>AF8y#xd(^;D<~3x>O7vYf$#m);|U+hoAc_SeGMv2ZJY+*hf(xP2JocW!N5Gh>(fq?+ts}+mI(T~AV*HlNM#ec4c%-zD89*-5W zoj3kXL$XrmvJT)MNZtpI|8%|ly(cPqz-9@rTLkUAoS)`Hqn^ieJ#j$+4frJ&sg!>B1V-0 zfq+K~=0jzRp|HDkq&((1 z4^sTRXxZnW?SQnSc0LySo5cU}$nFBvF(&`13#hnd=8Im5cx&cpoOA&>{Ra-O67MCI z&{4EyJ}s*>fq2TSrW{4Sogwp8<_}h%jHv>FfdJOeMWN~48AK`JI_MJJDU z8=_kU!4}(t3vFMBFF}{=yak@NV(~@!ROfCqUOUOBjX|tSjTWBzA{$rPt$=l?X&Xg< zGCkVbG5nX724gmcghG9WqLN(tyh?m2u) + +#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 From 85461e296b731e12fccb97cd2ab4a35aec85281d Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 31 Aug 2017 12:28:40 +0300 Subject: [PATCH 05/11] net: websocket: Add console support Add console driver that allows console session to be transferred over a websocket connection. Signed-off-by: Jukka Rissanen --- drivers/console/CMakeLists.txt | 2 +- drivers/console/Kconfig | 1 + drivers/console/Kconfig.ws | 112 +++++++ drivers/console/websocket_console.c | 338 ++++++++++++++++++++ include/drivers/console/websocket_console.h | 37 +++ include/net/websocket_console.h | 60 ++++ subsys/shell/shell.c | 6 + 7 files changed, 555 insertions(+), 1 deletion(-) create mode 100644 drivers/console/Kconfig.ws create mode 100644 drivers/console/websocket_console.c create mode 100644 include/drivers/console/websocket_console.h create mode 100644 include/net/websocket_console.h 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/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/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. From cb093d36947d2b16436e4da188a82eaffda9ae2e Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 31 Aug 2017 14:07:16 +0300 Subject: [PATCH 06/11] samples: net: websocket: Add console sample application This sample application implements a web service that provides zephyr console over websocket. Signed-off-by: Jukka Rissanen --- samples/net/ws_console/CMakeLists.txt | 37 ++ samples/net/ws_console/README.rst | 40 ++ samples/net/ws_console/prj.conf | 58 +++ samples/net/ws_console/prj_tls.conf | 60 +++ samples/net/ws_console/sample.yaml | 7 + samples/net/ws_console/src/common.h | 12 + samples/net/ws_console/src/config.h | 33 ++ samples/net/ws_console/src/echo-apps-cert.der | Bin 0 -> 767 bytes samples/net/ws_console/src/echo-apps-key.der | Bin 0 -> 1218 bytes samples/net/ws_console/src/favicon.ico | Bin 0 -> 318 bytes samples/net/ws_console/src/index.html | 137 +++++ samples/net/ws_console/src/main.c | 46 ++ samples/net/ws_console/src/style.css | 54 ++ samples/net/ws_console/src/ws_console.c | 469 ++++++++++++++++++ 14 files changed, 953 insertions(+) create mode 100644 samples/net/ws_console/CMakeLists.txt create mode 100644 samples/net/ws_console/README.rst create mode 100644 samples/net/ws_console/prj.conf create mode 100644 samples/net/ws_console/prj_tls.conf create mode 100644 samples/net/ws_console/sample.yaml create mode 100644 samples/net/ws_console/src/common.h create mode 100644 samples/net/ws_console/src/config.h create mode 100644 samples/net/ws_console/src/echo-apps-cert.der create mode 100644 samples/net/ws_console/src/echo-apps-key.der create mode 100644 samples/net/ws_console/src/favicon.ico create mode 100644 samples/net/ws_console/src/index.html create mode 100644 samples/net/ws_console/src/main.c create mode 100644 samples/net/ws_console/src/style.css create mode 100644 samples/net/ws_console/src/ws_console.c 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 0000000000000000000000000000000000000000..bfcb335e31c8c37fd5c964276c42a3554abc3f4e GIT binary patch literal 767 zcmXqLV)|{+#Q1mtGZP~d6DPwv0r`WU3|LlA&)ap-DdR6;hMk(GhDiIJZH z=nO8VCPqevV+_^27sZ{kS1zxd!{1vz@zQs9)6L?K?!L`s{JC+`NsopH^5?fNiT_~s zYX3vyA1jYOyCT`$q&%G?~a`7n%!BhJR8NxJ8LeAXMj8@Z}=^MXr+0N72oA?D7SXK(^cx;@x^i)my z(tRQdLffsY{O=sUs>_nL`zz1ck4Bc)1848L{c-s}-C57(WQW}Pc-Q0S^$(^seJXV> z`k(h(@=aU)(3QD6qd7+wwtn5CeIu6c$fmF-&;}Dq9gkyFl=eiRu;X}cf0k(j@>)) zviDuwwgnHgTeUi?cV#qa|IX8EcwRM~bF02W-`hPi@~2*nwpEi<6R~VsvBJr1b>w9C z=I@pJi?_zcR{Tp^^LD$O*V@A~EolM$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a4d67372ea41873b1c69e5e9371f6f9d2c5a4bd GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0LB1&4bc}v zYpJJsoDYq6k<#}^HM1Au-R*4w`LUA8NPyrU&$pys@HXnd;WPND#pcu*i-IND8FX-Y z?8a!x@6H;j@V5aqk^j?mZUVXnnkuZ%BEKsi!E!hvHR{@L-DjdJ88{gZMA30Lv~4DZ z*2ccUZ#?d=lspAiPOVdci_{}AX>uo%v^uOK=n$^;p9`jL({sug5z4-C09Gk9RLt5b zTP7))O<$p=xyviE4-fzZsSzwlv6-dHd}pP;6d)3}J9YgF3t-AMV@@HKpnBz{CM^S?O`maE}K1B+DL;kFThAp!#d009Dm0RaH7 z0UN?WjiWr2jsEeC*@o6{wYkmT#(VLVd8DRNY9H7lcyumSAs^->(9OQQ2?gklP=v-L`Gx}l}Epd9hrmzrWPB^HgY2; zPe4Raxg5}uhi0bmA2T-mx#s85P@au1X1#k-Aozb#I!LTKGTvnxtemE5>_qMcl?C!j z#SDE>AF8y#xd(^;D<~3x>O7vYf$#m);|U+hoAc_SeGMv2ZJY+*hf(xP2JocW!N5Gh>(fq?+ts}+mI(T~AV*HlNM#ec4c%-zD89*-5W zoj3kXL$XrmvJT)MNZtpI|8%|ly(cPqz-9@rTLkUAoS)`Hqn^ieJ#j$+4frJ&sg!>B1V-0 zfq+K~=0jzRp|HDkq&((1 z4^sTRXxZnW?SQnSc0LySo5cU}$nFBvF(&`13#hnd=8Im5cx&cpoOA&>{Ra-O67MCI z&{4EyJ}s*>fq2TSrW{4Sogwp8<_}h%jHv>FfdJOeMWN~48AK`JI_MJJDU z8=_kU!4}(t3vFMBFF}{=yak@NV(~@!ROfCqUOUOBjX|tSjTWBzA{$rPt$=l?X&Xg< zGCkVbG5nX724gmcghG9WqLN(tyh?m2u) + + + + + + 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); +} From 4f9a0fdccd7321aefabbdfa7e9ddc8d4c7d92553 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Wed, 16 Aug 2017 17:13:47 +0300 Subject: [PATCH 07/11] net: ipv6: Add routing support between interfaces Introduce CONFIG_NET_ROUTING option that allows the IP stack to route IPv6 packets between multiple network interfaces. No support for IPv4 routing is implemented by this commit. Signed-off-by: Jukka Rissanen --- include/net/net_pkt.h | 21 +++ subsys/net/ip/Kconfig | 8 + subsys/net/ip/ipv6.c | 369 +++++++++++++++++++++++++++++++-------- subsys/net/ip/net_core.c | 4 + subsys/net/ip/route.c | 6 +- 5 files changed, 336 insertions(+), 72 deletions(-) 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/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..f8ef583e8e13a 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; } @@ -851,6 +856,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 +977,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 +1005,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)); - - 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)); + bool try_route = false; - /* 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 +1030,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 +1040,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 +1287,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 +1396,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 +1504,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 +1534,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); @@ -3767,6 +3893,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 +4038,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_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); } From 72abc662c688e60f9421f1fc7aac672ad71b0af9 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Thu, 17 Aug 2017 15:47:11 +0300 Subject: [PATCH 08/11] net: ipv6: Add some extra debug when updating link address length As this is very specialized info which is not normally needed, do not print it by default. Signed-off-by: Jukka Rissanen --- subsys/net/ip/ipv6.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/subsys/net/ip/ipv6.c b/subsys/net/ip/ipv6.c index f8ef583e8e13a..feb57d7e10900 100644 --- a/subsys/net/ip/ipv6.c +++ b/subsys/net/ip/ipv6.c @@ -802,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; From fb202302850da0e6fdb6d59786c3b44164a7d528 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 4 Sep 2017 17:45:13 +0300 Subject: [PATCH 09/11] net: rpl: Do not do neighbor discovery for RPL network Doing neighbor discovery in RPL network is not necessary and that can be disabled in RPL nodes. Unfortunately the border router needs to have ND enabled as it has also non-RPL network interfaces in use. So in this case, mark RPL node as always reachable. Signed-off-by: Jukka Rissanen --- subsys/net/ip/ipv6.c | 9 +++++++++ subsys/net/ip/rpl.c | 5 +++++ subsys/net/ip/rpl.h | 10 ++++++++++ 3 files changed, 24 insertions(+) diff --git a/subsys/net/ip/ipv6.c b/subsys/net/ip/ipv6.c index feb57d7e10900..7002edc078a3a 100644 --- a/subsys/net/ip/ipv6.c +++ b/subsys/net/ip/ipv6.c @@ -1585,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"); diff --git a/subsys/net/ip/rpl.c b/subsys/net/ip/rpl.c index 5994fa8bf0dc1..a48dbe682c088 100644 --- a/subsys/net/ip/rpl.c +++ b/subsys/net/ip/rpl.c @@ -4229,6 +4229,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 From aa2cd3dbf4a692d04de011ef32ca702abf0387a9 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Mon, 18 Sep 2017 15:14:05 +0300 Subject: [PATCH 10/11] net: rpl: Setup DAG prefix into network interface This is needed in routing if we are acting as border router. Signed-off-by: Jukka Rissanen --- subsys/net/ip/rpl.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/subsys/net/ip/rpl.c b/subsys/net/ip/rpl.c index a48dbe682c088..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); } From 2b5dc6cf774856058f5678eab78d271797542083 Mon Sep 17 00:00:00 2001 From: Jukka Rissanen Date: Tue, 1 Aug 2017 17:17:42 +0300 Subject: [PATCH 11/11] samples: net: rpl: Simple RPL border router application This is a very simple RPL border router sample application. It provides HTTP(S) and net-shell interface for getting admin information about neighbors and routes. Signed-off-by: Jukka Rissanen --- samples/net/rpl_border_router/CMakeLists.txt | 38 + samples/net/rpl_border_router/README.rst | 177 ++ samples/net/rpl_border_router/prj.conf | 74 + .../prj_frdm_k64f_cc2520.conf | 111 ++ .../prj_frdm_k64f_cc2520_tls.conf | 110 ++ .../prj_frdm_k64f_mcr20a.conf | 79 + .../net/rpl_border_router/prj_qemu_x86.conf | 45 + samples/net/rpl_border_router/sample.yaml | 9 + .../src/br-info.schema.json.py | 292 ++++ samples/net/rpl_border_router/src/br.js | 377 +++++ samples/net/rpl_border_router/src/config.h | 51 + .../rpl_border_router/src/echo-apps-cert.der | Bin 0 -> 767 bytes .../rpl_border_router/src/echo-apps-key.der | Bin 0 -> 1218 bytes samples/net/rpl_border_router/src/favicon.ico | Bin 0 -> 1406 bytes samples/net/rpl_border_router/src/http.c | 1439 +++++++++++++++++ samples/net/rpl_border_router/src/index.html | 57 + samples/net/rpl_border_router/src/main.c | 56 + samples/net/rpl_border_router/src/rpl.c | 140 ++ samples/net/rpl_border_router/src/shell.c | 42 + samples/net/rpl_border_router/src/style.css | 105 ++ 20 files changed, 3202 insertions(+) create mode 100644 samples/net/rpl_border_router/CMakeLists.txt create mode 100644 samples/net/rpl_border_router/README.rst create mode 100644 samples/net/rpl_border_router/prj.conf create mode 100644 samples/net/rpl_border_router/prj_frdm_k64f_cc2520.conf create mode 100644 samples/net/rpl_border_router/prj_frdm_k64f_cc2520_tls.conf create mode 100644 samples/net/rpl_border_router/prj_frdm_k64f_mcr20a.conf create mode 100644 samples/net/rpl_border_router/prj_qemu_x86.conf create mode 100644 samples/net/rpl_border_router/sample.yaml create mode 100755 samples/net/rpl_border_router/src/br-info.schema.json.py create mode 100644 samples/net/rpl_border_router/src/br.js create mode 100644 samples/net/rpl_border_router/src/config.h create mode 100644 samples/net/rpl_border_router/src/echo-apps-cert.der create mode 100644 samples/net/rpl_border_router/src/echo-apps-key.der create mode 100644 samples/net/rpl_border_router/src/favicon.ico create mode 100644 samples/net/rpl_border_router/src/http.c create mode 100644 samples/net/rpl_border_router/src/index.html create mode 100644 samples/net/rpl_border_router/src/main.c create mode 100644 samples/net/rpl_border_router/src/rpl.c create mode 100644 samples/net/rpl_border_router/src/shell.c create mode 100644 samples/net/rpl_border_router/src/style.css 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 0000000000000000000000000000000000000000..bfcb335e31c8c37fd5c964276c42a3554abc3f4e GIT binary patch literal 767 zcmXqLV)|{+#Q1mtGZP~d6DPwv0r`WU3|LlA&)ap-DdR6;hMk(GhDiIJZH z=nO8VCPqevV+_^27sZ{kS1zxd!{1vz@zQs9)6L?K?!L`s{JC+`NsopH^5?fNiT_~s zYX3vyA1jYOyCT`$q&%G?~a`7n%!BhJR8NxJ8LeAXMj8@Z}=^MXr+0N72oA?D7SXK(^cx;@x^i)my z(tRQdLffsY{O=sUs>_nL`zz1ck4Bc)1848L{c-s}-C57(WQW}Pc-Q0S^$(^seJXV> z`k(h(@=aU)(3QD6qd7+wwtn5CeIu6c$fmF-&;}Dq9gkyFl=eiRu;X}cf0k(j@>)) zviDuwwgnHgTeUi?cV#qa|IX8EcwRM~bF02W-`hPi@~2*nwpEi<6R~VsvBJr1b>w9C z=I@pJi?_zcR{Tp^^LD$O*V@A~EolM$ literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..5a4d67372ea41873b1c69e5e9371f6f9d2c5a4bd GIT binary patch literal 1218 zcmV;z1U>sOf&{(-0RS)!1_>&LNQUrs4#*Aqyhl|0)hbn0LB1&4bc}v zYpJJsoDYq6k<#}^HM1Au-R*4w`LUA8NPyrU&$pys@HXnd;WPND#pcu*i-IND8FX-Y z?8a!x@6H;j@V5aqk^j?mZUVXnnkuZ%BEKsi!E!hvHR{@L-DjdJ88{gZMA30Lv~4DZ z*2ccUZ#?d=lspAiPOVdci_{}AX>uo%v^uOK=n$^;p9`jL({sug5z4-C09Gk9RLt5b zTP7))O<$p=xyviE4-fzZsSzwlv6-dHd}pP;6d)3}J9YgF3t-AMV@@HKpnBz{CM^S?O`maE}K1B+DL;kFThAp!#d009Dm0RaH7 z0UN?WjiWr2jsEeC*@o6{wYkmT#(VLVd8DRNY9H7lcyumSAs^->(9OQQ2?gklP=v-L`Gx}l}Epd9hrmzrWPB^HgY2; zPe4Raxg5}uhi0bmA2T-mx#s85P@au1X1#k-Aozb#I!LTKGTvnxtemE5>_qMcl?C!j z#SDE>AF8y#xd(^;D<~3x>O7vYf$#m);|U+hoAc_SeGMv2ZJY+*hf(xP2JocW!N5Gh>(fq?+ts}+mI(T~AV*HlNM#ec4c%-zD89*-5W zoj3kXL$XrmvJT)MNZtpI|8%|ly(cPqz-9@rTLkUAoS)`Hqn^ieJ#j$+4frJ&sg!>B1V-0 zfq+K~=0jzRp|HDkq&((1 z4^sTRXxZnW?SQnSc0LySo5cU}$nFBvF(&`13#hnd=8Im5cx&cpoOA&>{Ra-O67MCI z&{4EyJ}s*>fq2TSrW{4Sogwp8<_}h%jHv>FfdJOeMWN~48AK`JI_MJJDU z8=_kU!4}(t3vFMBFF}{=yak@NV(~@!ROfCqUOUOBjX|tSjTWBzA{$rPt$=l?X&Xg< zGCkVbG5nX724gmcghG9WqLN(tyh?m2u) +#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; +}