1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163
|
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*- */
/*
* Copyright (C) 2024 Axis Communications AB, SWEDEN.
*/
/*
To use this WebSocket test helper with valgrind, follow these steps.
1. Start a ws server in a separate terminal
docker run -it --rm \
-p 9001:9001 \
crossbario/autobahn-testsuite \
wstest --mode echoserver --wsuri=ws://127.0.0.1:9001
2. Build this helper
meson setup _build
meson compile -C _build tests/ws-test-helper
3. Run this helper with valgrind in yet another terminal
G_MESSAGES_DEBUG=all valgrind --leak-check=full --suppressions=tests/libsoup.supp _build/tests/ws-test-helper
4. In a third terminal, drop packets to and from the ws server:
# Change ACTION to "--delete" and re-run to undo the effects of this
ACTION=--append ; IP=127.0.0.1 ; PORT=9001
sudo iptables \
--table filter \
$ACTION INPUT \
--protocol tcp \
--destination $IP \
--destination-port $PORT \
--jump DROP
sudo iptables \
--table filter \
$ACTION OUTPUT \
--protocol tcp \
--source $IP \
--source-port $PORT \
--jump DROP
5. After waiting a few seconds you will see output similar to this:
==867041== Using Valgrind-3.19.0 and LibVEX; rerun with -h for copyright info
** Message: 15:56:37.721: Connecting to ws://127.0.0.1:9001
** Message: 15:56:38.415: Connected
(process:867041): libsoup-DEBUG: 15:56:41.308: ping libsoup-keepalive-1
(process:867041): libsoup-DEBUG: 15:56:41.318: received keepalive pong
...
(process:867041): libsoup-DEBUG: 15:58:23.306: ping libsoup-keepalive-35
(process:867041): libsoup-DEBUG: 15:58:23.307: expected pong never arrived; connection probably lost
** Message: 15:58:23.311: Error: Did not receive keepalive pong within 3 seconds
(process:867041): libsoup-DEBUG: 15:58:23.312: requesting close due to error
** Message: 15:58:28.319: Closed
==867041== LEAK SUMMARY:
==867041== definitely lost: 0 bytes in 0 blocks
==867041== indirectly lost: 0 bytes in 0 blocks
==867041== possibly lost: 0 bytes in 0 blocks
==867041== ERROR SUMMARY: 0 errors from 0 contexts (suppressed: 3 from 3)
*/
#include <glib-unix.h>
#include <libsoup/soup.h>
typedef struct {
SoupWebsocketConnection *connection;
gboolean closed;
} AppState;
static gboolean
on_sigint (AppState *app_state)
{
soup_websocket_connection_close (app_state->connection, SOUP_WEBSOCKET_CLOSE_NORMAL, NULL);
return G_SOURCE_CONTINUE;
}
static void
on_error (SoupWebsocketConnection *connection,
GError *err,
AppState *app_state)
{
g_message ("Error: %s", err->message);
}
static void
on_closed (SoupWebsocketConnection *connection,
AppState *app_state)
{
app_state->closed = TRUE;
g_message ("Closed");
}
static void
on_connect (GObject *session,
GAsyncResult *res,
AppState *app_state)
{
GError *error = NULL;
app_state->connection = soup_session_websocket_connect_finish (SOUP_SESSION (session), res, &error);
if (!app_state->connection) {
g_message ("Connection failed: %s", error->message);
g_error_free (error);
app_state->closed = TRUE;
return;
}
g_message ("Connected");
g_signal_connect (app_state->connection, "error", G_CALLBACK (on_error), app_state);
g_signal_connect (app_state->connection, "closed", G_CALLBACK (on_closed), app_state);
soup_websocket_connection_set_keepalive_interval (app_state->connection, 3);
soup_websocket_connection_set_keepalive_pong_timeout (app_state->connection, 3);
}
int
main (int argc, char *argv[])
{
AppState *app_state = g_new0 (AppState, 1);
// Connect
char *uri = "ws://127.0.0.1:9001";
SoupSession *session = soup_session_new ();
soup_session_add_feature_by_type (session, SOUP_TYPE_WEBSOCKET_EXTENSION_MANAGER);
SoupMessage *message = soup_message_new (SOUP_METHOD_GET, uri);
g_message ("Connecting to %s", uri);
soup_session_websocket_connect_async (
session,
message,
NULL, NULL,
G_PRIORITY_DEFAULT, NULL,
(GAsyncReadyCallback)on_connect, app_state);
// Setup a SIGINT handler for clean and valgrind friendly shutdown.
GMainContext *async_context = g_main_context_ref_thread_default ();
GSource *sigint_source = g_unix_signal_source_new (SIGINT);
g_source_set_callback (sigint_source, (GSourceFunc)on_sigint, app_state, NULL);
g_source_attach (sigint_source, async_context);
// Run the main loop.
while (!app_state->closed) {
g_main_context_iteration (async_context, TRUE);
}
// Properly free stuff so valgrind can give relevant leak reports.
g_source_destroy (sigint_source);
g_source_unref (sigint_source);
g_main_context_unref (async_context);
if (app_state->connection) {
g_signal_handlers_disconnect_by_func (app_state->connection, on_closed, app_state);
g_signal_handlers_disconnect_by_func (app_state->connection, on_error, app_state);
g_object_unref (app_state->connection);
}
g_object_unref (message);
g_object_unref (session);
g_free (app_state);
return 0;
}
|