platform: implement NMPObject and NMPCache
NMPObject is a simple "object" implemenation around NMPlatformObject. They are ref-counted and have a class-pointer. Several basic functions like equality, hash, to-string are implemented. NMPCache is can be used to store the NMPObject. Objects are indexed via their primary id, but there is also multi-lookup via NMCacheId and NMMultiIndex. Part of the implementation is inside "nm-linux-platform.c", because it depends on utility functions from there.
This commit is contained in:
@@ -15,7 +15,7 @@
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
*
|
||||
* Copyright (C) 2012-2013 Red Hat, Inc.
|
||||
* Copyright (C) 2012-2015 Red Hat, Inc.
|
||||
*/
|
||||
#include "config.h"
|
||||
|
||||
@@ -436,7 +436,7 @@ nm_linux_platform_setup (void)
|
||||
|
||||
/******************************************************************/
|
||||
|
||||
static ObjectType
|
||||
ObjectType
|
||||
_nlo_get_object_type (const struct nl_object *object)
|
||||
{
|
||||
const char *type_str;
|
||||
@@ -1011,6 +1011,86 @@ init_link (NMPlatform *platform, NMPlatformLink *info, struct rtnl_link *rtnllin
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_nmp_vt_cmd_plobj_init_from_nl_link (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
|
||||
{
|
||||
NMPlatformLink *obj = (NMPlatformLink *) _obj;
|
||||
NMPObjectLink *obj_priv = (NMPObjectLink *) _obj;
|
||||
struct rtnl_link *nlo = (struct rtnl_link *) _nlo;
|
||||
const char *name;
|
||||
struct nl_addr *nladdr;
|
||||
|
||||
nm_assert (memcmp (obj, ((char [sizeof (NMPObjectLink)]) { 0 }), sizeof (NMPObjectLink)) == 0);
|
||||
|
||||
obj->ifindex = rtnl_link_get_ifindex (nlo);
|
||||
|
||||
if (id_only)
|
||||
return TRUE;
|
||||
|
||||
name = rtnl_link_get_name (nlo);
|
||||
if (name)
|
||||
g_strlcpy (obj->name, name, sizeof (obj->name));
|
||||
obj->type = link_extract_type (platform, nlo);
|
||||
obj->kind = g_intern_string (rtnl_link_get_type (nlo));
|
||||
obj->flags = rtnl_link_get_flags (nlo);
|
||||
obj->up = NM_FLAGS_HAS (obj->flags, IFF_UP);
|
||||
obj->connected = NM_FLAGS_HAS (obj->flags, IFF_LOWER_UP);
|
||||
obj->arp = !NM_FLAGS_HAS (obj->flags, IFF_NOARP);
|
||||
obj->master = rtnl_link_get_master (nlo);
|
||||
obj->parent = rtnl_link_get_link (nlo);
|
||||
obj->mtu = rtnl_link_get_mtu (nlo);
|
||||
obj->arptype = rtnl_link_get_arptype (nlo);
|
||||
|
||||
if (obj->type == NM_LINK_TYPE_VLAN)
|
||||
obj->vlan_id = rtnl_link_vlan_get_id (nlo);
|
||||
|
||||
if ((nladdr = rtnl_link_get_addr (nlo))) {
|
||||
unsigned int l = 0;
|
||||
|
||||
l = nl_addr_get_len (nladdr);
|
||||
if (l > 0 && l <= NM_UTILS_HWADDR_LEN_MAX) {
|
||||
G_STATIC_ASSERT (NM_UTILS_HWADDR_LEN_MAX == sizeof (obj->addr.data));
|
||||
memcpy (obj->addr.data, nl_addr_get_binary_addr (nladdr), l);
|
||||
obj->addr.len = l;
|
||||
}
|
||||
}
|
||||
|
||||
#if HAVE_LIBNL_INET6_ADDR_GEN_MODE
|
||||
if (_support_user_ipv6ll_get ()) {
|
||||
guint8 mode = 0;
|
||||
|
||||
if (rtnl_link_inet6_get_addr_gen_mode (nlo, &mode) == 0)
|
||||
obj->inet6_addr_gen_mode_inv = ~mode;
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_LIBNL_INET6_TOKEN
|
||||
if ((rtnl_link_inet6_get_token (nlo, &nladdr)) == 0) {
|
||||
if ( nl_addr_get_family (nladdr) == AF_INET6
|
||||
&& nl_addr_get_len (nladdr) == sizeof (struct in6_addr)) {
|
||||
struct in6_addr *addr;
|
||||
NMUtilsIPv6IfaceId *iid = &obj->inet6_token.iid;
|
||||
|
||||
addr = nl_addr_get_binary_addr (nladdr);
|
||||
iid->id_u8[7] = addr->s6_addr[15];
|
||||
iid->id_u8[6] = addr->s6_addr[14];
|
||||
iid->id_u8[5] = addr->s6_addr[13];
|
||||
iid->id_u8[4] = addr->s6_addr[12];
|
||||
iid->id_u8[3] = addr->s6_addr[11];
|
||||
iid->id_u8[2] = addr->s6_addr[10];
|
||||
iid->id_u8[1] = addr->s6_addr[9];
|
||||
iid->id_u8[0] = addr->s6_addr[8];
|
||||
obj->inet6_token.is_valid = TRUE;
|
||||
}
|
||||
nl_addr_put (nladdr);
|
||||
}
|
||||
#endif
|
||||
|
||||
obj_priv->netlink.is_in_netlink = TRUE;
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
/* Hack: Empty bridges and bonds have IFF_LOWER_UP flag and therefore they break
|
||||
* the carrier detection. This hack makes nm-platform think they don't have the
|
||||
* IFF_LOWER_UP flag. This seems to also apply to bonds (specifically) with all
|
||||
@@ -1194,6 +1274,66 @@ _init_ip_address_lifetime (NMPlatformIPAddress *address, const struct rtnl_addr
|
||||
address->preferred = _get_remaining_time (address->timestamp, a_preferred);
|
||||
}
|
||||
|
||||
static guint32
|
||||
_extend_lifetime (guint32 lifetime, guint32 seconds)
|
||||
{
|
||||
guint64 v;
|
||||
|
||||
if ( lifetime == NM_PLATFORM_LIFETIME_PERMANENT
|
||||
|| seconds == 0)
|
||||
return lifetime;
|
||||
|
||||
v = (guint64) lifetime + (guint64) seconds;
|
||||
return MIN (v, NM_PLATFORM_LIFETIME_PERMANENT - 1);
|
||||
}
|
||||
|
||||
/* The rtnl_addr object contains relative lifetimes @valid and @preferred
|
||||
* that count in seconds, starting from the moment when the kernel constructed
|
||||
* the netlink message.
|
||||
*
|
||||
* There is also a field rtnl_addr_last_update_time(), which is the absolute
|
||||
* time in 1/100th of a second of clock_gettime (CLOCK_MONOTONIC) when the address
|
||||
* was modified (wrapping every 497 days).
|
||||
* Immediately at the time when the address was last modified, #NOW and @last_update_time
|
||||
* are the same, so (only) in that case @valid and @preferred are anchored at @last_update_time.
|
||||
* However, this is not true in general. As time goes by, whenever kernel sends a new address
|
||||
* via netlink, the lifetimes keep counting down.
|
||||
**/
|
||||
static void
|
||||
_nlo_rtnl_addr_get_lifetimes (const struct rtnl_addr *rtnladdr,
|
||||
guint32 *out_timestamp,
|
||||
guint32 *out_lifetime,
|
||||
guint32 *out_preferred)
|
||||
{
|
||||
guint32 timestamp = 0;
|
||||
gint32 now;
|
||||
guint32 lifetime = rtnl_addr_get_valid_lifetime ((struct rtnl_addr *) rtnladdr);
|
||||
guint32 preferred = rtnl_addr_get_preferred_lifetime ((struct rtnl_addr *) rtnladdr);
|
||||
|
||||
if ( lifetime != NM_PLATFORM_LIFETIME_PERMANENT
|
||||
|| preferred != NM_PLATFORM_LIFETIME_PERMANENT) {
|
||||
if (preferred > lifetime)
|
||||
preferred = lifetime;
|
||||
timestamp = _rtnl_addr_last_update_time_to_nm (rtnladdr, &now);
|
||||
|
||||
if (now == 0) {
|
||||
/* strange. failed to detect the last-update time and assumed that timestamp is 1. */
|
||||
nm_assert (timestamp == 1);
|
||||
now = nm_utils_get_monotonic_timestamp_s ();
|
||||
}
|
||||
if (timestamp < now) {
|
||||
guint32 diff = now - timestamp;
|
||||
|
||||
lifetime = _extend_lifetime (lifetime, diff);
|
||||
preferred = _extend_lifetime (preferred, diff);
|
||||
} else
|
||||
nm_assert (timestamp == now);
|
||||
}
|
||||
*out_timestamp = timestamp;
|
||||
*out_lifetime = lifetime;
|
||||
*out_preferred = preferred;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
|
||||
{
|
||||
@@ -1229,6 +1369,44 @@ init_ip4_address (NMPlatformIP4Address *address, struct rtnl_addr *rtnladdr)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_nmp_vt_cmd_plobj_init_from_nl_ip4_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
|
||||
{
|
||||
NMPlatformIP4Address *obj = (NMPlatformIP4Address *) _obj;
|
||||
struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
|
||||
struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
|
||||
struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
|
||||
const char *label;
|
||||
|
||||
if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
|
||||
g_return_val_if_reached (FALSE);
|
||||
|
||||
obj->ifindex = rtnl_addr_get_ifindex (nlo);
|
||||
obj->plen = rtnl_addr_get_prefixlen (nlo);
|
||||
memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
|
||||
|
||||
if (id_only)
|
||||
return TRUE;
|
||||
|
||||
obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
|
||||
_nlo_rtnl_addr_get_lifetimes (nlo,
|
||||
&obj->timestamp,
|
||||
&obj->lifetime,
|
||||
&obj->preferred);
|
||||
if (nlpeer) {
|
||||
if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
|
||||
g_warn_if_reached ();
|
||||
else
|
||||
memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
|
||||
}
|
||||
label = rtnl_addr_get_label (nlo);
|
||||
/* Check for ':'; we're only interested in labels used as interface aliases */
|
||||
if (label && strchr (label, ':'))
|
||||
g_strlcpy (obj->label, label, sizeof (obj->label));
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
|
||||
{
|
||||
@@ -1258,6 +1436,41 @@ init_ip6_address (NMPlatformIP6Address *address, struct rtnl_addr *rtnladdr)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_nmp_vt_cmd_plobj_init_from_nl_ip6_address (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
|
||||
{
|
||||
NMPlatformIP6Address *obj = (NMPlatformIP6Address *) _obj;
|
||||
struct rtnl_addr *nlo = (struct rtnl_addr *) _nlo;
|
||||
struct nl_addr *nladdr = rtnl_addr_get_local (nlo);
|
||||
struct nl_addr *nlpeer = rtnl_addr_get_peer (nlo);
|
||||
|
||||
if (!nladdr || nl_addr_get_len (nladdr) != sizeof (obj->address))
|
||||
g_return_val_if_reached (FALSE);
|
||||
|
||||
obj->ifindex = rtnl_addr_get_ifindex (nlo);
|
||||
obj->plen = rtnl_addr_get_prefixlen (nlo);
|
||||
memcpy (&obj->address, nl_addr_get_binary_addr (nladdr), sizeof (obj->address));
|
||||
|
||||
if (id_only)
|
||||
return TRUE;
|
||||
|
||||
obj->source = NM_IP_CONFIG_SOURCE_KERNEL;
|
||||
_nlo_rtnl_addr_get_lifetimes (nlo,
|
||||
&obj->timestamp,
|
||||
&obj->lifetime,
|
||||
&obj->preferred);
|
||||
obj->flags = rtnl_addr_get_flags (nlo);
|
||||
|
||||
if (nlpeer) {
|
||||
if (nl_addr_get_len (nlpeer) != sizeof (obj->peer_address))
|
||||
g_warn_if_reached ();
|
||||
else
|
||||
memcpy (&obj->peer_address, nl_addr_get_binary_addr (nlpeer), sizeof (obj->peer_address));
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static guint
|
||||
source_to_rtprot (NMIPConfigSource source)
|
||||
{
|
||||
@@ -1350,6 +1563,61 @@ init_ip4_route (NMPlatformIP4Route *route, struct rtnl_route *rtnlroute)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_nmp_vt_cmd_plobj_init_from_nl_ip4_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
|
||||
{
|
||||
NMPlatformIP4Route *obj = (NMPlatformIP4Route *) _obj;
|
||||
struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
|
||||
struct nl_addr *dst, *gw;
|
||||
struct rtnl_nexthop *nexthop;
|
||||
|
||||
if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
|
||||
rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
|
||||
rtnl_route_get_tos (nlo) != 0 ||
|
||||
rtnl_route_get_nnexthops (nlo) != 1)
|
||||
return FALSE;
|
||||
|
||||
nexthop = rtnl_route_nexthop_n (nlo, 0);
|
||||
if (!nexthop)
|
||||
g_return_val_if_reached (FALSE);
|
||||
|
||||
dst = rtnl_route_get_dst (nlo);
|
||||
if (!dst)
|
||||
g_return_val_if_reached (FALSE);
|
||||
|
||||
if (nl_addr_get_len (dst)) {
|
||||
if (nl_addr_get_len (dst) != sizeof (obj->network))
|
||||
g_return_val_if_reached (FALSE);
|
||||
memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
|
||||
}
|
||||
obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
|
||||
obj->plen = nl_addr_get_prefixlen (dst);
|
||||
obj->metric = rtnl_route_get_priority (nlo);
|
||||
obj->scope_inv = nm_platform_route_scope_inv (rtnl_route_get_scope (nlo));
|
||||
|
||||
gw = rtnl_route_nh_get_gateway (nexthop);
|
||||
if (gw) {
|
||||
if (nl_addr_get_len (gw) != sizeof (obj->gateway))
|
||||
g_warn_if_reached ();
|
||||
else
|
||||
memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
|
||||
}
|
||||
rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
|
||||
if (rtnl_route_get_flags (nlo) & RTM_F_CLONED) {
|
||||
/* we must not straight way reject cloned routes, because we might have cached
|
||||
* a non-cloned route. If we now receive an update of the route with the route
|
||||
* being cloned, we must still return the object, so that we can remove the old
|
||||
* one from the cache.
|
||||
*
|
||||
* This happens, because this route is not nmp_object_is_alive().
|
||||
* */
|
||||
obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
|
||||
} else
|
||||
obj->source = rtprot_to_source (rtnl_route_get_protocol (nlo), TRUE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static gboolean
|
||||
init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute)
|
||||
{
|
||||
@@ -1390,6 +1658,56 @@ init_ip6_route (NMPlatformIP6Route *route, struct rtnl_route *rtnlroute)
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
gboolean
|
||||
_nmp_vt_cmd_plobj_init_from_nl_ip6_route (NMPlatform *platform, NMPlatformObject *_obj, const struct nl_object *_nlo, gboolean id_only, gboolean complete_from_cache)
|
||||
{
|
||||
NMPlatformIP6Route *obj = (NMPlatformIP6Route *) _obj;
|
||||
struct rtnl_route *nlo = (struct rtnl_route *) _nlo;
|
||||
struct nl_addr *dst, *gw;
|
||||
struct rtnl_nexthop *nexthop;
|
||||
|
||||
if (rtnl_route_get_type (nlo) != RTN_UNICAST ||
|
||||
rtnl_route_get_table (nlo) != RT_TABLE_MAIN ||
|
||||
rtnl_route_get_tos (nlo) != 0 ||
|
||||
rtnl_route_get_nnexthops (nlo) != 1)
|
||||
return FALSE;
|
||||
|
||||
nexthop = rtnl_route_nexthop_n (nlo, 0);
|
||||
if (!nexthop)
|
||||
g_return_val_if_reached (FALSE);
|
||||
|
||||
dst = rtnl_route_get_dst (nlo);
|
||||
if (!dst)
|
||||
g_return_val_if_reached (FALSE);
|
||||
|
||||
if (nl_addr_get_len (dst)) {
|
||||
if (nl_addr_get_len (dst) != sizeof (obj->network))
|
||||
g_return_val_if_reached (FALSE);
|
||||
memcpy (&obj->network, nl_addr_get_binary_addr (dst), sizeof (obj->network));
|
||||
}
|
||||
obj->ifindex = rtnl_route_nh_get_ifindex (nexthop);
|
||||
obj->plen = nl_addr_get_prefixlen (dst);
|
||||
obj->metric = rtnl_route_get_priority (nlo);
|
||||
|
||||
if (id_only)
|
||||
return TRUE;
|
||||
|
||||
gw = rtnl_route_nh_get_gateway (nexthop);
|
||||
if (gw) {
|
||||
if (nl_addr_get_len (gw) != sizeof (obj->gateway))
|
||||
g_warn_if_reached ();
|
||||
else
|
||||
memcpy (&obj->gateway, nl_addr_get_binary_addr (gw), sizeof (obj->gateway));
|
||||
}
|
||||
rtnl_route_get_metric (nlo, RTAX_ADVMSS, &obj->mss);
|
||||
if (rtnl_route_get_flags (nlo) & RTM_F_CLONED)
|
||||
obj->source = _NM_IP_CONFIG_SOURCE_RTM_F_CLONED;
|
||||
else
|
||||
obj->source = rtprot_to_source (rtnl_route_get_protocol (nlo), TRUE);
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static char to_string_buffer[255];
|
||||
|
||||
#define SET_AND_RETURN_STRING_BUFFER(...) \
|
||||
@@ -2296,6 +2614,16 @@ build_rtnl_link (int ifindex, const char *name, NMLinkType type)
|
||||
return (struct nl_object *) rtnllink;
|
||||
}
|
||||
|
||||
struct nl_object *
|
||||
_nmp_vt_cmd_plobj_to_nl_link (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
|
||||
{
|
||||
const NMPlatformLink *obj = (const NMPlatformLink *) _obj;
|
||||
|
||||
return build_rtnl_link (obj->ifindex,
|
||||
obj->name[0] ? obj->name : NULL,
|
||||
obj->type);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
link_get_by_name (NMPlatform *platform, const char *name, NMPlatformLink *out_link)
|
||||
{
|
||||
@@ -3782,6 +4110,40 @@ build_rtnl_addr (NMPlatform *platform,
|
||||
return (struct nl_object *) rtnladdr_copy;
|
||||
}
|
||||
|
||||
struct nl_object *
|
||||
_nmp_vt_cmd_plobj_to_nl_ip4_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
|
||||
{
|
||||
const NMPlatformIP4Address *obj = (const NMPlatformIP4Address *) _obj;
|
||||
|
||||
return build_rtnl_addr (platform,
|
||||
AF_INET,
|
||||
obj->ifindex,
|
||||
&obj->address,
|
||||
obj->peer_address ? &obj->peer_address : NULL,
|
||||
obj->plen,
|
||||
obj->lifetime,
|
||||
obj->preferred,
|
||||
0,
|
||||
obj->label[0] ? obj->label : NULL);
|
||||
}
|
||||
|
||||
struct nl_object *
|
||||
_nmp_vt_cmd_plobj_to_nl_ip6_address (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
|
||||
{
|
||||
const NMPlatformIP6Address *obj = (const NMPlatformIP6Address *) _obj;
|
||||
|
||||
return build_rtnl_addr (platform,
|
||||
AF_INET6,
|
||||
obj->ifindex,
|
||||
&obj->address,
|
||||
!IN6_IS_ADDR_UNSPECIFIED (&obj->peer_address) ? &obj->peer_address : NULL,
|
||||
obj->plen,
|
||||
obj->lifetime,
|
||||
obj->preferred,
|
||||
0,
|
||||
NULL);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ip4_address_add (NMPlatform *platform,
|
||||
int ifindex,
|
||||
@@ -4036,6 +4398,38 @@ build_rtnl_route (int family, int ifindex, NMIPConfigSource source,
|
||||
return (struct nl_object *) rtnlroute;
|
||||
}
|
||||
|
||||
struct nl_object *
|
||||
_nmp_vt_cmd_plobj_to_nl_ip4_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
|
||||
{
|
||||
const NMPlatformIP4Route *obj = (const NMPlatformIP4Route *) _obj;
|
||||
|
||||
return build_rtnl_route (AF_INET,
|
||||
obj->ifindex,
|
||||
obj->source,
|
||||
&obj->network,
|
||||
obj->plen,
|
||||
&obj->gateway,
|
||||
NULL,
|
||||
obj->metric,
|
||||
obj->mss);
|
||||
}
|
||||
|
||||
struct nl_object *
|
||||
_nmp_vt_cmd_plobj_to_nl_ip6_route (NMPlatform *platform, const NMPlatformObject *_obj, gboolean id_only)
|
||||
{
|
||||
const NMPlatformIP6Route *obj = (const NMPlatformIP6Route *) _obj;
|
||||
|
||||
return build_rtnl_route (AF_INET6,
|
||||
obj->ifindex,
|
||||
obj->source,
|
||||
&obj->network,
|
||||
obj->plen,
|
||||
&obj->gateway,
|
||||
NULL,
|
||||
obj->metric,
|
||||
obj->mss);
|
||||
}
|
||||
|
||||
static gboolean
|
||||
ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source,
|
||||
in_addr_t network, int plen, in_addr_t gateway,
|
||||
|
Reference in New Issue
Block a user