From 5045dfab744bf9392c0bd8093e22f3f93e7bb224 Mon Sep 17 00:00:00 2001 From: Marko Mikulicic Date: Wed, 9 Nov 2016 15:48:49 +0100 Subject: [PATCH] Mongoose TCP tunnel PUBLISHED_FROM=f03b963aaec02574e47c28ee8033551440b94f2d --- docs/c-api/net.h/struct_mg_add_sock_opts.md | 1 + docs/c-api/net.h/struct_mg_bind_opts.md | 1 + docs/c-api/net.h/struct_mg_connect_opts.md | 1 + docs/c-api/util.h/intro.md | 3 + docs/c-api/util.h/mg_basic_auth_header.md | 12 + docs/c-api/util.h/mg_mbuf_append_base64.md | 10 + .../util.h/mg_mbuf_append_base64_putc.md | 10 + examples/tun/Makefile | 3 + examples/tun/tun.c | 66 +++ mongoose.c | 549 +++++++++++++++++- mongoose.h | 113 +++- 11 files changed, 751 insertions(+), 18 deletions(-) create mode 100644 docs/c-api/util.h/mg_basic_auth_header.md create mode 100644 docs/c-api/util.h/mg_mbuf_append_base64.md create mode 100644 docs/c-api/util.h/mg_mbuf_append_base64_putc.md create mode 100644 examples/tun/Makefile create mode 100644 examples/tun/tun.c diff --git a/docs/c-api/net.h/struct_mg_add_sock_opts.md b/docs/c-api/net.h/struct_mg_add_sock_opts.md index 1f399874..b4b1737b 100644 --- a/docs/c-api/net.h/struct_mg_add_sock_opts.md +++ b/docs/c-api/net.h/struct_mg_add_sock_opts.md @@ -7,6 +7,7 @@ signature: | void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Initial connection flags */ const char **error_string; /* Placeholder for the error string */ + struct mg_iface *iface; /* Interface instance */ }; --- diff --git a/docs/c-api/net.h/struct_mg_bind_opts.md b/docs/c-api/net.h/struct_mg_bind_opts.md index 50c0010b..2012e3d7 100644 --- a/docs/c-api/net.h/struct_mg_bind_opts.md +++ b/docs/c-api/net.h/struct_mg_bind_opts.md @@ -7,6 +7,7 @@ signature: | void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Extra connection flags */ const char **error_string; /* Placeholder for the error string */ + struct mg_iface *iface; /* Interface instance */ #if MG_ENABLE_SSL /* SSL settings. */ const char *ssl_cert; /* Server certificate to present to clients */ diff --git a/docs/c-api/net.h/struct_mg_connect_opts.md b/docs/c-api/net.h/struct_mg_connect_opts.md index 4bb9ea66..2d07a21f 100644 --- a/docs/c-api/net.h/struct_mg_connect_opts.md +++ b/docs/c-api/net.h/struct_mg_connect_opts.md @@ -7,6 +7,7 @@ signature: | void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Extra connection flags */ const char **error_string; /* Placeholder for the error string */ + struct mg_iface *iface; /* Interface instance */ #if MG_ENABLE_SSL /* SSL settings. */ const char *ssl_cert; /* Client certificate to present to the server */ diff --git a/docs/c-api/util.h/intro.md b/docs/c-api/util.h/intro.md index 213b3aa2..0268cd7d 100644 --- a/docs/c-api/util.h/intro.md +++ b/docs/c-api/util.h/intro.md @@ -7,6 +7,7 @@ items: - { name: mg_avprintf.md } - { name: mg_base64_decode.md } - { name: mg_base64_encode.md } + - { name: mg_basic_auth_header.md } - { name: mg_casecmp.md } - { name: mg_conn_addr_to_str.md } - { name: mg_fopen.md } @@ -14,6 +15,8 @@ items: - { name: mg_hexdump_connection.md } - { name: mg_is_big_endian.md } - { name: mg_match_prefix.md } + - { name: mg_mbuf_append_base64.md } + - { name: mg_mbuf_append_base64_putc.md } - { name: mg_ncasecmp.md } - { name: mg_next_comma_list_entry.md } - { name: mg_open.md } diff --git a/docs/c-api/util.h/mg_basic_auth_header.md b/docs/c-api/util.h/mg_basic_auth_header.md new file mode 100644 index 00000000..d8b582a5 --- /dev/null +++ b/docs/c-api/util.h/mg_basic_auth_header.md @@ -0,0 +1,12 @@ +--- +title: "mg_basic_auth_header()" +decl_name: "mg_basic_auth_header" +symbol_kind: "func" +signature: | + void mg_basic_auth_header(const char *user, const char *pass, struct mbuf *buf); +--- + +Generate a Basic Auth header and appends it to buf. +If pass is NULL, then user is expected to contain the credentials pair +already encoded as `user:pass`. + diff --git a/docs/c-api/util.h/mg_mbuf_append_base64.md b/docs/c-api/util.h/mg_mbuf_append_base64.md new file mode 100644 index 00000000..d0617fcf --- /dev/null +++ b/docs/c-api/util.h/mg_mbuf_append_base64.md @@ -0,0 +1,10 @@ +--- +title: "mg_mbuf_append_base64()" +decl_name: "mg_mbuf_append_base64" +symbol_kind: "func" +signature: | + void mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len); +--- + +Encode `len` bytes starting at `data` as base64 and append them to an mbuf. + diff --git a/docs/c-api/util.h/mg_mbuf_append_base64_putc.md b/docs/c-api/util.h/mg_mbuf_append_base64_putc.md new file mode 100644 index 00000000..a4d2b428 --- /dev/null +++ b/docs/c-api/util.h/mg_mbuf_append_base64_putc.md @@ -0,0 +1,10 @@ +--- +title: "mg_mbuf_append_base64_putc()" +decl_name: "mg_mbuf_append_base64_putc" +symbol_kind: "func" +signature: | + void mg_mbuf_append_base64_putc(char ch, void *user_data); +--- + +Use with cs_base64_init/update/finish in order to write out base64 in chunks. + diff --git a/examples/tun/Makefile b/examples/tun/Makefile new file mode 100644 index 00000000..986cfcab --- /dev/null +++ b/examples/tun/Makefile @@ -0,0 +1,3 @@ +PROG = tun +MODULE_CFLAGS = +include ../examples.mk diff --git a/examples/tun/tun.c b/examples/tun/tun.c new file mode 100644 index 00000000..f62ac658 --- /dev/null +++ b/examples/tun/tun.c @@ -0,0 +1,66 @@ +#include "mongoose.h" + +static const char *s_local_port = ":8001"; +static const char *s_dispatcher = "ws://localhost:8000"; +static const char *s_auth = "foo:bar"; + +void ev_handler(struct mg_connection *nc, int ev, void *ev_data) { + struct http_message *hm = (struct http_message *) ev_data; + switch (ev) { + case MG_EV_ACCEPT: + fprintf(stderr, "HTTP accept. nc=%p\n", nc); + break; + case MG_EV_RECV: + fprintf(stderr, "recvd: %d bytes\n", *(int *) ev_data); + break; + case MG_EV_HTTP_REQUEST: + fprintf(stderr, "HTTP got request. nc=%p path=%.*s\n", nc, + (int) hm->uri.len, hm->uri.p); + mg_http_send_error(nc, 200, "OK"); + break; + case MG_EV_CLOSE: + fprintf(stderr, "HTTP close\n"); + default: + break; + } +} + +int main(int argc, char **argv) { + struct mg_mgr mgr; + struct mg_connection *nc; + int i; + + mg_mgr_init(&mgr, NULL); + + /* Parse command line arguments */ + for (i = 1; i < argc; i++) { + if (strcmp(argv[i], "-D") == 0) { + mgr.hexdump_file = argv[++i]; + } else if (strcmp(argv[i], "-p") == 0) { + s_local_port = argv[++i]; + } else if (strcmp(argv[i], "-d") == 0) { + s_dispatcher = argv[++i]; + } else if (strcmp(argv[i], "-u") == 0) { + s_auth = argv[++i]; + } + } + + if ((nc = mg_tuna_bind(&mgr, ev_handler, s_dispatcher, s_auth)) == NULL) { + fprintf(stderr, "Cannot create tunneled listening socket on [%s]\n", + s_dispatcher); + exit(EXIT_FAILURE); + } + mg_set_protocol_http_websocket(nc); + fprintf(stderr, "Tun listener: %p\n", nc); + + if ((nc = mg_bind(&mgr, s_local_port, ev_handler)) == NULL) { + fprintf(stderr, "Cannot bind to local port %s\n", s_local_port); + exit(EXIT_FAILURE); + } + mg_set_protocol_http_websocket(nc); + fprintf(stderr, "Local listening connection: %p\n", nc); + + for (;;) { + mg_mgr_poll(&mgr, 1000); + } +} diff --git a/mongoose.c b/mongoose.c index e30122bf..b2cacea3 100644 --- a/mongoose.c +++ b/mongoose.c @@ -93,6 +93,8 @@ extern void *(*test_calloc)(size_t count, size_t size); #endif #if MG_ENABLE_HTTP +struct mg_serve_http_opts; + /* * Reassemble the content of the buffer (buf, blen) which should be * in the HTTP chunked encoding, by collapsing data chunks to the @@ -156,6 +158,8 @@ MG_INTERNAL void mg_ws_handshake(struct mg_connection *nc, MG_INTERNAL int mg_get_errno(void); +MG_INTERNAL void mg_close_conn(struct mg_connection *conn); + #endif /* CS_MONGOOSE_SRC_INTERNAL_H_ */ #ifdef MG_MODULE_LINES #line 1 "common/cs_dbg.h" @@ -1979,7 +1983,7 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data, { int i; if (opts.num_ifaces == 0) { - opts.num_ifaces = MG_NUM_IFACES; + opts.num_ifaces = mg_num_ifaces; opts.ifaces = mg_ifaces; } if (opts.main_iface != NULL) { @@ -1988,7 +1992,7 @@ void mg_mgr_init_opt(struct mg_mgr *m, void *user_data, m->num_ifaces = opts.num_ifaces; m->ifaces = (struct mg_iface **) MG_MALLOC(sizeof(*m->ifaces) * opts.num_ifaces); - for (i = 0; i < MG_NUM_IFACES; i++) { + for (i = 0; i < mg_num_ifaces; i++) { m->ifaces[i] = mg_if_create_iface(opts.ifaces[i], m); m->ifaces[i]->vtable->init(m->ifaces[i]); } @@ -2138,7 +2142,8 @@ MG_INTERNAL struct mg_connection *mg_create_connection_base( conn->handler = callback; conn->mgr = mgr; conn->last_io_time = (time_t) mg_time(); - conn->iface = mgr->ifaces[MG_MAIN_IFACE]; + conn->iface = + (opts.iface != NULL ? opts.iface : mgr->ifaces[MG_MAIN_IFACE]); conn->flags = opts.flags & _MG_ALLOWED_CONNECT_FLAGS_MASK; conn->user_data = opts.user_data; /* @@ -2461,6 +2466,7 @@ struct mg_connection *mg_if_accept_new_conn(struct mg_connection *lc) { nc->proto_handler = lc->proto_handler; nc->user_data = lc->user_data; nc->recv_mbuf_limit = lc->recv_mbuf_limit; + nc->iface = lc->iface; if (lc->flags & MG_F_SSL) nc->flags |= MG_F_SSL; mg_add_conn(nc->mgr, nc); DBG(("%p %p %d %d", lc, nc, nc->sock, (int) nc->flags)); @@ -2495,7 +2501,8 @@ void mg_if_sent_cb(struct mg_connection *nc, int num_sent) { mg_call(nc, NULL, MG_EV_SEND, &num_sent); } -static void mg_recv_common(struct mg_connection *nc, void *buf, int len) { +MG_INTERNAL void mg_recv_common(struct mg_connection *nc, void *buf, int len, + int own) { DBG(("%p %d %u", nc, len, (unsigned int) nc->recv_mbuf.len)); if (nc->flags & MG_F_CLOSE_IMMEDIATELY) { DBG(("%p discarded %d bytes", nc, len)); @@ -2503,11 +2510,15 @@ static void mg_recv_common(struct mg_connection *nc, void *buf, int len) { * This connection will not survive next poll. Do not deliver events, * send data to /dev/null without acking. */ - MG_FREE(buf); + if (own) { + MG_FREE(buf); + } return; } nc->last_io_time = (time_t) mg_time(); - if (nc->recv_mbuf.len == 0) { + if (!own) { + mbuf_append(&nc->recv_mbuf, buf, len); + } else if (nc->recv_mbuf.len == 0) { /* Adopt buf as recv_mbuf's backing store. */ mbuf_free(&nc->recv_mbuf); nc->recv_mbuf.buf = (char *) buf; @@ -2519,8 +2530,8 @@ static void mg_recv_common(struct mg_connection *nc, void *buf, int len) { mg_call(nc, NULL, MG_EV_RECV, &len); } -void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len) { - mg_recv_common(nc, buf, len); +void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own) { + mg_recv_common(nc, buf, len, own); } void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, @@ -2572,7 +2583,7 @@ void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, } } if (nc != NULL) { - mg_recv_common(nc, buf, len); + mg_recv_common(nc, buf, len, 1); } else { /* Drop on the floor. */ MG_FREE(buf); @@ -2982,15 +2993,56 @@ extern struct mg_iface_vtable mg_socket_iface_vtable; #endif /* CS_MONGOOSE_SRC_NET_IF_SOCKET_H_ */ #ifdef MG_MODULE_LINES +#line 1 "mongoose/src/net_if_tun.h" +#endif +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef CS_MONGOOSE_SRC_NET_IF_TUN_H_ +#define CS_MONGOOSE_SRC_NET_IF_TUN_H_ + +#if MG_ENABLE_TUN + +/* Amalgamated: #include "mongoose/src/net_if.h" */ + +struct mg_tun_client; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +extern struct mg_iface_vtable mg_tun_iface_vtable; + +struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client, + uint32_t stream_id); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* MG_ENABLE_TUN */ + +#endif /* CS_MONGOOSE_SRC_NET_IF_TUN_H_ */ +#ifdef MG_MODULE_LINES #line 1 "mongoose/src/net_if.c" #endif /* Amalgamated: #include "mongoose/src/net_if.h" */ /* Amalgamated: #include "mongoose/src/internal.h" */ /* Amalgamated: #include "mongoose/src/net_if_socket.h" */ +/* Amalgamated: #include "mongoose/src/net_if_tun.h" */ extern struct mg_iface_vtable mg_default_iface_vtable; +#if MG_ENABLE_TUN +struct mg_iface_vtable *mg_ifaces[] = {&mg_default_iface_vtable, + &mg_tun_iface_vtable}; +#else struct mg_iface_vtable *mg_ifaces[] = {&mg_default_iface_vtable}; +#endif + +int mg_num_ifaces = (int) (sizeof(mg_ifaces) / sizeof(mg_ifaces[0])); struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable, struct mg_mgr *mgr) { @@ -3000,6 +3052,27 @@ struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable, iface->vtable = vtable; return iface; } + +struct mg_iface *mg_find_iface(struct mg_mgr *mgr, + struct mg_iface_vtable *vtable, + struct mg_iface *from) { + int i = 0; + if (from != NULL) { + for (i = 0; i < mgr->num_ifaces; i++) { + if (mgr->ifaces[i] == from) { + i++; + break; + } + } + } + + for (; i < mgr->num_ifaces; i++) { + if (mgr->ifaces[i]->vtable == vtable) { + return mgr->ifaces[i]; + } + } + return NULL; +} #ifdef MG_MODULE_LINES #line 1 "mongoose/src/net_if_socket.c" #endif @@ -3291,7 +3364,7 @@ static void mg_handle_tcp_read(struct mg_connection *conn) { * we skip to the next select() cycle which can just timeout. */ while ((n = SSL_read(conn->ssl, buf, MG_TCP_RECV_BUFFER_SIZE)) > 0) { DBG(("%p %d bytes <- %d (SSL)", conn, n, conn->sock)); - mg_if_recv_tcp_cb(conn, buf, n); + mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */); buf = NULL; if (conn->flags & MG_F_CLOSE_IMMEDIATELY) break; /* buf has been freed, we need a new one. */ @@ -3312,7 +3385,7 @@ static void mg_handle_tcp_read(struct mg_connection *conn) { recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0); DBG(("%p %d bytes (PLAIN) <- %d", conn, n, conn->sock)); if (n > 0) { - mg_if_recv_tcp_cb(conn, buf, n); + mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */); } else { MG_FREE(buf); } @@ -3756,6 +3829,174 @@ struct mg_iface_vtable mg_default_iface_vtable = MG_SOCKET_IFACE_VTABLE; #endif /* MG_ENABLE_NET_IF_SOCKET */ #ifdef MG_MODULE_LINES +#line 1 "mongoose/src/net_if_tun.c" +#endif +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#if MG_ENABLE_TUN + +/* Amalgamated: #include "common/cs_dbg.h" */ +/* Amalgamated: #include "common/cs_time.h" */ +/* Amalgamated: #include "mongoose/src/internal.h" */ +/* Amalgamated: #include "mongoose/src/net_if_tun.h" */ +/* Amalgamated: #include "mongoose/src/tun.h" */ +/* Amalgamated: #include "mongoose/src/util.h" */ + +#define MG_TCP_RECV_BUFFER_SIZE 1024 +#define MG_UDP_RECV_BUFFER_SIZE 1500 + +void mg_tun_if_connect_tcp(struct mg_connection *nc, + const union socket_address *sa) { + (void) nc; + (void) sa; +} + +void mg_tun_if_connect_udp(struct mg_connection *nc) { + (void) nc; +} + +int mg_tun_if_listen_tcp(struct mg_connection *nc, union socket_address *sa) { + (void) nc; + (void) sa; + return 0; +} + +int mg_tun_if_listen_udp(struct mg_connection *nc, union socket_address *sa) { + (void) nc; + (void) sa; + return -1; +} + +void mg_tun_if_tcp_send(struct mg_connection *nc, const void *buf, size_t len) { + struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data; + uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data; + struct mg_str msg = {(char *) buf, len}; +#if MG_ENABLE_HEXDUMP + char hex[512]; + mg_hexdump(buf, len, hex, sizeof(hex)); + LOG(LL_DEBUG, ("sending to stream %zu:\n%s", stream_id, hex)); +#endif + + mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME, 0, msg); +} + +void mg_tun_if_udp_send(struct mg_connection *nc, const void *buf, size_t len) { + (void) nc; + (void) buf; + (void) len; +} + +void mg_tun_if_recved(struct mg_connection *nc, size_t len) { + (void) nc; + (void) len; +} + +int mg_tun_if_create_conn(struct mg_connection *nc) { + (void) nc; + return 1; +} + +void mg_tun_if_destroy_conn(struct mg_connection *nc) { + struct mg_tun_client *client = (struct mg_tun_client *) nc->iface->data; + uint32_t stream_id = (uint32_t)(uintptr_t) nc->mgr_data; + struct mg_str msg = {NULL, 0}; + + LOG(LL_DEBUG, ("closing %zu:", stream_id)); + mg_tun_send_frame(client->disp, stream_id, MG_TUN_DATA_FRAME, + MG_TUN_F_END_STREAM, msg); +} + +/* Associate a socket to a connection. */ +void mg_tun_if_sock_set(struct mg_connection *nc, sock_t sock) { + (void) nc; + (void) sock; +} + +void mg_tun_if_init(struct mg_iface *iface) { + (void) iface; +} + +void mg_tun_if_free(struct mg_iface *iface) { + (void) iface; +} + +void mg_tun_if_add_conn(struct mg_connection *nc) { + nc->sock = INVALID_SOCKET; +} + +void mg_tun_if_remove_conn(struct mg_connection *nc) { + (void) nc; +} + +time_t mg_tun_if_poll(struct mg_iface *iface, int timeout_ms) { + (void) iface; + (void) timeout_ms; + return (time_t) cs_time(); +} + +void mg_tun_if_get_conn_addr(struct mg_connection *nc, int remote, + union socket_address *sa) { + (void) nc; + (void) remote; + (void) sa; +} + +struct mg_connection *mg_tun_if_find_conn(struct mg_tun_client *client, + uint32_t stream_id) { + struct mg_connection *nc = NULL; + + for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) { + if (nc->iface != client->iface || (nc->flags & MG_F_LISTENING)) { + continue; + } + if (stream_id == (uint32_t)(uintptr_t) nc->mgr_data) { + return nc; + } + } + + if (stream_id > client->last_stream_id) { + /* create a new connection */ + LOG(LL_DEBUG, ("new stream 0x%lx, accepting", stream_id)); + nc = mg_if_accept_new_conn(client->listener); + nc->mgr_data = (void *) (uintptr_t) stream_id; + client->last_stream_id = stream_id; + } else { + LOG(LL_DEBUG, ("Ignoring stream 0x%lx (last_stream_id 0x%lx)", stream_id, + client->last_stream_id)); + } + + return nc; +} + +/* clang-format off */ +#define MG_TUN_IFACE_VTABLE \ + { \ + mg_tun_if_init, \ + mg_tun_if_free, \ + mg_tun_if_add_conn, \ + mg_tun_if_remove_conn, \ + mg_tun_if_poll, \ + mg_tun_if_listen_tcp, \ + mg_tun_if_listen_udp, \ + mg_tun_if_connect_tcp, \ + mg_tun_if_connect_udp, \ + mg_tun_if_tcp_send, \ + mg_tun_if_udp_send, \ + mg_tun_if_recved, \ + mg_tun_if_create_conn, \ + mg_tun_if_destroy_conn, \ + mg_tun_if_sock_set, \ + mg_tun_if_get_conn_addr, \ + } +/* clang-format on */ + +struct mg_iface_vtable mg_tun_iface_vtable = MG_TUN_IFACE_VTABLE; + +#endif /* MG_ENABLE_TUN */ +#ifdef MG_MODULE_LINES #line 1 "mongoose/src/multithreading.c" #endif /* @@ -8407,6 +8648,37 @@ MG_INTERNAL int mg_get_errno(void) { return GetLastError(); #endif } + +void mg_mbuf_append_base64_putc(char ch, void *user_data) { + struct mbuf *mbuf = (struct mbuf *) user_data; + mbuf_append(mbuf, &ch, sizeof(ch)); +} + +void mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len) { + struct cs_base64_ctx ctx; + cs_base64_init(&ctx, mg_mbuf_append_base64_putc, mbuf); + cs_base64_update(&ctx, (const char *) data, len); + cs_base64_finish(&ctx); +} + +void mg_basic_auth_header(const char *user, const char *pass, + struct mbuf *buf) { + const char *header_prefix = "Authorization: Basic "; + const char *header_suffix = "\r\n"; + + struct cs_base64_ctx ctx; + cs_base64_init(&ctx, mg_mbuf_append_base64_putc, buf); + + mbuf_append(buf, header_prefix, strlen(header_prefix)); + + cs_base64_update(&ctx, user, strlen(user)); + if (pass != NULL) { + cs_base64_update(&ctx, ":", 1); + cs_base64_update(&ctx, pass, strlen(pass)); + } + cs_base64_finish(&ctx); + mbuf_append(buf, header_suffix, strlen(header_suffix)); +} #ifdef MG_MODULE_LINES #line 1 "mongoose/src/mqtt.c" #endif @@ -10264,6 +10536,253 @@ int mg_set_protocol_coap(struct mg_connection *nc) { #endif /* MG_ENABLE_COAP */ #ifdef MG_MODULE_LINES +#line 1 "mongoose/src/tun.c" +#endif +/* + * Copyright (c) 2014 Cesanta Software Limited + * All rights reserved + */ + +#if MG_ENABLE_TUN + +/* Amalgamated: #include "common/cs_dbg.h" */ +/* Amalgamated: #include "mongoose/src/http.h" */ +/* Amalgamated: #include "mongoose/src/internal.h" */ +/* Amalgamated: #include "mongoose/src/net.h" */ +/* Amalgamated: #include "mongoose/src/net_if_tun.h" */ +/* Amalgamated: #include "mongoose/src/tun.h" */ +/* Amalgamated: #include "mongoose/src/util.h" */ + +static void mg_tun_reconnect(struct mg_tun_client *client); + +static void mg_tun_init_client(struct mg_tun_client *client, struct mg_mgr *mgr, + struct mg_iface *iface, const char *dispatcher, + const char *auth) { + client->mgr = mgr; + client->iface = iface; + client->disp_url = dispatcher; + client->auth = auth; + client->last_stream_id = 0; + + client->disp = NULL; /* will be set by mg_tun_reconnect */ + client->listener = NULL; /* will be set by mg_do_bind */ +} + +void mg_tun_log_frame(struct mg_tun_frame *frame) { + LOG(LL_DEBUG, ("Got TUN frame: type=0x%x, flags=0x%x stream_id=0x%lx, " + "len=%zu", + frame->type, frame->flags, frame->stream_id, frame->body.len)); +#if MG_ENABLE_HEXDUMP + { + char hex[512]; + mg_hexdump(frame->body.p, frame->body.len, hex, sizeof(hex)); + LOG(LL_DEBUG, ("body:\n%s", hex)); + } +#else + LOG(LL_DEBUG, ("body: '%.*s'", (int) frame->body.len, frame->body.p)); +#endif +} + +static void mg_tun_close_all(struct mg_tun_client *client) { + struct mg_connection *nc; + for (nc = client->mgr->active_connections; nc != NULL; nc = nc->next) { + if (nc->iface == client->iface && !(nc->flags & MG_F_LISTENING)) { + LOG(LL_DEBUG, ("Closing tunneled connection %p", nc)); + mg_close_conn(nc); + } + } +} + +static void mg_tun_client_handler(struct mg_connection *nc, int ev, + void *ev_data) { + struct mg_tun_client *client = (struct mg_tun_client *) nc->user_data; + + switch (ev) { + case MG_EV_CONNECT: { + int err = *(int *) ev_data; + + if (err) { + LOG(LL_ERROR, ("Cannot connect to the tunnel dispatcher: %d", err)); + } else { + LOG(LL_INFO, ("Connected to the tunnel dispatcher")); + } + break; + } + case MG_EV_HTTP_REPLY: { + struct http_message *hm = (struct http_message *) ev_data; + + if (hm->resp_code != 200) { + LOG(LL_ERROR, + ("Tunnel dispatcher reply non-OK status code %d", hm->resp_code)); + } + break; + } + case MG_EV_WEBSOCKET_HANDSHAKE_DONE: { + LOG(LL_INFO, ("Tunnel dispatcher handshake done")); + break; + } + case MG_EV_WEBSOCKET_FRAME: { + struct websocket_message *wm = (struct websocket_message *) ev_data; + struct mg_connection *tc; + struct mg_tun_frame frame; + + if (mg_tun_parse_frame(wm->data, wm->size, &frame) == -1) { + LOG(LL_ERROR, ("Got invalid tun frame dropping", wm->size)); + break; + } + + mg_tun_log_frame(&frame); + + tc = mg_tun_if_find_conn(client, frame.stream_id); + if (tc == NULL) { + if (frame.body.len > 0) { + LOG(LL_DEBUG, ("Got frame after receiving end has been closed")); + } + break; + } + if (frame.body.len > 0) { + mg_if_recv_tcp_cb(tc, (void *) frame.body.p, frame.body.len, + 0 /* own */); + } + if (frame.flags & MG_TUN_F_END_STREAM) { + LOG(LL_DEBUG, ("Closing tunneled connection because got end of stream " + "from other end")); + tc->flags |= MG_F_CLOSE_IMMEDIATELY; + mg_close_conn(tc); + } + break; + } + case MG_EV_CLOSE: { + LOG(LL_DEBUG, ("Closing all tunneled connections")); + mg_tun_close_all(client); + client->disp = NULL; + LOG(LL_INFO, ("Dispatcher connection is no more, reconnecting")); + mg_tun_reconnect(client); + break; + } + default: + break; + } +} + +static void mg_tun_do_reconnect(struct mg_tun_client *client) { + struct mg_connection *dc; + struct mbuf headers; + mbuf_init(&headers, 0); + + /* HTTP/Websocket listener */ + mg_basic_auth_header(client->auth, NULL, &headers); + mbuf_append(&headers, "", 1); /* nul terminate */ + if ((dc = mg_connect_ws(client->mgr, mg_tun_client_handler, client->disp_url, + "mg_tun", headers.buf)) == NULL) { + LOG(LL_ERROR, + ("Cannot connect to WS server on addr [%s]\n", client->disp_url)); + goto clean; + } + + client->disp = dc; + dc->user_data = client; + +clean: + mbuf_free(&headers); +} + +void mg_tun_reconnect_ev_handler(struct mg_connection *nc, int ev, + void *ev_data) { + struct mg_tun_client *client = (struct mg_tun_client *) nc->user_data; + (void) ev_data; + + switch (ev) { + case MG_EV_TIMER: + mg_tun_do_reconnect(client); + break; + } +} + +static void mg_tun_reconnect(struct mg_tun_client *client) { + struct mg_connection *nc; + nc = mg_add_sock(client->mgr, INVALID_SOCKET, mg_tun_reconnect_ev_handler); + nc->user_data = client; + /* TODO(mkm): implement exp back off */ + nc->ev_timer_time = mg_time() + MG_TUN_RECONNECT_INTERVAL; +} + +static struct mg_tun_client *mg_tun_create_client(struct mg_mgr *mgr, + const char *dispatcher, + const char *auth) { + struct mg_tun_client *client = NULL; + struct mg_iface *iface = mg_find_iface(mgr, &mg_tun_iface_vtable, NULL); + if (iface == NULL) { + LOG(LL_ERROR, ("The tun feature requires the manager to have a tun " + "interface enabled")); + return NULL; + } + + client = (struct mg_tun_client *) MG_MALLOC(sizeof(*client)); + mg_tun_init_client(client, mgr, iface, dispatcher, auth); + iface->data = client; + + mg_tun_do_reconnect(client); + return client; +} + +static struct mg_connection *mg_tuna_do_bind(struct mg_tun_client *client, + mg_event_handler_t handler) { + struct mg_connection *lc; + struct mg_bind_opts opts; + const char *err; + memset(&opts, 0, sizeof(opts)); + opts.iface = client->iface; + opts.error_string = &err; + lc = mg_bind_opt(client->mgr, ":1234" /* dummy port */, handler, opts); + if (lc == NULL) { + LOG(LL_ERROR, ("Cannot bind: %s", err)); + } + client->listener = lc; + return lc; +} + +struct mg_connection *mg_tuna_bind(struct mg_mgr *mgr, + mg_event_handler_t handler, + const char *dispatcher, const char *auth) { + struct mg_tun_client *client = mg_tun_create_client(mgr, dispatcher, auth); + if (client == NULL) { + return NULL; + } + return mg_tuna_do_bind(client, handler); +} + +int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame) { + const size_t header_size = sizeof(uint32_t) + sizeof(uint8_t) * 2; + if (len < header_size) { + return -1; + } + + frame->type = *(uint8_t *) (data); + frame->flags = *(uint8_t *) ((char *) data + 1); + memcpy(&frame->stream_id, (char *) data + 2, sizeof(uint32_t)); + frame->stream_id = ntohl(frame->stream_id); + frame->body.p = (char *) data + header_size; + frame->body.len = len - header_size; + return 0; +} + +void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id, + uint8_t type, uint8_t flags, struct mg_str msg) { + stream_id = htonl(stream_id); + { + struct mg_str parts[] = { + {(char *) &type, sizeof(type)}, + {(char *) &flags, sizeof(flags)}, + {(char *) &stream_id, sizeof(stream_id)}, + {msg.p, msg.len} /* vc6 doesn't like just `msg` here */}; + mg_send_websocket_framev(ws, WEBSOCKET_OP_BINARY, parts, + sizeof(parts) / sizeof(parts[0])); + } +} + +#endif /* MG_ENABLE_TUN */ +#ifdef MG_MODULE_LINES #line 1 "common/platforms/cc3200/cc3200_libc.c" #endif /* @@ -11478,7 +11997,7 @@ static void mg_handle_tcp_read(struct mg_connection *conn) { recv_avail_size(conn, MG_TCP_RECV_BUFFER_SIZE), 0); DBG(("%p %d bytes <- %d", conn, n, conn->sock)); if (n > 0) { - mg_if_recv_tcp_cb(conn, buf, n); + mg_if_recv_tcp_cb(conn, buf, n, 1 /* own */); } else { MG_FREE(buf); } @@ -11977,7 +12496,7 @@ static void mg_lwip_handle_recv(struct mg_connection *nc) { return; } pbuf_copy_partial(seg, data, len, cs->rx_offset); - mg_if_recv_tcp_cb(nc, data, len); /* callee takes over data */ + mg_if_recv_tcp_cb(nc, data, len, 1 /* own */); cs->rx_offset += len; if (cs->rx_offset == cs->rx_chain->len) { cs->rx_chain = pbuf_dechain(cs->rx_chain); @@ -12664,7 +13183,7 @@ void mg_lwip_ssl_recv(struct mg_connection *nc) { return; } } else { - mg_if_recv_tcp_cb(nc, buf, ret); /* callee takes over data */ + mg_if_recv_tcp_cb(nc, buf, ret, 1 /* own */); } } if (nc->recv_mbuf.len >= MG_LWIP_SSL_RECV_MBUF_LIMIT) { @@ -13011,7 +13530,7 @@ static void mg_handle_recv(struct mg_connection *nc) { } if (bytes_read != 0) { - mg_if_recv_tcp_cb(nc, buf, bytes_read); + mg_if_recv_tcp_cb(nc, buf, bytes_read, 1 /* own */); } } diff --git a/mongoose.h b/mongoose.h index 146a5d36..14ae4976 100644 --- a/mongoose.h +++ b/mongoose.h @@ -2709,7 +2709,7 @@ struct { \ #endif #ifndef MG_ENABLE_HTTP_WEBSOCKET -#define MG_ENABLE_HTTP_WEBSOCKET 1 +#define MG_ENABLE_HTTP_WEBSOCKET MG_ENABLE_HTTP #endif #ifndef MG_ENABLE_IPV6 @@ -2767,6 +2767,10 @@ struct { \ (CS_PLATFORM == CS_P_WINDOWS || CS_PLATFORM == CS_P_UNIX) #endif +#ifndef MG_ENABLE_TUN +#define MG_ENABLE_TUN MG_ENABLE_HTTP_WEBSOCKET +#endif + #endif /* CS_MONGOOSE_SRC_FEATURES_H_ */ #ifdef MG_MODULE_LINES #line 1 "mongoose/src/net_if.h" @@ -2796,7 +2800,6 @@ extern "C" { #endif /* __cplusplus */ #define MG_MAIN_IFACE 0 -#define MG_NUM_IFACES 1 struct mg_mgr; struct mg_connection; @@ -2847,11 +2850,19 @@ struct mg_iface_vtable { }; extern struct mg_iface_vtable *mg_ifaces[]; +extern int mg_num_ifaces; /* Creates a new interface instance. */ struct mg_iface *mg_if_create_iface(struct mg_iface_vtable *vtable, struct mg_mgr *mgr); +/* + * Find an interface with a given implementation. The search is started from + * interface `from`, exclusive. Returns NULL if none is found. + */ +struct mg_iface *mg_find_iface(struct mg_mgr *mgr, + struct mg_iface_vtable *vtable, + struct mg_iface *from); /* * Deliver a new TCP connection. Returns NULL in case on error (unable to * create connection, in which case interface state should be discarded. @@ -2866,12 +2877,18 @@ void mg_if_accept_tcp_cb(struct mg_connection *nc, union socket_address *sa, void mg_if_connect_cb(struct mg_connection *nc, int err); /* Callback that reports that data has been put on the wire. */ void mg_if_sent_cb(struct mg_connection *nc, int num_sent); +/* + * Receive callback. + * if `own` is true, buf must be heap-allocated and ownership is transferred + * to the core. + * Core will acknowledge consumption by calling iface::recved. + */ +void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len, int own); /* * Receive callback. * buf must be heap-allocated and ownership is transferred to the core. * Core will acknowledge consumption by calling iface::recved. */ -void mg_if_recv_tcp_cb(struct mg_connection *nc, void *buf, int len); void mg_if_recv_udp_cb(struct mg_connection *nc, void *buf, int len, union socket_address *sa, size_t sa_len); @@ -3170,6 +3187,7 @@ struct mg_add_sock_opts { void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Initial connection flags */ const char **error_string; /* Placeholder for the error string */ + struct mg_iface *iface; /* Interface instance */ }; /* @@ -3200,6 +3218,7 @@ struct mg_bind_opts { void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Extra connection flags */ const char **error_string; /* Placeholder for the error string */ + struct mg_iface *iface; /* Interface instance */ #if MG_ENABLE_SSL /* SSL settings. */ const char *ssl_cert; /* Server certificate to present to clients */ @@ -3244,6 +3263,7 @@ struct mg_connect_opts { void *user_data; /* Initial value for connection's user_data */ unsigned int flags; /* Extra connection flags */ const char **error_string; /* Placeholder for the error string */ + struct mg_iface *iface; /* Interface instance */ #if MG_ENABLE_SSL /* SSL settings. */ const char *ssl_cert; /* Client certificate to present to the server */ @@ -3746,6 +3766,23 @@ const char *mg_next_comma_list_entry(const char *list, struct mg_str *val, int mg_match_prefix(const char *pattern, int pattern_len, const char *str); int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str); +/* + * Use with cs_base64_init/update/finish in order to write out base64 in chunks. + */ +void mg_mbuf_append_base64_putc(char ch, void *user_data); + +/* + * Encode `len` bytes starting at `data` as base64 and append them to an mbuf. + */ +void mg_mbuf_append_base64(struct mbuf *mbuf, const void *data, size_t len); + +/* + * Generate a Basic Auth header and appends it to buf. + * If pass is NULL, then user is expected to contain the credentials pair + * already encoded as `user:pass`. + */ +void mg_basic_auth_header(const char *user, const char *pass, struct mbuf *buf); + #ifdef __cplusplus } #endif /* __cplusplus */ @@ -3768,6 +3805,7 @@ int mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str); #if MG_ENABLE_HTTP /* Amalgamated: #include "mongoose/src/net.h" */ +/* Amalgamated: #include "common/mg_str.h" */ #ifdef __cplusplus extern "C" { @@ -5480,3 +5518,72 @@ uint32_t mg_coap_compose(struct mg_coap_message *cm, struct mbuf *io); #endif /* MG_ENABLE_COAP */ #endif /* CS_MONGOOSE_SRC_COAP_H_ */ +#ifdef MG_MODULE_LINES +#line 1 "mongoose/src/tun.h" +#endif +/* + * Copyright (c) 2014-2016 Cesanta Software Limited + * All rights reserved + */ + +#ifndef CS_MONGOOSE_SRC_TUN_H_ +#define CS_MONGOOSE_SRC_TUN_H_ + +#if MG_ENABLE_TUN + +/* Amalgamated: #include "mongoose/src/net.h" */ +/* Amalgamated: #include "common/mg_str.h" */ + +#ifndef MG_TUN_RECONNECT_INTERVAL +#define MG_TUN_RECONNECT_INTERVAL 1 +#endif + +#define MG_TUN_DATA_FRAME 0x0 +#define MG_TUN_F_END_STREAM 0x1 + +/* + * MG TUN frame format is loosely based on HTTP/2. + * However since the communication happens via WebSocket + * there is no need to encode the frame length, since that's + * solved by WebSocket framing. + * + * TODO(mkm): Detailed description of the protocol. + */ +struct mg_tun_frame { + uint8_t type; + uint8_t flags; + uint32_t stream_id; /* opaque stream identifier */ + struct mg_str body; +}; + +struct mg_tun_client { + struct mg_mgr *mgr; + struct mg_iface *iface; + const char *disp_url; + const char *auth; + uint32_t last_stream_id; /* stream id of most recently accepted connection */ + + struct mg_connection *disp; + struct mg_connection *listener; +}; + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +struct mg_connection *mg_tuna_bind(struct mg_mgr *mgr, + mg_event_handler_t handler, + const char *dispatcher, const char *auth); + +int mg_tun_parse_frame(void *data, size_t len, struct mg_tun_frame *frame); + +void mg_tun_send_frame(struct mg_connection *ws, uint32_t stream_id, + uint8_t type, uint8_t flags, struct mg_str msg); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* MG_ENABLE_TUN */ + +#endif /* CS_MONGOOSE_SRC_TUN_H_ */