zephyr/subsys/net/lib/http/http_server.c
Jukka Rissanen 35d28fb567 net: http: Connection close fix if old connection is active
If we receive a HTTP request and if the earlier context is still
active and it is not the same as the new one, then close the earlier
one. Otherwise it is possible that the old context will be left into
TCP ESTABLISHED state and would never be released. Example of this
is that we had IPv4 connection active and then IPv6 connection is
established, in this case we will disconnect the IPv4 connection
after this commit.

Signed-off-by: Jukka Rissanen <jukka.rissanen@linux.intel.com>
2017-06-12 10:23:19 +03:00

1632 lines
36 KiB
C

/*
* Copyright (c) 2017 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#if defined(CONFIG_NET_DEBUG_HTTP)
#if defined(CONFIG_HTTPS)
#define SYS_LOG_DOMAIN "https/server"
#else
#define SYS_LOG_DOMAIN "http/server"
#endif
#define NET_LOG_ENABLED 1
#endif
#define RX_EXTRA_DEBUG 0
#include <misc/printk.h>
#include <net/net_core.h>
#include <net/net_pkt.h>
#include <net/net_context.h>
#include <net/http.h>
#if defined(CONFIG_HTTPS)
static void https_enable(struct http_server_ctx *ctx);
static void https_disable(struct http_server_ctx *ctx);
#if defined(MBEDTLS_DEBUG_C)
#include <mbedtls/debug.h>
/* - Debug levels (from ext/lib/crypto/mbedtls/include/mbedtls/debug.h)
* - 0 No debug
* - 1 Error
* - 2 State change
* - 3 Informational
* - 4 Verbose
*/
#define DEBUG_THRESHOLD 0
#endif
#endif /* CONFIG_HTTPS */
#define HTTP_DEFAULT_PORT 80
#define HTTPS_DEFAULT_PORT 443
#define RC_STR(rc) (rc == 0 ? "OK" : "ERROR")
#define HTTP_STATUS_200_OK "HTTP/1.1 200 OK\r\n" \
"Content-Type: text/html\r\n" \
"Transfer-Encoding: chunked\r\n" \
"\r\n"
#define HTTP_STATUS_400_BR "HTTP/1.1 400 Bad Request\r\n" \
"\r\n"
#define HTTP_STATUS_403_FBD "HTTP/1.1 403 Forbidden\r\n" \
"\r\n"
#define HTTP_STATUS_404_NF "HTTP/1.1 404 Not Found\r\n" \
"\r\n"
#if defined(CONFIG_NET_DEBUG_HTTP_CONN)
/** List of HTTP connections */
static sys_slist_t http_conn;
static http_server_cb_t ctx_mon;
static void *mon_user_data;
static void http_server_conn_add(struct http_server_ctx *ctx)
{
sys_slist_prepend(&http_conn, &ctx->node);
if (ctx_mon) {
ctx_mon(ctx, mon_user_data);
}
}
static void http_server_conn_del(struct http_server_ctx *ctx)
{
sys_slist_find_and_remove(&http_conn, &ctx->node);
}
void http_server_conn_foreach(http_server_cb_t cb, void *user_data)
{
struct http_server_ctx *ctx;
SYS_SLIST_FOR_EACH_CONTAINER(&http_conn, ctx, node) {
cb(ctx, user_data);
}
}
void http_server_conn_monitor(http_server_cb_t cb, void *user_data)
{
ctx_mon = cb;
mon_user_data = user_data;
}
#else
#define http_server_conn_add(...)
#define http_server_conn_del(...)
#endif /* CONFIG_NET_DEBUG_HTTP_CONN */
static inline u16_t http_strlen(const char *str)
{
if (str) {
return strlen(str);
}
return 0;
}
static int http_add_header(struct net_pkt *pkt, s32_t timeout, const char *str)
{
if (net_pkt_append_all(pkt, strlen(str), (u8_t *)str, timeout)) {
return 0;
}
return -ENOMEM;
}
static int http_add_chunk(struct net_pkt *pkt, s32_t timeout, const char *str)
{
char chunk_header[16];
u16_t str_len;
bool ret;
str_len = http_strlen(str);
snprintk(chunk_header, sizeof(chunk_header), "%x\r\n", str_len);
ret = net_pkt_append_all(pkt, strlen(chunk_header), chunk_header,
timeout);
if (!ret) {
return -ENOMEM;
}
if (str_len > 0) {
ret = net_pkt_append_all(pkt, str_len, (u8_t *)str, timeout);
if (!ret) {
return -ENOMEM;
}
}
ret = net_pkt_append_all(pkt, sizeof(HTTP_CRLF) - 1, HTTP_CRLF,
timeout);
if (!ret) {
return -ENOMEM;
}
return 0;
}
static void req_timer_cancel(struct http_server_ctx *ctx)
{
ctx->req.timer_cancelled = true;
k_delayed_work_cancel(&ctx->req.timer);
NET_DBG("Context %p request timer cancelled", ctx);
}
static void req_timeout(struct k_work *work)
{
struct http_server_ctx *ctx = CONTAINER_OF(work,
struct http_server_ctx,
req.timer);
if (ctx->req.timer_cancelled) {
return;
}
NET_DBG("Context %p request timeout", ctx);
net_context_unref(ctx->req.net_ctx);
ctx->req.net_ctx = NULL;
http_server_conn_del(ctx);
}
static void pkt_sent(struct net_context *context,
int status,
void *token,
void *user_data)
{
s32_t timeout = POINTER_TO_INT(token);
struct http_server_ctx *ctx = user_data;
req_timer_cancel(ctx);
if (timeout == K_NO_WAIT) {
/* We can just close the context after the packet is sent. */
net_context_unref(context);
http_server_conn_del(ctx);
} else if (timeout > 0) {
NET_DBG("Context %p starting timer", ctx);
k_delayed_work_submit(&ctx->req.timer, timeout);
ctx->req.timer_cancelled = false;
}
/* Note that if the timeout is K_FOREVER, we do not close
* the connection.
*/
}
int http_response_wait(struct http_server_ctx *ctx, const char *http_header,
const char *html_payload, s32_t timeout)
{
struct net_pkt *pkt;
int ret = -EINVAL;
pkt = net_pkt_get_tx(ctx->req.net_ctx, ctx->timeout);
if (!pkt) {
return ret;
}
ret = http_add_header(pkt, ctx->timeout, http_header);
if (ret != 0) {
goto exit_routine;
}
if (html_payload) {
ret = http_add_chunk(pkt, ctx->timeout, html_payload);
if (ret != 0) {
goto exit_routine;
}
/* like EOF */
ret = http_add_chunk(pkt, ctx->timeout, NULL);
if (ret != 0) {
goto exit_routine;
}
}
net_pkt_set_appdatalen(pkt, net_buf_frags_len(pkt->frags));
ret = ctx->send_data(pkt, pkt_sent, 0, INT_TO_POINTER(timeout), ctx);
if (ret != 0) {
goto exit_routine;
}
pkt = NULL;
exit_routine:
if (pkt) {
net_pkt_unref(pkt);
}
return ret;
}
int http_response(struct http_server_ctx *ctx, const char *http_header,
const char *html_payload)
{
return http_response_wait(ctx, http_header, html_payload, K_NO_WAIT);
}
int http_response_400(struct http_server_ctx *ctx, const char *html_payload)
{
return http_response(ctx, HTTP_STATUS_400_BR, html_payload);
}
int http_response_403(struct http_server_ctx *ctx, const char *html_payload)
{
return http_response(ctx, HTTP_STATUS_403_FBD, html_payload);
}
int http_response_404(struct http_server_ctx *ctx, const char *html_payload)
{
return http_response(ctx, HTTP_STATUS_404_NF, html_payload);
}
int http_server_set_local_addr(struct sockaddr *addr, const char *myaddr,
u16_t port)
{
if (!addr) {
return -EINVAL;
}
if (myaddr) {
void *inaddr;
if (addr->family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
inaddr = &net_sin(addr)->sin_addr;
net_sin(addr)->sin_port = htons(port);
#else
return -EPFNOSUPPORT;
#endif
} else if (addr->family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
inaddr = &net_sin6(addr)->sin6_addr;
net_sin6(addr)->sin6_port = htons(port);
#else
return -EPFNOSUPPORT;
#endif
} else {
return -EAFNOSUPPORT;
}
return net_addr_pton(addr->family, myaddr, inaddr);
}
/* If the caller did not supply the address where to bind, then
* try to figure it out ourselves.
*/
if (addr->family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
net_ipaddr_copy(&net_sin6(addr)->sin6_addr,
net_if_ipv6_select_src_addr(NULL,
(struct in6_addr *)
net_ipv6_unspecified_address()));
#else
return -EPFNOSUPPORT;
#endif
} else if (addr->family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
struct net_if *iface = net_if_get_default();
/* For IPv4 we take the first address in the interface */
net_ipaddr_copy(&net_sin(addr)->sin_addr,
&iface->ipv4.unicast[0].address.in_addr);
#else
return -EPFNOSUPPORT;
#endif
}
return 0;
}
struct http_root_url *http_server_add_url(struct http_server_urls *my,
const char *url, u8_t flags,
http_url_cb_t write_cb)
{
int i;
for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) {
if (my->urls[i].is_used) {
continue;
}
my->urls[i].is_used = true;
my->urls[i].root = url;
/* This will speed-up some future operations */
my->urls[i].root_len = strlen(url);
my->urls[i].flags = flags;
my->urls[i].write_cb = write_cb;
return &my->urls[i];
}
return NULL;
}
int http_server_del_url(struct http_server_urls *my, const char *url)
{
int i;
for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) {
if (!my->urls[i].is_used) {
continue;
}
if (strncmp(my->urls[i].root, url, my->urls[i].root_len)) {
continue;
}
my->urls[i].is_used = false;
my->urls[i].root = NULL;
return 0;
}
return -ENOENT;
}
struct http_root_url *http_server_add_default(struct http_server_urls *my,
http_url_cb_t write_cb)
{
if (my->default_url.is_used) {
return NULL;
}
my->default_url.is_used = true;
my->default_url.root = NULL;
my->default_url.root_len = 0;
my->default_url.flags = 0;
my->default_url.write_cb = write_cb;
return &my->default_url;
}
int http_server_del_default(struct http_server_urls *my)
{
if (!my->default_url.is_used) {
return -ENOENT;
}
my->default_url.is_used = false;
return 0;
}
#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2)
static char *sprint_ipaddr(char *buf, int buflen, const struct sockaddr *addr)
{
if (addr->family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
char ipaddr[NET_IPV6_ADDR_LEN];
net_addr_ntop(addr->family,
&net_sin6(addr)->sin6_addr,
ipaddr, sizeof(ipaddr));
snprintk(buf, buflen, "[%s]:%u", ipaddr,
ntohs(net_sin6(addr)->sin6_port));
#endif
} else if (addr->family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
char ipaddr[NET_IPV4_ADDR_LEN];
net_addr_ntop(addr->family,
&net_sin(addr)->sin_addr,
ipaddr, sizeof(ipaddr));
snprintk(buf, buflen, "%s:%u", ipaddr,
ntohs(net_sin(addr)->sin_port));
#endif
}
return buf;
}
#endif /* CONFIG_NET_DEBUG_HTTP */
static inline void new_client(struct http_server_ctx *http_ctx,
struct net_context *net_ctx,
const struct sockaddr *addr)
{
#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2)
#if defined(CONFIG_NET_IPV6)
#define PORT_STR sizeof("[]:xxxxx")
char buf[NET_IPV6_ADDR_LEN + PORT_STR];
#elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
#define PORT_STR sizeof(":xxxxx")
char buf[NET_IPV4_ADDR_LEN + PORT_STR];
#endif
NET_INFO("%s connection from %s (%p)",
http_ctx->is_https ? "HTTPS" : "HTTP",
sprint_ipaddr(buf, sizeof(buf), addr),
net_ctx);
#endif /* CONFIG_NET_DEBUG_HTTP */
}
static inline void new_server(struct http_server_ctx *ctx,
const char *server_banner,
const struct sockaddr *addr)
{
#if defined(CONFIG_NET_DEBUG_HTTP) && (CONFIG_SYS_LOG_NET_LEVEL > 2)
#if defined(CONFIG_NET_IPV6)
#define PORT_STR sizeof("[]:xxxxx")
char buf[NET_IPV6_ADDR_LEN + PORT_STR];
#elif defined(CONFIG_NET_IPV4) && !defined(CONFIG_NET_IPV6)
#define PORT_STR sizeof(":xxxxx")
char buf[NET_IPV4_ADDR_LEN + PORT_STR];
#endif
if (addr) {
NET_INFO("%s %s (%p)", server_banner,
sprint_ipaddr(buf, sizeof(buf), addr), ctx);
} else {
NET_INFO("%s (%p)", server_banner, ctx);
}
#endif /* CONFIG_NET_DEBUG_HTTP */
}
static int on_header_field(struct http_parser *parser,
const char *at, size_t length)
{
struct http_server_ctx *ctx = parser->data;
if (ctx->req.field_values_ctr >= CONFIG_HTTP_HEADER_FIELD_ITEMS) {
return 0;
}
ctx->req.field_values[ctx->req.field_values_ctr].key = at;
ctx->req.field_values[ctx->req.field_values_ctr].key_len = length;
return 0;
}
static int on_header_value(struct http_parser *parser,
const char *at, size_t length)
{
struct http_server_ctx *ctx = parser->data;
if (ctx->req.field_values_ctr >= CONFIG_HTTP_HEADER_FIELD_ITEMS) {
return 0;
}
ctx->req.field_values[ctx->req.field_values_ctr].value = at;
ctx->req.field_values[ctx->req.field_values_ctr].value_len = length;
ctx->req.field_values_ctr++;
return 0;
}
static int on_url(struct http_parser *parser, const char *at, size_t length)
{
struct http_server_ctx *ctx = parser->data;
ctx->req.url = at;
ctx->req.url_len = length;
http_server_conn_add(ctx);
return 0;
}
static int parser_init(struct http_server_ctx *ctx)
{
memset(ctx->req.field_values, 0, sizeof(ctx->req.field_values));
ctx->req.settings.on_header_field = on_header_field;
ctx->req.settings.on_header_value = on_header_value;
ctx->req.settings.on_url = on_url;
http_parser_init(&ctx->req.parser, HTTP_REQUEST);
ctx->req.parser.data = ctx;
return 0;
}
static int http_url_cmp(const char *url, u16_t url_len,
const char *root_url, u16_t root_url_len)
{
if (url_len < root_url_len) {
return -EINVAL;
}
if (memcmp(url, root_url, root_url_len) == 0) {
if (url_len == root_url_len) {
return 0;
}
/* Here we evaluate the following conditions:
* root_url = /images, url = /images/ -> OK
* root_url = /images/, url = /images/img.png -> OK
* root_url = /images/, url = /images_and_docs -> ERROR
*/
if (url_len > root_url_len) {
if (root_url[root_url_len - 1] == '/') {
return 0;
}
if (url[root_url_len] == '/') {
return 0;
}
}
}
return -EINVAL;
}
static struct http_root_url *http_url_find(struct http_server_ctx *http_ctx)
{
u16_t url_len = http_ctx->req.url_len;
const char *url = http_ctx->req.url;
struct http_root_url *root_url;
u8_t i;
int ret;
for (i = 0; i < CONFIG_HTTP_SERVER_NUM_URLS; i++) {
root_url = &http_ctx->urls->urls[i];
if (!root_url->is_used) {
continue;
}
ret = http_url_cmp(url, url_len,
root_url->root, root_url->root_len);
if (!ret) {
return root_url;
}
}
return NULL;
}
static int http_process_recv(struct http_server_ctx *http_ctx)
{
struct http_root_url *root_url;
int ret;
root_url = http_url_find(http_ctx);
if (!root_url) {
root_url = &http_ctx->urls->default_url;
if (!root_url->is_used) {
NET_DBG("No default handler found (%p)", http_ctx);
ret = -ENOENT;
goto out;
}
}
if (root_url->write_cb) {
NET_DBG("Calling handler %p context %p",
root_url->write_cb, http_ctx);
ret = root_url->write_cb(http_ctx);
} else {
NET_ERR("No handler for %s", http_ctx->req.url);
ret = -ENOENT;
}
out:
return ret;
}
static void http_recv(struct net_context *net_ctx,
struct net_pkt *pkt, int status,
void *user_data)
{
struct http_server_ctx *http_ctx = user_data;
struct net_buf *frag;
size_t start = http_ctx->req.data_len;
int parsed_len;
int header_len;
u16_t len = 0, recv_len;
if (!pkt) {
NET_DBG("Connection closed by peer");
return;
}
if (!http_ctx->enabled) {
goto quit;
}
recv_len = net_pkt_appdatalen(pkt);
if (recv_len == 0) {
/* don't print info about zero-length app data buffers */
goto quit;
}
if (status) {
NET_DBG("Status %d <%s>", status, RC_STR(status));
goto out;
}
/* Get rid of possible IP headers in the first fragment. */
frag = pkt->frags;
header_len = net_pkt_appdata(pkt) - frag->data;
NET_DBG("Received %d bytes data", net_pkt_appdatalen(pkt));
/* After this pull, the frag->data points directly to application data.
*/
net_buf_pull(frag, header_len);
while (frag) {
/* If this fragment cannot be copied to result buf,
* then parse what we have which will cause the callback to be
* called in function on_body(), and continue copying.
*/
if (http_ctx->req.data_len + frag->len >
http_ctx->req.request_buf_len) {
/* If the caller has not supplied a callback, then
* we cannot really continue if the request buffer
* overflows. Set the data_len to mark how many bytes
* should be needed in the response_buf.
*/
if (http_process_recv(http_ctx) < 0) {
http_ctx->req.data_len = net_pkt_get_len(pkt);
goto out;
}
parsed_len =
http_parser_execute(&http_ctx->req.parser,
&http_ctx->req.settings,
http_ctx->req.request_buf +
start,
len);
if (parsed_len <= 0) {
goto fail;
}
http_ctx->req.data_len = 0;
len = 0;
start = 0;
}
memcpy(http_ctx->req.request_buf + http_ctx->req.data_len,
frag->data, frag->len);
http_ctx->req.data_len += frag->len;
len += frag->len;
frag = frag->frags;
}
out:
parsed_len = http_parser_execute(&http_ctx->req.parser,
&http_ctx->req.settings,
http_ctx->req.request_buf + start,
len);
if (parsed_len < 0) {
fail:
NET_DBG("Received %u bytes, only parsed %d bytes (%s %s)",
recv_len, parsed_len,
http_errno_name(http_ctx->req.parser.http_errno),
http_errno_description(
http_ctx->req.parser.http_errno));
}
if (http_ctx->req.parser.http_errno != HPE_OK) {
http_response_400(http_ctx, NULL);
} else {
http_process_recv(http_ctx);
}
quit:
http_parser_init(&http_ctx->req.parser, HTTP_REQUEST);
http_ctx->req.data_len = 0;
net_pkt_unref(pkt);
}
static void accept_cb(struct net_context *net_ctx,
struct sockaddr *addr, socklen_t addrlen,
int status, void *data)
{
struct http_server_ctx *http_ctx = data;
ARG_UNUSED(addr);
ARG_UNUSED(addrlen);
if (status != 0) {
net_context_put(net_ctx);
return;
}
/* If we receive a HTTP request and if the earlier context is still
* active and it is not the same as the new one, then close the earlier
* one. Otherwise it is possible that the context will be left into
* TCP ESTABLISHED state and would never be released. Example of this
* is that we had IPv4 connection active and then IPv6 connection is
* established, in this case we disconnect the IPv4 here.
*/
if (http_ctx->req.net_ctx && http_ctx->req.net_ctx != net_ctx &&
net_context_get_state(http_ctx->req.net_ctx) ==
NET_CONTEXT_CONNECTED) {
net_context_unref(http_ctx->req.net_ctx);
}
http_ctx->req.net_ctx = net_ctx;
new_client(http_ctx, net_ctx, addr);
net_context_recv(net_ctx, http_ctx->recv_cb, K_NO_WAIT, http_ctx);
}
static int set_net_ctx(struct http_server_ctx *http_ctx,
struct net_context *ctx,
struct sockaddr *addr,
socklen_t socklen)
{
int ret;
ret = net_context_bind(ctx, addr, socklen);
if (ret < 0) {
NET_ERR("Cannot bind context (%d)", ret);
goto out;
}
ret = net_context_listen(ctx, 0);
if (ret < 0) {
NET_ERR("Cannot listen context (%d)", ret);
goto out;
}
ret = net_context_accept(ctx, accept_cb, 0, http_ctx);
if (ret < 0) {
NET_ERR("Cannot accept context (%d)", ret);
goto out;
}
out:
return ret;
}
#if defined(CONFIG_NET_IPV4)
static int setup_ipv4_ctx(struct http_server_ctx *http_ctx,
struct sockaddr *addr)
{
socklen_t socklen;
int ret;
socklen = sizeof(struct sockaddr_in);
ret = net_context_get(AF_INET, SOCK_STREAM, IPPROTO_TCP,
&http_ctx->net_ipv4_ctx);
if (ret < 0) {
NET_ERR("Cannot get network context (%d)", ret);
http_ctx->net_ipv4_ctx = NULL;
return ret;
}
if (addr->family == AF_UNSPEC) {
addr->family = AF_INET;
http_server_set_local_addr(addr, NULL,
net_sin(addr)->sin_port);
}
ret = set_net_ctx(http_ctx, http_ctx->net_ipv4_ctx,
addr, socklen);
if (ret < 0) {
net_context_put(http_ctx->net_ipv4_ctx);
http_ctx->net_ipv4_ctx = NULL;
}
return ret;
}
#endif
#if defined(CONFIG_NET_IPV6)
int setup_ipv6_ctx(struct http_server_ctx *http_ctx, struct sockaddr *addr)
{
socklen_t socklen;
int ret;
socklen = sizeof(struct sockaddr_in6);
ret = net_context_get(AF_INET6, SOCK_STREAM, IPPROTO_TCP,
&http_ctx->net_ipv6_ctx);
if (ret < 0) {
NET_ERR("Cannot get network context (%d)", ret);
http_ctx->net_ipv6_ctx = NULL;
return ret;
}
if (addr->family == AF_UNSPEC) {
addr->family = AF_INET6;
http_server_set_local_addr(addr, NULL,
net_sin6(addr)->sin6_port);
}
ret = set_net_ctx(http_ctx, http_ctx->net_ipv6_ctx,
addr, socklen);
if (ret < 0) {
net_context_put(http_ctx->net_ipv6_ctx);
http_ctx->net_ipv6_ctx = NULL;
}
return ret;
}
#endif
static int init_net(struct http_server_ctx *ctx,
struct sockaddr *server_addr,
u16_t port)
{
struct sockaddr addr;
int ret;
memset(&addr, 0, sizeof(addr));
if (server_addr) {
memcpy(&addr, server_addr, sizeof(addr));
} else {
addr.family = AF_UNSPEC;
net_sin(&addr)->sin_port = htons(port);
}
if (addr.family == AF_INET6) {
#if defined(CONFIG_NET_IPV6)
ret = setup_ipv6_ctx(ctx, &addr);
#else
return -EPFNOSUPPORT;
#endif
} else if (addr.family == AF_INET) {
#if defined(CONFIG_NET_IPV4)
ret = setup_ipv4_ctx(ctx, &addr);
#else
return -EPFNOSUPPORT;
#endif
} else if (addr.family == AF_UNSPEC) {
#if defined(CONFIG_NET_IPV4)
ret = setup_ipv4_ctx(ctx, &addr);
#endif
/* We ignore the IPv4 error if IPv6 is enabled */
#if defined(CONFIG_NET_IPV6)
memset(&addr, 0, sizeof(addr));
addr.family = AF_UNSPEC;
net_sin6(&addr)->sin6_port = htons(port);
ret = setup_ipv6_ctx(ctx, &addr);
#endif
} else {
return -EINVAL;
}
return ret;
}
bool http_server_enable(struct http_server_ctx *http_ctx)
{
bool old;
NET_ASSERT(http_ctx);
old = http_ctx->enabled;
http_ctx->enabled = true;
#if defined(CONFIG_HTTPS)
if (http_ctx->is_https) {
https_enable(http_ctx);
}
#endif
return old;
}
bool http_server_disable(struct http_server_ctx *http_ctx)
{
bool old;
NET_ASSERT(http_ctx);
req_timer_cancel(http_ctx);
old = http_ctx->enabled;
http_ctx->enabled = false;
#if defined(CONFIG_HTTPS)
if (http_ctx->is_https) {
https_disable(http_ctx);
}
#endif
return old;
}
int http_server_init(struct http_server_ctx *http_ctx,
struct http_server_urls *urls,
struct sockaddr *server_addr,
u8_t *request_buf,
size_t request_buf_len,
const char *server_banner)
{
int ret;
if (http_ctx->urls) {
NET_ERR("Server context %p already initialized", http_ctx);
return -EINVAL;
}
if (!request_buf || request_buf_len == 0) {
NET_ERR("Request buf must be set");
return -EINVAL;
}
ret = init_net(http_ctx, server_addr, HTTP_DEFAULT_PORT);
if (ret < 0) {
return ret;
}
if (server_banner) {
new_server(http_ctx, server_banner, server_addr);
}
http_ctx->req.request_buf = request_buf;
http_ctx->req.request_buf_len = request_buf_len;
http_ctx->req.data_len = 0;
http_ctx->urls = urls;
http_ctx->recv_cb = http_recv;
http_ctx->send_data = net_context_send;
k_delayed_work_init(&http_ctx->req.timer, req_timeout);
parser_init(http_ctx);
return 0;
}
void http_server_release(struct http_server_ctx *http_ctx)
{
if (!http_ctx->urls) {
return;
}
http_server_disable(http_ctx);
#if defined(CONFIG_NET_IPV4)
if (http_ctx->net_ipv4_ctx) {
net_context_put(http_ctx->net_ipv4_ctx);
http_ctx->net_ipv4_ctx = NULL;
}
#endif
#if defined(CONFIG_NET_IPV6)
if (http_ctx->net_ipv6_ctx) {
net_context_put(http_ctx->net_ipv6_ctx);
http_ctx->net_ipv6_ctx = NULL;
}
#endif
http_ctx->req.net_ctx = NULL;
http_ctx->urls = NULL;
}
#if defined(CONFIG_HTTPS)
struct rx_fifo_block {
sys_snode_t snode;
struct k_mem_block block;
struct net_pkt *pkt;
};
#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP)
static void my_debug(void *ctx, int level,
const char *file, int line, const char *str)
{
const char *p, *basename;
int len;
ARG_UNUSED(ctx);
/* Extract basename from file */
for (p = basename = file; *p != '\0'; p++) {
if (*p == '/' || *p == '\\') {
basename = p + 1;
}
}
/* Avoid printing double newlines */
len = strlen(str);
if (str[len - 1] == '\n') {
((char *)str)[len - 1] = '\0';
}
NET_DBG("%s:%04d: |%d| %s", basename, line, level, str);
}
#endif /* MBEDTLS_DEBUG_C && CONFIG_NET_DEBUG_HTTP */
#if defined(MBEDTLS_ERROR_C)
#define print_error(fmt, ret) \
do { \
char error[64]; \
\
mbedtls_strerror(ret, error, sizeof(error)); \
\
NET_ERR(fmt " (%s)", -ret, error); \
} while (0)
#else
#define print_error(fmt, ret) \
do { \
NET_ERR(fmt, -ret); \
} while (0)
#endif
#define BUF_ALLOC_TIMEOUT 100
/* Receive encrypted data from network. Put that data into fifo
* that will be read by https thread.
*/
static void ssl_received(struct net_context *context,
struct net_pkt *pkt,
int status,
void *user_data)
{
struct http_server_ctx *http_ctx = user_data;
struct rx_fifo_block *rx_data = NULL;
struct k_mem_block block;
int ret;
ARG_UNUSED(context);
ARG_UNUSED(status);
if (pkt && !net_pkt_appdatalen(pkt)) {
net_pkt_unref(pkt);
return;
}
ret = k_mem_pool_alloc(http_ctx->https.pool, &block,
sizeof(struct rx_fifo_block),
BUF_ALLOC_TIMEOUT);
if (ret < 0) {
net_pkt_unref(pkt);
return;
}
rx_data = block.data;
rx_data->pkt = pkt;
/* For freeing memory later */
memcpy(&rx_data->block, &block, sizeof(struct k_mem_block));
k_fifo_put(&http_ctx->https.mbedtls.ssl_ctx.rx_fifo, (void *)rx_data);
}
/* This will copy data from received net_pkt buf into mbedtls internal buffers.
*/
static int ssl_rx(void *context, unsigned char *buf, size_t size)
{
struct http_server_ctx *ctx = context;
struct rx_fifo_block *rx_data;
u16_t read_bytes;
u8_t *ptr;
int pos;
int len;
int ret = 0;
if (!ctx->https.mbedtls.ssl_ctx.frag) {
rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo,
K_FOREVER);
if (!rx_data->pkt) {
NET_DBG("Closing %p connection", ctx);
k_mem_pool_free(&rx_data->block);
return -EIO;
}
ctx->https.mbedtls.ssl_ctx.rx_pkt = rx_data->pkt;
k_mem_pool_free(&rx_data->block);
read_bytes = net_pkt_appdatalen(
ctx->https.mbedtls.ssl_ctx.rx_pkt);
ctx->https.mbedtls.ssl_ctx.remaining = read_bytes;
ctx->https.mbedtls.ssl_ctx.frag =
ctx->https.mbedtls.ssl_ctx.rx_pkt->frags;
ptr = net_pkt_appdata(ctx->https.mbedtls.ssl_ctx.rx_pkt);
len = ptr - ctx->https.mbedtls.ssl_ctx.frag->data;
if (len > ctx->https.mbedtls.ssl_ctx.frag->size) {
NET_ERR("Buf overflow (%d > %u)", len,
ctx->https.mbedtls.ssl_ctx.frag->size);
return -EINVAL;
} else {
/* This will get rid of IP header */
net_buf_pull(ctx->https.mbedtls.ssl_ctx.frag, len);
}
} else {
read_bytes = ctx->https.mbedtls.ssl_ctx.remaining;
ptr = ctx->https.mbedtls.ssl_ctx.frag->data;
}
len = ctx->https.mbedtls.ssl_ctx.frag->len;
pos = 0;
if (read_bytes > size) {
while (ctx->https.mbedtls.ssl_ctx.frag) {
read_bytes = len < (size - pos) ? len : (size - pos);
#if RX_EXTRA_DEBUG == 1
NET_DBG("Copying %d bytes", read_bytes);
#endif
memcpy(buf + pos, ptr, read_bytes);
pos += read_bytes;
if (pos < size) {
ctx->https.mbedtls.ssl_ctx.frag =
ctx->https.mbedtls.ssl_ctx.frag->frags;
ptr = ctx->https.mbedtls.ssl_ctx.frag->data;
len = ctx->https.mbedtls.ssl_ctx.frag->len;
} else {
if (read_bytes == len) {
ctx->https.mbedtls.ssl_ctx.frag =
ctx->https.mbedtls.ssl_ctx.frag->frags;
} else {
net_buf_pull(
ctx->https.mbedtls.ssl_ctx.frag,
read_bytes);
}
ctx->https.mbedtls.ssl_ctx.remaining -= size;
return size;
}
}
} else {
while (ctx->https.mbedtls.ssl_ctx.frag) {
#if RX_EXTRA_DEBUG == 1
NET_DBG("Copying all %d bytes", len);
#endif
memcpy(buf + pos, ptr, len);
pos += len;
ctx->https.mbedtls.ssl_ctx.frag =
ctx->https.mbedtls.ssl_ctx.frag->frags;
if (!ctx->https.mbedtls.ssl_ctx.frag) {
break;
}
ptr = ctx->https.mbedtls.ssl_ctx.frag->data;
len = ctx->https.mbedtls.ssl_ctx.frag->len;
}
net_pkt_unref(ctx->https.mbedtls.ssl_ctx.rx_pkt);
ctx->https.mbedtls.ssl_ctx.rx_pkt = NULL;
ctx->https.mbedtls.ssl_ctx.frag = NULL;
ctx->https.mbedtls.ssl_ctx.remaining = 0;
if (read_bytes != pos) {
return -EIO;
}
ret = read_bytes;
}
return ret;
}
static void ssl_sent(struct net_context *context,
int status, void *token, void *user_data)
{
struct http_server_ctx *http_ctx = user_data;
k_sem_give(&http_ctx->https.mbedtls.ssl_ctx.tx_sem);
}
/* Send encrypted data */
static int ssl_tx(void *context, const unsigned char *buf, size_t size)
{
struct http_server_ctx *ctx = context;
struct net_pkt *send_buf;
int ret, len;
send_buf = net_pkt_get_tx(ctx->req.net_ctx, BUF_ALLOC_TIMEOUT);
if (!send_buf) {
return MBEDTLS_ERR_SSL_ALLOC_FAILED;
}
ret = net_pkt_append_all(send_buf, size, (u8_t *)buf,
BUF_ALLOC_TIMEOUT);
if (!ret) {
/* Cannot append data */
net_pkt_unref(send_buf);
return 0;
}
len = size;
ret = net_context_send(send_buf, ssl_sent, K_NO_WAIT, NULL, ctx);
if (ret < 0) {
net_pkt_unref(send_buf);
return MBEDTLS_ERR_SSL_INTERNAL_ERROR;
}
k_sem_take(&ctx->https.mbedtls.ssl_ctx.tx_sem, K_FOREVER);
return len;
}
static int entropy_source(void *data, unsigned char *output, size_t len,
size_t *olen)
{
u32_t seed;
ARG_UNUSED(data);
seed = sys_rand32_get();
if (len > sizeof(seed)) {
len = sizeof(seed);
}
memcpy(output, &seed, len);
*olen = len;
return 0;
}
/* This gets plain data and it sends encrypted one to peer */
static int https_send(struct net_pkt *pkt,
net_context_send_cb_t cb,
s32_t timeout,
void *token,
void *user_data)
{
struct http_server_ctx *ctx = user_data;
int ret;
u16_t len;
len = net_pkt_appdatalen(pkt);
ret = net_frag_linearize(ctx->req.request_buf,
ctx->req.request_buf_len,
pkt, net_pkt_ip_hdr_len(pkt),
len);
if (ret < 0) {
NET_DBG("Cannot linearize send data (%d)", ret);
return ret;
}
if (ret != len) {
NET_DBG("Linear copy error (%u vs %d)", len, ret);
return -EINVAL;
}
do {
ret = mbedtls_ssl_write(&ctx->https.mbedtls.ssl,
ctx->req.request_buf, len);
if (ret == MBEDTLS_ERR_NET_CONN_RESET) {
print_error("peer closed the connection -0x%x", ret);
goto out;
}
if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
if (ret < 0) {
print_error("mbedtls_ssl_write returned "
"-0x%x", ret);
goto out;
}
}
} while (ret <= 0);
out:
if (cb) {
cb(net_pkt_context(pkt), ret, token, user_data);
}
return ret;
}
static void https_handler(struct http_server_ctx *ctx)
{
size_t len;
int ret;
NET_DBG("HTTPS handler starting");
mbedtls_platform_set_printf(printk);
http_heap_init();
#if defined(MBEDTLS_X509_CRT_PARSE_C)
mbedtls_x509_crt_init(&ctx->https.mbedtls.srvcert);
#endif
mbedtls_pk_init(&ctx->https.mbedtls.pkey);
mbedtls_ssl_init(&ctx->https.mbedtls.ssl);
mbedtls_ssl_config_init(&ctx->https.mbedtls.conf);
mbedtls_entropy_init(&ctx->https.mbedtls.entropy);
mbedtls_ctr_drbg_init(&ctx->https.mbedtls.ctr_drbg);
#if defined(MBEDTLS_DEBUG_C) && defined(CONFIG_NET_DEBUG_HTTP)
mbedtls_debug_set_threshold(DEBUG_THRESHOLD);
mbedtls_ssl_conf_dbg(&ctx->https.mbedtls.conf, my_debug, NULL);
#endif
/* Load the certificates and private RSA key. This needs to be done
* by the user so we call a callback that user must have provided.
*/
ret = ctx->https.mbedtls.cert_cb(ctx, &ctx->https.mbedtls.srvcert,
&ctx->https.mbedtls.pkey);
if (ret != 0) {
goto exit;
}
/* Seed the RNG */
mbedtls_entropy_add_source(&ctx->https.mbedtls.entropy,
ctx->https.mbedtls.entropy_src_cb,
NULL,
MBEDTLS_ENTROPY_MAX_GATHER,
MBEDTLS_ENTROPY_SOURCE_STRONG);
ret = mbedtls_ctr_drbg_seed(
&ctx->https.mbedtls.ctr_drbg,
mbedtls_entropy_func,
&ctx->https.mbedtls.entropy,
(const unsigned char *)ctx->https.mbedtls.personalization_data,
ctx->https.mbedtls.personalization_data_len);
if (ret != 0) {
print_error("mbedtls_ctr_drbg_seed returned -0x%x", ret);
goto exit;
}
/* Setup SSL defaults etc. */
ret = mbedtls_ssl_config_defaults(&ctx->https.mbedtls.conf,
MBEDTLS_SSL_IS_SERVER,
MBEDTLS_SSL_TRANSPORT_STREAM,
MBEDTLS_SSL_PRESET_DEFAULT);
if (ret != 0) {
print_error("mbedtls_ssl_config_defaults returned -0x%x", ret);
goto exit;
}
mbedtls_ssl_conf_rng(&ctx->https.mbedtls.conf,
mbedtls_ctr_drbg_random,
&ctx->https.mbedtls.ctr_drbg);
#if defined(MBEDTLS_X509_CRT_PARSE_C)
mbedtls_ssl_conf_ca_chain(&ctx->https.mbedtls.conf,
ctx->https.mbedtls.srvcert.next,
NULL);
ret = mbedtls_ssl_conf_own_cert(&ctx->https.mbedtls.conf,
&ctx->https.mbedtls.srvcert,
&ctx->https.mbedtls.pkey);
if (ret != 0) {
print_error("mbedtls_ssl_conf_own_cert returned -0x%x", ret);
goto exit;
}
#endif /* MBEDTLS_X509_CRT_PARSE_C */
ret = mbedtls_ssl_setup(&ctx->https.mbedtls.ssl,
&ctx->https.mbedtls.conf);
if (ret != 0) {
print_error("mbedtls_ssl_setup returned -0x%x", ret);
goto exit;
}
reset:
mbedtls_ssl_session_reset(&ctx->https.mbedtls.ssl);
mbedtls_ssl_set_bio(&ctx->https.mbedtls.ssl, ctx, ssl_tx,
ssl_rx, NULL);
/* SSL handshake. The ssl_rx() function will be called next by
* mbedtls library. The ssl_rx() will block and wait that data is
* received by ssl_received() and passed to it via fifo. After
* receiving the data, this function will then proceed with secure
* connection establishment.
*/
/* Waiting SSL handshake */
do {
ret = mbedtls_ssl_handshake(&ctx->https.mbedtls.ssl);
if (ret != MBEDTLS_ERR_SSL_WANT_READ &&
ret != MBEDTLS_ERR_SSL_WANT_WRITE) {
if (ret < 0) {
goto reset;
}
}
} while (ret != 0);
/* Read the HTTPS Request */
NET_DBG("Read HTTPS request");
do {
len = ctx->req.request_buf_len - 1;
memset(ctx->req.request_buf, 0, ctx->req.request_buf_len);
ret = mbedtls_ssl_read(&ctx->https.mbedtls.ssl,
ctx->req.request_buf, len);
if (ret == MBEDTLS_ERR_SSL_WANT_READ ||
ret == MBEDTLS_ERR_SSL_WANT_WRITE) {
continue;
}
if (ret <= 0) {
switch (ret) {
case MBEDTLS_ERR_SSL_PEER_CLOSE_NOTIFY:
NET_DBG("Connection was closed gracefully");
goto close;
case MBEDTLS_ERR_NET_CONN_RESET:
NET_DBG("Connection was reset by peer");
break;
default:
print_error("mbedtls_ssl_read returned "
"-0x%x", ret);
break;
}
goto close;
}
ret = http_parser_execute(&ctx->req.parser,
&ctx->req.settings,
ctx->req.request_buf,
ret);
} while (ret < 0);
/* Write the Response */
NET_DBG("Write HTTPS response");
if (ctx->req.parser.http_errno != HPE_OK) {
http_response_400(ctx, NULL);
} else {
http_process_recv(ctx);
}
close:
http_parser_init(&ctx->req.parser, HTTP_REQUEST);
mbedtls_ssl_close_notify(&ctx->https.mbedtls.ssl);
goto reset;
exit:
return;
}
static void https_enable(struct http_server_ctx *ctx)
{
/* Start the thread that handles HTTPS traffic. */
if (ctx->https.tid) {
return;
}
ctx->https.tid = k_thread_create(&ctx->https.thread,
ctx->https.stack,
ctx->https.stack_size,
(k_thread_entry_t)https_handler,
ctx, NULL, NULL,
K_PRIO_COOP(7), 0, 0);
}
static void https_disable(struct http_server_ctx *ctx)
{
if (!ctx->https.tid) {
return;
}
mbedtls_ssl_free(&ctx->https.mbedtls.ssl);
mbedtls_ssl_config_free(&ctx->https.mbedtls.conf);
mbedtls_ctr_drbg_free(&ctx->https.mbedtls.ctr_drbg);
mbedtls_entropy_free(&ctx->https.mbedtls.entropy);
/* Empty the fifo just in case there is any received packets
* still there.
*/
while (1) {
struct rx_fifo_block *rx_data;
rx_data = k_fifo_get(&ctx->https.mbedtls.ssl_ctx.rx_fifo,
K_NO_WAIT);
if (!rx_data) {
break;
}
net_pkt_unref(rx_data->pkt);
k_mem_pool_free(&rx_data->block);
}
NET_DBG("HTTPS thread %p stopped", ctx->https.tid);
k_thread_abort(ctx->https.tid);
ctx->https.tid = 0;
}
static int https_init(struct http_server_ctx *ctx)
{
k_sem_init(&ctx->https.mbedtls.ssl_ctx.tx_sem, 0, UINT_MAX);
k_fifo_init(&ctx->https.mbedtls.ssl_ctx.rx_fifo);
/* Next we return to application which must then enable the HTTPS
* service. The enable function will then start the https thread and
* do what ever further configuration needed.
*
* We do the mbedtls initialization in its own thread because it uses
* of of stack and the main stack runs out of memory very easily.
*
* See https_handler() how the things proceed from now on.
*/
return 0;
}
int https_server_init(struct http_server_ctx *ctx,
struct http_server_urls *urls,
struct sockaddr *server_addr,
u8_t *request_buf,
size_t request_buf_len,
const char *server_banner,
u8_t *personalization_data,
size_t personalization_data_len,
https_server_cert_cb_t cert_cb,
https_entropy_src_cb_t entropy_src_cb,
struct k_mem_pool *pool,
u8_t *https_stack,
size_t https_stack_size)
{
int ret;
if (ctx->urls) {
NET_ERR("Server context %p already initialized", ctx);
return -EALREADY;
}
if (!request_buf || request_buf_len == 0) {
NET_ERR("Request buf must be set");
return -EINVAL;
}
if (!cert_cb) {
NET_ERR("Cert callback must be set");
return -EINVAL;
}
ret = init_net(ctx, server_addr, HTTPS_DEFAULT_PORT);
if (ret < 0) {
return ret;
}
if (server_banner) {
new_server(ctx, server_banner, server_addr);
}
ctx->req.request_buf = request_buf;
ctx->req.request_buf_len = request_buf_len;
ctx->req.data_len = 0;
ctx->urls = urls;
ctx->is_https = true;
ctx->https.stack = https_stack;
ctx->https.stack_size = https_stack_size;
ctx->https.mbedtls.cert_cb = cert_cb;
ctx->https.pool = pool;
if (entropy_src_cb) {
ctx->https.mbedtls.entropy_src_cb = entropy_src_cb;
} else {
ctx->https.mbedtls.entropy_src_cb = entropy_source;
}
ctx->https.mbedtls.personalization_data = personalization_data;
ctx->https.mbedtls.personalization_data_len = personalization_data_len;
ctx->send_data = https_send;
ctx->recv_cb = ssl_received;
k_delayed_work_init(&ctx->req.timer, req_timeout);
parser_init(ctx);
/* Then mbedtls specific initialization */
return https_init(ctx);
}
#endif /* CONFIG_HTTPS */