mirror of
https://gitlab.gnome.org/GNOME/calls.git
synced 2024-11-15 04:45:36 +00:00
8b126484cb
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
580 lines
18 KiB
C
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();
|
|
}
|