1
0
Fork 0
mirror of https://gitlab.gnome.org/GNOME/calls.git synced 2024-11-15 04:45:36 +00:00
Purism-Calls/tests/test-sip.c
Evangelos Ribeiro Tzaras 8b126484cb sip: Use per origin IP instead of a global IP
Sofia detects a NAT by presence of the "received" parameter in the Via header in
the response to a REGISTER. Sofia will then update the Contact header to use the
IP as reported by the registrar.

The "received" parameter MUST be included in the response according to
https://datatracker.ietf.org/doc/html/rfc3261#section-18.2.1
when the registrar detects a difference between the domain part of the top Via
header and the packet source address but practice has shown that this will not
always be the case.

Addditionally this change allows us to have origins bound to different network
interfaces which would be useful when a registrar can only be accessed through a
VPN.

This also fixes an issue with SDP introduced in
36880c3d34 which was only seen on some SIP
providers:

The session name ("s=") line is not relevant for establishing a connection,
the connection data (c=") line is.

See https://datatracker.ietf.org/doc/html/rfc4566 section 5.3 and 5.7
2022-01-08 21:25:09 +00:00

580 lines
18 KiB
C

/*
* Copyright (C) 2021 Purism SPC
*
* SPDX-License-Identifier: GPL-3.0+
*
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
*/
#include "calls-provider.h"
#include "calls-sip-media-manager.h"
#include "calls-sip-provider.h"
#include "calls-sip-origin.h"
#include "calls-sip-util.h"
#include "calls-account.h"
#include "gst-rfc3551.h"
#include <gtk/gtk.h>
#include <sofia-sip/su_uniqueid.h>
#include <libpeas/peas.h>
typedef struct {
CallsSipProvider *provider;
CallsSipOrigin *origin_alice;
CallsSipOrigin *origin_bob;
CallsSipOrigin *origin_offline;
} SipFixture;
static gboolean is_call_test_done = FALSE;
static gboolean are_call_tests_done = FALSE;
static void
test_sip_provider_object (SipFixture *fixture,
gconstpointer user_data)
{
SipEngineState state;
g_assert_true (G_IS_OBJECT (fixture->provider));
g_assert_true (CALLS_IS_MESSAGE_SOURCE (fixture->provider));
g_assert_true (CALLS_IS_PROVIDER (fixture->provider));
g_assert_true (CALLS_IS_SIP_PROVIDER (fixture->provider));
g_object_get (fixture->provider,
"sip-state", &state,
NULL);
g_assert_cmpint (state, ==, SIP_ENGINE_READY);
}
static void
test_sip_provider_origins (SipFixture *fixture,
gconstpointer user_data)
{
GListModel *origins;
origins = calls_provider_get_origins (CALLS_PROVIDER (fixture->provider));
g_assert_cmpuint (g_list_model_get_n_items (origins), ==, 0);
}
static void
setup_sip_provider (SipFixture *fixture,
gconstpointer user_data)
{
CallsProvider *provider = calls_provider_load_plugin ("sip");
fixture->provider = CALLS_SIP_PROVIDER (provider);
is_call_test_done = FALSE;
are_call_tests_done = FALSE;
}
static void
tear_down_sip_provider (SipFixture *fixture,
gconstpointer user_data)
{
g_clear_object (&fixture->provider);
calls_provider_unload_plugin ("sip");
}
static void
test_sip_origin_objects (SipFixture *fixture,
gconstpointer user_data)
{
CallsAccountState state_alice, state_bob, state_offline;
g_assert_true (G_IS_OBJECT (fixture->origin_alice));
g_assert_true (G_IS_OBJECT (fixture->origin_bob));
g_assert_true (G_IS_OBJECT (fixture->origin_offline));
g_assert_true (CALLS_IS_MESSAGE_SOURCE (fixture->origin_alice));
g_assert_true (CALLS_IS_MESSAGE_SOURCE (fixture->origin_bob));
g_assert_true (CALLS_IS_MESSAGE_SOURCE (fixture->origin_offline));
g_assert_true (CALLS_IS_ORIGIN (fixture->origin_alice));
g_assert_true (CALLS_IS_ORIGIN (fixture->origin_bob));
g_assert_true (CALLS_IS_ORIGIN (fixture->origin_offline));
g_assert_true (CALLS_IS_SIP_ORIGIN (fixture->origin_alice));
g_assert_true (CALLS_IS_SIP_ORIGIN (fixture->origin_bob));
g_assert_true (CALLS_IS_SIP_ORIGIN (fixture->origin_offline));
g_object_get (fixture->origin_alice,
"account-state", &state_alice,
NULL);
g_object_get (fixture->origin_bob,
"account-state", &state_bob,
NULL);
g_object_get (fixture->origin_offline,
"account-state", &state_offline,
NULL);
g_assert_cmpint (state_alice, ==, CALLS_ACCOUNT_ONLINE);
g_assert_cmpint (state_bob, ==, CALLS_ACCOUNT_ONLINE);
g_assert_cmpint (state_offline, ==, CALLS_ACCOUNT_OFFLINE);
}
static void
test_sip_origin_call_lists (SipFixture *fixture,
gconstpointer user_data)
{
g_autoptr (GList) calls_alice = NULL;
g_autoptr (GList) calls_bob = NULL;
g_autoptr (GList) calls_offline = NULL;
calls_alice = calls_origin_get_calls (CALLS_ORIGIN (fixture->origin_alice));
g_assert_null (calls_alice);
calls_bob = calls_origin_get_calls (CALLS_ORIGIN (fixture->origin_bob));
g_assert_null (calls_bob);
calls_offline = calls_origin_get_calls (CALLS_ORIGIN (fixture->origin_offline));
g_assert_null (calls_offline);
}
static gboolean
on_call_hangup_cb (gpointer user_data)
{
CallsCall *call = CALLS_CALL (user_data);
g_debug ("Hanging up call");
calls_call_hang_up (call);
return G_SOURCE_REMOVE;
}
static gboolean
on_call_answer_cb (gpointer user_data)
{
CallsCall *call = CALLS_CALL (user_data);
g_debug ("Answering incoming call");
calls_call_answer (call);
return G_SOURCE_REMOVE;
}
static void
on_autoreject_state_changed_cb (CallsCall *call,
CallsCallState new_state,
CallsCallState old_state,
gpointer user_data)
{
g_assert_cmpint (old_state, ==, CALLS_CALL_STATE_INCOMING);
g_assert_cmpint (new_state, ==, CALLS_CALL_STATE_DISCONNECTED);
g_object_unref (call);
is_call_test_done = TRUE;
}
static void
on_state_changed_cb (CallsCall *call,
CallsCallState new_state,
CallsCallState old_state,
gpointer user_data)
{
gboolean schedule_hangup = GPOINTER_TO_INT (user_data);
switch (old_state) {
case CALLS_CALL_STATE_INCOMING:
case CALLS_CALL_STATE_DIALING:
g_assert_cmpint (new_state, ==, CALLS_CALL_STATE_ACTIVE);
if (schedule_hangup)
g_idle_add ((GSourceFunc) on_call_hangup_cb, call);
break;
case CALLS_CALL_STATE_ACTIVE:
g_assert_cmpint (new_state, ==, CALLS_CALL_STATE_DISCONNECTED);
g_object_unref (call);
if (is_call_test_done)
are_call_tests_done = TRUE;
is_call_test_done = TRUE;
break;
default:
g_assert_not_reached ();
}
}
static gboolean
on_incoming_call_autoaccept_cb (CallsOrigin *origin,
CallsCall *call,
gpointer user_data)
{
CallsCallState state = calls_call_get_state (call);
g_assert_cmpint (state, ==, CALLS_CALL_STATE_INCOMING);
g_object_ref (call);
g_idle_add ((GSourceFunc) on_call_answer_cb, call);
g_signal_connect (call, "state-changed",
(GCallback) on_state_changed_cb, user_data);
return G_SOURCE_REMOVE;
}
static gboolean
on_incoming_call_autoreject_cb (CallsOrigin *origin,
CallsCall *call,
gpointer user_data)
{
CallsCallState state = calls_call_get_state (call);
g_assert_cmpint (state, ==, CALLS_CALL_STATE_INCOMING);
g_object_ref (call);
g_idle_add ((GSourceFunc) on_call_hangup_cb, call);
g_signal_connect (call, "state-changed",
(GCallback) on_autoreject_state_changed_cb, NULL);
return G_SOURCE_REMOVE;
}
static gboolean
on_outgoing_call_cb (CallsOrigin *origin,
CallsCall *call,
gpointer user_data)
{
CallsCallState state = calls_call_get_state (call);
g_assert_cmpint (state, ==, CALLS_CALL_STATE_DIALING);
g_object_ref (call);
g_signal_connect (call, "state-changed",
(GCallback) on_state_changed_cb, user_data);
return G_SOURCE_REMOVE;
}
static void
test_sip_call_direct_calls (SipFixture *fixture,
gconstpointer user_data)
{
gint local_port_alice, local_port_bob;
g_autofree gchar *address_alice = NULL;
g_autofree gchar *address_bob = NULL;
gulong handler_alice, handler_bob;
g_object_get (fixture->origin_alice,
"local-port", &local_port_alice,
NULL);
address_alice = g_strdup_printf ("sip:alice@127.0.0.1:%d",
local_port_alice);
g_object_get (fixture->origin_bob,
"local-port", &local_port_bob,
NULL);
address_bob = g_strdup_printf ("sip:bob@127.0.0.1:%d",
local_port_bob);
/* Case 1: Bob calls Alice, Alice rejects call */
g_debug ("Call test: Stage 1");
handler_alice =
g_signal_connect (fixture->origin_alice,
"call-added",
G_CALLBACK (on_incoming_call_autoreject_cb),
NULL);
calls_origin_dial (CALLS_ORIGIN (fixture->origin_bob), address_alice);
while (!is_call_test_done)
g_main_context_iteration (NULL, TRUE);
is_call_test_done = FALSE;
are_call_tests_done = FALSE;
g_signal_handler_disconnect (fixture->origin_alice, handler_alice);
/* Case 2: Alice calls Bob, Bob accepts and hangs up shortly after */
g_debug ("Call test: Stage 2");
handler_alice =
g_signal_connect (fixture->origin_alice,
"call-added",
G_CALLBACK (on_outgoing_call_cb),
GINT_TO_POINTER (FALSE));
handler_bob =
g_signal_connect (fixture->origin_bob,
"call-added",
G_CALLBACK (on_incoming_call_autoaccept_cb),
GINT_TO_POINTER (TRUE));
calls_origin_dial (CALLS_ORIGIN (fixture->origin_alice), address_bob);
while (!are_call_tests_done)
g_main_context_iteration (NULL, TRUE);
is_call_test_done = FALSE;
are_call_tests_done = FALSE;
g_signal_handler_disconnect (fixture->origin_alice, handler_alice);
g_signal_handler_disconnect (fixture->origin_bob, handler_bob);
/* Case 3: Alice calls Bob, Bob accepts and Alice hangs up shortly after */
g_debug ("Call test: Stage 3");
handler_alice =
g_signal_connect (fixture->origin_alice,
"call-added",
G_CALLBACK (on_outgoing_call_cb),
GINT_TO_POINTER (TRUE));
handler_bob =
g_signal_connect (fixture->origin_bob,
"call-added",
G_CALLBACK (on_incoming_call_autoaccept_cb),
GINT_TO_POINTER (FALSE));
calls_origin_dial (CALLS_ORIGIN (fixture->origin_alice), address_bob);
while (!are_call_tests_done)
g_main_context_iteration (NULL, TRUE);
is_call_test_done = FALSE;
are_call_tests_done = FALSE;
g_signal_handler_disconnect (fixture->origin_alice, handler_alice);
g_signal_handler_disconnect (fixture->origin_bob, handler_bob);
}
static void
setup_sip_origins (SipFixture *fixture,
gconstpointer user_data)
{
setup_sip_provider (fixture, user_data);
fixture->origin_alice =
calls_sip_provider_add_origin_full (fixture->provider,
NULL,
"alice",
NULL,
NULL,
NULL,
0,
FALSE,
TRUE,
5060,
FALSE,
FALSE);
fixture->origin_bob =
calls_sip_provider_add_origin_full (fixture->provider,
NULL,
"bob",
NULL,
NULL,
NULL,
0,
FALSE,
TRUE,
5061,
FALSE,
FALSE);
fixture->origin_offline =
calls_sip_provider_add_origin_full (fixture->provider,
"sip.imaginary-host.org",
"username",
"password",
NULL,
"UDP",
0,
FALSE,
FALSE,
0,
FALSE,
FALSE);
}
static void
tear_down_sip_origins (SipFixture *fixture,
gconstpointer user_data)
{
tear_down_sip_provider (fixture, user_data);
}
static gboolean
find_string_in_sdp_message (const char *sdp,
const char *string)
{
char **split_string = NULL;
gboolean found = FALSE;
split_string = g_strsplit (sdp, "\r\n", -1);
for (guint i = 0; split_string[i] != NULL; i++) {
if (g_strcmp0 (split_string[i], string) == 0) {
found = TRUE;
break;
}
}
g_strfreev (split_string);
return found;
}
static void
test_sip_media_manager (void)
{
g_autoptr (CallsSipMediaManager) manager = calls_sip_media_manager_default ();
char *sdp_message = NULL;
GList *codecs = NULL;
/* Check single codecs */
codecs = g_list_append (NULL, media_codec_by_name ("PCMA"));
g_debug ("Testing generated SDP messages");
/* PCMA RTP */
sdp_message =
calls_sip_media_manager_get_capabilities (manager, NULL, 40002, FALSE, codecs);
g_assert_true (sdp_message);
g_assert_true (find_string_in_sdp_message (sdp_message,
"m=audio 40002 RTP/AVP 8"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtpmap:8 PCMA/8000"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtcp:40003"));
g_free (sdp_message);
g_debug ("PCMA RTP test OK");
/* PCMA SRTP */
sdp_message =
calls_sip_media_manager_get_capabilities (manager, NULL, 42002, TRUE, codecs);
g_assert_true (sdp_message);
g_assert_true (find_string_in_sdp_message (sdp_message,
"m=audio 42002 RTP/SAVP 8"));
g_clear_pointer (&codecs, g_list_free);
g_free (sdp_message);
g_debug ("PCMA SRTP test OK");
/* G722 RTP */
codecs = g_list_append (NULL, media_codec_by_name ("G722"));
sdp_message =
calls_sip_media_manager_get_capabilities (manager, NULL, 42042, FALSE, codecs);
g_assert_true (sdp_message);
g_assert_true (find_string_in_sdp_message (sdp_message,
"m=audio 42042 RTP/AVP 9"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtpmap:9 G722/8000"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtcp:42043"));
g_clear_pointer (&codecs, g_list_free);
g_free (sdp_message);
g_debug ("G722 RTP test OK");
/* G722 PCMU PCMA RTP (in this order) */
codecs = g_list_append (NULL, media_codec_by_name ("G722"));
codecs = g_list_append (codecs, media_codec_by_name ("PCMU"));
codecs = g_list_append (codecs, media_codec_by_name ("PCMA"));
sdp_message =
calls_sip_media_manager_get_capabilities (manager, NULL, 33340, FALSE, codecs);
g_assert_true (sdp_message);
g_assert_true (find_string_in_sdp_message (sdp_message,
"m=audio 33340 RTP/AVP 9 0 8"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtpmap:9 G722/8000"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtpmap:0 PCMU/8000"));
g_assert_true (find_string_in_sdp_message (sdp_message,
"a=rtpmap:8 PCMA/8000"));
g_clear_pointer (&codecs, g_list_free);
g_free (sdp_message);
g_debug ("multiple codecs RTP test OK");
/* GSM PCMA G722 PCMU SRTP (in this order) */
codecs = g_list_append (NULL, media_codec_by_name ("GSM"));
codecs = g_list_append (codecs, media_codec_by_name ("PCMA"));
codecs = g_list_append (codecs, media_codec_by_name ("G722"));
codecs = g_list_append (codecs, media_codec_by_name ("PCMU"));
sdp_message =
calls_sip_media_manager_get_capabilities (manager, NULL, 18098, TRUE, codecs);
g_assert_true (sdp_message);
g_assert_true (find_string_in_sdp_message (sdp_message,
"m=audio 18098 RTP/SAVP 3 8 9 0"));
g_clear_pointer (&codecs, g_list_free);
g_free (sdp_message);
g_debug ("multiple codecs SRTP test OK");
/* no codecs */
g_test_expect_message ("CallsSipMediaManager", G_LOG_LEVEL_WARNING,
"No supported codecs found. Can't build meaningful SDP message");
sdp_message =
calls_sip_media_manager_get_capabilities (manager, NULL, 25048, FALSE, NULL);
g_test_assert_expected_messages ();
g_assert_true (sdp_message);
g_assert_true (find_string_in_sdp_message (sdp_message,
"m=audio 0 RTP/AVP"));
g_free (sdp_message);
g_debug ("no codecs test OK");
}
gint
main (gint argc,
gchar *argv[])
{
gtk_test_init (&argc, &argv, NULL);
#ifdef PLUGIN_BUILDDIR
peas_engine_add_search_path (peas_engine_get_default (), PLUGIN_BUILDDIR, NULL);
#endif
/* this is a workaround for an issue with sofia: https://github.com/freeswitch/sofia-sip/issues/58 */
su_random64 ();
g_test_add ("/Calls/SIP/provider_object", SipFixture, NULL,
setup_sip_provider, test_sip_provider_object, tear_down_sip_provider);
g_test_add ("/Calls/SIP/provider_origins", SipFixture, NULL,
setup_sip_provider, test_sip_provider_origins, tear_down_sip_provider);
g_test_add ("/Calls/SIP/origin_objects", SipFixture, NULL,
setup_sip_origins, test_sip_origin_objects, tear_down_sip_origins);
g_test_add ("/Calls/SIP/origin_call_lists", SipFixture, NULL,
setup_sip_origins, test_sip_origin_call_lists, tear_down_sip_origins);
g_test_add ("/Calls/SIP/calls_direct_call", SipFixture, NULL,
setup_sip_origins, test_sip_call_direct_calls, tear_down_sip_origins);
g_test_add_func ("/Calls/SIP/media_manager", test_sip_media_manager);
return g_test_run();
}