blob: fe5cea1081dac0f84d1f3e6d64190e8834f7310a [file] [log] [blame]
/*
* 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 */