/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* nm-platform-fake.c - Fake platform interaction code for testing NetworkManager * * This program 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 2, or (at your option) * any later version. * * This program 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 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. */ #include "nm-default.h" #include "nm-fake-platform.h" #include #include #include #include #include #include "nm-utils.h" #include "nm-core-utils.h" #include "nm-platform-utils.h" #include "nmp-object.h" #include "nm-test-utils-core.h" /*****************************************************************************/ typedef struct { NMPlatformLink link; char *udi; NMPObject *lnk; struct in6_addr ip6_lladdr; } NMFakePlatformLink; typedef struct { GHashTable *options; GArray *links; GArray *ip4_addresses; GArray *ip6_addresses; GArray *ip4_routes; GArray *ip6_routes; } NMFakePlatformPrivate; struct _NMFakePlatform { NMPlatform parent; NMFakePlatformPrivate _priv; }; struct _NMFakePlatformClass { NMPlatformClass parent; }; G_DEFINE_TYPE (NMFakePlatform, nm_fake_platform, NM_TYPE_PLATFORM) #define NM_FAKE_PLATFORM_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMFakePlatform, NM_IS_FAKE_PLATFORM) /*****************************************************************************/ #define _NMLOG_PREFIX_NAME "platform-fake" #define _NMLOG_DOMAIN LOGD_PLATFORM #define _NMLOG(level, ...) _LOG(level, _NMLOG_DOMAIN, platform, __VA_ARGS__) #define _LOG(level, domain, self, ...) \ G_STMT_START { \ const NMLogLevel __level = (level); \ const NMLogDomain __domain = (domain); \ \ if (nm_logging_enabled (__level, __domain)) { \ char __prefix[32]; \ const char *__p_prefix = _NMLOG_PREFIX_NAME; \ const void *const __self = (self); \ \ if (__self && __self != nm_platform_try_get ()) { \ g_snprintf (__prefix, sizeof (__prefix), "%s[%p]", _NMLOG_PREFIX_NAME, __self); \ __p_prefix = __prefix; \ } \ _nm_log (__level, __domain, 0, \ "%s: " _NM_UTILS_MACRO_FIRST (__VA_ARGS__), \ __p_prefix _NM_UTILS_MACRO_REST (__VA_ARGS__)); \ } \ } G_STMT_END /*****************************************************************************/ static void link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal); static gboolean ip6_address_add (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen, struct in6_addr peer_addr, guint32 lifetime, guint32 preferred, guint flags); static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen); /*****************************************************************************/ static gboolean _ip4_address_equal_peer_net (in_addr_t peer1, in_addr_t peer2, guint8 plen) { return ((peer1 ^ peer2) & nm_utils_ip4_prefix_to_netmask (plen)) == 0; } /*****************************************************************************/ static gboolean sysctl_set (NMPlatform *platform, const char *path, const char *value) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); g_hash_table_insert (priv->options, g_strdup (path), g_strdup (value)); return TRUE; } static char * sysctl_get (NMPlatform *platform, const char *path) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); return g_strdup (g_hash_table_lookup (priv->options, path)); } static const char * type_to_type_name (NMLinkType type) { switch (type) { case NM_LINK_TYPE_UNKNOWN: return "unknown"; case NM_LINK_TYPE_LOOPBACK: return "loopback"; case NM_LINK_TYPE_ETHERNET: return "ethernet"; case NM_LINK_TYPE_DUMMY: return "dummy"; case NM_LINK_TYPE_BRIDGE: return "bridge"; case NM_LINK_TYPE_BOND: return "bond"; case NM_LINK_TYPE_TEAM: return "team"; case NM_LINK_TYPE_VLAN: return "vlan"; case NM_LINK_TYPE_NONE: default: return NULL; } } static void link_init (NMFakePlatformLink *device, int ifindex, int type, const char *name) { gs_free char *ip6_lladdr = NULL; g_assert (!name || strlen (name) < sizeof(device->link.name)); memset (device, 0, sizeof (*device)); ip6_lladdr = ifindex > 0 ? g_strdup_printf ("fe80::fa1e:%0x:%0x", ifindex / 256, ifindex % 256) : NULL; device->link.ifindex = name ? ifindex : 0; device->link.type = type; device->link.kind = type_to_type_name (type); device->link.driver = type_to_type_name (type); device->udi = g_strdup_printf ("fake:%d", ifindex); device->link.initialized = TRUE; device->ip6_lladdr = *nmtst_inet6_from_string (ip6_lladdr); if (name) strcpy (device->link.name, name); switch (device->link.type) { case NM_LINK_TYPE_DUMMY: device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP); break; default: device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP); break; } } static NMFakePlatformLink * link_get (NMPlatform *platform, int ifindex) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMFakePlatformLink *device; if (ifindex >= priv->links->len) goto not_found; device = &g_array_index (priv->links, NMFakePlatformLink, ifindex); if (!device->link.ifindex) goto not_found; return device; not_found: _LOGD ("link not found: %d", ifindex); return NULL; } static GArray * link_get_all (NMPlatform *platform) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); GArray *links = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformLink), priv->links->len); int i; for (i = 0; i < priv->links->len; i++) if (g_array_index (priv->links, NMFakePlatformLink, i).link.ifindex) g_array_append_val (links, g_array_index (priv->links, NMFakePlatformLink, i).link); return links; } static const NMPlatformLink * _nm_platform_link_get (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); return device ? &device->link : NULL; } static const NMPlatformLink * _nm_platform_link_get_by_ifname (NMPlatform *platform, const char *ifname) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); guint i; for (i = 0; i < priv->links->len; i++) { NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i); if (!strcmp (device->link.name, ifname)) return &device->link; } return NULL; } static const NMPlatformLink * _nm_platform_link_get_by_address (NMPlatform *platform, gconstpointer address, size_t length) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); guint i; if ( length == 0 || length > NM_UTILS_HWADDR_LEN_MAX || !address) g_return_val_if_reached (NULL); for (i = 0; i < priv->links->len; i++) { NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i); if ( device->link.addr.len == length && memcmp (device->link.addr.data, address, length) == 0) { return &device->link; } } return NULL; } static const NMPObject * link_get_lnk (NMPlatform *platform, int ifindex, NMLinkType link_type, const NMPlatformLink **out_link) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) return NULL; NM_SET_OUT (out_link, &device->link); if (!device->lnk) return NULL; if (link_type == NM_LINK_TYPE_NONE) return device->lnk; if ( link_type != device->link.type || link_type != NMP_OBJECT_GET_CLASS (device->lnk)->lnk_link_type) return NULL; return device->lnk; } static gboolean link_add (NMPlatform *platform, const char *name, NMLinkType type, const void *address, size_t address_len, const NMPlatformLink **out_link) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMFakePlatformLink device; NMFakePlatformLink *new_device; link_init (&device, priv->links->len, type, name); if (address) { g_return_val_if_fail (address_len > 0 && address_len <= sizeof (device.link.addr.data), FALSE); memcpy (device.link.addr.data, address, address_len); device.link.addr.len = address_len; } g_array_append_val (priv->links, device); new_device = &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1); if (device.link.ifindex) { g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, device.link.ifindex, &device, NM_PLATFORM_SIGNAL_ADDED); link_changed (platform, &g_array_index (priv->links, NMFakePlatformLink, priv->links->len - 1), FALSE); } if (out_link) *out_link = &new_device->link; return TRUE; } static gboolean link_delete (NMPlatform *platform, int ifindex) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMFakePlatformLink *device = link_get (platform, ifindex); NMPlatformLink deleted_device; int i; if (!device || !device->link.ifindex) return FALSE; memcpy (&deleted_device, &device->link, sizeof (deleted_device)); memset (&device->link, 0, sizeof (device->link)); g_clear_pointer (&device->lnk, nmp_object_unref); g_clear_pointer (&device->udi, g_free); /* Remove addresses and routes which belong to the deleted interface */ for (i = 0; i < priv->ip4_addresses->len; i++) { NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i); if (address->ifindex == ifindex) memset (address, 0, sizeof (*address)); } for (i = 0; i < priv->ip6_addresses->len; i++) { NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i); if (address->ifindex == ifindex) memset (address, 0, sizeof (*address)); } for (i = 0; i < priv->ip4_routes->len; i++) { NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i); if (route->ifindex == ifindex) memset (route, 0, sizeof (*route)); } for (i = 0; i < priv->ip6_routes->len; i++) { NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i); if (route->ifindex == ifindex) memset (route, 0, sizeof (*route)); } g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, ifindex, &deleted_device, NM_PLATFORM_SIGNAL_REMOVED); return TRUE; } static const char * link_get_type_name (NMPlatform *platform, int ifindex) { return type_to_type_name (nm_platform_link_get_type (platform, ifindex)); } static void link_changed (NMPlatform *platform, NMFakePlatformLink *device, gboolean raise_signal) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; if (raise_signal) g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, NMP_OBJECT_TYPE_LINK, device->link.ifindex, &device->link, NM_PLATFORM_SIGNAL_CHANGED); if (device->link.ifindex && !IN6_IS_ADDR_UNSPECIFIED (&device->ip6_lladdr)) { if (device->link.connected) ip6_address_add (platform, device->link.ifindex, in6addr_any, 64, device->ip6_lladdr, NM_PLATFORM_LIFETIME_PERMANENT, NM_PLATFORM_LIFETIME_PERMANENT, 0); else ip6_address_delete (platform, device->link.ifindex, device->ip6_lladdr, 64); } if (device->link.master) { gboolean connected = FALSE; NMFakePlatformLink *master = link_get (platform, device->link.master); g_return_if_fail (master && master != device); for (i = 0; i < priv->links->len; i++) { NMFakePlatformLink *slave = &g_array_index (priv->links, NMFakePlatformLink, i); if (slave && slave->link.master == master->link.ifindex && slave->link.connected) connected = TRUE; } if (master->link.connected != connected) { master->link.connected = connected; link_changed (platform, master, TRUE); } } } static gboolean link_set_up (NMPlatform *platform, int ifindex, gboolean *out_no_firmware) { NMFakePlatformLink *device = link_get (platform, ifindex); gboolean up, connected; if (out_no_firmware) *out_no_firmware = FALSE; if (!device) { _LOGE ("failure changing link: netlink error (No such device)"); return FALSE; } up = TRUE; connected = TRUE; switch (device->link.type) { case NM_LINK_TYPE_DUMMY: case NM_LINK_TYPE_VLAN: break; case NM_LINK_TYPE_BRIDGE: case NM_LINK_TYPE_BOND: case NM_LINK_TYPE_TEAM: connected = FALSE; break; default: connected = FALSE; g_error ("Unexpected device type: %d", device->link.type); } if ( NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) != !!up || device->link.connected != connected) { device->link.n_ifi_flags = NM_FLAGS_ASSIGN (device->link.n_ifi_flags, IFF_UP, up); device->link.connected = connected; link_changed (platform, device, TRUE); } return TRUE; } static gboolean link_set_down (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) { _LOGE ("failure changing link: netlink error (No such device)"); return FALSE; } if (NM_FLAGS_HAS (device->link.n_ifi_flags, IFF_UP) || device->link.connected) { device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_UP); device->link.connected = FALSE; link_changed (platform, device, TRUE); } return TRUE; } static gboolean link_set_arp (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) { _LOGE ("failure changing link: netlink error (No such device)"); return FALSE; } device->link.n_ifi_flags = NM_FLAGS_UNSET (device->link.n_ifi_flags, IFF_NOARP); link_changed (platform, device, TRUE); return TRUE; } static gboolean link_set_noarp (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) { _LOGE ("failure changing link: netlink error (No such device)"); return FALSE; } device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_NOARP); link_changed (platform, device, TRUE); return TRUE; } static NMPlatformError link_set_address (NMPlatform *platform, int ifindex, gconstpointer addr, size_t len) { NMFakePlatformLink *device = link_get (platform, ifindex); if ( !device || len == 0 || len > NM_UTILS_HWADDR_LEN_MAX || !addr) g_return_val_if_reached (NM_PLATFORM_ERROR_BUG); if ( device->link.addr.len != len || ( len > 0 && memcmp (device->link.addr.data, addr, len) != 0)) { memcpy (device->link.addr.data, addr, len); device->link.addr.len = len; link_changed (platform, link_get (platform, ifindex), TRUE); } return NM_PLATFORM_ERROR_SUCCESS; } static gboolean link_set_mtu (NMPlatform *platform, int ifindex, guint32 mtu) { NMFakePlatformLink *device = link_get (platform, ifindex); if (device) { device->link.mtu = mtu; link_changed (platform, device, TRUE); } else _LOGE ("failure changing link: netlink error (No such device)"); return !!device; } static const char * link_get_udi (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) return NULL; return device->udi; } static gboolean link_get_driver_info (NMPlatform *platform, int ifindex, char **out_driver_name, char **out_driver_version, char **out_fw_version) { if (out_driver_name) *out_driver_name = NULL; if (out_driver_version) *out_driver_version = NULL; if (out_fw_version) *out_fw_version = NULL; return TRUE; } static gboolean link_supports_carrier_detect (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) return FALSE; switch (device->link.type) { case NM_LINK_TYPE_DUMMY: return FALSE; default: return TRUE; } } static gboolean link_supports_vlans (NMPlatform *platform, int ifindex) { NMFakePlatformLink *device = link_get (platform, ifindex); if (!device) return FALSE; switch (device->link.type) { case NM_LINK_TYPE_LOOPBACK: return FALSE; default: return TRUE; } } static gboolean link_enslave (NMPlatform *platform, int master, int slave) { NMFakePlatformLink *device = link_get (platform, slave); NMFakePlatformLink *master_device = link_get (platform, master); g_return_val_if_fail (device, FALSE); g_return_val_if_fail (master_device, FALSE); if (device->link.master != master) { device->link.master = master; if (NM_IN_SET (master_device->link.type, NM_LINK_TYPE_BOND, NM_LINK_TYPE_TEAM)) { device->link.n_ifi_flags = NM_FLAGS_SET (device->link.n_ifi_flags, IFF_UP); device->link.connected = TRUE; } link_changed (platform, device, TRUE); } return TRUE; } static gboolean link_release (NMPlatform *platform, int master_idx, int slave_idx) { NMFakePlatformLink *master = link_get (platform, master_idx); NMFakePlatformLink *slave = link_get (platform, slave_idx); g_return_val_if_fail (master, FALSE); g_return_val_if_fail (slave, FALSE); if (slave->link.master != master->link.ifindex) return FALSE; slave->link.master = 0; link_changed (platform, slave, TRUE); link_changed (platform, master, TRUE); return TRUE; } static gboolean vlan_add (NMPlatform *platform, const char *name, int parent, int vlan_id, guint32 vlan_flags, const NMPlatformLink **out_link) { NMFakePlatformLink *device; if (!link_add (platform, name, NM_LINK_TYPE_VLAN, NULL, 0, out_link)) return FALSE; device = link_get (platform, nm_platform_link_get_ifindex (platform, name)); g_return_val_if_fail (device, FALSE); g_return_val_if_fail (!device->lnk, FALSE); device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL); device->lnk->lnk_vlan.id = vlan_id; device->link.parent = parent; if (out_link) *out_link = &device->link; return TRUE; } static gboolean link_vlan_change (NMPlatform *platform, int ifindex, NMVlanFlags flags_mask, NMVlanFlags flags_set, gboolean ingress_reset_all, const NMVlanQosMapping *ingress_map, gsize n_ingress_map, gboolean egress_reset_all, const NMVlanQosMapping *egress_map, gsize n_egress_map) { return FALSE; } static gboolean link_vxlan_add (NMPlatform *platform, const char *name, const NMPlatformLnkVxlan *props, const NMPlatformLink **out_link) { NMFakePlatformLink *device; if (!link_add (platform, name, NM_LINK_TYPE_VXLAN, NULL, 0, out_link)) return FALSE; device = link_get (platform, nm_platform_link_get_ifindex (platform, name)); g_return_val_if_fail (device, FALSE); g_return_val_if_fail (!device->lnk, FALSE); device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VXLAN, NULL); device->lnk->lnk_vxlan = *props; device->link.parent = props->parent_ifindex; if (out_link) *out_link = &device->link; return TRUE; } static gboolean infiniband_partition_add (NMPlatform *platform, int parent, int p_key, const NMPlatformLink **out_link) { NMFakePlatformLink *device, *parent_device; char name[IFNAMSIZ]; parent_device = link_get (platform, parent); g_return_val_if_fail (parent_device != NULL, FALSE); nm_utils_new_infiniband_name (name, parent_device->link.name, p_key); if (!link_add (platform, name, NM_LINK_TYPE_INFINIBAND, NULL, 0, out_link)) return FALSE; device = link_get (platform, nm_platform_link_get_ifindex (platform, name)); g_return_val_if_fail (device, FALSE); g_return_val_if_fail (!device->lnk, FALSE); device->lnk = nmp_object_new (NMP_OBJECT_TYPE_LNK_VLAN, NULL); device->lnk->lnk_infiniband.p_key = p_key; device->lnk->lnk_infiniband.mode = "datagram"; device->link.parent = parent; return TRUE; } static gboolean infiniband_partition_delete (NMPlatform *platform, int parent, int p_key) { NMFakePlatformLink *parent_device; gs_free char *name = NULL; parent_device = link_get (platform, parent); g_return_val_if_fail (parent_device != NULL, FALSE); nm_utils_new_infiniband_name (name, parent_device->link.name, p_key); return link_delete (platform, nm_platform_link_get_ifindex (platform, name)); } static gboolean wifi_get_capabilities (NMPlatform *platform, int ifindex, NMDeviceWifiCapabilities *caps) { NMFakePlatformLink *device = link_get (platform, ifindex); g_return_val_if_fail (device, FALSE); if (device->link.type != NM_LINK_TYPE_WIFI) return FALSE; if (caps) { *caps = ( NM_WIFI_DEVICE_CAP_CIPHER_WEP40 | NM_WIFI_DEVICE_CAP_CIPHER_WEP104 | NM_WIFI_DEVICE_CAP_CIPHER_TKIP | NM_WIFI_DEVICE_CAP_CIPHER_CCMP | NM_WIFI_DEVICE_CAP_WPA | NM_WIFI_DEVICE_CAP_RSN | NM_WIFI_DEVICE_CAP_AP | NM_WIFI_DEVICE_CAP_ADHOC); } return TRUE; } static gboolean wifi_get_bssid (NMPlatform *platform, int ifindex, guint8 *bssid) { return FALSE; } static GByteArray * wifi_get_ssid (NMPlatform *platform, int ifindex) { return NULL; } static guint32 wifi_get_frequency (NMPlatform *platform, int ifindex) { return 0; } static int wifi_get_quality (NMPlatform *platform, int ifindex) { return 0; } static guint32 wifi_get_rate (NMPlatform *platform, int ifindex) { return 0; } static NM80211Mode wifi_get_mode (NMPlatform *platform, int ifindex) { return NM_802_11_MODE_UNKNOWN; } static void wifi_set_mode (NMPlatform *platform, int ifindex, NM80211Mode mode) { ; } static guint32 wifi_find_frequency (NMPlatform *platform, int ifindex, const guint32 *freqs) { return freqs[0]; } static void wifi_indicate_addressing_running (NMPlatform *platform, int ifindex, gboolean running) { ; } static guint32 mesh_get_channel (NMPlatform *platform, int ifindex) { return 0; } static gboolean mesh_set_channel (NMPlatform *platform, int ifindex, guint32 channel) { return FALSE; } static gboolean mesh_set_ssid (NMPlatform *platform, int ifindex, const guint8 *ssid, gsize len) { return FALSE; } /*****************************************************************************/ static GArray * ip4_address_get_all (NMPlatform *platform, int ifindex) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); GArray *addresses; NMPlatformIP4Address *address; int count = 0, i; /* Count addresses */ for (i = 0; i < priv->ip4_addresses->len; i++) { address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i); if (address && address->ifindex == ifindex) count++; } addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP4Address), count); /* Fill addresses */ for (i = 0; i < priv->ip4_addresses->len; i++) { address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i); if (address && address->ifindex == ifindex) g_array_append_val (addresses, *address); } return addresses; } static GArray * ip6_address_get_all (NMPlatform *platform, int ifindex) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); GArray *addresses; NMPlatformIP6Address *address; int count = 0, i; /* Count addresses */ for (i = 0; i < priv->ip6_addresses->len; i++) { address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i); if (address && address->ifindex == ifindex) count++; } addresses = g_array_sized_new (TRUE, TRUE, sizeof (NMPlatformIP6Address), count); /* Fill addresses */ count = 0; for (i = 0; i < priv->ip6_addresses->len; i++) { address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i); if (address && address->ifindex == ifindex) g_array_append_val (addresses, *address); } return addresses; } static gboolean ip4_address_add (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_addr, guint32 lifetime, guint32 preferred, guint32 flags, const char *label) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMPlatformIP4Address address; int i; memset (&address, 0, sizeof (address)); address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; address.ifindex = ifindex; address.address = addr; address.peer_address = peer_addr; address.plen = plen; address.timestamp = nm_utils_get_monotonic_timestamp_s (); address.lifetime = lifetime; address.preferred = preferred; address.n_ifa_flags = flags; if (label) g_strlcpy (address.label, label, sizeof (address.label)); for (i = 0; i < priv->ip4_addresses->len; i++) { NMPlatformIP4Address *item = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i); gboolean changed; if ( item->ifindex != address.ifindex || item->address != address.address || item->plen != address.plen || !_ip4_address_equal_peer_net (item->peer_address, address.peer_address, address.plen)) continue; changed = !nm_platform_ip4_address_cmp (item, &address); memcpy (item, &address, sizeof (address)); if (changed) g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED); return TRUE; } g_array_append_val (priv->ip4_addresses, address); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED); return TRUE; } static gboolean ip6_address_add (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen, struct in6_addr peer_addr, guint32 lifetime, guint32 preferred, guint32 flags) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMPlatformIP6Address address; int i; memset (&address, 0, sizeof (address)); address.addr_source = NM_IP_CONFIG_SOURCE_KERNEL; address.ifindex = ifindex; address.address = addr; address.peer_address = (IN6_IS_ADDR_UNSPECIFIED (&peer_addr) || IN6_ARE_ADDR_EQUAL (&addr, &peer_addr)) ? in6addr_any : peer_addr; address.plen = plen; address.timestamp = nm_utils_get_monotonic_timestamp_s (); address.lifetime = lifetime; address.preferred = preferred; address.n_ifa_flags = flags; for (i = 0; i < priv->ip6_addresses->len; i++) { NMPlatformIP6Address *item = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i); gboolean changed; if ( item->ifindex != address.ifindex || !IN6_ARE_ADDR_EQUAL (&item->address, &address.address)) continue; changed = !nm_platform_ip6_address_cmp (item, &address); memcpy (item, &address, sizeof (address)); if (changed) g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_CHANGED); return TRUE; } g_array_append_val (priv->ip6_addresses, address); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &address, NM_PLATFORM_SIGNAL_ADDED); return TRUE; } static gboolean ip4_address_delete (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; for (i = 0; i < priv->ip4_addresses->len; i++) { NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i); if ( address->ifindex == ifindex && address->plen == plen && address->address == addr && ((peer_address ^ address->peer_address) & nm_utils_ip4_prefix_to_netmask (plen)) == 0) { NMPlatformIP4Address deleted_address; memcpy (&deleted_address, address, sizeof (deleted_address)); memset (address, 0, sizeof (*address)); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP4_ADDRESS, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED); return TRUE; } } return TRUE; } static gboolean ip6_address_delete (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; for (i = 0; i < priv->ip6_addresses->len; i++) { NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i); if ( address->ifindex == ifindex && address->plen == plen && IN6_ARE_ADDR_EQUAL (&address->address, &addr)) { NMPlatformIP6Address deleted_address; memcpy (&deleted_address, address, sizeof (deleted_address)); memset (address, 0, sizeof (*address)); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED, NMP_OBJECT_TYPE_IP6_ADDRESS, ifindex, &deleted_address, NM_PLATFORM_SIGNAL_REMOVED); return TRUE; } } return TRUE; } static const NMPlatformIP4Address * ip4_address_get (NMPlatform *platform, int ifindex, in_addr_t addr, guint8 plen, in_addr_t peer_address) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; for (i = 0; i < priv->ip4_addresses->len; i++) { NMPlatformIP4Address *address = &g_array_index (priv->ip4_addresses, NMPlatformIP4Address, i); if ( address->ifindex == ifindex && address->plen == plen && address->address == addr && _ip4_address_equal_peer_net (address->peer_address, peer_address, plen)) return address; } return NULL; } static const NMPlatformIP6Address * ip6_address_get (NMPlatform *platform, int ifindex, struct in6_addr addr, guint8 plen) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; for (i = 0; i < priv->ip6_addresses->len; i++) { NMPlatformIP6Address *address = &g_array_index (priv->ip6_addresses, NMPlatformIP6Address, i); if ( address->ifindex == ifindex && address->plen == plen && IN6_ARE_ADDR_EQUAL (&address->address, &addr)) return address; } return NULL; } /*****************************************************************************/ static GArray * ip4_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); GArray *routes; NMPlatformIP4Route *route; guint i; routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route)); if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT)) flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT; /* Fill routes */ for (i = 0; i < priv->ip4_routes->len; i++) { route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i); if (route && (!ifindex || route->ifindex == ifindex)) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) { if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT)) g_array_append_val (routes, *route); } else { if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT)) g_array_append_val (routes, *route); } } } return routes; } static GArray * ip6_route_get_all (NMPlatform *platform, int ifindex, NMPlatformGetRouteFlags flags) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); GArray *routes; NMPlatformIP6Route *route; guint i; routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route)); if (!NM_FLAGS_ANY (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT)) flags |= NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT | NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT; /* Fill routes */ for (i = 0; i < priv->ip6_routes->len; i++) { route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i); if (route && (!ifindex || route->ifindex == ifindex)) { if (NM_PLATFORM_IP_ROUTE_IS_DEFAULT (route)) { if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_DEFAULT)) g_array_append_val (routes, *route); } else { if (NM_FLAGS_HAS (flags, NM_PLATFORM_GET_ROUTE_FLAGS_WITH_NON_DEFAULT)) g_array_append_val (routes, *route); } } } return routes; } static gboolean ip4_route_delete (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; for (i = 0; i < priv->ip4_routes->len; i++) { NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i); NMPlatformIP4Route deleted_route; if ( route->ifindex != ifindex || route->network != network || route->plen != plen || route->metric != metric) continue; memcpy (&deleted_route, route, sizeof (deleted_route)); g_array_remove_index (priv->ip4_routes, i); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED); } return TRUE; } static gboolean ip6_route_delete (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; metric = nm_utils_ip6_route_metric_normalize (metric); for (i = 0; i < priv->ip6_routes->len; i++) { NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i); NMPlatformIP6Route deleted_route; if ( route->ifindex != ifindex || !IN6_ARE_ADDR_EQUAL (&route->network, &network) || route->plen != plen || route->metric != metric) continue; memcpy (&deleted_route, route, sizeof (deleted_route)); g_array_remove_index (priv->ip6_routes, i); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &deleted_route, NM_PLATFORM_SIGNAL_REMOVED); } return TRUE; } static gboolean ip4_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source, in_addr_t network, guint8 plen, in_addr_t gateway, in_addr_t pref_src, guint32 metric, guint32 mss) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMPlatformIP4Route route; guint i; guint8 scope; g_assert (plen <= 32); scope = gateway == 0 ? RT_SCOPE_LINK : RT_SCOPE_UNIVERSE; memset (&route, 0, sizeof (route)); route.ifindex = ifindex; route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (source); route.network = nm_utils_ip4_address_clear_host_address (network, plen); route.plen = plen; route.gateway = gateway; route.metric = metric; route.mss = mss; route.scope_inv = nm_platform_route_scope_inv (scope); if (gateway) { for (i = 0; i < priv->ip4_routes->len; i++) { NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i); guint32 gate = ntohl (item->network) >> (32 - item->plen); guint32 host = ntohl (gateway) >> (32 - item->plen); if (ifindex == item->ifindex && gate == host) break; } if (i == priv->ip4_routes->len) { nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip4-route '%d: %s/%d %d': Network Unreachable", route.ifindex, nm_utils_inet4_ntop (route.network, NULL), route.plen, route.metric); return FALSE; } } for (i = 0; i < priv->ip4_routes->len; i++) { NMPlatformIP4Route *item = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i); if (item->network != route.network) continue; if (item->plen != route.plen) continue; if (item->metric != metric) continue; if (item->ifindex != route.ifindex) { ip4_route_delete (platform, item->ifindex, item->network, item->plen, item->metric); i--; continue; } memcpy (item, &route, sizeof (route)); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED); return TRUE; } g_array_append_val (priv->ip4_routes, route); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP4_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED); return TRUE; } static gboolean ip6_route_add (NMPlatform *platform, int ifindex, NMIPConfigSource source, struct in6_addr network, guint8 plen, struct in6_addr gateway, guint32 metric, guint32 mss) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); NMPlatformIP6Route route; guint i; metric = nm_utils_ip6_route_metric_normalize (metric); memset (&route, 0, sizeof (route)); route.ifindex = ifindex; route.rt_source = nmp_utils_ip_config_source_round_trip_rtprot (source); nm_utils_ip6_address_clear_host_address (&route.network, &network, plen); route.plen = plen; route.gateway = gateway; route.metric = metric; route.mss = mss; if (!IN6_IS_ADDR_UNSPECIFIED(&gateway)) { for (i = 0; i < priv->ip6_routes->len; i++) { NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i); guint8 gate_bits = gateway.s6_addr[item->plen / 8] >> (8 - item->plen % 8); guint8 host_bits = item->network.s6_addr[item->plen / 8] >> (8 - item->plen % 8); if ( ifindex == item->ifindex && memcmp (&gateway, &item->network, item->plen / 8) == 0 && gate_bits == host_bits) break; } if (i == priv->ip6_routes->len) { nm_log_warn (LOGD_PLATFORM, "Fake platform: failure adding ip6-route '%d: %s/%d %d': Network Unreachable", route.ifindex, nm_utils_inet6_ntop (&route.network, NULL), route.plen, route.metric); return FALSE; } } for (i = 0; i < priv->ip6_routes->len; i++) { NMPlatformIP6Route *item = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i); if (!IN6_ARE_ADDR_EQUAL (&item->network, &route.network)) continue; if (item->plen != route.plen) continue; if (item->metric != metric) continue; if (item->ifindex != route.ifindex) { ip6_route_delete (platform, item->ifindex, item->network, item->plen, item->metric); i--; continue; } memcpy (item, &route, sizeof (route)); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_CHANGED); return TRUE; } g_array_append_val (priv->ip6_routes, route); g_signal_emit_by_name (platform, NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED, NMP_OBJECT_TYPE_IP6_ROUTE, ifindex, &route, NM_PLATFORM_SIGNAL_ADDED); return TRUE; } static const NMPlatformIP4Route * ip4_route_get (NMPlatform *platform, int ifindex, in_addr_t network, guint8 plen, guint32 metric) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; for (i = 0; i < priv->ip4_routes->len; i++) { NMPlatformIP4Route *route = &g_array_index (priv->ip4_routes, NMPlatformIP4Route, i); if (route->ifindex == ifindex && route->network == network && route->plen == plen && route->metric == metric) return route; } return NULL; } static const NMPlatformIP6Route * ip6_route_get (NMPlatform *platform, int ifindex, struct in6_addr network, guint8 plen, guint32 metric) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) platform); int i; metric = nm_utils_ip6_route_metric_normalize (metric); for (i = 0; i < priv->ip6_routes->len; i++) { NMPlatformIP6Route *route = &g_array_index (priv->ip6_routes, NMPlatformIP6Route, i); if (route->ifindex == ifindex && IN6_ARE_ADDR_EQUAL (&route->network, &network) && route->plen == plen && route->metric == metric) return route; } return NULL; } /*****************************************************************************/ static void nm_fake_platform_init (NMFakePlatform *fake_platform) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE (fake_platform); priv->options = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free); priv->links = g_array_new (TRUE, TRUE, sizeof (NMFakePlatformLink)); priv->ip4_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Address)); priv->ip6_addresses = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Address)); priv->ip4_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP4Route)); priv->ip6_routes = g_array_new (TRUE, TRUE, sizeof (NMPlatformIP6Route)); } void nm_fake_platform_setup (void) { NMPlatform *platform; platform = g_object_new (NM_TYPE_FAKE_PLATFORM, NULL); nm_platform_setup (platform); /* skip zero element */ link_add (platform, NULL, NM_LINK_TYPE_NONE, NULL, 0, NULL); /* add loopback interface */ link_add (platform, "lo", NM_LINK_TYPE_LOOPBACK, NULL, 0, NULL); /* add some ethernets */ link_add (platform, "eth0", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL); link_add (platform, "eth1", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL); link_add (platform, "eth2", NM_LINK_TYPE_ETHERNET, NULL, 0, NULL); } static void finalize (GObject *object) { NMFakePlatformPrivate *priv = NM_FAKE_PLATFORM_GET_PRIVATE ((NMFakePlatform *) object); int i; g_hash_table_unref (priv->options); for (i = 0; i < priv->links->len; i++) { NMFakePlatformLink *device = &g_array_index (priv->links, NMFakePlatformLink, i); g_free (device->udi); g_clear_pointer (&device->lnk, nmp_object_unref); } g_array_unref (priv->links); g_array_unref (priv->ip4_addresses); g_array_unref (priv->ip6_addresses); g_array_unref (priv->ip4_routes); g_array_unref (priv->ip6_routes); G_OBJECT_CLASS (nm_fake_platform_parent_class)->finalize (object); } static void nm_fake_platform_class_init (NMFakePlatformClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS (klass); NMPlatformClass *platform_class = NM_PLATFORM_CLASS (klass); object_class->finalize = finalize; platform_class->sysctl_set = sysctl_set; platform_class->sysctl_get = sysctl_get; platform_class->link_get = _nm_platform_link_get; platform_class->link_get_by_ifname = _nm_platform_link_get_by_ifname; platform_class->link_get_by_address = _nm_platform_link_get_by_address; platform_class->link_get_all = link_get_all; platform_class->link_add = link_add; platform_class->link_delete = link_delete; platform_class->link_get_type_name = link_get_type_name; platform_class->link_get_lnk = link_get_lnk; platform_class->link_get_udi = link_get_udi; platform_class->link_set_up = link_set_up; platform_class->link_set_down = link_set_down; platform_class->link_set_arp = link_set_arp; platform_class->link_set_noarp = link_set_noarp; platform_class->link_set_address = link_set_address; platform_class->link_set_mtu = link_set_mtu; platform_class->link_get_driver_info = link_get_driver_info; platform_class->link_supports_carrier_detect = link_supports_carrier_detect; platform_class->link_supports_vlans = link_supports_vlans; platform_class->link_enslave = link_enslave; platform_class->link_release = link_release; platform_class->vlan_add = vlan_add; platform_class->link_vlan_change = link_vlan_change; platform_class->link_vxlan_add = link_vxlan_add; platform_class->infiniband_partition_add = infiniband_partition_add; platform_class->infiniband_partition_delete = infiniband_partition_delete; platform_class->wifi_get_capabilities = wifi_get_capabilities; platform_class->wifi_get_bssid = wifi_get_bssid; platform_class->wifi_get_ssid = wifi_get_ssid; platform_class->wifi_get_frequency = wifi_get_frequency; platform_class->wifi_get_quality = wifi_get_quality; platform_class->wifi_get_rate = wifi_get_rate; platform_class->wifi_get_mode = wifi_get_mode; platform_class->wifi_set_mode = wifi_set_mode; platform_class->wifi_find_frequency = wifi_find_frequency; platform_class->wifi_indicate_addressing_running = wifi_indicate_addressing_running; platform_class->mesh_get_channel = mesh_get_channel; platform_class->mesh_set_channel = mesh_set_channel; platform_class->mesh_set_ssid = mesh_set_ssid; platform_class->ip4_address_get = ip4_address_get; platform_class->ip6_address_get = ip6_address_get; platform_class->ip4_address_get_all = ip4_address_get_all; platform_class->ip6_address_get_all = ip6_address_get_all; platform_class->ip4_address_add = ip4_address_add; platform_class->ip6_address_add = ip6_address_add; platform_class->ip4_address_delete = ip4_address_delete; platform_class->ip6_address_delete = ip6_address_delete; platform_class->ip4_route_get = ip4_route_get; platform_class->ip6_route_get = ip6_route_get; platform_class->ip4_route_get_all = ip4_route_get_all; platform_class->ip6_route_get_all = ip6_route_get_all; platform_class->ip4_route_add = ip4_route_add; platform_class->ip6_route_add = ip6_route_add; platform_class->ip4_route_delete = ip4_route_delete; platform_class->ip6_route_delete = ip6_route_delete; }