mirror of
https://gitlab.gnome.org/GNOME/calls.git
synced 2025-01-11 14:25:32 +00:00
Introduce CallsNetworkWatch to notify of network changes
The libsofia-sip stack needs to bind to a specific interface when there are multiple network interfaces available. Handles should be recreated when the default route changes.
This commit is contained in:
parent
2df221c94c
commit
3714f99d38
3 changed files with 491 additions and 0 deletions
451
src/calls-network-watch.c
Normal file
451
src/calls-network-watch.c
Normal file
|
@ -0,0 +1,451 @@
|
|||
/*
|
||||
* Copyright (C) 2021 Purism SPC
|
||||
*
|
||||
* This file is part of Calls.
|
||||
*
|
||||
* Calls is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calls 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#define G_LOG_DOMAIN "CallsNetworkWatch"
|
||||
|
||||
#include "calls-network-watch.h"
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
#include <arpa/inet.h>
|
||||
#include <linux/rtnetlink.h>
|
||||
|
||||
/**
|
||||
* SECTION:calls-network-watch
|
||||
* @short_description: Watches network interfaces
|
||||
* @Title: CallsNetworkWatch
|
||||
*
|
||||
* The #CallsNetworkWatch uses rtnetlink messages to keep
|
||||
* track of network interfaces.
|
||||
*
|
||||
* This allows the sofia SIP stack to bind to the
|
||||
* correct interface and can later help in deciding which codec to
|
||||
* use for media (f.e. lower bandwidth on a metered connection).
|
||||
*/
|
||||
|
||||
|
||||
typedef struct {
|
||||
struct nlmsghdr n;
|
||||
struct rtmsg r;
|
||||
char buf[1024];
|
||||
} RequestData;
|
||||
|
||||
enum {
|
||||
PROP_0,
|
||||
PROP_IPV4,
|
||||
PROP_IPV6,
|
||||
PROP_LAST_PROP
|
||||
};
|
||||
static GParamSpec *props[PROP_LAST_PROP];
|
||||
|
||||
enum {
|
||||
NETWORK_CHANGED,
|
||||
N_SIGNALS
|
||||
};
|
||||
static guint signals[N_SIGNALS];
|
||||
|
||||
|
||||
typedef struct _CallsNetworkWatch {
|
||||
GObject parent;
|
||||
|
||||
RequestData *req;
|
||||
int fd;
|
||||
unsigned int seq;
|
||||
char buf[1024]; /* buffer for responses to rtnetlink requests */
|
||||
|
||||
guint timeout_id;
|
||||
|
||||
char *ipv4;
|
||||
char *ipv6;
|
||||
char tmp_addr[INET6_ADDRSTRLEN];
|
||||
} CallsNetworkWatch;
|
||||
|
||||
|
||||
static void initable_iface_init (GInitableIface *iface);
|
||||
|
||||
G_DEFINE_TYPE_WITH_CODE (CallsNetworkWatch, calls_network_watch, G_TYPE_OBJECT,
|
||||
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init))
|
||||
|
||||
#define DUMMY_TARGET_V4 "1.2.3.4"
|
||||
#define DUMMY_TARGET_V6 "::1.2.3.4"
|
||||
|
||||
static gboolean
|
||||
req_route_v4 (CallsNetworkWatch *self)
|
||||
{
|
||||
int addr_len = 4;
|
||||
int len = RTA_LENGTH (addr_len);
|
||||
struct rtattr *rta;
|
||||
|
||||
g_assert (CALLS_IS_NETWORK_WATCH (self));
|
||||
|
||||
self->req->n.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg));
|
||||
self->req->n.nlmsg_flags = NLM_F_REQUEST;
|
||||
self->req->n.nlmsg_type = RTM_GETROUTE;
|
||||
self->req->r.rtm_family = AF_INET;
|
||||
|
||||
|
||||
rta = ((struct rtattr *) (((void *) (&self->req->n)) +
|
||||
NLMSG_ALIGN (self->req->n.nlmsg_len)));
|
||||
rta->rta_type = RTA_DST;
|
||||
rta->rta_len = len;
|
||||
|
||||
/* use a dummy target destination */
|
||||
if (inet_pton (AF_INET, DUMMY_TARGET_V4, RTA_DATA (rta)) != 1)
|
||||
return FALSE;
|
||||
|
||||
self->req->n.nlmsg_len = NLMSG_ALIGN (self->req->n.nlmsg_len) + RTA_ALIGN (len);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
req_route_v6 (CallsNetworkWatch *self)
|
||||
{
|
||||
int addr_len = 16;
|
||||
int len = RTA_LENGTH (addr_len);
|
||||
struct rtattr *rta;
|
||||
|
||||
g_assert (CALLS_IS_NETWORK_WATCH (self));
|
||||
|
||||
self->req->n.nlmsg_len = NLMSG_LENGTH (sizeof (struct rtmsg));
|
||||
self->req->n.nlmsg_flags = NLM_F_REQUEST;
|
||||
self->req->n.nlmsg_type = RTM_GETROUTE;
|
||||
self->req->r.rtm_family = AF_INET6;
|
||||
|
||||
rta = ((struct rtattr *) (((void *) (&self->req->n)) +
|
||||
NLMSG_ALIGN (self->req->n.nlmsg_len)));
|
||||
rta->rta_type = RTA_DST;
|
||||
rta->rta_len = len;
|
||||
|
||||
/* use a dummy target destination */
|
||||
if (inet_pton (AF_INET6, DUMMY_TARGET_V6, RTA_DATA (rta)) != 1)
|
||||
return FALSE;
|
||||
|
||||
self->req->n.nlmsg_len = NLMSG_ALIGN (self->req->n.nlmsg_len) + RTA_ALIGN (len);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
#undef DUMMY_TARGET_V4
|
||||
#undef DUMMY_TARGET_V6
|
||||
|
||||
static gboolean
|
||||
talk_rtnl (CallsNetworkWatch *self)
|
||||
{
|
||||
struct iovec iov;
|
||||
struct iovec riov;
|
||||
struct sockaddr_nl nladdr = { .nl_family = AF_NETLINK };
|
||||
struct msghdr msg = {
|
||||
.msg_name = &nladdr,
|
||||
.msg_namelen = sizeof (nladdr),
|
||||
.msg_iov = &iov,
|
||||
.msg_iovlen = 1,
|
||||
};
|
||||
|
||||
struct nlmsghdr *h;
|
||||
int status;
|
||||
|
||||
g_assert (CALLS_IS_NETWORK_WATCH (self));
|
||||
|
||||
iov.iov_base = (void *) &self->req->n;
|
||||
iov.iov_len = self->req->n.nlmsg_len;
|
||||
self->req->n.nlmsg_seq = self->seq++;
|
||||
|
||||
status = sendmsg (self->fd, &msg, 0);
|
||||
if (status < 0) {
|
||||
g_warning ("Could not send rtnetlink: %d", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
/* change msg to use response iov */
|
||||
riov.iov_base = self->buf;
|
||||
riov.iov_len = sizeof (self->buf);
|
||||
|
||||
msg.msg_iov = &riov;
|
||||
msg.msg_iovlen = 1;
|
||||
|
||||
status = recvmsg (self->fd, &msg, 0);
|
||||
|
||||
if (status == -1) {
|
||||
g_warning ("Could not receive rtnetlink: %d", errno);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
h = (struct nlmsghdr *) self->buf;
|
||||
if (h->nlmsg_type == NLMSG_ERROR) {
|
||||
g_warning ("An error occured in the netlink stack");
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
get_prefsrc (CallsNetworkWatch *self,
|
||||
int family)
|
||||
{
|
||||
struct nlmsghdr *h;
|
||||
struct rtmsg *r;
|
||||
struct rtattr *rta;
|
||||
int len;
|
||||
gboolean found = FALSE;
|
||||
|
||||
g_assert (CALLS_IS_NETWORK_WATCH (self));
|
||||
|
||||
h = (struct nlmsghdr *) self->buf;
|
||||
r = NLMSG_DATA (self->buf);
|
||||
rta = RTM_RTA (r);
|
||||
len = h->nlmsg_len - NLMSG_LENGTH (sizeof (*r));
|
||||
|
||||
while (RTA_OK (rta, len)) {
|
||||
if (rta->rta_type == RTA_PREFSRC) {
|
||||
found = TRUE;
|
||||
break;
|
||||
}
|
||||
rta = RTA_NEXT (rta, len);
|
||||
}
|
||||
|
||||
if (!found)
|
||||
return FALSE;
|
||||
|
||||
if (family == AF_INET) {
|
||||
inet_ntop (AF_INET, RTA_DATA (rta), self->tmp_addr, INET_ADDRSTRLEN);
|
||||
} else if (family == AF_INET6) {
|
||||
inet_ntop (AF_INET6, RTA_DATA (rta), self->tmp_addr, INET6_ADDRSTRLEN);
|
||||
} else {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fetch_ipv4 (CallsNetworkWatch *self)
|
||||
{
|
||||
g_assert (CALLS_IS_NETWORK_WATCH (self));
|
||||
|
||||
if (!req_route_v4 (self))
|
||||
return FALSE;
|
||||
|
||||
if (!talk_rtnl (self))
|
||||
return FALSE;
|
||||
|
||||
return get_prefsrc (self, AF_INET);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
fetch_ipv6 (CallsNetworkWatch *self)
|
||||
{
|
||||
g_assert (CALLS_IS_NETWORK_WATCH (self));
|
||||
|
||||
if (!req_route_v6 (self))
|
||||
return FALSE;
|
||||
|
||||
if (!talk_rtnl (self))
|
||||
return FALSE;
|
||||
|
||||
return get_prefsrc (self, AF_INET6);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
on_watch_network (CallsNetworkWatch *self)
|
||||
{
|
||||
gboolean changed_v4 = FALSE;
|
||||
gboolean changed_v6 = FALSE;
|
||||
|
||||
changed_v4 = fetch_ipv4 (self) && g_strcmp0 (self->tmp_addr, self->ipv4) != 0;
|
||||
if (changed_v4) {
|
||||
g_free (self->ipv4);
|
||||
self->ipv4 = g_strdup (self->tmp_addr);
|
||||
g_debug ("New IPv4: %s", self->ipv4);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_IPV4]);
|
||||
}
|
||||
|
||||
changed_v6 = fetch_ipv6 (self) && g_strcmp0 (self->tmp_addr, self->ipv6) != 0;
|
||||
if (changed_v6) {
|
||||
g_free (self->ipv6);
|
||||
self->ipv6 = g_strdup (self->tmp_addr);
|
||||
g_debug ("New IPv6: %s", self->ipv6);
|
||||
g_object_notify_by_pspec (G_OBJECT (self), props[PROP_IPV6]);
|
||||
}
|
||||
|
||||
if (changed_v4 || changed_v6)
|
||||
g_signal_emit (self, signals[NETWORK_CHANGED], 0);
|
||||
|
||||
return G_SOURCE_CONTINUE;
|
||||
}
|
||||
|
||||
static void
|
||||
calls_network_watch_get_property (GObject *object,
|
||||
guint property_id,
|
||||
GValue *value,
|
||||
GParamSpec *pspec)
|
||||
{
|
||||
CallsNetworkWatch *self = CALLS_NETWORK_WATCH (object);
|
||||
|
||||
switch (property_id) {
|
||||
case PROP_IPV4:
|
||||
g_value_set_string (value, calls_network_watch_get_ipv4 (self));
|
||||
break;
|
||||
|
||||
case PROP_IPV6:
|
||||
g_value_set_string (value, calls_network_watch_get_ipv6 (self));
|
||||
break;
|
||||
|
||||
default:
|
||||
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_network_watch_finalize (GObject *object)
|
||||
{
|
||||
CallsNetworkWatch *self = CALLS_NETWORK_WATCH (object);
|
||||
|
||||
g_source_remove (self->timeout_id);
|
||||
g_free (self->req);
|
||||
g_free (self->ipv4);
|
||||
g_free (self->ipv6);
|
||||
close (self->fd);
|
||||
|
||||
G_OBJECT_CLASS (calls_network_watch_parent_class)->finalize (object);
|
||||
}
|
||||
|
||||
static void
|
||||
calls_network_watch_class_init (CallsNetworkWatchClass *klass)
|
||||
{
|
||||
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
||||
|
||||
object_class->get_property = calls_network_watch_get_property;
|
||||
object_class->finalize = calls_network_watch_finalize;
|
||||
|
||||
signals[NETWORK_CHANGED] = g_signal_new ("network-changed",
|
||||
CALLS_TYPE_NETWORK_WATCH,
|
||||
G_SIGNAL_RUN_LAST,
|
||||
0,
|
||||
NULL, NULL, NULL,
|
||||
G_TYPE_NONE,
|
||||
0);
|
||||
|
||||
props[PROP_IPV4] =
|
||||
g_param_spec_string ("ipv4",
|
||||
"IPv4",
|
||||
"The preferred source address for IPv4",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
props[PROP_IPV6] =
|
||||
g_param_spec_string ("ipv6",
|
||||
"IPv6",
|
||||
"The preferred source address for IPv6",
|
||||
NULL,
|
||||
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
|
||||
|
||||
g_object_class_install_properties (object_class, PROP_LAST_PROP, props);
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
calls_network_watch_init (CallsNetworkWatch *self)
|
||||
{
|
||||
self->seq = time (NULL);
|
||||
self->req = g_new0 (RequestData, 1);
|
||||
self->timeout_id = g_timeout_add_seconds (15,
|
||||
G_SOURCE_FUNC (on_watch_network),
|
||||
self);
|
||||
}
|
||||
|
||||
|
||||
static gboolean
|
||||
calls_network_watch_initable_init (GInitable *initable,
|
||||
GCancellable *cancelable,
|
||||
GError **error)
|
||||
{
|
||||
CallsNetworkWatch *self = CALLS_NETWORK_WATCH (initable);
|
||||
gboolean ret = FALSE;
|
||||
|
||||
self->fd = socket (AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
|
||||
if (self->fd == -1) {
|
||||
int errsv = errno;
|
||||
g_set_error (error, G_IO_ERROR, G_IO_ERROR_FAILED,
|
||||
"Failed to create netlink socket: %d", errsv);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
if (fetch_ipv4 (self)) {
|
||||
ret = TRUE;
|
||||
self->ipv4 = g_strdup (self->tmp_addr);
|
||||
}
|
||||
if (fetch_ipv6 (self)) {
|
||||
ret = TRUE;
|
||||
self->ipv6 = g_strdup (self->tmp_addr);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
initable_iface_init (GInitableIface *iface)
|
||||
{
|
||||
iface->init = calls_network_watch_initable_init;
|
||||
}
|
||||
|
||||
|
||||
CallsNetworkWatch *
|
||||
calls_network_watch_get_default (void)
|
||||
{
|
||||
static CallsNetworkWatch *instance;
|
||||
|
||||
if (instance == NULL) {
|
||||
g_autoptr (GError) error = NULL;
|
||||
instance = g_initable_new (CALLS_TYPE_NETWORK_WATCH, NULL, &error, NULL);
|
||||
|
||||
if (!instance)
|
||||
g_warning ("Network watch could not be initialized: %s", error->message);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
|
||||
const char *
|
||||
calls_network_watch_get_ipv4 (CallsNetworkWatch *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_NETWORK_WATCH (self), NULL);
|
||||
|
||||
return self->ipv4;
|
||||
}
|
||||
|
||||
|
||||
const char *
|
||||
calls_network_watch_get_ipv6 (CallsNetworkWatch *self)
|
||||
{
|
||||
g_return_val_if_fail (CALLS_IS_NETWORK_WATCH (self), NULL);
|
||||
|
||||
return self->ipv6;
|
||||
}
|
39
src/calls-network-watch.h
Normal file
39
src/calls-network-watch.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright (C) 2021 Purism SPC
|
||||
*
|
||||
* This file is part of Calls.
|
||||
*
|
||||
* Calls is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Calls 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
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Calls. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Author: Evangelos Ribeiro Tzaras <evangelos.tzaras@puri.sm>
|
||||
*
|
||||
* SPDX-License-Identifier: GPL-3.0-or-later
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <glib-object.h>
|
||||
|
||||
G_BEGIN_DECLS
|
||||
|
||||
#define CALLS_TYPE_NETWORK_WATCH (calls_network_watch_get_type ())
|
||||
|
||||
G_DECLARE_FINAL_TYPE (CallsNetworkWatch, calls_network_watch, CALLS, NETWORK_WATCH, GObject)
|
||||
|
||||
CallsNetworkWatch *calls_network_watch_get_default (void);
|
||||
const char *calls_network_watch_get_ipv4 (CallsNetworkWatch *self);
|
||||
const char *calls_network_watch_get_ipv6 (CallsNetworkWatch *self);
|
||||
|
||||
G_END_DECLS
|
|
@ -114,6 +114,7 @@ calls_sources = files(['calls-message-source.c', 'calls-message-source.h',
|
|||
'calls-account-row.c', 'calls-account-row.h',
|
||||
'calls-settings.c', 'calls-settings.h',
|
||||
'calls-secret-store.c', 'calls-secret-store.h',
|
||||
'calls-network-watch.c', 'calls-network-watch.h',
|
||||
]) + calls_generated_sources
|
||||
|
||||
calls_config_data = config_data
|
||||
|
|
Loading…
Reference in a new issue