/* * libwebsockets-test-server - libwebsockets test implementation * * Copyright (C) 2010-2011 Andy Green * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation: * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include "../lib/libwebsockets.h" static int close_testing; #ifdef EXTERNAL_POLL #define LWS_NO_FORK #define MAX_POLL_ELEMENTS 32000 struct pollfd pollfds[MAX_POLL_ELEMENTS]; int fd_lookup[MAX_POLL_ELEMENTS]; int count_pollfds; #endif /* EXTERNAL_POLL */ /* * This demo server shows how to use libwebsockets for one or more * websocket protocols in the same server * * It defines the following websocket protocols: * * dumb-increment-protocol: once the socket is opened, an incrementing * ascii string is sent down it every 50ms. * If you send "reset\n" on the websocket, then * the incrementing number is reset to 0. * * lws-mirror-protocol: copies any received packet to every connection also * using this protocol, including the sender */ enum demo_protocols { /* always first */ PROTOCOL_HTTP = 0, PROTOCOL_DUMB_INCREMENT, PROTOCOL_LWS_MIRROR, /* always last */ DEMO_PROTOCOL_COUNT }; #define LOCAL_RESOURCE_PATH INSTALL_DATADIR"/libwebsockets-test-server" /* * We take a strict whitelist approach to stop ../ attacks */ struct serveable { const char *urlpath; const char *mimetype; }; static const struct serveable whitelist[] = { { "/favicon.ico", "image/x-icon" }, { "/libwebsockets.org-logo.png", "image/png" }, /* last one is the default served if no match */ { "/test.html", "text/html" }, }; /* this protocol server (always the first one) just knows how to do HTTP */ static int callback_http(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { #if 0 char client_name[128]; char client_ip[128]; #endif char buf[256]; int n; #ifdef EXTERNAL_POLL int m; int fd = (int)(long)user; #endif switch (reason) { case LWS_CALLBACK_HTTP: for (n = 0; n < (sizeof(whitelist) / sizeof(whitelist[0]) - 1); n++) if (in && strcmp(in, whitelist[n].urlpath) == 0) break; sprintf(buf, LOCAL_RESOURCE_PATH"%s", whitelist[n].urlpath); if (libwebsockets_serve_http_file(context, wsi, buf, whitelist[n].mimetype)) lwsl_err("Failed to send HTTP file\n"); /* * notice that the sending of the file completes asynchronously, * we'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when * it's done */ break; case LWS_CALLBACK_HTTP_FILE_COMPLETION: // lwsl_info("LWS_CALLBACK_HTTP_FILE_COMPLETION seen\n"); /* kill the connection after we sent one file */ return 1; /* * callback for confirming to continue with client IP appear in * protocol 0 callback since no websocket protocol has been agreed * yet. You can just ignore this if you won't filter on client IP * since the default uhandled callback return is 0 meaning let the * connection continue. */ case LWS_CALLBACK_FILTER_NETWORK_CONNECTION: #if 0 libwebsockets_get_peer_addresses((int)(long)user, client_name, sizeof(client_name), client_ip, sizeof(client_ip)); fprintf(stderr, "Received network connect from %s (%s)\n", client_name, client_ip); #endif /* if we returned non-zero from here, we kill the connection */ break; #ifdef EXTERNAL_POLL /* * callbacks for managing the external poll() array appear in * protocol 0 callback */ case LWS_CALLBACK_ADD_POLL_FD: if (count_pollfds >= MAX_POLL_ELEMENTS) { lwsl_err("LWS_CALLBACK_ADD_POLL_FD: too many sockets to track\n"); return 1; } fd_lookup[fd] = count_pollfds; pollfds[count_pollfds].fd = fd; pollfds[count_pollfds].events = (int)(long)len; pollfds[count_pollfds++].revents = 0; break; case LWS_CALLBACK_DEL_POLL_FD: if (!--count_pollfds) break; m = fd_lookup[fd]; /* have the last guy take up the vacant slot */ pollfds[m] = pollfds[count_pollfds]; fd_lookup[pollfds[count_pollfds].fd] = m; break; case LWS_CALLBACK_SET_MODE_POLL_FD: pollfds[fd_lookup[fd]].events |= (int)(long)len; break; case LWS_CALLBACK_CLEAR_MODE_POLL_FD: pollfds[fd_lookup[fd]].events &= ~(int)(long)len; break; #endif default: break; } return 0; } /* * this is just an example of parsing handshake headers, you don't need this * in your code unless you will filter allowing connections by the header * content */ static void dump_handshake_info(struct lws_tokens *lwst) { int n; static const char *token_names[WSI_TOKEN_COUNT] = { /*[WSI_TOKEN_GET_URI] =*/ "GET URI", /*[WSI_TOKEN_HOST] =*/ "Host", /*[WSI_TOKEN_CONNECTION] =*/ "Connection", /*[WSI_TOKEN_KEY1] =*/ "key 1", /*[WSI_TOKEN_KEY2] =*/ "key 2", /*[WSI_TOKEN_PROTOCOL] =*/ "Protocol", /*[WSI_TOKEN_UPGRADE] =*/ "Upgrade", /*[WSI_TOKEN_ORIGIN] =*/ "Origin", /*[WSI_TOKEN_DRAFT] =*/ "Draft", /*[WSI_TOKEN_CHALLENGE] =*/ "Challenge", /* new for 04 */ /*[WSI_TOKEN_KEY] =*/ "Key", /*[WSI_TOKEN_VERSION] =*/ "Version", /*[WSI_TOKEN_SWORIGIN] =*/ "Sworigin", /* new for 05 */ /*[WSI_TOKEN_EXTENSIONS] =*/ "Extensions", /* client receives these */ /*[WSI_TOKEN_ACCEPT] =*/ "Accept", /*[WSI_TOKEN_NONCE] =*/ "Nonce", /*[WSI_TOKEN_HTTP] =*/ "Http", /*[WSI_TOKEN_MUXURL] =*/ "MuxURL", }; for (n = 0; n < WSI_TOKEN_COUNT; n++) { if (lwst[n].token == NULL) continue; fprintf(stderr, " %s = %s\n", token_names[n], lwst[n].token); } } /* dumb_increment protocol */ /* * one of these is auto-created for each connection and a pointer to the * appropriate instance is passed to the callback in the user parameter * * for this example protocol we use it to individualize the count for each * connection. */ struct per_session_data__dumb_increment { int number; }; static int callback_dumb_increment(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { int n; unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 512 + LWS_SEND_BUFFER_POST_PADDING]; unsigned char *p = &buf[LWS_SEND_BUFFER_PRE_PADDING]; struct per_session_data__dumb_increment *pss = user; switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_info("callback_dumb_increment: " "LWS_CALLBACK_ESTABLISHED\n"); pss->number = 0; break; /* * in this protocol, we just use the broadcast action as the chance to * send our own connection-specific data and ignore the broadcast info * that is available in the 'in' parameter */ case LWS_CALLBACK_BROADCAST: n = sprintf((char *)p, "%d", pss->number++); n = libwebsocket_write(wsi, p, n, LWS_WRITE_TEXT); if (n < 0) { lwsl_err("ERROR %d writing to socket\n", n); return 1; } if (close_testing && pss->number == 50) { lwsl_info("close tesing limit, closing\n"); libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NORMAL); } break; case LWS_CALLBACK_RECEIVE: // fprintf(stderr, "rx %d\n", (int)len); if (len < 6) break; if (strcmp(in, "reset\n") == 0) pss->number = 0; break; /* * this just demonstrates how to use the protocol filter. If you won't * study and reject connections based on header content, you don't need * to handle this callback */ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: dump_handshake_info((struct lws_tokens *)(long)user); /* you could return non-zero here and kill the connection */ break; default: break; } return 0; } /* lws-mirror_protocol */ #define MAX_MESSAGE_QUEUE 128 struct per_session_data__lws_mirror { struct libwebsocket *wsi; int ringbuffer_tail; }; struct a_message { void *payload; size_t len; }; static struct a_message ringbuffer[MAX_MESSAGE_QUEUE]; static int ringbuffer_head; static struct libwebsocket *wsi_choked[20]; static int num_wsi_choked; static int callback_lws_mirror(struct libwebsocket_context *context, struct libwebsocket *wsi, enum libwebsocket_callback_reasons reason, void *user, void *in, size_t len) { int n; struct per_session_data__lws_mirror *pss = user; switch (reason) { case LWS_CALLBACK_ESTABLISHED: lwsl_info("callback_lws_mirror: " "LWS_CALLBACK_ESTABLISHED\n"); pss->ringbuffer_tail = ringbuffer_head; pss->wsi = wsi; break; case LWS_CALLBACK_SERVER_WRITEABLE: if (close_testing) break; while (pss->ringbuffer_tail != ringbuffer_head) { n = libwebsocket_write(wsi, (unsigned char *) ringbuffer[pss->ringbuffer_tail].payload + LWS_SEND_BUFFER_PRE_PADDING, ringbuffer[pss->ringbuffer_tail].len, LWS_WRITE_TEXT); if (n < 0) { lwsl_err("ERROR %d writing to socket\n", n); return 1; } if (pss->ringbuffer_tail == (MAX_MESSAGE_QUEUE - 1)) pss->ringbuffer_tail = 0; else pss->ringbuffer_tail++; if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 15)) { for (n = 0; n < num_wsi_choked; n++) libwebsocket_rx_flow_control(wsi_choked[n], 1); num_wsi_choked = 0; } // lwsl_debug("tx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)); if (lws_send_pipe_choked(wsi)) { libwebsocket_callback_on_writable(context, wsi); return 0; } } break; case LWS_CALLBACK_BROADCAST: n = libwebsocket_write(wsi, in, len, LWS_WRITE_TEXT); if (n < 0) lwsl_err("mirror write failed\n"); break; case LWS_CALLBACK_RECEIVE: if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) == (MAX_MESSAGE_QUEUE - 1)) { lwsl_err("dropping!\n"); goto choke; } if (ringbuffer[ringbuffer_head].payload) free(ringbuffer[ringbuffer_head].payload); ringbuffer[ringbuffer_head].payload = malloc(LWS_SEND_BUFFER_PRE_PADDING + len + LWS_SEND_BUFFER_POST_PADDING); ringbuffer[ringbuffer_head].len = len; memcpy((char *)ringbuffer[ringbuffer_head].payload + LWS_SEND_BUFFER_PRE_PADDING, in, len); if (ringbuffer_head == (MAX_MESSAGE_QUEUE - 1)) ringbuffer_head = 0; else ringbuffer_head++; if (((ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)) != (MAX_MESSAGE_QUEUE - 2)) goto done; choke: if (num_wsi_choked < sizeof wsi_choked / sizeof wsi_choked[0]) { libwebsocket_rx_flow_control(wsi, 0); wsi_choked[num_wsi_choked++] = wsi; } // lwsl_debug("rx fifo %d\n", (ringbuffer_head - pss->ringbuffer_tail) & (MAX_MESSAGE_QUEUE - 1)); done: libwebsocket_callback_on_writable_all_protocol( libwebsockets_get_protocol(wsi)); break; /* * this just demonstrates how to use the protocol filter. If you won't * study and reject connections based on header content, you don't need * to handle this callback */ case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: dump_handshake_info((struct lws_tokens *)(long)user); /* you could return non-zero here and kill the connection */ break; default: break; } return 0; } /* list of supported protocols and callbacks */ static struct libwebsocket_protocols protocols[] = { /* first protocol must always be HTTP handler */ { "http-only", /* name */ callback_http, /* callback */ 0 /* per_session_data_size */ }, { "dumb-increment-protocol", callback_dumb_increment, sizeof(struct per_session_data__dumb_increment), }, { "lws-mirror-protocol", callback_lws_mirror, sizeof(struct per_session_data__lws_mirror) }, { NULL, NULL, 0 /* End of list */ } }; static struct option options[] = { { "help", no_argument, NULL, 'h' }, { "debug", required_argument, NULL, 'd' }, { "port", required_argument, NULL, 'p' }, { "ssl", no_argument, NULL, 's' }, { "killmask", no_argument, NULL, 'k' }, { "interface", required_argument, NULL, 'i' }, { "closetest", no_argument, NULL, 'c' }, #ifndef NO_DAEMONIZE { "daemonize", no_argument, NULL, 'D' }, #endif { NULL, 0, 0, 0 } }; int main(int argc, char **argv) { int n = 0; const char *cert_path = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.pem"; const char *key_path = LOCAL_RESOURCE_PATH"/libwebsockets-test-server.key.pem"; unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1024 + LWS_SEND_BUFFER_POST_PADDING]; int port = 7681; int use_ssl = 0; struct libwebsocket_context *context; int opts = 0; char interface_name[128] = ""; const char *interface = NULL; int syslog_options = LOG_PID | LOG_PERROR; #ifdef LWS_NO_FORK unsigned int oldus = 0; #endif int debug_level = 7; #ifndef NO_DAEMONIZE int daemonize = 0; #endif while (n >= 0) { n = getopt_long(argc, argv, "ci:khsp:d:D", options, NULL); if (n < 0) continue; switch (n) { #ifndef NO_DAEMONIZE case 'D': daemonize = 1; syslog_options &= ~LOG_PERROR; break; #endif case 'd': debug_level = atoi(optarg); break; case 's': use_ssl = 1; break; case 'k': opts = LWS_SERVER_OPTION_DEFEAT_CLIENT_MASK; break; case 'p': port = atoi(optarg); break; case 'i': strncpy(interface_name, optarg, sizeof interface_name); interface_name[(sizeof interface_name) - 1] = '\0'; interface = interface_name; break; case 'c': close_testing = 1; fprintf(stderr, " Close testing mode -- closes on " "client after 50 dumb increments" "and suppresses lws_mirror spam\n"); break; case 'h': fprintf(stderr, "Usage: test-server " "[--port=

] [--ssl] " "[-d ]\n"); exit(1); } } #ifndef NO_DAEMONIZE /* * normally lock path would be /var/lock/lwsts or similar, to * simplify getting started without having to take care about * permissions or running as root, set to /tmp/.lwsts-lock */ if (daemonize && lws_daemonize("/tmp/.lwsts-lock")) { fprintf(stderr, "Failed to daemonize\n"); return 1; } #endif /* we will only try to log things according to our debug_level */ setlogmask(LOG_UPTO (LOG_DEBUG)); openlog("lwsts", syslog_options, LOG_DAEMON); /* tell the library what debug level to emit and to send it to syslog */ lws_set_log_level(debug_level, lwsl_emit_syslog); lwsl_notice("libwebsockets test server - " "(C) Copyright 2010-2013 Andy Green - " "licensed under LGPL2.1\n"); if (!use_ssl) cert_path = key_path = NULL; context = libwebsocket_create_context(port, interface, protocols, #ifndef LWS_NO_EXTENSIONS libwebsocket_internal_extensions, #else NULL, #endif cert_path, key_path, NULL, -1, -1, opts, NULL); if (context == NULL) { lwsl_err("libwebsocket init failed\n"); return -1; } buf[LWS_SEND_BUFFER_PRE_PADDING] = 'x'; #ifdef LWS_NO_FORK /* * This example shows how to work with no forked service loop */ lwsl_info(" Using no-fork service loop\n"); n = 0; while (n >= 0) { struct timeval tv; gettimeofday(&tv, NULL); /* * This broadcasts to all dumb-increment-protocol connections * at 20Hz. * * We're just sending a character 'x', in these examples the * callbacks send their own per-connection content. * * You have to send something with nonzero length to get the * callback actions delivered. * * We take care of pre-and-post padding allocation. */ if (((unsigned int)tv.tv_usec - oldus) > 50000) { libwebsockets_broadcast( &protocols[PROTOCOL_DUMB_INCREMENT], &buf[LWS_SEND_BUFFER_PRE_PADDING], 1); oldus = tv.tv_usec; } /* * This example server does not fork or create a thread for * websocket service, it all runs in this single loop. So, * we have to give the websockets an opportunity to service * "manually". * * If no socket is needing service, the call below returns * immediately and quickly. Negative return means we are * in process of closing */ #ifdef EXTERNAL_POLL /* * this represents an existing server's single poll action * which also includes libwebsocket sockets */ n = poll(pollfds, count_pollfds, 50); if (n < 0) continue; if (n) for (n = 0; n < count_pollfds; n++) if (pollfds[n].revents) /* * returns immediately if the fd does not * match anything under libwebsockets * control */ if (libwebsocket_service_fd(context, &pollfds[n]) < 0) goto done; #else n = libwebsocket_service(context, 50); #endif } #else /* !LWS_NO_FORK */ /* * This example shows how to work with the forked websocket service loop */ lwsl_info(" Using forked service loop\n"); /* * This forks the websocket service action into a subprocess so we * don't have to take care about it. */ n = libwebsockets_fork_service_loop(context); if (n < 0) { lwsl_err("Unable to fork service loop %d\n", n); return 1; } if (n) exit(0); while (1) { usleep(50000); /* * This broadcasts to all dumb-increment-protocol connections * at 20Hz. * * We're just sending a character 'x', in these examples the * callbacks send their own per-connection content. * * You have to send something with nonzero length to get the * callback actions delivered. * * We take care of pre-and-post padding allocation. */ libwebsockets_broadcast(&protocols[PROTOCOL_DUMB_INCREMENT], &buf[LWS_SEND_BUFFER_PRE_PADDING], 1); } #endif #ifdef EXTERNAL_POLL done: #endif libwebsocket_context_destroy(context); lwsl_notice("libwebsockets-test-server exited cleanly\n"); closelog(); return 0; }