diff --git a/README-test-server b/README-test-server index f10520e1..cb9e6b51 100644 --- a/README-test-server +++ b/README-test-server @@ -37,6 +37,11 @@ There are a couple of other possible configure options look for trust certificates to validate the remote certificate against. +--enable-noping Don't try to build the ping test app + It needs some unixy environment that + may choke in other build contexts, this + lets you cleanly stop it being built + Testing server with a browser ----------------------------- @@ -107,6 +112,53 @@ use_ssl var being set to 2. Set it to 1 to reject any server certificate that it doesn't have a trusted CA cert for. +Using the websocket ping utility +-------------------------------- + +libwebsockets-test-ping connects as a client to a remote +websocket server using 04 protocol and pings it like the +normal unix ping utility. + +$ libwebsockets-test-ping localhost +handshake OK for protocol lws-mirror-protocol +Websocket PING localhost.localdomain (127.0.0.1) 64 bytes of data. +64 bytes from localhost: req=1 time=0.1ms +64 bytes from localhost: req=2 time=0.1ms +64 bytes from localhost: req=3 time=0.1ms +64 bytes from localhost: req=4 time=0.2ms +64 bytes from localhost: req=5 time=0.1ms +64 bytes from localhost: req=6 time=0.2ms +64 bytes from localhost: req=7 time=0.2ms +64 bytes from localhost: req=8 time=0.1ms +^C +--- localhost.localdomain websocket ping statistics --- +8 packets transmitted, 8 received, 0% packet loss, time 7458ms +rtt min/avg/max = 0.110/0.185/0.218 ms +$ + +By default it sends 64 byte payload packets using the 04 +PING packet opcode type. You can change the payload size +using the -s= flag, up to a maximum of 125 mandated by the +04 standard. + +Using the lws-mirror protocol that is provided by the test +server, libwebsockets-test-ping can also use larger payload +sizes up to 4096 is BINARY packets; lws-mirror will copy +them back to the client and they appear as a PONG. Use the +-m flag to select this operation. + +The default interval between pings is 1s, you can use the -i= +flag to set this, including fractions like -i=0.01 for 10ms +interval. + +Before you can even use the PING opcode that is part of the +standard, you must complete a handshake with a specified +protocol. By default lws-mirror-protocol is used which is +supported by the test server. But if you are using it on +another server, you can specify the protcol to handshake with +by --protocol=protocolname + + Websocket version supported --------------------------- diff --git a/configure b/configure index e301a985..c39b140f 100755 --- a/configure +++ b/configure @@ -616,6 +616,8 @@ ac_subst_vars='am__EXEEXT_FALSE am__EXEEXT_TRUE LTLIBOBJS LIBOBJS +NOPING_FALSE +NOPING_TRUE clientcertdir LIBCRYPTO_FALSE LIBCRYPTO_TRUE @@ -741,6 +743,7 @@ enable_openssl enable_nofork enable_libcrypto with_client_cert_dir +enable_noping ' ac_precious_vars='build_alias host_alias @@ -1386,6 +1389,7 @@ Optional Features: --enable-openssl Enables https support and needs openssl libs --enable-nofork Disables fork-related options --enable-libcrypto Use libcrypto MD5 and SHA1 implementations + --enable-noping Do not build ping test app, which has some unixy stuff in sources Optional Packages: --with-PACKAGE[=ARG] use PACKAGE [ARG=yes] @@ -12307,6 +12311,26 @@ fi +# +# +# +# Check whether --enable-noping was given. +if test "${enable_noping+set}" = set; then : + enableval=$enable_noping; noping=yes + +fi + + + if test x$noping = xyes; then + NOPING_TRUE= + NOPING_FALSE='#' +else + NOPING_TRUE='#' + NOPING_FALSE= +fi + + + # Checks for header files. for ac_header in fcntl.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h @@ -12621,6 +12645,10 @@ if test -z "${LIBCRYPTO_TRUE}" && test -z "${LIBCRYPTO_FALSE}"; then as_fn_error $? "conditional \"LIBCRYPTO\" was never defined. Usually this means the macro was only invoked conditionally." "$LINENO" 5 fi +if test -z "${NOPING_TRUE}" && test -z "${NOPING_FALSE}"; then + as_fn_error $? "conditional \"NOPING\" was never defined. +Usually this means the macro was only invoked conditionally." "$LINENO" 5 +fi : "${CONFIG_STATUS=./config.status}" ac_write_fail=0 diff --git a/configure.ac b/configure.ac index 251a49d8..c7d91b09 100644 --- a/configure.ac +++ b/configure.ac @@ -73,6 +73,17 @@ AC_SUBST([clientcertdir]) AC_SUBST([CFLAGS]) +# +# +# +AC_ARG_ENABLE(noping, + [ --enable-noping Do not build ping test app, which has some unixy stuff in sources], + [ noping=yes + ]) + +AM_CONDITIONAL(NOPING, test x$noping = xyes) + + # Checks for header files. AC_CHECK_HEADERS([fcntl.h netinet/in.h stdlib.h string.h sys/socket.h unistd.h]) diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 8d462095..e6db1571 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -514,6 +514,21 @@ libwebsocket_callback_on_writable_all_protocol( return 0; } + +/** + * libwebsocket_get_socket_fd() - returns the socket file descriptor + * + * You will not need this unless you are doing something special + * + * @wsi: Websocket connection instance + */ + +int +libwebsocket_get_socket_fd(struct libwebsocket *wsi) +{ + return wsi->sock; +} + /** * libwebsocket_rx_flow_control() - Enable and disable socket servicing for * receieved packets. diff --git a/lib/libwebsockets.h b/lib/libwebsockets.h index a4862217..ac28abf8 100644 --- a/lib/libwebsockets.h +++ b/lib/libwebsockets.h @@ -30,6 +30,7 @@ enum libwebsocket_callback_reasons { LWS_CALLBACK_CLOSED, LWS_CALLBACK_RECEIVE, LWS_CALLBACK_CLIENT_RECEIVE, + LWS_CALLBACK_CLIENT_RECEIVE_PONG, LWS_CALLBACK_CLIENT_WRITEABLE, LWS_CALLBACK_HTTP, LWS_CALLBACK_BROADCAST @@ -87,6 +88,10 @@ struct libwebsocket_context; * remote client, it can be found at *in and is * len bytes long * + * LWS_CALLBACK_CLIENT_RECEIVE_PONG: if you elected to see PONG packets, + * they appear with this callback reason. PONG + * packets only exist in 04+ protocol + * * LWS_CALLBACK_CLIENT_RECEIVE: data has appeared from the server for the * client connection, it can be found at *in and * is len bytes long @@ -225,6 +230,8 @@ extern int libwebsocket_callback_on_writable_all_protocol( const struct libwebsocket_protocols *protocol); +extern int +libwebsocket_get_socket_fd(struct libwebsocket *wsi); extern int libwebsocket_rx_flow_control(struct libwebsocket *wsi, int enable); diff --git a/lib/parsers.c b/lib/parsers.c index d023e9b6..ef683821 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -560,7 +560,9 @@ spill: n = libwebsocket_write(wsi, (unsigned char *) &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], wsi->rx_user_buffer_head, LWS_WRITE_PONG); - break; + /* ... then just drop it */ + wsi->rx_user_buffer_head = 0; + return 0; case LWS_WS_OPCODE_04__PONG: /* keep the statistics... */ @@ -599,6 +601,7 @@ int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c) { int n; unsigned char buf[20 + 4]; + int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; switch (wsi->lws_rx_parse_state) { case LWS_RXPS_NEW: @@ -875,9 +878,10 @@ spill: case LWS_WS_OPCODE_04__PONG: /* keep the statistics... */ wsi->pings_vs_pongs--; - /* ... then just drop it */ - wsi->rx_user_buffer_head = 0; - return 0; + + /* issue it */ + callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; + break; default: break; @@ -890,8 +894,7 @@ spill: */ if (wsi->protocol->callback) - wsi->protocol->callback(wsi, - LWS_CALLBACK_CLIENT_RECEIVE, + wsi->protocol->callback(wsi, callback_action, wsi->user_space, &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], wsi->rx_user_buffer_head); diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index dfeb373e..c3bce439 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -73,7 +73,7 @@ static inline void debug(const char *format, ...) #define LWS_MAX_HEADER_LEN 4096 #define LWS_INITIAL_HDR_ALLOC 256 #define LWS_ADDITIONAL_HDR_ALLOC 64 -#define MAX_USER_RX_BUFFER 512 +#define MAX_USER_RX_BUFFER 4096 #define MAX_BROADCAST_PAYLOAD 2048 #define LWS_MAX_PROTOCOLS 10 diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html index f649d795..d0703436 100644 --- a/libwebsockets-api-doc.html +++ b/libwebsockets-api-doc.html @@ -78,6 +78,21 @@ nothing is pending, or as soon as it services whatever was pending.
+++You will not need this unless you are doing something special +
+if you elected to see PONG packets, +they appear with this callback reason. PONG +packets only exist in 04+ protocol +
data has appeared from the server for the diff --git a/libwebsockets.spec b/libwebsockets.spec index 31a770f8..07215c7f 100644 --- a/libwebsockets.spec +++ b/libwebsockets.spec @@ -1,6 +1,6 @@ Name: libwebsockets Version: 0.1 -Release: 44.gmaster_f55830db%{?dist} +Release: 45.gmaster_f1d2113d%{?dist} Summary: Websocket Server Library Group: System @@ -50,6 +50,7 @@ rm -rf $RPM_BUILD_ROOT /%{_libdir}/libwebsockets.la %attr(755,root,root) /usr/share/libwebsockets-test-server %attr(755,root,root) /usr/share/libwebsockets-test-client +%attr(755,root,root) /usr/share/libwebsockets-test-ping %doc %files devel %defattr(-,root,root,-) diff --git a/test-server/Makefile.am b/test-server/Makefile.am index c1083a5c..c94d3132 100644 --- a/test-server/Makefile.am +++ b/test-server/Makefile.am @@ -5,10 +5,18 @@ libwebsockets_test_client_SOURCES=test-client.c libwebsockets_test_client_LDADD=-L../lib -lwebsockets - libwebsockets_test_server_CFLAGS:= -Wall -Werror -std=gnu99 -pedantic -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" libwebsockets_test_client_CFLAGS:= -Wall -Werror -std=gnu99 -pedantic -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" +if NOPING +else +bin_PROGRAMS+=libwebsockets-test-ping +libwebsockets_test_ping_SOURCES=test-ping.c +libwebsockets_test_ping_LDADD=-L../lib -lwebsockets +libwebsockets_test_ping_CFLAGS:= -Wall -Werror -std=gnu99 -pedantic -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" +endif + + # # cook a random test cert and key # notice your real cert and key will want to be 0600 permissions diff --git a/test-server/Makefile.in b/test-server/Makefile.in index c7555af2..d6f0da6b 100644 --- a/test-server/Makefile.in +++ b/test-server/Makefile.in @@ -35,7 +35,8 @@ POST_UNINSTALL = : build_triplet = @build@ host_triplet = @host@ bin_PROGRAMS = libwebsockets-test-server$(EXEEXT) \ - libwebsockets-test-client$(EXEEXT) + libwebsockets-test-client$(EXEEXT) $(am__EXEEXT_1) +@NOPING_FALSE@am__append_1 = libwebsockets-test-ping subdir = test-server DIST_COMMON = $(srcdir)/Makefile.am $(srcdir)/Makefile.in ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 @@ -46,6 +47,7 @@ mkinstalldirs = $(install_sh) -d CONFIG_HEADER = $(top_builddir)/config.h CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = +@NOPING_FALSE@am__EXEEXT_1 = libwebsockets-test-ping$(EXEEXT) am__installdirs = "$(DESTDIR)$(bindir)" PROGRAMS = $(bin_PROGRAMS) am_libwebsockets_test_client_OBJECTS = \ @@ -57,6 +59,16 @@ libwebsockets_test_client_LINK = $(LIBTOOL) --tag=CC \ $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=link $(CCLD) \ $(libwebsockets_test_client_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ +am__libwebsockets_test_ping_SOURCES_DIST = test-ping.c +@NOPING_FALSE@am_libwebsockets_test_ping_OBJECTS = \ +@NOPING_FALSE@ libwebsockets_test_ping-test-ping.$(OBJEXT) +libwebsockets_test_ping_OBJECTS = \ + $(am_libwebsockets_test_ping_OBJECTS) +libwebsockets_test_ping_DEPENDENCIES = +libwebsockets_test_ping_LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) \ + $(libwebsockets_test_ping_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ + $(LDFLAGS) -o $@ am_libwebsockets_test_server_OBJECTS = \ libwebsockets_test_server-test-server.$(OBJEXT) libwebsockets_test_server_OBJECTS = \ @@ -80,8 +92,10 @@ LINK = $(LIBTOOL) --tag=CC $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) \ --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) \ $(LDFLAGS) -o $@ SOURCES = $(libwebsockets_test_client_SOURCES) \ + $(libwebsockets_test_ping_SOURCES) \ $(libwebsockets_test_server_SOURCES) DIST_SOURCES = $(libwebsockets_test_client_SOURCES) \ + $(am__libwebsockets_test_ping_SOURCES_DIST) \ $(libwebsockets_test_server_SOURCES) ETAGS = etags CTAGS = ctags @@ -206,6 +220,9 @@ libwebsockets_test_client_SOURCES = test-client.c libwebsockets_test_client_LDADD = -L../lib -lwebsockets libwebsockets_test_server_CFLAGS := -Wall -Werror -std=gnu99 -pedantic -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" libwebsockets_test_client_CFLAGS := -Wall -Werror -std=gnu99 -pedantic -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" +@NOPING_FALSE@libwebsockets_test_ping_SOURCES = test-ping.c +@NOPING_FALSE@libwebsockets_test_ping_LDADD = -L../lib -lwebsockets +@NOPING_FALSE@libwebsockets_test_ping_CFLAGS := -Wall -Werror -std=gnu99 -pedantic -DDATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" all: all-am .SUFFIXES: @@ -286,6 +303,9 @@ clean-binPROGRAMS: libwebsockets-test-client$(EXEEXT): $(libwebsockets_test_client_OBJECTS) $(libwebsockets_test_client_DEPENDENCIES) @rm -f libwebsockets-test-client$(EXEEXT) $(libwebsockets_test_client_LINK) $(libwebsockets_test_client_OBJECTS) $(libwebsockets_test_client_LDADD) $(LIBS) +libwebsockets-test-ping$(EXEEXT): $(libwebsockets_test_ping_OBJECTS) $(libwebsockets_test_ping_DEPENDENCIES) + @rm -f libwebsockets-test-ping$(EXEEXT) + $(libwebsockets_test_ping_LINK) $(libwebsockets_test_ping_OBJECTS) $(libwebsockets_test_ping_LDADD) $(LIBS) libwebsockets-test-server$(EXEEXT): $(libwebsockets_test_server_OBJECTS) $(libwebsockets_test_server_DEPENDENCIES) @rm -f libwebsockets-test-server$(EXEEXT) $(libwebsockets_test_server_LINK) $(libwebsockets_test_server_OBJECTS) $(libwebsockets_test_server_LDADD) $(LIBS) @@ -297,6 +317,7 @@ distclean-compile: -rm -f *.tab.c @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_test_client-test-client.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_test_ping-test-ping.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libwebsockets_test_server-test-server.Po@am__quote@ .c.o: @@ -334,6 +355,20 @@ libwebsockets_test_client-test-client.obj: test-client.c @AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ @am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_test_client_CFLAGS) $(CFLAGS) -c -o libwebsockets_test_client-test-client.obj `if test -f 'test-client.c'; then $(CYGPATH_W) 'test-client.c'; else $(CYGPATH_W) '$(srcdir)/test-client.c'; fi` +libwebsockets_test_ping-test-ping.o: test-ping.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_test_ping_CFLAGS) $(CFLAGS) -MT libwebsockets_test_ping-test-ping.o -MD -MP -MF $(DEPDIR)/libwebsockets_test_ping-test-ping.Tpo -c -o libwebsockets_test_ping-test-ping.o `test -f 'test-ping.c' || echo '$(srcdir)/'`test-ping.c +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libwebsockets_test_ping-test-ping.Tpo $(DEPDIR)/libwebsockets_test_ping-test-ping.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-ping.c' object='libwebsockets_test_ping-test-ping.o' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_test_ping_CFLAGS) $(CFLAGS) -c -o libwebsockets_test_ping-test-ping.o `test -f 'test-ping.c' || echo '$(srcdir)/'`test-ping.c + +libwebsockets_test_ping-test-ping.obj: test-ping.c +@am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_test_ping_CFLAGS) $(CFLAGS) -MT libwebsockets_test_ping-test-ping.obj -MD -MP -MF $(DEPDIR)/libwebsockets_test_ping-test-ping.Tpo -c -o libwebsockets_test_ping-test-ping.obj `if test -f 'test-ping.c'; then $(CYGPATH_W) 'test-ping.c'; else $(CYGPATH_W) '$(srcdir)/test-ping.c'; fi` +@am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libwebsockets_test_ping-test-ping.Tpo $(DEPDIR)/libwebsockets_test_ping-test-ping.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ source='test-ping.c' object='libwebsockets_test_ping-test-ping.obj' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_test_ping_CFLAGS) $(CFLAGS) -c -o libwebsockets_test_ping-test-ping.obj `if test -f 'test-ping.c'; then $(CYGPATH_W) 'test-ping.c'; else $(CYGPATH_W) '$(srcdir)/test-ping.c'; fi` + libwebsockets_test_server-test-server.o: test-server.c @am__fastdepCC_TRUE@ $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) $(libwebsockets_test_server_CFLAGS) $(CFLAGS) -MT libwebsockets_test_server-test-server.o -MD -MP -MF $(DEPDIR)/libwebsockets_test_server-test-server.Tpo -c -o libwebsockets_test_server-test-server.o `test -f 'test-server.c' || echo '$(srcdir)/'`test-server.c @am__fastdepCC_TRUE@ $(am__mv) $(DEPDIR)/libwebsockets_test_server-test-server.Tpo $(DEPDIR)/libwebsockets_test_server-test-server.Po diff --git a/test-server/test-ping.c b/test-server/test-ping.c new file mode 100644 index 00000000..69119238 --- /dev/null +++ b/test-server/test-ping.c @@ -0,0 +1,462 @@ +/* + * libwebsockets-test-ping - libwebsockets floodping + * + * Copyright (C) 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 +#include +#include + +#include + +#include "../lib/libwebsockets.h" +#include + +/* + * this is specified in the 04 standard, control frames can only have small + * payload length styles + */ +#define MAX_PING_PAYLOAD 125 +#define MAX_MIRROR_PAYLOAD 4096 + +static unsigned int interval_us = 1000000; +static unsigned int size = 64; +static int flood = 0; +static const char *address; +static unsigned char pingbuf[LWS_SEND_BUFFER_PRE_PADDING + MAX_MIRROR_PAYLOAD + + LWS_SEND_BUFFER_POST_PADDING]; +static unsigned long oldus = 0; +static unsigned long ping_index = 1; +static char *hname = "(unknown)"; +static unsigned long rx_count = 0; +static unsigned long started; + +static unsigned long rtt_min = 100000000; +static unsigned long rtt_max = 0; +static unsigned long rtt_avg = 0; +static int screen_width = 80; +static int use_mirror = 0; + +struct ping { + unsigned long issue_timestamp; + unsigned long index; + unsigned int seen; +}; + +#define PING_RINGBUFFER_SIZE 256 + +struct ping ringbuffer[PING_RINGBUFFER_SIZE]; +int ringbuffer_head; +int ringbuffer_tail; + +/* + * uses the ping pong protocol features to provide an equivalent for the + * ping utility for 04+ websockets + */ + +enum demo_protocols { + + PROTOCOL_LWS_MIRROR, + + /* always last */ + DEMO_PROTOCOL_COUNT +}; + + +static int +callback_lws_mirror(struct libwebsocket *wsi, + enum libwebsocket_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct timeval tv; + unsigned char *p; + int shift; + unsigned long l; + unsigned long iv; + int n; + int match = 0; + + switch (reason) { + case LWS_CALLBACK_CLIENT_ESTABLISHED: + + /* + * start the ball rolling, + * LWS_CALLBACK_CLIENT_WRITEABLE will come next service + */ + + libwebsocket_callback_on_writable(wsi); + break; + + case LWS_CALLBACK_CLIENT_RECEIVE: + case LWS_CALLBACK_CLIENT_RECEIVE_PONG: + gettimeofday(&tv, NULL); + iv = (tv.tv_sec * 1000000) + tv.tv_usec; + + rx_count++; + + shift = 56; + p = in; + l = 0; + + while (shift >= 0) { + l |= (*p++) << shift; + shift -= 8; + } + + /* find it in the ringbuffer, look backwards from head */ + n = ringbuffer_head; + while (!match) { + + if (ringbuffer[n].index == l) { + ringbuffer[n].seen++; + match = 1; + continue; + } + + if (n == ringbuffer_tail) { + match = -1; + continue; + } + + if (n == 0) + n = PING_RINGBUFFER_SIZE - 1; + else + n--; + } + + if (match < 1) { + + if (!flood) + fprintf(stderr, "%d bytes from %s: req=%ld " + "time=(unknown)\n", (int)len, address, l); + else + fprintf(stderr, "\b \b"); + + break; + } + + if (ringbuffer[n].seen > 1) + fprintf(stderr, "DUP! "); + + if ((iv - ringbuffer[n].issue_timestamp) < rtt_min) + rtt_min = iv - ringbuffer[n].issue_timestamp; + + if ((iv - ringbuffer[n].issue_timestamp) > rtt_max) + rtt_max = iv - ringbuffer[n].issue_timestamp; + + rtt_avg += iv - ringbuffer[n].issue_timestamp; + + + if (!flood) + fprintf(stderr, "%d bytes from %s: req=%ld " + "time=%lu.%lums\n", (int)len, address, l, + (iv - ringbuffer[n].issue_timestamp) / 1000, + ((iv - ringbuffer[n].issue_timestamp) / 100) % 10 + ); + else + fprintf(stderr, "\b \b"); + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + + shift = 56; + p = &pingbuf[LWS_SEND_BUFFER_PRE_PADDING]; + + while (shift >= 0) { + *p++ = ping_index >> shift; + shift -= 8; + } + + gettimeofday(&tv, NULL); + + ringbuffer[ringbuffer_head].issue_timestamp = + (tv.tv_sec * 1000000) + tv.tv_usec; + ringbuffer[ringbuffer_head].index = ping_index++; + ringbuffer[ringbuffer_head].seen = 0; + + if (ringbuffer_head == PING_RINGBUFFER_SIZE - 1) + ringbuffer_head = 0; + else + ringbuffer_head++; + + /* snip any re-used tail so we keep the whole buffer length */ + + if (ringbuffer_tail == ringbuffer_head) { + if (ringbuffer_tail == PING_RINGBUFFER_SIZE - 1) + ringbuffer_tail = 0; + else + ringbuffer_tail++; + } + + if (use_mirror) + libwebsocket_write(wsi, + &pingbuf[LWS_SEND_BUFFER_PRE_PADDING], + size, LWS_WRITE_BINARY); + else + libwebsocket_write(wsi, + &pingbuf[LWS_SEND_BUFFER_PRE_PADDING], + size, LWS_WRITE_PING); + + if (flood && (ping_index - rx_count) < (screen_width - 1)) + fprintf(stderr, "."); + break; + + default: + break; + } + + return 0; +} + + +/* list of supported protocols and callbacks */ + +static struct libwebsocket_protocols protocols[] = { + + [PROTOCOL_LWS_MIRROR] = { + .name = "lws-mirror-protocol", + .callback = callback_lws_mirror, + }, + [DEMO_PROTOCOL_COUNT] = { /* end of list */ + .callback = NULL + } +}; + +static struct option options[] = { + { "help", no_argument, NULL, 'h' }, + { "port", required_argument, NULL, 'p' }, + { "ssl", no_argument, NULL, 't' }, + { "interval", required_argument, NULL, 'i' }, + { "size", required_argument, NULL, 's' }, + { "protocol", required_argument, NULL, 'n' }, + { "flood", no_argument, NULL, 'f' }, + { "mirror", no_argument, NULL, 'm' }, + { NULL, 0, 0, 0 } +}; + + +static void +signal_handler(int sig, siginfo_t *si, void *v) +{ + struct timeval tv; + unsigned long l; + + gettimeofday(&tv, NULL); + l = (tv.tv_sec * 1000000) + tv.tv_usec; + + fprintf(stderr, "\n--- %s websocket ping statistics ---\n" + "%lu packets transmitted, %lu received, %lu%% packet loss, time %ldms\n" + "rtt min/avg/max = %0.3f/%0.3f/%0.3f ms\n", + hname, ping_index - 1, rx_count, + (((ping_index - 1) - rx_count) * 100) / (ping_index - 1), + (l - started) / 1000, + ((double)rtt_min) / 1000.0, + ((double)rtt_avg / rx_count) / 1000.0, + ((double)rtt_max) / 1000.0 + ); + + exit(0); +} + + +int main(int argc, char **argv) +{ + int n = 0; + int port = 7681; + int use_ssl = 0; + struct libwebsocket_context *context; + struct libwebsocket *wsi_mirror; + char protocol_name[256]; + unsigned int len; + struct sockaddr_in sin; + struct hostent *host; + struct hostent *host1; + char ip[30]; + char *p; + struct sigaction sa; + struct timeval tv; + struct winsize w; + + if (argc < 2) + goto usage; + + address = argv[1]; + optind++; + + while (n >= 0) { + n = getopt_long(argc, argv, "hmfts:n:i:p:", options, NULL); + if (n < 0) + continue; + switch (n) { + case 'm': + use_mirror = 1; + break; + case 't': + use_ssl = 2; /* 2 = allow selfsigned */ + break; + case 'p': + port = atoi(optarg); + break; + case 'n': + strncpy(protocol_name, optarg, sizeof protocol_name); + protocol_name[(sizeof protocol_name) -1] = '\0'; + protocols[PROTOCOL_LWS_MIRROR].name = protocol_name; + break; + case 'i': + interval_us = 1000000.0 * atof(optarg); + break; + case 's': + size = atoi(optarg); + break; + case 'f': + flood = 1; + break; + case 'h': + goto usage; + } + } + + if (!use_mirror) { + if (size > MAX_PING_PAYLOAD) { + fprintf(stderr, "Max ping opcode payload size %d\n", + MAX_PING_PAYLOAD); + return 1; + } + } else { + if (size > MAX_MIRROR_PAYLOAD) { + fprintf(stderr, "Max mirror payload size %d\n", + MAX_MIRROR_PAYLOAD); + return 1; + } + } + + + if (isatty(STDOUT_FILENO)) + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &w) != -1) + if (w.ws_col > 0) + screen_width = w.ws_col; + + context = libwebsocket_create_context(CONTEXT_PORT_NO_LISTEN, + protocols, NULL, NULL, -1, -1); + if (context == NULL) { + fprintf(stderr, "Creating libwebsocket context failed\n"); + return 1; + } + + /* create a client websocket using dumb increment protocol */ + + wsi_mirror = libwebsocket_client_connect(context, address, port, use_ssl, + "/", "http://host", "origin", + protocols[PROTOCOL_LWS_MIRROR].name); + + if (wsi_mirror == NULL) { + fprintf(stderr, "libwebsocket connect failed\n"); + return -1; + } + + strcpy(ip, "(unknown)"); + len = sizeof sin; + if (getpeername(libwebsocket_get_socket_fd(wsi_mirror), + (struct sockaddr *) &sin, &len) < 0) + perror("getpeername"); + else { + host = gethostbyaddr((char *) &sin.sin_addr, + sizeof sin.sin_addr, + AF_INET); + if (host == NULL) + perror("gethostbyaddr"); + else { + hname = host->h_name; + + host1 = gethostbyname(hname); + if (host1 != NULL) { + p = (char *)host1; + n = 0; + while (p != NULL) { + p = host1->h_addr_list[n++]; + if (p == NULL) + continue; + if (host1->h_addrtype != AF_INET) + continue; + + sprintf(ip, "%d.%d.%d.%d", + p[0], p[1], p[2], p[3]); + p = NULL; + } + } + } + } + + fprintf(stderr, "Websocket PING %s (%s) %d bytes of data.\n", + hname, ip, size); + + /* set the ^C handler */ + + sa.sa_sigaction = signal_handler; + sa.sa_flags = SA_SIGINFO; + sigemptyset(&sa.sa_mask); + sigaction(SIGINT, &sa, NULL); + + gettimeofday(&tv, NULL); + started = (tv.tv_sec * 1000000) + tv.tv_usec; + + /* service loop */ + + n = 0; + while (n >= 0) { + unsigned long l; + + gettimeofday(&tv, NULL); + + l = (tv.tv_sec * 1000000) + tv.tv_usec; + if ((l - oldus) > interval_us) { + libwebsocket_callback_on_writable(wsi_mirror); + oldus = l; + } + + if (!interval_us) + n = libwebsocket_service(context, 0); + else + n = libwebsocket_service(context, 1); + } + + libwebsocket_client_close(wsi_mirror); + libwebsocket_context_destroy(context); + + return 0; + +usage: + fprintf(stderr, "Usage: libwebsockets-test-ping " + " [--port= ] " + "[--ssl] [--interval=
] " + "[--size= ] " + "[--protocol= ] " + "[--mirror] " + "\n"); + return 1; +}