dnl Checks for typedefs, structures, and compiler characteristics.
dnl
AC_TYPE_PID_T
++AC_CHECK_SIZEOF(dev_t)
dnl
dnl translation support
# primarily for its side effect of removing duplicates.
AM_CPPFLAGS += $(foreach d,$(sort $(dir $(libNetworkManager_la_SOURCES))),-I$(top_srcdir)/src/$d)
- systemd/src/libsystemd-network/ipv4ll-internal.h \
- systemd/src/libsystemd-network/ipv4ll-network.c \
- systemd/src/libsystemd-network/ipv4ll-packet.c \
+noinst_LTLIBRARIES = \
+ libNetworkManager.la \
+ libnm-iface-helper.la \
+ libsystemd-nm.la
+
+######################
+# libsystemd-nm
+######################
+
+SYSTEMD_NM_CFLAGS_PATHS = \
+ -I$(top_srcdir)/src/systemd/src/systemd \
+ -I$(top_srcdir)/src/systemd/src/libsystemd-network \
++ -I$(top_srcdir)/src/systemd/src/libsystemd/sd-event \
+ -I$(top_srcdir)/src/systemd/src/basic \
+ -I$(top_srcdir)/src/systemd
+
+libsystemd_nm_la_SOURCES = \
+ systemd/nm-sd-adapt.c \
+ systemd/nm-sd-adapt.h \
+ systemd/src/basic/async.h \
+ systemd/src/basic/fileio.c \
+ systemd/src/basic/fileio.h \
++ systemd/src/basic/hashmap.c \
++ systemd/src/basic/hashmap.h \
+ systemd/src/basic/hostname-util.c \
+ systemd/src/basic/hostname-util.h \
+ systemd/src/basic/in-addr-util.c \
+ systemd/src/basic/in-addr-util.h \
+ systemd/src/basic/list.h \
+ systemd/src/basic/log.h \
+ systemd/src/basic/macro.h \
++ systemd/src/basic/mempool.c \
++ systemd/src/basic/mempool.h \
+ systemd/src/basic/path-util.c \
+ systemd/src/basic/path-util.h \
++ systemd/src/basic/prioq.c \
++ systemd/src/basic/prioq.h \
+ systemd/src/basic/random-util.c \
+ systemd/src/basic/random-util.h \
+ systemd/src/basic/refcnt.h \
+ systemd/src/basic/siphash24.c \
+ systemd/src/basic/siphash24.h \
++ systemd/src/basic/set.h \
+ systemd/src/basic/socket-util.h \
+ systemd/src/basic/sparse-endian.h \
+ systemd/src/basic/strv.c \
+ systemd/src/basic/strv.h \
+ systemd/src/basic/time-util.c \
+ systemd/src/basic/time-util.h \
+ systemd/src/basic/unaligned.h \
+ systemd/src/basic/utf8.c \
+ systemd/src/basic/utf8.h \
+ systemd/src/basic/util.c \
+ systemd/src/basic/util.h \
++ systemd/src/libsystemd-network/arp-util.c \
++ systemd/src/libsystemd-network/arp-util.h \
+ systemd/src/libsystemd-network/dhcp-identifier.c \
+ systemd/src/libsystemd-network/dhcp-identifier.h \
+ systemd/src/libsystemd-network/dhcp-internal.h \
+ systemd/src/libsystemd-network/dhcp-lease-internal.h \
+ systemd/src/libsystemd-network/dhcp-network.c \
+ systemd/src/libsystemd-network/dhcp-option.c \
+ systemd/src/libsystemd-network/dhcp-packet.c \
+ systemd/src/libsystemd-network/dhcp-protocol.h \
+ systemd/src/libsystemd-network/dhcp6-internal.h \
+ systemd/src/libsystemd-network/dhcp6-lease-internal.h \
+ systemd/src/libsystemd-network/dhcp6-network.c \
+ systemd/src/libsystemd-network/dhcp6-option.c \
+ systemd/src/libsystemd-network/dhcp6-protocol.h \
++ systemd/src/libsystemd-network/lldp.h \
++ systemd/src/libsystemd-network/lldp-network.h \
++ systemd/src/libsystemd-network/lldp-network.c \
++ systemd/src/libsystemd-network/lldp-tlv.c \
++ systemd/src/libsystemd-network/lldp-tlv.h \
++ systemd/src/libsystemd-network/lldp-port.c \
++ systemd/src/libsystemd-network/lldp-port.h \
++ systemd/src/libsystemd-network/lldp-util.h \
++ systemd/src/libsystemd-network/lldp-internal.h \
++ systemd/src/libsystemd-network/lldp-internal.c \
+ systemd/src/libsystemd-network/network-internal.c \
+ systemd/src/libsystemd-network/network-internal.h \
+ systemd/src/libsystemd-network/sd-dhcp-client.c \
+ systemd/src/libsystemd-network/sd-dhcp-lease.c \
+ systemd/src/libsystemd-network/sd-dhcp6-client.c \
+ systemd/src/libsystemd-network/sd-dhcp6-lease.c \
++ systemd/src/libsystemd-network/sd-ipv4acd.c \
+ systemd/src/libsystemd-network/sd-ipv4ll.c \
++ systemd/src/libsystemd-network/sd-lldp.c \
+ systemd/src/libsystemd/sd-id128/sd-id128.c \
++ systemd/src/libsystemd/sd-event/event-util.h \
+ systemd/src/shared/dns-domain.c \
+ systemd/src/shared/dns-domain.h \
+ systemd/src/systemd/_sd-common.h \
+ systemd/src/systemd/sd-dhcp-client.h \
+ systemd/src/systemd/sd-dhcp-lease.h \
+ systemd/src/systemd/sd-dhcp6-client.h \
+ systemd/src/systemd/sd-dhcp6-lease.h \
++ systemd/src/systemd/sd-lldp.h \
+ systemd/src/systemd/sd-event.h \
+ systemd/src/systemd/sd-icmp6-nd.h \
+ systemd/src/systemd/sd-id128.h \
++ systemd/src/systemd/sd-ipv4acd.h \
+ systemd/src/systemd/sd-ipv4ll.h
+
+libsystemd_nm_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_builddir)/include \
+ -I$(top_srcdir)/libnm-core \
+ -I$(top_builddir)/libnm-core \
+ $(SYSTEMD_NM_CFLAGS_PATHS) \
+ -I$(top_srcdir)/src/systemd/src/shared \
+ -DNETWORKMANAGER_COMPILATION=NM_NETWORKMANAGER_COMPILATION_SYSTEMD \
+ $(GLIB_CFLAGS)
+
+libsystemd_nm_la_LIBADD = \
+ $(GLIB_LIBS)
+
###########################################
# NetworkManager
###########################################
if (g_strcmp0 (method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL) != 0)
return;
- if (strcmp (event, "BIND") == 0) {
- guint32 lla;
- NMIP4Config *config;
-
- if (inet_pton (AF_INET, address, &lla) <= 0) {
- _LOGE (LOGD_AUTOIP4, "invalid address %s received from avahi-autoipd.", address);
- nm_device_state_changed (self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
+ switch (event) {
- case IPV4LL_EVENT_BIND:
++ case SD_IPV4LL_EVENT_BIND:
+ r = sd_ipv4ll_get_address (ll, &address);
+ if (r < 0) {
+ _LOGE (LOGD_AUTOIP4, "invalid IPv4 link-local address received, error %d.", r);
+ priv->ip4_state = IP_FAIL;
+ nm_device_check_ip_failed (self, FALSE);
return;
}
--- /dev/null
- case DHCP_EVENT_EXPIRED:
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* 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) 2014 Red Hat, Inc.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <ctype.h>
+#include <net/if_arp.h>
+
+#include "nm-default.h"
+#include "nm-dhcp-systemd.h"
+#include "nm-utils.h"
+#include "nm-dhcp-utils.h"
+#include "NetworkManagerUtils.h"
+#include "nm-platform.h"
+
+#include "sd-dhcp-client.h"
+#include "sd-dhcp6-client.h"
+#include "dhcp-protocol.h"
+#include "dhcp-lease-internal.h"
+#include "dhcp6-protocol.h"
+#include "dhcp6-lease-internal.h"
+
+G_DEFINE_TYPE (NMDhcpSystemd, nm_dhcp_systemd, NM_TYPE_DHCP_CLIENT)
+
+#define NM_DHCP_SYSTEMD_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdPrivate))
+
+typedef struct {
+ struct sd_dhcp_client *client4;
+ struct sd_dhcp6_client *client6;
+ char *lease_file;
+
+ guint timeout_id;
+ guint request_count;
+
+ gboolean privacy;
+} NMDhcpSystemdPrivate;
+
+/************************************************************/
+
+#define DHCP_OPTION_NIS_DOMAIN 40
+#define DHCP_OPTION_NIS_SERVERS 41
+#define DHCP_OPTION_DOMAIN_SEARCH 119
+#define DHCP_OPTION_RFC3442_ROUTES 121
+#define DHCP_OPTION_MS_ROUTES 249
+#define DHCP_OPTION_WPAD 252
+
+/* Internal values */
+#define DHCP_OPTION_IP_ADDRESS 1024
+#define DHCP_OPTION_EXPIRY 1025
+#define DHCP6_OPTION_IP_ADDRESS 1026
+#define DHCP6_OPTION_PREFIXLEN 1027
+#define DHCP6_OPTION_PREFERRED_LIFE 1028
+#define DHCP6_OPTION_MAX_LIFE 1029
+#define DHCP6_OPTION_STARTS 1030
+#define DHCP6_OPTION_LIFE_STARTS 1031
+#define DHCP6_OPTION_RENEW 1032
+#define DHCP6_OPTION_REBIND 1033
+#define DHCP6_OPTION_IAID 1034
+
+typedef struct {
+ guint num;
+ const char *name;
+ gboolean include;
+} ReqOption;
+
+#define REQPREFIX "requested_"
+
+static const ReqOption dhcp4_requests[] = {
+ { DHCP_OPTION_SUBNET_MASK, REQPREFIX "subnet_mask", TRUE },
+ { DHCP_OPTION_TIME_OFFSET, REQPREFIX "time_offset", TRUE },
+ { DHCP_OPTION_ROUTER, REQPREFIX "routers", TRUE },
+ { DHCP_OPTION_DOMAIN_NAME_SERVER, REQPREFIX "domain_name_servers", TRUE },
+ { DHCP_OPTION_HOST_NAME, REQPREFIX "host_name", TRUE },
+ { DHCP_OPTION_DOMAIN_NAME, REQPREFIX "domain_name", TRUE },
+ { DHCP_OPTION_INTERFACE_MTU, REQPREFIX "interface_mtu", TRUE },
+ { DHCP_OPTION_BROADCAST, REQPREFIX "broadcast_address", TRUE },
+ { DHCP_OPTION_STATIC_ROUTE, REQPREFIX "static_routes", TRUE },
+ { DHCP_OPTION_NIS_DOMAIN, REQPREFIX "nis_domain", TRUE },
+ { DHCP_OPTION_NIS_SERVERS, REQPREFIX "nis_servers", TRUE },
+ { DHCP_OPTION_NTP_SERVER, REQPREFIX "ntp_servers", TRUE },
+ { DHCP_OPTION_SERVER_IDENTIFIER, REQPREFIX "dhcp_server_identifier", TRUE },
+ { DHCP_OPTION_DOMAIN_SEARCH, REQPREFIX "domain_search", TRUE },
+ { DHCP_OPTION_CLASSLESS_STATIC_ROUTE, REQPREFIX "rfc3442_classless_static_routes", TRUE },
+ { DHCP_OPTION_MS_ROUTES, REQPREFIX "ms_classless_static_routes", TRUE },
+ { DHCP_OPTION_WPAD, REQPREFIX "wpad", TRUE },
+
+ /* Internal values */
+ { DHCP_OPTION_IP_ADDRESS_LEASE_TIME, REQPREFIX "expiry", FALSE },
+ { DHCP_OPTION_CLIENT_IDENTIFIER, REQPREFIX "dhcp_client_identifier", FALSE },
+ { DHCP_OPTION_IP_ADDRESS, REQPREFIX "ip_address", FALSE },
+ { 0, NULL, FALSE }
+};
+
+static const ReqOption dhcp6_requests[] = {
+ { DHCP6_OPTION_CLIENTID, REQPREFIX "dhcp6_client_id", TRUE },
+
+ /* Don't request server ID by default; some servers don't reply to
+ * Information Requests that request the Server ID.
+ */
+ { DHCP6_OPTION_SERVERID, REQPREFIX "dhcp6_server_id", FALSE },
+
+ { DHCP6_OPTION_DNS_SERVERS, REQPREFIX "dhcp6_name_servers", TRUE },
+ { DHCP6_OPTION_DOMAIN_LIST, REQPREFIX "dhcp6_domain_search", TRUE },
+ { DHCP6_OPTION_SNTP_SERVERS, REQPREFIX "dhcp6_sntp_servers", TRUE },
+
+ /* Internal values */
+ { DHCP6_OPTION_IP_ADDRESS, REQPREFIX "ip6_address", FALSE },
+ { DHCP6_OPTION_PREFIXLEN, REQPREFIX "ip6_prefixlen", FALSE },
+ { DHCP6_OPTION_PREFERRED_LIFE, REQPREFIX "preferred_life", FALSE },
+ { DHCP6_OPTION_MAX_LIFE, REQPREFIX "max_life", FALSE },
+ { DHCP6_OPTION_STARTS, REQPREFIX "starts", FALSE },
+ { DHCP6_OPTION_LIFE_STARTS, REQPREFIX "life_starts", FALSE },
+ { DHCP6_OPTION_RENEW, REQPREFIX "renew", FALSE },
+ { DHCP6_OPTION_REBIND, REQPREFIX "rebind", FALSE },
+ { DHCP6_OPTION_IAID, REQPREFIX "iaid", FALSE },
+ { 0, NULL, FALSE }
+};
+
+static void
+take_option (GHashTable *options,
+ const ReqOption *requests,
+ guint option,
+ char *value)
+{
+ guint i;
+
+ g_return_if_fail (value != NULL);
+
+ for (i = 0; requests[i].name; i++) {
+ if (requests[i].num == option) {
+ g_hash_table_insert (options,
+ (gpointer) (requests[i].name + STRLEN (REQPREFIX)),
+ value);
+ break;
+ }
+ }
+ /* Option should always be found */
+ g_assert (requests[i].name);
+}
+
+static void
+add_option (GHashTable *options, const ReqOption *requests, guint option, const char *value)
+{
+ if (options)
+ take_option (options, requests, option, g_strdup (value));
+}
+
+static void
+add_option_u32 (GHashTable *options, const ReqOption *requests, guint option, guint32 value)
+{
+ if (options)
+ take_option (options, requests, option, g_strdup_printf ("%u", value));
+}
+
+static void
+add_option_u64 (GHashTable *options, const ReqOption *requests, guint option, guint64 value)
+{
+ if (options)
+ take_option (options, requests, option, g_strdup_printf ("%" G_GUINT64_FORMAT, value));
+}
+
+static void
+add_requests_to_options (GHashTable *options, const ReqOption *requests)
+{
+ guint i;
+
+ for (i = 0; options && requests[i].name; i++) {
+ if (requests[i].include)
+ g_hash_table_insert (options, (gpointer) requests[i].name, g_strdup ("1"));
+ }
+}
+
+#define LOG_LEASE(domain, ...) \
+G_STMT_START { \
+ if (log_lease) { \
+ nm_log (LOGL_INFO, (domain), __VA_ARGS__); \
+ } \
+} G_STMT_END
+
+static NMIP4Config *
+lease_to_ip4_config (const char *iface,
+ int ifindex,
+ sd_dhcp_lease *lease,
+ GHashTable *options,
+ guint32 default_priority,
+ gboolean log_lease,
+ GError **error)
+{
+ NMIP4Config *ip4_config = NULL;
+ struct in_addr tmp_addr;
+ const struct in_addr *addr_list;
+ char buf[INET_ADDRSTRLEN];
+ const char *str;
+ guint32 lifetime = 0, i;
+ NMPlatformIP4Address address;
+ GString *l;
+ struct sd_dhcp_route *routes;
+ guint16 mtu;
+ int r, num;
+ guint64 end_time;
+ const void *data;
+ gsize data_len;
+ gboolean metered = FALSE;
+
+ g_return_val_if_fail (lease != NULL, NULL);
+
+ ip4_config = nm_ip4_config_new (ifindex);
+
+ /* Address */
+ sd_dhcp_lease_get_address (lease, &tmp_addr);
+ memset (&address, 0, sizeof (address));
+ address.address = tmp_addr.s_addr;
+ str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+ LOG_LEASE (LOGD_DHCP4, " address %s", str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_IP_ADDRESS, str);
+
+ /* Prefix/netmask */
+ sd_dhcp_lease_get_netmask (lease, &tmp_addr);
+ address.plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr);
+ LOG_LEASE (LOGD_DHCP4, " plen %d", address.plen);
+ add_option (options,
+ dhcp4_requests,
+ DHCP_OPTION_SUBNET_MASK,
+ nm_utils_inet4_ntop (tmp_addr.s_addr, NULL));
+
+ /* Lease time */
+ sd_dhcp_lease_get_lifetime (lease, &lifetime);
+ address.timestamp = nm_utils_get_monotonic_timestamp_s ();
+ address.lifetime = address.preferred = lifetime;
+ end_time = (guint64) time (NULL) + lifetime;
+ LOG_LEASE (LOGD_DHCP4, " expires in %" G_GUINT32_FORMAT " seconds", lifetime);
+ add_option_u64 (options,
+ dhcp4_requests,
+ DHCP_OPTION_IP_ADDRESS_LEASE_TIME,
+ end_time);
+
+ address.source = NM_IP_CONFIG_SOURCE_DHCP;
+ nm_ip4_config_add_address (ip4_config, &address);
+
+ /* Gateway */
+ r = sd_dhcp_lease_get_router (lease, &tmp_addr);
+ if (r == 0) {
+ nm_ip4_config_set_gateway (ip4_config, tmp_addr.s_addr);
+ str = nm_utils_inet4_ntop (tmp_addr.s_addr, NULL);
+ LOG_LEASE (LOGD_DHCP4, " gateway %s", str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_ROUTER, str);
+ }
+
+ /* DNS Servers */
+ num = sd_dhcp_lease_get_dns (lease, &addr_list);
+ if (num > 0) {
+ l = g_string_sized_new (30);
+ for (i = 0; i < num; i++) {
+ if (addr_list[i].s_addr) {
+ nm_ip4_config_add_nameserver (ip4_config, addr_list[i].s_addr);
+ str = nm_utils_inet4_ntop (addr_list[i].s_addr, NULL);
+ LOG_LEASE (LOGD_DHCP4, " nameserver '%s'", str);
+ g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+ }
+ }
+ if (l->len)
+ add_option (options, dhcp4_requests, DHCP_OPTION_DOMAIN_NAME_SERVER, l->str);
+ g_string_free (l, TRUE);
+ }
+
+ /* Domain Name */
+ r = sd_dhcp_lease_get_domainname (lease, &str);
+ if (r == 0) {
+ /* Multiple domains sometimes stuffed into the option */
+ char **domains = g_strsplit (str, " ", 0);
+ char **s;
+
+ for (s = domains; *s; s++) {
+ LOG_LEASE (LOGD_DHCP4, " domain name '%s'", *s);
+ nm_ip4_config_add_domain (ip4_config, *s);
+ }
+ g_strfreev (domains);
+ add_option (options, dhcp4_requests, DHCP_OPTION_DOMAIN_NAME, str);
+ }
+
+ /* Hostname */
+ r = sd_dhcp_lease_get_hostname (lease, &str);
+ if (r == 0) {
+ LOG_LEASE (LOGD_DHCP4, " hostname '%s'", str);
+ add_option (options, dhcp4_requests, DHCP_OPTION_HOST_NAME, str);
+ }
+
+ /* Routes */
+ num = sd_dhcp_lease_get_routes (lease, &routes);
+ if (num > 0) {
+ l = g_string_sized_new (30);
+ for (i = 0; i < num; i++) {
+ NMPlatformIP4Route route;
+ const char *gw_str;
+
+ memset (&route, 0, sizeof (route));
+ route.network = routes[i].dst_addr.s_addr;
+ route.plen = routes[i].dst_prefixlen;
+ route.gateway = routes[i].gw_addr.s_addr;
+ route.source = NM_IP_CONFIG_SOURCE_DHCP;
+ route.metric = default_priority;
+ nm_ip4_config_add_route (ip4_config, &route);
+
+ str = nm_utils_inet4_ntop (route.network, buf);
+ gw_str = nm_utils_inet4_ntop (route.gateway, NULL);
+ LOG_LEASE (LOGD_DHCP4, " static route %s/%d gw %s", str, route.plen, gw_str);
+
+ g_string_append_printf (l, "%s%s/%d %s", l->len ? " " : "", str, route.plen, gw_str);
+ }
+ add_option (options, dhcp4_requests, DHCP_OPTION_RFC3442_ROUTES, l->str);
+ g_string_free (l, TRUE);
+ }
+
+ /* MTU */
+ r = sd_dhcp_lease_get_mtu (lease, &mtu);
+ if (r == 0 && mtu) {
+ nm_ip4_config_set_mtu (ip4_config, mtu, NM_IP_CONFIG_SOURCE_DHCP);
+ add_option_u32 (options, dhcp4_requests, DHCP_OPTION_INTERFACE_MTU, mtu);
+ LOG_LEASE (LOGD_DHCP4, " mtu %u", mtu);
+ }
+
+ /* NTP servers */
+ num = sd_dhcp_lease_get_ntp (lease, &addr_list);
+ if (num > 0) {
+ l = g_string_sized_new (30);
+ for (i = 0; i < num; i++) {
+ str = nm_utils_inet4_ntop (addr_list[i].s_addr, buf);
+ LOG_LEASE (LOGD_DHCP4, " ntp server '%s'", str);
+ g_string_append_printf (l, "%s%s", l->len ? " " : "", str);
+ }
+ add_option (options, dhcp4_requests, DHCP_OPTION_NTP_SERVER, l->str);
+ g_string_free (l, TRUE);
+ }
+
+ r = sd_dhcp_lease_get_vendor_specific (lease, &data, &data_len);
+ if (r >= 0)
+ metered = !!memmem (data, data_len, "ANDROID_METERED", STRLEN ("ANDROID_METERED"));
+ nm_ip4_config_set_metered (ip4_config, metered);
+
+ return ip4_config;
+}
+
+/************************************************************/
+
+static char *
+get_leasefile_path (const char *iface, const char *uuid, gboolean ipv6)
+{
+ return g_strdup_printf (NMSTATEDIR "/internal%s-%s-%s.lease",
+ ipv6 ? "6" : "",
+ uuid,
+ iface);
+}
+
+static GSList *
+nm_dhcp_systemd_get_lease_ip_configs (const char *iface,
+ int ifindex,
+ const char *uuid,
+ gboolean ipv6,
+ guint32 default_route_metric)
+{
+ GSList *leases = NULL;
+ gs_free char *path = NULL;
+ sd_dhcp_lease *lease = NULL;
+ NMIP4Config *ip4_config;
+ int r;
+
+ if (ipv6)
+ return NULL;
+
+ path = get_leasefile_path (iface, uuid, FALSE);
+ r = dhcp_lease_load (&lease, path);
+ if (r == 0 && lease) {
+ ip4_config = lease_to_ip4_config (iface, ifindex, lease, NULL, default_route_metric, FALSE, NULL);
+ if (ip4_config)
+ leases = g_slist_append (leases, ip4_config);
+ sd_dhcp_lease_unref (lease);
+ }
+
+ return leases;
+}
+
+/************************************************************/
+
+static void
+_save_client_id (NMDhcpSystemd *self,
+ uint8_t type,
+ const uint8_t *client_id,
+ size_t len)
+{
+ gs_unref_bytes GBytes *b = NULL;
+ gs_free char *buf = NULL;
+
+ g_return_if_fail (self != NULL);
+ g_return_if_fail (client_id != NULL);
+ g_return_if_fail (len > 0);
+
+ if (!nm_dhcp_client_get_client_id (NM_DHCP_CLIENT (self))) {
+ buf = g_malloc (len + 1);
+ buf[0] = type;
+ memcpy (buf + 1, client_id, len);
+ b = g_bytes_new (buf, len + 1);
+ nm_dhcp_client_set_client_id (NM_DHCP_CLIENT (self), b);
+ }
+}
+
+static void
+bound4_handle (NMDhcpSystemd *self)
+{
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+ const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
+ sd_dhcp_lease *lease;
+ NMIP4Config *ip4_config;
+ GHashTable *options;
+ GError *error = NULL;
+ int r;
+
+ nm_log_dbg (LOGD_DHCP4, "(%s): lease available", iface);
+
+ r = sd_dhcp_client_get_lease (priv->client4, &lease);
+ if (r < 0 || !lease) {
+ nm_log_warn (LOGD_DHCP4, "(%s): no lease!", iface);
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+ return;
+ }
+
+ options = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, g_free);
+ ip4_config = lease_to_ip4_config (iface,
+ nm_dhcp_client_get_ifindex (NM_DHCP_CLIENT (self)),
+ lease,
+ options,
+ nm_dhcp_client_get_priority (NM_DHCP_CLIENT (self)),
+ TRUE,
+ &error);
+ if (ip4_config) {
+ const uint8_t *client_id = NULL;
+ size_t client_id_len = 0;
+ uint8_t type = 0;
+
+ add_requests_to_options (options, dhcp4_requests);
+ dhcp_lease_save (lease, priv->lease_file);
+
+ sd_dhcp_client_get_client_id(priv->client4, &type, &client_id, &client_id_len);
+ if (client_id)
+ _save_client_id (self, type, client_id, client_id_len);
+
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (self),
+ NM_DHCP_STATE_BOUND,
+ G_OBJECT (ip4_config),
+ options);
+ } else {
+ nm_log_warn (LOGD_DHCP4, "(%s): %s", iface, error->message);
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+ g_clear_error (&error);
+ }
+
+ g_hash_table_destroy (options);
+ g_clear_object (&ip4_config);
+}
+
+static void
+dhcp_event_cb (sd_dhcp_client *client, int event, gpointer user_data)
+{
+ NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+ const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
+
+ g_assert (priv->client4 == client);
+
+ nm_log_dbg (LOGD_DHCP4, "(%s): DHCPv4 client event %d", iface, event);
+
+ switch (event) {
- case DHCP_EVENT_STOP:
++ case SD_DHCP_CLIENT_EVENT_EXPIRED:
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_EXPIRE, NULL, NULL);
+ break;
- case DHCP_EVENT_RENEW:
- case DHCP_EVENT_IP_CHANGE:
- case DHCP_EVENT_IP_ACQUIRE:
++ case SD_DHCP_CLIENT_EVENT_STOP:
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_FAIL, NULL, NULL);
+ break;
- case DHCP6_EVENT_RETRANS_MAX:
++ case SD_DHCP_CLIENT_EVENT_RENEW:
++ case SD_DHCP_CLIENT_EVENT_IP_CHANGE:
++ case SD_DHCP_CLIENT_EVENT_IP_ACQUIRE:
+ bound4_handle (self);
+ break;
+ default:
+ nm_log_warn (LOGD_DHCP4, "(%s): unhandled DHCP event %d", iface, event);
+ break;
+ }
+}
+
+static guint16
+get_arp_type (const GByteArray *hwaddr)
+{
+ if (hwaddr->len == ETH_ALEN)
+ return ARPHRD_ETHER;
+ else if (hwaddr->len == INFINIBAND_ALEN)
+ return ARPHRD_INFINIBAND;
+ else
+ g_assert_not_reached ();
+}
+
+static gboolean
+ip4_start (NMDhcpClient *client, const char *dhcp_anycast_addr, const char *last_ip4_address)
+{
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (client);
+ const char *iface = nm_dhcp_client_get_iface (client);
+ const GByteArray *hwaddr;
+ sd_dhcp_lease *lease = NULL;
+ GBytes *override_client_id;
+ const uint8_t *client_id = NULL;
+ size_t client_id_len = 0;
+ struct in_addr last_addr = { 0 };
+ const char *hostname;
+ int r, i;
+ gboolean success = FALSE;
+
+ g_assert (priv->client4 == NULL);
+ g_assert (priv->client6 == NULL);
+
+ g_free (priv->lease_file);
+ priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), FALSE);
+
+ r = sd_dhcp_client_new (&priv->client4);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to create DHCPv4 client (%d)", iface, r);
+ return FALSE;
+ }
+
+ r = sd_dhcp_client_attach_event (priv->client4, NULL, 0);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to attach DHCP event (%d)", iface, r);
+ goto error;
+ }
+
+ hwaddr = nm_dhcp_client_get_hw_addr (client);
+ if (hwaddr) {
+ r = sd_dhcp_client_set_mac (priv->client4,
+ hwaddr->data,
+ hwaddr->len,
+ get_arp_type (hwaddr));
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to set DHCP MAC address (%d)", iface, r);
+ goto error;
+ }
+ }
+
+ r = sd_dhcp_client_set_index (priv->client4, nm_dhcp_client_get_ifindex (client));
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to set DHCP ifindex (%d)", iface, r);
+ goto error;
+ }
+
+ r = sd_dhcp_client_set_callback (priv->client4, dhcp_event_cb, client);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to set DHCP callback (%d)", iface, r);
+ goto error;
+ }
+
+ r = sd_dhcp_client_set_request_broadcast (priv->client4, true);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to set DHCP broadcast (%d)", iface, r);
+ goto error;
+ }
+
+ dhcp_lease_load (&lease, priv->lease_file);
+
+ if (last_ip4_address)
+ inet_pton (AF_INET, last_ip4_address, &last_addr);
+ else if (lease)
+ sd_dhcp_lease_get_address (lease, &last_addr);
+
+ if (last_addr.s_addr) {
+ r = sd_dhcp_client_set_request_address (priv->client4, &last_addr);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to set last IPv4 address (%d)", iface, r);
+ goto error;
+ }
+ }
+
+ override_client_id = nm_dhcp_client_get_client_id (client);
+ if (override_client_id) {
+ client_id = g_bytes_get_data (override_client_id, &client_id_len);
+ g_assert (client_id && client_id_len);
+ sd_dhcp_client_set_client_id (priv->client4,
+ client_id[0],
+ client_id + 1,
+ client_id_len - 1);
+ } else if (lease) {
+ r = sd_dhcp_lease_get_client_id (lease, (const void **) &client_id, &client_id_len);
+ if (r == 0 && client_id_len) {
+ sd_dhcp_client_set_client_id (priv->client4,
+ client_id[0],
+ client_id + 1,
+ client_id_len - 1);
+ _save_client_id (NM_DHCP_SYSTEMD (client),
+ client_id[0],
+ client_id + 1,
+ client_id_len - 1);
+ }
+ }
+
+
+ /* Add requested options */
+ for (i = 0; dhcp4_requests[i].name; i++) {
+ if (dhcp4_requests[i].include)
+ sd_dhcp_client_set_request_option (priv->client4, dhcp4_requests[i].num);
+ }
+
+ hostname = nm_dhcp_client_get_hostname (client);
+ if (hostname) {
+ r = sd_dhcp_client_set_hostname (priv->client4, hostname);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to set DHCP hostname (%d)", iface, r);
+ goto error;
+ }
+ }
+
+ r = sd_dhcp_client_start (priv->client4);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP4, "(%s): failed to start DHCP (%d)", iface, r);
+ goto error;
+ }
+
+ success = TRUE;
+
+error:
+ sd_dhcp_lease_unref (lease);
+ if (!success)
+ priv->client4 = sd_dhcp_client_unref (priv->client4);
+ return success;
+}
+
+static void
+bound6_handle (NMDhcpSystemd *self)
+{
+ /* not yet supported... */
+ nm_log_warn (LOGD_DHCP6, "(%s): internal DHCP does not yet support DHCPv6",
+ nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self)));
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (self), NM_DHCP_STATE_FAIL, NULL, NULL);
+}
+
+static void
+dhcp6_event_cb (sd_dhcp6_client *client, int event, gpointer user_data)
+{
+ NMDhcpSystemd *self = NM_DHCP_SYSTEMD (user_data);
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (self);
+ const char *iface = nm_dhcp_client_get_iface (NM_DHCP_CLIENT (self));
+
+ g_assert (priv->client6 == client);
+
+ nm_log_dbg (LOGD_DHCP6, "(%s): DHCPv6 client event %d", iface, event);
+
+ switch (event) {
- case DHCP6_EVENT_RESEND_EXPIRE:
- case DHCP6_EVENT_STOP:
++ case SD_DHCP6_CLIENT_EVENT_RETRANS_MAX:
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_TIMEOUT, NULL, NULL);
+ break;
- case DHCP6_EVENT_IP_ACQUIRE:
++ case SD_DHCP6_CLIENT_EVENT_RESEND_EXPIRE:
++ case SD_DHCP6_CLIENT_EVENT_STOP:
+ nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_FAIL, NULL, NULL);
+ break;
++ case SD_DHCP6_CLIENT_EVENT_IP_ACQUIRE:
+ bound6_handle (self);
+ break;
+ default:
+ nm_log_warn (LOGD_DHCP6, "(%s): unhandled DHCPv6 event %d", iface, event);
+ break;
+ }
+}
+
+static gboolean
+ip6_start (NMDhcpClient *client,
+ const char *dhcp_anycast_addr,
+ gboolean info_only,
+ NMSettingIP6ConfigPrivacy privacy,
+ const GByteArray *duid)
+{
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (client);
+ const char *iface = nm_dhcp_client_get_iface (client);
+ const GByteArray *hwaddr;
+ int r, i;
+
+ g_assert (priv->client4 == NULL);
+ g_assert (priv->client6 == NULL);
+ g_return_val_if_fail (duid != NULL, FALSE);
+
+ g_free (priv->lease_file);
+ priv->lease_file = get_leasefile_path (iface, nm_dhcp_client_get_uuid (client), TRUE);
+
+ r = sd_dhcp6_client_new (&priv->client6);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to create DHCPv6 client (%d)", iface, r);
+ return FALSE;
+ }
+
+ /* NM stores the entire DUID which includes the uint16 "type", while systemd
+ * wants the type passed separately from the following data.
+ */
+ r = sd_dhcp6_client_set_duid (priv->client6,
+ ntohs (((const guint16 *) duid->data)[0]),
+ duid->data + 2,
+ duid->len - 2);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to create DHCPv6 client (%d)", iface, r);
+ return FALSE;
+ }
+
+ r = sd_dhcp6_client_attach_event (priv->client6, NULL, 0);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to attach DHCP event (%d)", iface, r);
+ goto error;
+ }
+
+ hwaddr = nm_dhcp_client_get_hw_addr (client);
+ if (hwaddr) {
+ r = sd_dhcp6_client_set_mac (priv->client6,
+ hwaddr->data,
+ hwaddr->len,
+ get_arp_type (hwaddr));
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to set DHCP MAC address (%d)", iface, r);
+ goto error;
+ }
+ }
+
+ r = sd_dhcp6_client_set_index (priv->client6, nm_dhcp_client_get_ifindex (client));
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to set DHCP ifindex (%d)", iface, r);
+ goto error;
+ }
+
+ r = sd_dhcp6_client_set_callback (priv->client6, dhcp6_event_cb, client);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to set DHCP callback (%d)", iface, r);
+ goto error;
+ }
+
+ /* Add requested options */
+ for (i = 0; dhcp6_requests[i].name; i++) {
+ if (dhcp6_requests[i].include)
+ sd_dhcp6_client_set_request_option (priv->client6, dhcp6_requests[i].num);
+ }
+
+ r = sd_dhcp6_client_start (priv->client6);
+ if (r < 0) {
+ nm_log_warn (LOGD_DHCP6, "(%s): failed to start DHCP (%d)", iface, r);
+ goto error;
+ }
+
+ return TRUE;
+
+error:
+ sd_dhcp6_client_unref (priv->client6);
+ priv->client6 = NULL;
+ return FALSE;
+}
+
+static void
+stop (NMDhcpClient *client, gboolean release, const GByteArray *duid)
+{
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (client);
+ int r = 0;
+
+ if (priv->client4)
+ r = sd_dhcp_client_stop (priv->client4);
+ else if (priv->client6)
+ r = sd_dhcp6_client_stop (priv->client6);
+
+ if (r) {
+ nm_log_warn (priv->client6 ? LOGD_DHCP6 : LOGD_DHCP4,
+ "(%s): failed to stop DHCP client (%d)",
+ nm_dhcp_client_get_iface (client),
+ r);
+ }
+}
+
+/***************************************************/
+
+static void
+nm_dhcp_systemd_init (NMDhcpSystemd *self)
+{
+}
+
+static void
+dispose (GObject *object)
+{
+ NMDhcpSystemdPrivate *priv = NM_DHCP_SYSTEMD_GET_PRIVATE (object);
+
+ g_clear_pointer (&priv->lease_file, g_free);
+
+ if (priv->client4) {
+ sd_dhcp_client_stop (priv->client4);
+ sd_dhcp_client_unref (priv->client4);
+ priv->client4 = NULL;
+ }
+
+ if (priv->client6) {
+ sd_dhcp6_client_stop (priv->client6);
+ sd_dhcp6_client_unref (priv->client6);
+ priv->client6 = NULL;
+ }
+
+ G_OBJECT_CLASS (nm_dhcp_systemd_parent_class)->dispose (object);
+}
+
+static void
+nm_dhcp_systemd_class_init (NMDhcpSystemdClass *sdhcp_class)
+{
+ NMDhcpClientClass *client_class = NM_DHCP_CLIENT_CLASS (sdhcp_class);
+ GObjectClass *object_class = G_OBJECT_CLASS (sdhcp_class);
+
+ g_type_class_add_private (sdhcp_class, sizeof (NMDhcpSystemdPrivate));
+
+ /* virtual methods */
+ object_class->dispose = dispose;
+
+ client_class->ip4_start = ip4_start;
+ client_class->ip6_start = ip6_start;
+ client_class->stop = stop;
+}
+
+static void __attribute__((constructor))
+register_dhcp_dhclient (void)
+{
+ nm_g_type_init ();
+ _nm_dhcp_client_register (NM_TYPE_DHCP_SYSTEMD,
+ "internal",
+ NULL,
+ nm_dhcp_systemd_get_lease_ip_configs);
+}
+
--- /dev/null
-
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
+/* 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) 2014 - 2015 Red Hat, Inc.
+ */
+
+#ifndef NM_SD_ADAPT_H
+#define NM_SD_ADAPT_H
+
+#include "config.h"
+
+#include <stdbool.h>
+#include <syslog.h>
+#include <sys/resource.h>
+
+#include "nm-default.h"
+
+#define noreturn G_GNUC_NORETURN
+
+/*****************************************************************************/
+
+static inline NMLogLevel
+_slog_level_to_nm (int slevel)
+{
+ switch (slevel) {
+ case LOG_DEBUG: return LOGL_DEBUG;
+ case LOG_WARNING: return LOGL_WARN;
+ case LOG_CRIT:
+ case LOG_ERR: return LOGL_ERR;
+ case LOG_INFO:
+ case LOG_NOTICE:
+ default: return LOGL_INFO;
+ }
+}
+
+#define log_internal(level, error, file, line, func, format, ...) \
+({ \
+ const int _nm_e = (error); \
+ const NMLogLevel _nm_l = _slog_level_to_nm ((level)); \
+ \
+ if (nm_logging_enabled (_nm_l, LOGD_DHCP)) { \
+ const char *_nm_location = strrchr ((""file), '/'); \
+ \
+ _nm_log_impl (_nm_location ? _nm_location + 1 : (""file), (line), (func), _nm_l, LOGD_DHCP, _nm_e, ("%s"format), "libsystemd: ", ## __VA_ARGS__); \
+ } \
+ (_nm_e > 0 ? -_nm_e : _nm_e); \
+})
+
+#define log_full_errno(level, error, ...) \
+({ \
+ log_internal(level, error, __FILE__, __LINE__, __func__, __VA_ARGS__); \
+})
+
+#define log_assert_failed(text, file, line, func) \
+G_STMT_START { \
+ log_internal (LOG_CRIT, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Aborting.", text, file, line, func); \
+ g_assert_not_reached (); \
+} G_STMT_END
+
++#define log_assert_failed_unreachable(text, file, line, func) \
++G_STMT_START { \
++ log_internal (LOG_CRIT, 0, file, line, func, "Code should not be reached '%s' at %s:%u, function %s(). Aborting.", text, file, line, func); \
++ g_assert_not_reached (); \
++} G_STMT_END
++
+#define log_assert_failed_return(text, file, line, func) \
+({ \
+ log_internal (LOG_DEBUG, 0, file, line, func, "Assertion '%s' failed at %s:%u, function %s(). Ignoring.", text, file, line, func); \
+ g_return_if_fail_warning (G_LOG_DOMAIN, G_STRFUNC, text); \
+ (void) 0; \
+})
+
+/*****************************************************************************
+ * The remainder of the header is only enabled when building the systemd code
+ * itself.
+ *****************************************************************************/
+
+#if (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD
+
+#include <netinet/in.h>
+#include <string.h>
+#include <stdio.h>
+#include <errno.h>
+#include <elf.h>
+#ifdef HAVE_SYS_AUXV_H
+#include <sys/auxv.h>
+#endif
+#include <unistd.h>
+#include <sys/syscall.h>
+
+#include <net/if_arp.h>
+
+/* Missing in Linux 3.2.0, in Ubuntu 12.04 */
+#ifndef BPF_XOR
+#define BPF_XOR 0xa0
+#endif
+
+/*****************************************************************************/
+
+/* Can't include both net/if.h and linux/if.h; so have to define this here */
+#ifndef IFNAMSIZ
+#define IFNAMSIZ 16
+#endif
+
+#ifndef MAX_HANDLE_SZ
+#define MAX_HANDLE_SZ 128
+#endif
+
+
+/*
+ * Some toolchains (E.G. uClibc 0.9.33 and earlier) don't export
+ * CLOCK_BOOTTIME even though the kernel supports it, so provide a
+ * local definition
+ */
+#ifndef CLOCK_BOOTTIME
+#define CLOCK_BOOTTIME 7
+#endif
+
+#include "sd-id128.h"
+#include "sparse-endian.h"
+#include "async.h"
+#include "util.h"
+
+static inline pid_t gettid(void) {
+ return (pid_t) syscall(SYS_gettid);
+}
+
++static inline bool is_main_thread(void) {
++ return TRUE;
++}
++
+#endif /* (NETWORKMANAGER_COMPILATION) == NM_NETWORKMANAGER_COMPILATION_SYSTEMD */
+
+#endif /* NM_SD_ADAPT_H */
+
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2014 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <stdlib.h>
+ #include <errno.h>
+ #include <pthread.h>
+
+ #include "util.h"
+ #include "hashmap.h"
+ #include "set.h"
+ #include "macro.h"
+ #include "siphash24.h"
+ #include "strv.h"
+ #include "mempool.h"
+ #include "random-util.h"
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ #include "list.h"
+ #endif
+
+ /*
+ * Implementation of hashmaps.
+ * Addressing: open
+ * - uses less RAM compared to closed addressing (chaining), because
+ * our entries are small (especially in Sets, which tend to contain
+ * the majority of entries in systemd).
+ * Collision resolution: Robin Hood
+ * - tends to equalize displacement of entries from their optimal buckets.
+ * Probe sequence: linear
+ * - though theoretically worse than random probing/uniform hashing/double
+ * hashing, it is good for cache locality.
+ *
+ * References:
+ * Celis, P. 1986. Robin Hood Hashing.
+ * Ph.D. Dissertation. University of Waterloo, Waterloo, Ont., Canada, Canada.
+ * https://cs.uwaterloo.ca/research/tr/1986/CS-86-14.pdf
+ * - The results are derived for random probing. Suggests deletion with
+ * tombstones and two mean-centered search methods. None of that works
+ * well for linear probing.
+ *
+ * Janson, S. 2005. Individual displacements for linear probing hashing with different insertion policies.
+ * ACM Trans. Algorithms 1, 2 (October 2005), 177-213.
+ * DOI=10.1145/1103963.1103964 http://doi.acm.org/10.1145/1103963.1103964
+ * http://www.math.uu.se/~svante/papers/sj157.pdf
+ * - Applies to Robin Hood with linear probing. Contains remarks on
+ * the unsuitability of mean-centered search with linear probing.
+ *
+ * Viola, A. 2005. Exact distribution of individual displacements in linear probing hashing.
+ * ACM Trans. Algorithms 1, 2 (October 2005), 214-242.
+ * DOI=10.1145/1103963.1103965 http://doi.acm.org/10.1145/1103963.1103965
+ * - Similar to Janson. Note that Viola writes about C_{m,n} (number of probes
+ * in a successful search), and Janson writes about displacement. C = d + 1.
+ *
+ * Goossaert, E. 2013. Robin Hood hashing: backward shift deletion.
+ * http://codecapsule.com/2013/11/17/robin-hood-hashing-backward-shift-deletion/
+ * - Explanation of backward shift deletion with pictures.
+ *
+ * Khuong, P. 2013. The Other Robin Hood Hashing.
+ * http://www.pvk.ca/Blog/2013/11/26/the-other-robin-hood-hashing/
+ * - Short summary of random vs. linear probing, and tombstones vs. backward shift.
+ */
+
+ /*
+ * XXX Ideas for improvement:
+ * For unordered hashmaps, randomize iteration order, similarly to Perl:
+ * http://blog.booking.com/hardening-perls-hash-function.html
+ */
+
+ /* INV_KEEP_FREE = 1 / (1 - max_load_factor)
+ * e.g. 1 / (1 - 0.8) = 5 ... keep one fifth of the buckets free. */
+ #define INV_KEEP_FREE 5U
+
+ /* Fields common to entries of all hashmap/set types */
+ struct hashmap_base_entry {
+ const void *key;
+ };
+
+ /* Entry types for specific hashmap/set types
+ * hashmap_base_entry must be at the beginning of each entry struct. */
+
+ struct plain_hashmap_entry {
+ struct hashmap_base_entry b;
+ void *value;
+ };
+
+ struct ordered_hashmap_entry {
+ struct plain_hashmap_entry p;
+ unsigned iterate_next, iterate_previous;
+ };
+
+ struct set_entry {
+ struct hashmap_base_entry b;
+ };
+
+ /* In several functions it is advantageous to have the hash table extended
+ * virtually by a couple of additional buckets. We reserve special index values
+ * for these "swap" buckets. */
+ #define _IDX_SWAP_BEGIN (UINT_MAX - 3)
+ #define IDX_PUT (_IDX_SWAP_BEGIN + 0)
+ #define IDX_TMP (_IDX_SWAP_BEGIN + 1)
+ #define _IDX_SWAP_END (_IDX_SWAP_BEGIN + 2)
+
+ #define IDX_FIRST (UINT_MAX - 1) /* special index for freshly initialized iterators */
+ #define IDX_NIL UINT_MAX /* special index value meaning "none" or "end" */
+
+ assert_cc(IDX_FIRST == _IDX_SWAP_END);
+ assert_cc(IDX_FIRST == _IDX_ITERATOR_FIRST);
+
+ /* Storage space for the "swap" buckets.
+ * All entry types can fit into a ordered_hashmap_entry. */
+ struct swap_entries {
+ struct ordered_hashmap_entry e[_IDX_SWAP_END - _IDX_SWAP_BEGIN];
+ };
+
+ /* Distance from Initial Bucket */
+ typedef uint8_t dib_raw_t;
+ #define DIB_RAW_OVERFLOW ((dib_raw_t)0xfdU) /* indicates DIB value is greater than representable */
+ #define DIB_RAW_REHASH ((dib_raw_t)0xfeU) /* entry yet to be rehashed during in-place resize */
+ #define DIB_RAW_FREE ((dib_raw_t)0xffU) /* a free bucket */
+ #define DIB_RAW_INIT ((char)DIB_RAW_FREE) /* a byte to memset a DIB store with when initializing */
+
+ #define DIB_FREE UINT_MAX
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ struct hashmap_debug_info {
+ LIST_FIELDS(struct hashmap_debug_info, debug_list);
+ unsigned max_entries; /* high watermark of n_entries */
+
+ /* who allocated this hashmap */
+ int line;
+ const char *file;
+ const char *func;
+
+ /* fields to detect modification while iterating */
+ unsigned put_count; /* counts puts into the hashmap */
+ unsigned rem_count; /* counts removals from hashmap */
+ unsigned last_rem_idx; /* remembers last removal index */
+ };
+
+ /* Tracks all existing hashmaps. Get at it from gdb. See sd_dump_hashmaps.py */
+ static LIST_HEAD(struct hashmap_debug_info, hashmap_debug_list);
+ static pthread_mutex_t hashmap_debug_list_mutex = PTHREAD_MUTEX_INITIALIZER;
+
+ #define HASHMAP_DEBUG_FIELDS struct hashmap_debug_info debug;
+
+ #else /* !ENABLE_DEBUG_HASHMAP */
+ #define HASHMAP_DEBUG_FIELDS
+ #endif /* ENABLE_DEBUG_HASHMAP */
+
+ enum HashmapType {
+ HASHMAP_TYPE_PLAIN,
+ HASHMAP_TYPE_ORDERED,
+ HASHMAP_TYPE_SET,
+ _HASHMAP_TYPE_MAX
+ };
+
+ struct _packed_ indirect_storage {
+ char *storage; /* where buckets and DIBs are stored */
+ uint8_t hash_key[HASH_KEY_SIZE]; /* hash key; changes during resize */
+
+ unsigned n_entries; /* number of stored entries */
+ unsigned n_buckets; /* number of buckets */
+
+ unsigned idx_lowest_entry; /* Index below which all buckets are free.
+ Makes "while(hashmap_steal_first())" loops
+ O(n) instead of O(n^2) for unordered hashmaps. */
+ uint8_t _pad[3]; /* padding for the whole HashmapBase */
+ /* The bitfields in HashmapBase complete the alignment of the whole thing. */
+ };
+
+ struct direct_storage {
+ /* This gives us 39 bytes on 64bit, or 35 bytes on 32bit.
+ * That's room for 4 set_entries + 4 DIB bytes + 3 unused bytes on 64bit,
+ * or 7 set_entries + 7 DIB bytes + 0 unused bytes on 32bit. */
+ char storage[sizeof(struct indirect_storage)];
+ };
+
+ #define DIRECT_BUCKETS(entry_t) \
+ (sizeof(struct direct_storage) / (sizeof(entry_t) + sizeof(dib_raw_t)))
+
+ /* We should be able to store at least one entry directly. */
+ assert_cc(DIRECT_BUCKETS(struct ordered_hashmap_entry) >= 1);
+
+ /* We have 3 bits for n_direct_entries. */
+ assert_cc(DIRECT_BUCKETS(struct set_entry) < (1 << 3));
+
+ /* Hashmaps with directly stored entries all use this shared hash key.
+ * It's no big deal if the key is guessed, because there can be only
+ * a handful of directly stored entries in a hashmap. When a hashmap
+ * outgrows direct storage, it gets its own key for indirect storage. */
+ static uint8_t shared_hash_key[HASH_KEY_SIZE];
+ static bool shared_hash_key_initialized;
+
+ /* Fields that all hashmap/set types must have */
+ struct HashmapBase {
+ const struct hash_ops *hash_ops; /* hash and compare ops to use */
+
+ union _packed_ {
+ struct indirect_storage indirect; /* if has_indirect */
+ struct direct_storage direct; /* if !has_indirect */
+ };
+
+ enum HashmapType type:2; /* HASHMAP_TYPE_* */
+ bool has_indirect:1; /* whether indirect storage is used */
+ unsigned n_direct_entries:3; /* Number of entries in direct storage.
+ * Only valid if !has_indirect. */
+ bool from_pool:1; /* whether was allocated from mempool */
+ HASHMAP_DEBUG_FIELDS /* optional hashmap_debug_info */
+ };
+
+ /* Specific hash types
+ * HashmapBase must be at the beginning of each hashmap struct. */
+
+ struct Hashmap {
+ struct HashmapBase b;
+ };
+
+ struct OrderedHashmap {
+ struct HashmapBase b;
+ unsigned iterate_list_head, iterate_list_tail;
+ };
+
+ struct Set {
+ struct HashmapBase b;
+ };
+
+ DEFINE_MEMPOOL(hashmap_pool, Hashmap, 8);
+ DEFINE_MEMPOOL(ordered_hashmap_pool, OrderedHashmap, 8);
+ /* No need for a separate Set pool */
+ assert_cc(sizeof(Hashmap) == sizeof(Set));
+
+ struct hashmap_type_info {
+ size_t head_size;
+ size_t entry_size;
+ struct mempool *mempool;
+ unsigned n_direct_buckets;
+ };
+
+ static const struct hashmap_type_info hashmap_type_info[_HASHMAP_TYPE_MAX] = {
+ [HASHMAP_TYPE_PLAIN] = {
+ .head_size = sizeof(Hashmap),
+ .entry_size = sizeof(struct plain_hashmap_entry),
+ .mempool = &hashmap_pool,
+ .n_direct_buckets = DIRECT_BUCKETS(struct plain_hashmap_entry),
+ },
+ [HASHMAP_TYPE_ORDERED] = {
+ .head_size = sizeof(OrderedHashmap),
+ .entry_size = sizeof(struct ordered_hashmap_entry),
+ .mempool = &ordered_hashmap_pool,
+ .n_direct_buckets = DIRECT_BUCKETS(struct ordered_hashmap_entry),
+ },
+ [HASHMAP_TYPE_SET] = {
+ .head_size = sizeof(Set),
+ .entry_size = sizeof(struct set_entry),
+ .mempool = &hashmap_pool,
+ .n_direct_buckets = DIRECT_BUCKETS(struct set_entry),
+ },
+ };
+
+ void string_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(p, strlen(p) + 1, state);
+ }
+
+ int string_compare_func(const void *a, const void *b) {
+ return strcmp(a, b);
+ }
+
+ const struct hash_ops string_hash_ops = {
+ .hash = string_hash_func,
+ .compare = string_compare_func
+ };
+
+ void trivial_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(&p, sizeof(p), state);
+ }
+
+ int trivial_compare_func(const void *a, const void *b) {
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
+
+ const struct hash_ops trivial_hash_ops = {
+ .hash = trivial_hash_func,
+ .compare = trivial_compare_func
+ };
+
+ void uint64_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(p, sizeof(uint64_t), state);
+ }
+
+ int uint64_compare_func(const void *_a, const void *_b) {
+ uint64_t a, b;
+ a = *(const uint64_t*) _a;
+ b = *(const uint64_t*) _b;
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
+
+ const struct hash_ops uint64_hash_ops = {
+ .hash = uint64_hash_func,
+ .compare = uint64_compare_func
+ };
+
+ #if SIZEOF_DEV_T != 8
+ void devt_hash_func(const void *p, struct siphash *state) {
+ siphash24_compress(p, sizeof(dev_t), state);
+ }
+
+ int devt_compare_func(const void *_a, const void *_b) {
+ dev_t a, b;
+ a = *(const dev_t*) _a;
+ b = *(const dev_t*) _b;
+ return a < b ? -1 : (a > b ? 1 : 0);
+ }
+
+ const struct hash_ops devt_hash_ops = {
+ .hash = devt_hash_func,
+ .compare = devt_compare_func
+ };
+ #endif
+
+ static unsigned n_buckets(HashmapBase *h) {
+ return h->has_indirect ? h->indirect.n_buckets
+ : hashmap_type_info[h->type].n_direct_buckets;
+ }
+
+ static unsigned n_entries(HashmapBase *h) {
+ return h->has_indirect ? h->indirect.n_entries
+ : h->n_direct_entries;
+ }
+
+ static void n_entries_inc(HashmapBase *h) {
+ if (h->has_indirect)
+ h->indirect.n_entries++;
+ else
+ h->n_direct_entries++;
+ }
+
+ static void n_entries_dec(HashmapBase *h) {
+ if (h->has_indirect)
+ h->indirect.n_entries--;
+ else
+ h->n_direct_entries--;
+ }
+
+ static char *storage_ptr(HashmapBase *h) {
+ return h->has_indirect ? h->indirect.storage
+ : h->direct.storage;
+ }
+
+ static uint8_t *hash_key(HashmapBase *h) {
+ return h->has_indirect ? h->indirect.hash_key
+ : shared_hash_key;
+ }
+
+ static unsigned base_bucket_hash(HashmapBase *h, const void *p) {
+ struct siphash state;
+ uint64_t hash;
+
+ siphash24_init(&state, hash_key(h));
+
+ h->hash_ops->hash(p, &state);
+
+ siphash24_finalize((uint8_t*)&hash, &state);
+
+ return (unsigned) (hash % n_buckets(h));
+ }
+ #define bucket_hash(h, p) base_bucket_hash(HASHMAP_BASE(h), p)
+
+ static void get_hash_key(uint8_t hash_key[HASH_KEY_SIZE], bool reuse_is_ok) {
+ static uint8_t current[HASH_KEY_SIZE];
+ static bool current_initialized = false;
+
+ /* Returns a hash function key to use. In order to keep things
+ * fast we will not generate a new key each time we allocate a
+ * new hash table. Instead, we'll just reuse the most recently
+ * generated one, except if we never generated one or when we
+ * are rehashing an entire hash table because we reached a
+ * fill level */
+
+ if (!current_initialized || !reuse_is_ok) {
+ random_bytes(current, sizeof(current));
+ current_initialized = true;
+ }
+
+ memcpy(hash_key, current, sizeof(current));
+ }
+
+ static struct hashmap_base_entry *bucket_at(HashmapBase *h, unsigned idx) {
+ return (struct hashmap_base_entry*)
+ (storage_ptr(h) + idx * hashmap_type_info[h->type].entry_size);
+ }
+
+ static struct plain_hashmap_entry *plain_bucket_at(Hashmap *h, unsigned idx) {
+ return (struct plain_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx);
+ }
+
+ static struct ordered_hashmap_entry *ordered_bucket_at(OrderedHashmap *h, unsigned idx) {
+ return (struct ordered_hashmap_entry*) bucket_at(HASHMAP_BASE(h), idx);
+ }
+
+ static struct set_entry *set_bucket_at(Set *h, unsigned idx) {
+ return (struct set_entry*) bucket_at(HASHMAP_BASE(h), idx);
+ }
+
+ static struct ordered_hashmap_entry *bucket_at_swap(struct swap_entries *swap, unsigned idx) {
+ return &swap->e[idx - _IDX_SWAP_BEGIN];
+ }
+
+ /* Returns a pointer to the bucket at index idx.
+ * Understands real indexes and swap indexes, hence "_virtual". */
+ static struct hashmap_base_entry *bucket_at_virtual(HashmapBase *h, struct swap_entries *swap,
+ unsigned idx) {
+ if (idx < _IDX_SWAP_BEGIN)
+ return bucket_at(h, idx);
+
+ if (idx < _IDX_SWAP_END)
+ return &bucket_at_swap(swap, idx)->p.b;
+
+ assert_not_reached("Invalid index");
+ }
+
+ static dib_raw_t *dib_raw_ptr(HashmapBase *h) {
+ return (dib_raw_t*)
+ (storage_ptr(h) + hashmap_type_info[h->type].entry_size * n_buckets(h));
+ }
+
+ static unsigned bucket_distance(HashmapBase *h, unsigned idx, unsigned from) {
+ return idx >= from ? idx - from
+ : n_buckets(h) + idx - from;
+ }
+
+ static unsigned bucket_calculate_dib(HashmapBase *h, unsigned idx, dib_raw_t raw_dib) {
+ unsigned initial_bucket;
+
+ if (raw_dib == DIB_RAW_FREE)
+ return DIB_FREE;
+
+ if (_likely_(raw_dib < DIB_RAW_OVERFLOW))
+ return raw_dib;
+
+ /*
+ * Having an overflow DIB value is very unlikely. The hash function
+ * would have to be bad. For example, in a table of size 2^24 filled
+ * to load factor 0.9 the maximum observed DIB is only about 60.
+ * In theory (assuming I used Maxima correctly), for an infinite size
+ * hash table with load factor 0.8 the probability of a given entry
+ * having DIB > 40 is 1.9e-8.
+ * This returns the correct DIB value by recomputing the hash value in
+ * the unlikely case. XXX Hitting this case could be a hint to rehash.
+ */
+ initial_bucket = bucket_hash(h, bucket_at(h, idx)->key);
+ return bucket_distance(h, idx, initial_bucket);
+ }
+
+ static void bucket_set_dib(HashmapBase *h, unsigned idx, unsigned dib) {
+ dib_raw_ptr(h)[idx] = dib != DIB_FREE ? MIN(dib, DIB_RAW_OVERFLOW) : DIB_RAW_FREE;
+ }
+
+ static unsigned skip_free_buckets(HashmapBase *h, unsigned idx) {
+ dib_raw_t *dibs;
+
+ dibs = dib_raw_ptr(h);
+
+ for ( ; idx < n_buckets(h); idx++)
+ if (dibs[idx] != DIB_RAW_FREE)
+ return idx;
+
+ return IDX_NIL;
+ }
+
+ static void bucket_mark_free(HashmapBase *h, unsigned idx) {
+ memzero(bucket_at(h, idx), hashmap_type_info[h->type].entry_size);
+ bucket_set_dib(h, idx, DIB_FREE);
+ }
+
+ static void bucket_move_entry(HashmapBase *h, struct swap_entries *swap,
+ unsigned from, unsigned to) {
+ struct hashmap_base_entry *e_from, *e_to;
+
+ assert(from != to);
+
+ e_from = bucket_at_virtual(h, swap, from);
+ e_to = bucket_at_virtual(h, swap, to);
+
+ memcpy(e_to, e_from, hashmap_type_info[h->type].entry_size);
+
+ if (h->type == HASHMAP_TYPE_ORDERED) {
+ OrderedHashmap *lh = (OrderedHashmap*) h;
+ struct ordered_hashmap_entry *le, *le_to;
+
+ le_to = (struct ordered_hashmap_entry*) e_to;
+
+ if (le_to->iterate_next != IDX_NIL) {
+ le = (struct ordered_hashmap_entry*)
+ bucket_at_virtual(h, swap, le_to->iterate_next);
+ le->iterate_previous = to;
+ }
+
+ if (le_to->iterate_previous != IDX_NIL) {
+ le = (struct ordered_hashmap_entry*)
+ bucket_at_virtual(h, swap, le_to->iterate_previous);
+ le->iterate_next = to;
+ }
+
+ if (lh->iterate_list_head == from)
+ lh->iterate_list_head = to;
+ if (lh->iterate_list_tail == from)
+ lh->iterate_list_tail = to;
+ }
+ }
+
+ static unsigned next_idx(HashmapBase *h, unsigned idx) {
+ return (idx + 1U) % n_buckets(h);
+ }
+
+ static unsigned prev_idx(HashmapBase *h, unsigned idx) {
+ return (n_buckets(h) + idx - 1U) % n_buckets(h);
+ }
+
+ static void *entry_value(HashmapBase *h, struct hashmap_base_entry *e) {
+ switch (h->type) {
+
+ case HASHMAP_TYPE_PLAIN:
+ case HASHMAP_TYPE_ORDERED:
+ return ((struct plain_hashmap_entry*)e)->value;
+
+ case HASHMAP_TYPE_SET:
+ return (void*) e->key;
+
+ default:
+ assert_not_reached("Unknown hashmap type");
+ }
+ }
+
+ static void base_remove_entry(HashmapBase *h, unsigned idx) {
+ unsigned left, right, prev, dib;
+ dib_raw_t raw_dib, *dibs;
+
+ dibs = dib_raw_ptr(h);
+ assert(dibs[idx] != DIB_RAW_FREE);
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ h->debug.rem_count++;
+ h->debug.last_rem_idx = idx;
+ #endif
+
+ left = idx;
+ /* Find the stop bucket ("right"). It is either free or has DIB == 0. */
+ for (right = next_idx(h, left); ; right = next_idx(h, right)) {
+ raw_dib = dibs[right];
+ if (raw_dib == 0 || raw_dib == DIB_RAW_FREE)
+ break;
+
+ /* The buckets are not supposed to be all occupied and with DIB > 0.
+ * That would mean we could make everyone better off by shifting them
+ * backward. This scenario is impossible. */
+ assert(left != right);
+ }
+
+ if (h->type == HASHMAP_TYPE_ORDERED) {
+ OrderedHashmap *lh = (OrderedHashmap*) h;
+ struct ordered_hashmap_entry *le = ordered_bucket_at(lh, idx);
+
+ if (le->iterate_next != IDX_NIL)
+ ordered_bucket_at(lh, le->iterate_next)->iterate_previous = le->iterate_previous;
+ else
+ lh->iterate_list_tail = le->iterate_previous;
+
+ if (le->iterate_previous != IDX_NIL)
+ ordered_bucket_at(lh, le->iterate_previous)->iterate_next = le->iterate_next;
+ else
+ lh->iterate_list_head = le->iterate_next;
+ }
+
+ /* Now shift all buckets in the interval (left, right) one step backwards */
+ for (prev = left, left = next_idx(h, left); left != right;
+ prev = left, left = next_idx(h, left)) {
+ dib = bucket_calculate_dib(h, left, dibs[left]);
+ assert(dib != 0);
+ bucket_move_entry(h, NULL, left, prev);
+ bucket_set_dib(h, prev, dib - 1);
+ }
+
+ bucket_mark_free(h, prev);
+ n_entries_dec(h);
+ }
+ #define remove_entry(h, idx) base_remove_entry(HASHMAP_BASE(h), idx)
+
+ static unsigned hashmap_iterate_in_insertion_order(OrderedHashmap *h, Iterator *i) {
+ struct ordered_hashmap_entry *e;
+ unsigned idx;
+
+ assert(h);
+ assert(i);
+
+ if (i->idx == IDX_NIL)
+ goto at_end;
+
+ if (i->idx == IDX_FIRST && h->iterate_list_head == IDX_NIL)
+ goto at_end;
+
+ if (i->idx == IDX_FIRST) {
+ idx = h->iterate_list_head;
+ e = ordered_bucket_at(h, idx);
+ } else {
+ idx = i->idx;
+ e = ordered_bucket_at(h, idx);
+ /*
+ * We allow removing the current entry while iterating, but removal may cause
+ * a backward shift. The next entry may thus move one bucket to the left.
+ * To detect when it happens, we remember the key pointer of the entry we were
+ * going to iterate next. If it does not match, there was a backward shift.
+ */
+ if (e->p.b.key != i->next_key) {
+ idx = prev_idx(HASHMAP_BASE(h), idx);
+ e = ordered_bucket_at(h, idx);
+ }
+ assert(e->p.b.key == i->next_key);
+ }
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ i->prev_idx = idx;
+ #endif
+
+ if (e->iterate_next != IDX_NIL) {
+ struct ordered_hashmap_entry *n;
+ i->idx = e->iterate_next;
+ n = ordered_bucket_at(h, i->idx);
+ i->next_key = n->p.b.key;
+ } else
+ i->idx = IDX_NIL;
+
+ return idx;
+
+ at_end:
+ i->idx = IDX_NIL;
+ return IDX_NIL;
+ }
+
+ static unsigned hashmap_iterate_in_internal_order(HashmapBase *h, Iterator *i) {
+ unsigned idx;
+
+ assert(h);
+ assert(i);
+
+ if (i->idx == IDX_NIL)
+ goto at_end;
+
+ if (i->idx == IDX_FIRST) {
+ /* fast forward to the first occupied bucket */
+ if (h->has_indirect) {
+ i->idx = skip_free_buckets(h, h->indirect.idx_lowest_entry);
+ h->indirect.idx_lowest_entry = i->idx;
+ } else
+ i->idx = skip_free_buckets(h, 0);
+
+ if (i->idx == IDX_NIL)
+ goto at_end;
+ } else {
+ struct hashmap_base_entry *e;
+
+ assert(i->idx > 0);
+
+ e = bucket_at(h, i->idx);
+ /*
+ * We allow removing the current entry while iterating, but removal may cause
+ * a backward shift. The next entry may thus move one bucket to the left.
+ * To detect when it happens, we remember the key pointer of the entry we were
+ * going to iterate next. If it does not match, there was a backward shift.
+ */
+ if (e->key != i->next_key)
+ e = bucket_at(h, --i->idx);
+
+ assert(e->key == i->next_key);
+ }
+
+ idx = i->idx;
+ #ifdef ENABLE_DEBUG_HASHMAP
+ i->prev_idx = idx;
+ #endif
+
+ i->idx = skip_free_buckets(h, i->idx + 1);
+ if (i->idx != IDX_NIL)
+ i->next_key = bucket_at(h, i->idx)->key;
+ else
+ i->idx = IDX_NIL;
+
+ return idx;
+
+ at_end:
+ i->idx = IDX_NIL;
+ return IDX_NIL;
+ }
+
+ static unsigned hashmap_iterate_entry(HashmapBase *h, Iterator *i) {
+ if (!h) {
+ i->idx = IDX_NIL;
+ return IDX_NIL;
+ }
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ if (i->idx == IDX_FIRST) {
+ i->put_count = h->debug.put_count;
+ i->rem_count = h->debug.rem_count;
+ } else {
+ /* While iterating, must not add any new entries */
+ assert(i->put_count == h->debug.put_count);
+ /* ... or remove entries other than the current one */
+ assert(i->rem_count == h->debug.rem_count ||
+ (i->rem_count == h->debug.rem_count - 1 &&
+ i->prev_idx == h->debug.last_rem_idx));
+ /* Reset our removals counter */
+ i->rem_count = h->debug.rem_count;
+ }
+ #endif
+
+ return h->type == HASHMAP_TYPE_ORDERED ? hashmap_iterate_in_insertion_order((OrderedHashmap*) h, i)
+ : hashmap_iterate_in_internal_order(h, i);
+ }
+
+ bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key) {
+ struct hashmap_base_entry *e;
+ void *data;
+ unsigned idx;
+
+ idx = hashmap_iterate_entry(h, i);
+ if (idx == IDX_NIL) {
+ if (value)
+ *value = NULL;
+ if (key)
+ *key = NULL;
+
+ return false;
+ }
+
+ e = bucket_at(h, idx);
+ data = entry_value(h, e);
+ if (value)
+ *value = data;
+ if (key)
+ *key = e->key;
+
+ return true;
+ }
+
+ bool set_iterate(Set *s, Iterator *i, void **value) {
+ return internal_hashmap_iterate(HASHMAP_BASE(s), i, value, NULL);
+ }
+
+ #define HASHMAP_FOREACH_IDX(idx, h, i) \
+ for ((i) = ITERATOR_FIRST, (idx) = hashmap_iterate_entry((h), &(i)); \
+ (idx != IDX_NIL); \
+ (idx) = hashmap_iterate_entry((h), &(i)))
+
+ static void reset_direct_storage(HashmapBase *h) {
+ const struct hashmap_type_info *hi = &hashmap_type_info[h->type];
+ void *p;
+
+ assert(!h->has_indirect);
+
+ p = mempset(h->direct.storage, 0, hi->entry_size * hi->n_direct_buckets);
+ memset(p, DIB_RAW_INIT, sizeof(dib_raw_t) * hi->n_direct_buckets);
+ }
+
+ static struct HashmapBase *hashmap_base_new(const struct hash_ops *hash_ops, enum HashmapType type HASHMAP_DEBUG_PARAMS) {
+ HashmapBase *h;
+ const struct hashmap_type_info *hi = &hashmap_type_info[type];
+ bool use_pool;
+
+ use_pool = is_main_thread();
+
+ h = use_pool ? mempool_alloc0_tile(hi->mempool) : malloc0(hi->head_size);
+
+ if (!h)
+ return NULL;
+
+ h->type = type;
+ h->from_pool = use_pool;
+ h->hash_ops = hash_ops ? hash_ops : &trivial_hash_ops;
+
+ if (type == HASHMAP_TYPE_ORDERED) {
+ OrderedHashmap *lh = (OrderedHashmap*)h;
+ lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
+ }
+
+ reset_direct_storage(h);
+
+ if (!shared_hash_key_initialized) {
+ random_bytes(shared_hash_key, sizeof(shared_hash_key));
+ shared_hash_key_initialized= true;
+ }
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ h->debug.func = func;
+ h->debug.file = file;
+ h->debug.line = line;
+ assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
+ LIST_PREPEND(debug_list, hashmap_debug_list, &h->debug);
+ assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
+ #endif
+
+ return h;
+ }
+
+ Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) {
+ return (Hashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
+ }
+
+ OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) {
+ return (OrderedHashmap*) hashmap_base_new(hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
+ }
+
+ Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) {
+ return (Set*) hashmap_base_new(hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
+ }
+
+ static int hashmap_base_ensure_allocated(HashmapBase **h, const struct hash_ops *hash_ops,
+ enum HashmapType type HASHMAP_DEBUG_PARAMS) {
+ HashmapBase *q;
+
+ assert(h);
+
+ if (*h)
+ return 0;
+
+ q = hashmap_base_new(hash_ops, type HASHMAP_DEBUG_PASS_ARGS);
+ if (!q)
+ return -ENOMEM;
+
+ *h = q;
+ return 0;
+ }
+
+ int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) {
+ return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_PLAIN HASHMAP_DEBUG_PASS_ARGS);
+ }
+
+ int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) {
+ return hashmap_base_ensure_allocated((HashmapBase**)h, hash_ops, HASHMAP_TYPE_ORDERED HASHMAP_DEBUG_PASS_ARGS);
+ }
+
+ int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS) {
+ return hashmap_base_ensure_allocated((HashmapBase**)s, hash_ops, HASHMAP_TYPE_SET HASHMAP_DEBUG_PASS_ARGS);
+ }
+
+ static void hashmap_free_no_clear(HashmapBase *h) {
+ assert(!h->has_indirect);
+ assert(!h->n_direct_entries);
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ assert_se(pthread_mutex_lock(&hashmap_debug_list_mutex) == 0);
+ LIST_REMOVE(debug_list, hashmap_debug_list, &h->debug);
+ assert_se(pthread_mutex_unlock(&hashmap_debug_list_mutex) == 0);
+ #endif
+
+ if (h->from_pool)
+ mempool_free_tile(hashmap_type_info[h->type].mempool, h);
+ else
+ free(h);
+ }
+
+ HashmapBase *internal_hashmap_free(HashmapBase *h) {
+
+ /* Free the hashmap, but nothing in it */
+
+ if (h) {
+ internal_hashmap_clear(h);
+ hashmap_free_no_clear(h);
+ }
+
+ return NULL;
+ }
+
+ HashmapBase *internal_hashmap_free_free(HashmapBase *h) {
+
+ /* Free the hashmap and all data objects in it, but not the
+ * keys */
+
+ if (h) {
+ internal_hashmap_clear_free(h);
+ hashmap_free_no_clear(h);
+ }
+
+ return NULL;
+ }
+
+ Hashmap *hashmap_free_free_free(Hashmap *h) {
+
+ /* Free the hashmap and all data and key objects in it */
+
+ if (h) {
+ hashmap_clear_free_free(h);
+ hashmap_free_no_clear(HASHMAP_BASE(h));
+ }
+
+ return NULL;
+ }
+
+ void internal_hashmap_clear(HashmapBase *h) {
+ if (!h)
+ return;
+
+ if (h->has_indirect) {
+ free(h->indirect.storage);
+ h->has_indirect = false;
+ }
+
+ h->n_direct_entries = 0;
+ reset_direct_storage(h);
+
+ if (h->type == HASHMAP_TYPE_ORDERED) {
+ OrderedHashmap *lh = (OrderedHashmap*) h;
+ lh->iterate_list_head = lh->iterate_list_tail = IDX_NIL;
+ }
+ }
+
+ void internal_hashmap_clear_free(HashmapBase *h) {
+ unsigned idx;
+
+ if (!h)
+ return;
+
+ for (idx = skip_free_buckets(h, 0); idx != IDX_NIL;
+ idx = skip_free_buckets(h, idx + 1))
+ free(entry_value(h, bucket_at(h, idx)));
+
+ internal_hashmap_clear(h);
+ }
+
+ void hashmap_clear_free_free(Hashmap *h) {
+ unsigned idx;
+
+ if (!h)
+ return;
+
+ for (idx = skip_free_buckets(HASHMAP_BASE(h), 0); idx != IDX_NIL;
+ idx = skip_free_buckets(HASHMAP_BASE(h), idx + 1)) {
+ struct plain_hashmap_entry *e = plain_bucket_at(h, idx);
+ free((void*)e->b.key);
+ free(e->value);
+ }
+
+ internal_hashmap_clear(HASHMAP_BASE(h));
+ }
+
+ static int resize_buckets(HashmapBase *h, unsigned entries_add);
+
+ /*
+ * Finds an empty bucket to put an entry into, starting the scan at 'idx'.
+ * Performs Robin Hood swaps as it goes. The entry to put must be placed
+ * by the caller into swap slot IDX_PUT.
+ * If used for in-place resizing, may leave a displaced entry in swap slot
+ * IDX_PUT. Caller must rehash it next.
+ * Returns: true if it left a displaced entry to rehash next in IDX_PUT,
+ * false otherwise.
+ */
+ static bool hashmap_put_robin_hood(HashmapBase *h, unsigned idx,
+ struct swap_entries *swap) {
+ dib_raw_t raw_dib, *dibs;
+ unsigned dib, distance;
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ h->debug.put_count++;
+ #endif
+
+ dibs = dib_raw_ptr(h);
+
+ for (distance = 0; ; distance++) {
+ raw_dib = dibs[idx];
+ if (raw_dib == DIB_RAW_FREE || raw_dib == DIB_RAW_REHASH) {
+ if (raw_dib == DIB_RAW_REHASH)
+ bucket_move_entry(h, swap, idx, IDX_TMP);
+
+ if (h->has_indirect && h->indirect.idx_lowest_entry > idx)
+ h->indirect.idx_lowest_entry = idx;
+
+ bucket_set_dib(h, idx, distance);
+ bucket_move_entry(h, swap, IDX_PUT, idx);
+ if (raw_dib == DIB_RAW_REHASH) {
+ bucket_move_entry(h, swap, IDX_TMP, IDX_PUT);
+ return true;
+ }
+
+ return false;
+ }
+
+ dib = bucket_calculate_dib(h, idx, raw_dib);
+
+ if (dib < distance) {
+ /* Found a wealthier entry. Go Robin Hood! */
+ bucket_set_dib(h, idx, distance);
+
+ /* swap the entries */
+ bucket_move_entry(h, swap, idx, IDX_TMP);
+ bucket_move_entry(h, swap, IDX_PUT, idx);
+ bucket_move_entry(h, swap, IDX_TMP, IDX_PUT);
+
+ distance = dib;
+ }
+
+ idx = next_idx(h, idx);
+ }
+ }
+
+ /*
+ * Puts an entry into a hashmap, boldly - no check whether key already exists.
+ * The caller must place the entry (only its key and value, not link indexes)
+ * in swap slot IDX_PUT.
+ * Caller must ensure: the key does not exist yet in the hashmap.
+ * that resize is not needed if !may_resize.
+ * Returns: 1 if entry was put successfully.
+ * -ENOMEM if may_resize==true and resize failed with -ENOMEM.
+ * Cannot return -ENOMEM if !may_resize.
+ */
+ static int hashmap_base_put_boldly(HashmapBase *h, unsigned idx,
+ struct swap_entries *swap, bool may_resize) {
+ struct ordered_hashmap_entry *new_entry;
+ int r;
+
+ assert(idx < n_buckets(h));
+
+ new_entry = bucket_at_swap(swap, IDX_PUT);
+
+ if (may_resize) {
+ r = resize_buckets(h, 1);
+ if (r < 0)
+ return r;
+ if (r > 0)
+ idx = bucket_hash(h, new_entry->p.b.key);
+ }
+ assert(n_entries(h) < n_buckets(h));
+
+ if (h->type == HASHMAP_TYPE_ORDERED) {
+ OrderedHashmap *lh = (OrderedHashmap*) h;
+
+ new_entry->iterate_next = IDX_NIL;
+ new_entry->iterate_previous = lh->iterate_list_tail;
+
+ if (lh->iterate_list_tail != IDX_NIL) {
+ struct ordered_hashmap_entry *old_tail;
+
+ old_tail = ordered_bucket_at(lh, lh->iterate_list_tail);
+ assert(old_tail->iterate_next == IDX_NIL);
+ old_tail->iterate_next = IDX_PUT;
+ }
+
+ lh->iterate_list_tail = IDX_PUT;
+ if (lh->iterate_list_head == IDX_NIL)
+ lh->iterate_list_head = IDX_PUT;
+ }
+
+ assert_se(hashmap_put_robin_hood(h, idx, swap) == false);
+
+ n_entries_inc(h);
+ #ifdef ENABLE_DEBUG_HASHMAP
+ h->debug.max_entries = MAX(h->debug.max_entries, n_entries(h));
+ #endif
+
+ return 1;
+ }
+ #define hashmap_put_boldly(h, idx, swap, may_resize) \
+ hashmap_base_put_boldly(HASHMAP_BASE(h), idx, swap, may_resize)
+
+ /*
+ * Returns 0 if resize is not needed.
+ * 1 if successfully resized.
+ * -ENOMEM on allocation failure.
+ */
+ static int resize_buckets(HashmapBase *h, unsigned entries_add) {
+ struct swap_entries swap;
+ char *new_storage;
+ dib_raw_t *old_dibs, *new_dibs;
+ const struct hashmap_type_info *hi;
+ unsigned idx, optimal_idx;
+ unsigned old_n_buckets, new_n_buckets, n_rehashed, new_n_entries;
+ uint8_t new_shift;
+ bool rehash_next;
+
+ assert(h);
+
+ hi = &hashmap_type_info[h->type];
+ new_n_entries = n_entries(h) + entries_add;
+
+ /* overflow? */
+ if (_unlikely_(new_n_entries < entries_add))
+ return -ENOMEM;
+
+ /* For direct storage we allow 100% load, because it's tiny. */
+ if (!h->has_indirect && new_n_entries <= hi->n_direct_buckets)
+ return 0;
+
+ /*
+ * Load factor = n/m = 1 - (1/INV_KEEP_FREE).
+ * From it follows: m = n + n/(INV_KEEP_FREE - 1)
+ */
+ new_n_buckets = new_n_entries + new_n_entries / (INV_KEEP_FREE - 1);
+ /* overflow? */
+ if (_unlikely_(new_n_buckets < new_n_entries))
+ return -ENOMEM;
+
+ if (_unlikely_(new_n_buckets > UINT_MAX / (hi->entry_size + sizeof(dib_raw_t))))
+ return -ENOMEM;
+
+ old_n_buckets = n_buckets(h);
+
+ if (_likely_(new_n_buckets <= old_n_buckets))
+ return 0;
+
+ new_shift = log2u_round_up(MAX(
+ new_n_buckets * (hi->entry_size + sizeof(dib_raw_t)),
+ 2 * sizeof(struct direct_storage)));
+
+ /* Realloc storage (buckets and DIB array). */
+ new_storage = realloc(h->has_indirect ? h->indirect.storage : NULL,
+ 1U << new_shift);
+ if (!new_storage)
+ return -ENOMEM;
+
+ /* Must upgrade direct to indirect storage. */
+ if (!h->has_indirect) {
+ memcpy(new_storage, h->direct.storage,
+ old_n_buckets * (hi->entry_size + sizeof(dib_raw_t)));
+ h->indirect.n_entries = h->n_direct_entries;
+ h->indirect.idx_lowest_entry = 0;
+ h->n_direct_entries = 0;
+ }
+
+ /* Get a new hash key. If we've just upgraded to indirect storage,
+ * allow reusing a previously generated key. It's still a different key
+ * from the shared one that we used for direct storage. */
+ get_hash_key(h->indirect.hash_key, !h->has_indirect);
+
+ h->has_indirect = true;
+ h->indirect.storage = new_storage;
+ h->indirect.n_buckets = (1U << new_shift) /
+ (hi->entry_size + sizeof(dib_raw_t));
+
+ old_dibs = (dib_raw_t*)(new_storage + hi->entry_size * old_n_buckets);
+ new_dibs = dib_raw_ptr(h);
+
+ /*
+ * Move the DIB array to the new place, replacing valid DIB values with
+ * DIB_RAW_REHASH to indicate all of the used buckets need rehashing.
+ * Note: Overlap is not possible, because we have at least doubled the
+ * number of buckets and dib_raw_t is smaller than any entry type.
+ */
+ for (idx = 0; idx < old_n_buckets; idx++) {
+ assert(old_dibs[idx] != DIB_RAW_REHASH);
+ new_dibs[idx] = old_dibs[idx] == DIB_RAW_FREE ? DIB_RAW_FREE
+ : DIB_RAW_REHASH;
+ }
+
+ /* Zero the area of newly added entries (including the old DIB area) */
+ memzero(bucket_at(h, old_n_buckets),
+ (n_buckets(h) - old_n_buckets) * hi->entry_size);
+
+ /* The upper half of the new DIB array needs initialization */
+ memset(&new_dibs[old_n_buckets], DIB_RAW_INIT,
+ (n_buckets(h) - old_n_buckets) * sizeof(dib_raw_t));
+
+ /* Rehash entries that need it */
+ n_rehashed = 0;
+ for (idx = 0; idx < old_n_buckets; idx++) {
+ if (new_dibs[idx] != DIB_RAW_REHASH)
+ continue;
+
+ optimal_idx = bucket_hash(h, bucket_at(h, idx)->key);
+
+ /*
+ * Not much to do if by luck the entry hashes to its current
+ * location. Just set its DIB.
+ */
+ if (optimal_idx == idx) {
+ new_dibs[idx] = 0;
+ n_rehashed++;
+ continue;
+ }
+
+ new_dibs[idx] = DIB_RAW_FREE;
+ bucket_move_entry(h, &swap, idx, IDX_PUT);
+ /* bucket_move_entry does not clear the source */
+ memzero(bucket_at(h, idx), hi->entry_size);
+
+ do {
+ /*
+ * Find the new bucket for the current entry. This may make
+ * another entry homeless and load it into IDX_PUT.
+ */
+ rehash_next = hashmap_put_robin_hood(h, optimal_idx, &swap);
+ n_rehashed++;
+
+ /* Did the current entry displace another one? */
+ if (rehash_next)
+ optimal_idx = bucket_hash(h, bucket_at_swap(&swap, IDX_PUT)->p.b.key);
+ } while (rehash_next);
+ }
+
+ assert(n_rehashed == n_entries(h));
+
+ return 1;
+ }
+
+ /*
+ * Finds an entry with a matching key
+ * Returns: index of the found entry, or IDX_NIL if not found.
+ */
+ static unsigned base_bucket_scan(HashmapBase *h, unsigned idx, const void *key) {
+ struct hashmap_base_entry *e;
+ unsigned dib, distance;
+ dib_raw_t *dibs = dib_raw_ptr(h);
+
+ assert(idx < n_buckets(h));
+
+ for (distance = 0; ; distance++) {
+ if (dibs[idx] == DIB_RAW_FREE)
+ return IDX_NIL;
+
+ dib = bucket_calculate_dib(h, idx, dibs[idx]);
+
+ if (dib < distance)
+ return IDX_NIL;
+ if (dib == distance) {
+ e = bucket_at(h, idx);
+ if (h->hash_ops->compare(e->key, key) == 0)
+ return idx;
+ }
+
+ idx = next_idx(h, idx);
+ }
+ }
+ #define bucket_scan(h, idx, key) base_bucket_scan(HASHMAP_BASE(h), idx, key)
+
+ int hashmap_put(Hashmap *h, const void *key, void *value) {
+ struct swap_entries swap;
+ struct plain_hashmap_entry *e;
+ unsigned hash, idx;
+
+ assert(h);
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx != IDX_NIL) {
+ e = plain_bucket_at(h, idx);
+ if (e->value == value)
+ return 0;
+ return -EEXIST;
+ }
+
+ e = &bucket_at_swap(&swap, IDX_PUT)->p;
+ e->b.key = key;
+ e->value = value;
+ return hashmap_put_boldly(h, hash, &swap, true);
+ }
+
+ int set_put(Set *s, const void *key) {
+ struct swap_entries swap;
+ struct hashmap_base_entry *e;
+ unsigned hash, idx;
+
+ assert(s);
+
+ hash = bucket_hash(s, key);
+ idx = bucket_scan(s, hash, key);
+ if (idx != IDX_NIL)
+ return 0;
+
+ e = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+ e->key = key;
+ return hashmap_put_boldly(s, hash, &swap, true);
+ }
+
+ int hashmap_replace(Hashmap *h, const void *key, void *value) {
+ struct swap_entries swap;
+ struct plain_hashmap_entry *e;
+ unsigned hash, idx;
+
+ assert(h);
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx != IDX_NIL) {
+ e = plain_bucket_at(h, idx);
+ #ifdef ENABLE_DEBUG_HASHMAP
+ /* Although the key is equal, the key pointer may have changed,
+ * and this would break our assumption for iterating. So count
+ * this operation as incompatible with iteration. */
+ if (e->b.key != key) {
+ h->b.debug.put_count++;
+ h->b.debug.rem_count++;
+ h->b.debug.last_rem_idx = idx;
+ }
+ #endif
+ e->b.key = key;
+ e->value = value;
+ return 0;
+ }
+
+ e = &bucket_at_swap(&swap, IDX_PUT)->p;
+ e->b.key = key;
+ e->value = value;
+ return hashmap_put_boldly(h, hash, &swap, true);
+ }
+
+ int hashmap_update(Hashmap *h, const void *key, void *value) {
+ struct plain_hashmap_entry *e;
+ unsigned hash, idx;
+
+ assert(h);
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL)
+ return -ENOENT;
+
+ e = plain_bucket_at(h, idx);
+ e->value = value;
+ return 0;
+ }
+
+ void *internal_hashmap_get(HashmapBase *h, const void *key) {
+ struct hashmap_base_entry *e;
+ unsigned hash, idx;
+
+ if (!h)
+ return NULL;
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = bucket_at(h, idx);
+ return entry_value(h, e);
+ }
+
+ void *hashmap_get2(Hashmap *h, const void *key, void **key2) {
+ struct plain_hashmap_entry *e;
+ unsigned hash, idx;
+
+ if (!h)
+ return NULL;
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = plain_bucket_at(h, idx);
+ if (key2)
+ *key2 = (void*) e->b.key;
+
+ return e->value;
+ }
+
+ bool internal_hashmap_contains(HashmapBase *h, const void *key) {
+ unsigned hash;
+
+ if (!h)
+ return false;
+
+ hash = bucket_hash(h, key);
+ return bucket_scan(h, hash, key) != IDX_NIL;
+ }
+
+ void *internal_hashmap_remove(HashmapBase *h, const void *key) {
+ struct hashmap_base_entry *e;
+ unsigned hash, idx;
+ void *data;
+
+ if (!h)
+ return NULL;
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = bucket_at(h, idx);
+ data = entry_value(h, e);
+ remove_entry(h, idx);
+
+ return data;
+ }
+
+ void *hashmap_remove2(Hashmap *h, const void *key, void **rkey) {
+ struct plain_hashmap_entry *e;
+ unsigned hash, idx;
+ void *data;
+
+ if (!h) {
+ if (rkey)
+ *rkey = NULL;
+ return NULL;
+ }
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL) {
+ if (rkey)
+ *rkey = NULL;
+ return NULL;
+ }
+
+ e = plain_bucket_at(h, idx);
+ data = e->value;
+ if (rkey)
+ *rkey = (void*) e->b.key;
+
+ remove_entry(h, idx);
+
+ return data;
+ }
+
+ int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+ struct swap_entries swap;
+ struct plain_hashmap_entry *e;
+ unsigned old_hash, new_hash, idx;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = bucket_hash(h, old_key);
+ idx = bucket_scan(h, old_hash, old_key);
+ if (idx == IDX_NIL)
+ return -ENOENT;
+
+ new_hash = bucket_hash(h, new_key);
+ if (bucket_scan(h, new_hash, new_key) != IDX_NIL)
+ return -EEXIST;
+
+ remove_entry(h, idx);
+
+ e = &bucket_at_swap(&swap, IDX_PUT)->p;
+ e->b.key = new_key;
+ e->value = value;
+ assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1);
+
+ return 0;
+ }
+
+ int set_remove_and_put(Set *s, const void *old_key, const void *new_key) {
+ struct swap_entries swap;
+ struct hashmap_base_entry *e;
+ unsigned old_hash, new_hash, idx;
+
+ if (!s)
+ return -ENOENT;
+
+ old_hash = bucket_hash(s, old_key);
+ idx = bucket_scan(s, old_hash, old_key);
+ if (idx == IDX_NIL)
+ return -ENOENT;
+
+ new_hash = bucket_hash(s, new_key);
+ if (bucket_scan(s, new_hash, new_key) != IDX_NIL)
+ return -EEXIST;
+
+ remove_entry(s, idx);
+
+ e = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+ e->key = new_key;
+ assert_se(hashmap_put_boldly(s, new_hash, &swap, false) == 1);
+
+ return 0;
+ }
+
+ int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value) {
+ struct swap_entries swap;
+ struct plain_hashmap_entry *e;
+ unsigned old_hash, new_hash, idx_old, idx_new;
+
+ if (!h)
+ return -ENOENT;
+
+ old_hash = bucket_hash(h, old_key);
+ idx_old = bucket_scan(h, old_hash, old_key);
+ if (idx_old == IDX_NIL)
+ return -ENOENT;
+
+ old_key = bucket_at(HASHMAP_BASE(h), idx_old)->key;
+
+ new_hash = bucket_hash(h, new_key);
+ idx_new = bucket_scan(h, new_hash, new_key);
+ if (idx_new != IDX_NIL)
+ if (idx_old != idx_new) {
+ remove_entry(h, idx_new);
+ /* Compensate for a possible backward shift. */
+ if (old_key != bucket_at(HASHMAP_BASE(h), idx_old)->key)
+ idx_old = prev_idx(HASHMAP_BASE(h), idx_old);
+ assert(old_key == bucket_at(HASHMAP_BASE(h), idx_old)->key);
+ }
+
+ remove_entry(h, idx_old);
+
+ e = &bucket_at_swap(&swap, IDX_PUT)->p;
+ e->b.key = new_key;
+ e->value = value;
+ assert_se(hashmap_put_boldly(h, new_hash, &swap, false) == 1);
+
+ return 0;
+ }
+
+ void *hashmap_remove_value(Hashmap *h, const void *key, void *value) {
+ struct plain_hashmap_entry *e;
+ unsigned hash, idx;
+
+ if (!h)
+ return NULL;
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = plain_bucket_at(h, idx);
+ if (e->value != value)
+ return NULL;
+
+ remove_entry(h, idx);
+
+ return value;
+ }
+
+ static unsigned find_first_entry(HashmapBase *h) {
+ Iterator i = ITERATOR_FIRST;
+
+ if (!h || !n_entries(h))
+ return IDX_NIL;
+
+ return hashmap_iterate_entry(h, &i);
+ }
+
+ void *internal_hashmap_first(HashmapBase *h) {
+ unsigned idx;
+
+ idx = find_first_entry(h);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ return entry_value(h, bucket_at(h, idx));
+ }
+
+ void *internal_hashmap_first_key(HashmapBase *h) {
+ struct hashmap_base_entry *e;
+ unsigned idx;
+
+ idx = find_first_entry(h);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = bucket_at(h, idx);
+ return (void*) e->key;
+ }
+
+ void *internal_hashmap_steal_first(HashmapBase *h) {
+ struct hashmap_base_entry *e;
+ void *data;
+ unsigned idx;
+
+ idx = find_first_entry(h);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = bucket_at(h, idx);
+ data = entry_value(h, e);
+ remove_entry(h, idx);
+
+ return data;
+ }
+
+ void *internal_hashmap_steal_first_key(HashmapBase *h) {
+ struct hashmap_base_entry *e;
+ void *key;
+ unsigned idx;
+
+ idx = find_first_entry(h);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = bucket_at(h, idx);
+ key = (void*) e->key;
+ remove_entry(h, idx);
+
+ return key;
+ }
+
+ unsigned internal_hashmap_size(HashmapBase *h) {
+
+ if (!h)
+ return 0;
+
+ return n_entries(h);
+ }
+
+ unsigned internal_hashmap_buckets(HashmapBase *h) {
+
+ if (!h)
+ return 0;
+
+ return n_buckets(h);
+ }
+
+ int internal_hashmap_merge(Hashmap *h, Hashmap *other) {
+ Iterator i;
+ unsigned idx;
+
+ assert(h);
+
+ HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) {
+ struct plain_hashmap_entry *pe = plain_bucket_at(other, idx);
+ int r;
+
+ r = hashmap_put(h, pe->b.key, pe->value);
+ if (r < 0 && r != -EEXIST)
+ return r;
+ }
+
+ return 0;
+ }
+
+ int set_merge(Set *s, Set *other) {
+ Iterator i;
+ unsigned idx;
+
+ assert(s);
+
+ HASHMAP_FOREACH_IDX(idx, HASHMAP_BASE(other), i) {
+ struct set_entry *se = set_bucket_at(other, idx);
+ int r;
+
+ r = set_put(s, se->b.key);
+ if (r < 0)
+ return r;
+ }
+
+ return 0;
+ }
+
+ int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add) {
+ int r;
+
+ assert(h);
+
+ r = resize_buckets(h, entries_add);
+ if (r < 0)
+ return r;
+
+ return 0;
+ }
+
+ /*
+ * The same as hashmap_merge(), but every new item from other is moved to h.
+ * Keys already in h are skipped and stay in other.
+ * Returns: 0 on success.
+ * -ENOMEM on alloc failure, in which case no move has been done.
+ */
+ int internal_hashmap_move(HashmapBase *h, HashmapBase *other) {
+ struct swap_entries swap;
+ struct hashmap_base_entry *e, *n;
+ Iterator i;
+ unsigned idx;
+ int r;
+
+ assert(h);
+
+ if (!other)
+ return 0;
+
+ assert(other->type == h->type);
+
+ /*
+ * This reserves buckets for the worst case, where none of other's
+ * entries are yet present in h. This is preferable to risking
+ * an allocation failure in the middle of the moving and having to
+ * rollback or return a partial result.
+ */
+ r = resize_buckets(h, n_entries(other));
+ if (r < 0)
+ return r;
+
+ HASHMAP_FOREACH_IDX(idx, other, i) {
+ unsigned h_hash;
+
+ e = bucket_at(other, idx);
+ h_hash = bucket_hash(h, e->key);
+ if (bucket_scan(h, h_hash, e->key) != IDX_NIL)
+ continue;
+
+ n = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+ n->key = e->key;
+ if (h->type != HASHMAP_TYPE_SET)
+ ((struct plain_hashmap_entry*) n)->value =
+ ((struct plain_hashmap_entry*) e)->value;
+ assert_se(hashmap_put_boldly(h, h_hash, &swap, false) == 1);
+
+ remove_entry(other, idx);
+ }
+
+ return 0;
+ }
+
+ int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key) {
+ struct swap_entries swap;
+ unsigned h_hash, other_hash, idx;
+ struct hashmap_base_entry *e, *n;
+ int r;
+
+ assert(h);
+
+ h_hash = bucket_hash(h, key);
+ if (bucket_scan(h, h_hash, key) != IDX_NIL)
+ return -EEXIST;
+
+ if (!other)
+ return -ENOENT;
+
+ assert(other->type == h->type);
+
+ other_hash = bucket_hash(other, key);
+ idx = bucket_scan(other, other_hash, key);
+ if (idx == IDX_NIL)
+ return -ENOENT;
+
+ e = bucket_at(other, idx);
+
+ n = &bucket_at_swap(&swap, IDX_PUT)->p.b;
+ n->key = e->key;
+ if (h->type != HASHMAP_TYPE_SET)
+ ((struct plain_hashmap_entry*) n)->value =
+ ((struct plain_hashmap_entry*) e)->value;
+ r = hashmap_put_boldly(h, h_hash, &swap, true);
+ if (r < 0)
+ return r;
+
+ remove_entry(other, idx);
+ return 0;
+ }
+
+ HashmapBase *internal_hashmap_copy(HashmapBase *h) {
+ HashmapBase *copy;
+ int r;
+
+ assert(h);
+
+ copy = hashmap_base_new(h->hash_ops, h->type HASHMAP_DEBUG_SRC_ARGS);
+ if (!copy)
+ return NULL;
+
+ switch (h->type) {
+ case HASHMAP_TYPE_PLAIN:
+ case HASHMAP_TYPE_ORDERED:
+ r = hashmap_merge((Hashmap*)copy, (Hashmap*)h);
+ break;
+ case HASHMAP_TYPE_SET:
+ r = set_merge((Set*)copy, (Set*)h);
+ break;
+ default:
+ assert_not_reached("Unknown hashmap type");
+ }
+
+ if (r < 0) {
+ internal_hashmap_free(copy);
+ return NULL;
+ }
+
+ return copy;
+ }
+
+ char **internal_hashmap_get_strv(HashmapBase *h) {
+ char **sv;
+ Iterator i;
+ unsigned idx, n;
+
+ sv = new(char*, n_entries(h)+1);
+ if (!sv)
+ return NULL;
+
+ n = 0;
+ HASHMAP_FOREACH_IDX(idx, h, i)
+ sv[n++] = entry_value(h, bucket_at(h, idx));
+ sv[n] = NULL;
+
+ return sv;
+ }
+
+ void *ordered_hashmap_next(OrderedHashmap *h, const void *key) {
+ struct ordered_hashmap_entry *e;
+ unsigned hash, idx;
+
+ if (!h)
+ return NULL;
+
+ hash = bucket_hash(h, key);
+ idx = bucket_scan(h, hash, key);
+ if (idx == IDX_NIL)
+ return NULL;
+
+ e = ordered_bucket_at(h, idx);
+ if (e->iterate_next == IDX_NIL)
+ return NULL;
+ return ordered_bucket_at(h, e->iterate_next)->p.value;
+ }
+
+ int set_consume(Set *s, void *value) {
+ int r;
+
+ r = set_put(s, value);
+ if (r <= 0)
+ free(value);
+
+ return r;
+ }
+
+ int set_put_strdup(Set *s, const char *p) {
+ char *c;
+ int r;
+
+ assert(s);
+ assert(p);
+
+ c = strdup(p);
+ if (!c)
+ return -ENOMEM;
+
+ r = set_consume(s, c);
+ if (r == -EEXIST)
+ return 0;
+
+ return r;
+ }
+
+ int set_put_strdupv(Set *s, char **l) {
+ int n = 0, r;
+ char **i;
+
+ STRV_FOREACH(i, l) {
+ r = set_put_strdup(s, *i);
+ if (r < 0)
+ return r;
+
+ n += r;
+ }
+
+ return n;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ #pragma once
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+ Copyright 2014 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <stdbool.h>
+
+ #include "macro.h"
+ #include "siphash24.h"
+ #include "util.h"
+
+ /*
+ * A hash table implementation. As a minor optimization a NULL hashmap object
+ * will be treated as empty hashmap for all read operations. That way it is not
+ * necessary to instantiate an object for each Hashmap use.
+ *
+ * If ENABLE_DEBUG_HASHMAP is defined (by configuring with --enable-debug=hashmap),
+ * the implemention will:
+ * - store extra data for debugging and statistics (see tools/gdb-sd_dump_hashmaps.py)
+ * - perform extra checks for invalid use of iterators
+ */
+
+ #define HASH_KEY_SIZE 16
+
+ /* The base type for all hashmap and set types. Many functions in the
+ * implementation take (HashmapBase*) parameters and are run-time polymorphic,
+ * though the API is not meant to be polymorphic (do not call functions
+ * internal_*() directly). */
+ typedef struct HashmapBase HashmapBase;
+
+ /* Specific hashmap/set types */
+ typedef struct Hashmap Hashmap; /* Maps keys to values */
+ typedef struct OrderedHashmap OrderedHashmap; /* Like Hashmap, but also remembers entry insertion order */
+ typedef struct Set Set; /* Stores just keys */
+
+ /* Ideally the Iterator would be an opaque struct, but it is instantiated
+ * by hashmap users, so the definition has to be here. Do not use its fields
+ * directly. */
+ typedef struct {
+ unsigned idx; /* index of an entry to be iterated next */
+ const void *next_key; /* expected value of that entry's key pointer */
+ #ifdef ENABLE_DEBUG_HASHMAP
+ unsigned put_count; /* hashmap's put_count recorded at start of iteration */
+ unsigned rem_count; /* hashmap's rem_count in previous iteration */
+ unsigned prev_idx; /* idx in previous iteration */
+ #endif
+ } Iterator;
+
+ #define _IDX_ITERATOR_FIRST (UINT_MAX - 1)
+ #define ITERATOR_FIRST ((Iterator) { .idx = _IDX_ITERATOR_FIRST, .next_key = NULL })
+
+ typedef void (*hash_func_t)(const void *p, struct siphash *state);
+ typedef int (*compare_func_t)(const void *a, const void *b);
+
+ struct hash_ops {
+ hash_func_t hash;
+ compare_func_t compare;
+ };
+
+ void string_hash_func(const void *p, struct siphash *state);
+ int string_compare_func(const void *a, const void *b) _pure_;
+ extern const struct hash_ops string_hash_ops;
+
+ /* This will compare the passed pointers directly, and will not
+ * dereference them. This is hence not useful for strings or
+ * suchlike. */
+ void trivial_hash_func(const void *p, struct siphash *state);
+ int trivial_compare_func(const void *a, const void *b) _const_;
+ extern const struct hash_ops trivial_hash_ops;
+
+ /* 32bit values we can always just embedd in the pointer itself, but
+ * in order to support 32bit archs we need store 64bit values
+ * indirectly, since they don't fit in a pointer. */
+ void uint64_hash_func(const void *p, struct siphash *state);
+ int uint64_compare_func(const void *a, const void *b) _pure_;
+ extern const struct hash_ops uint64_hash_ops;
+
+ /* On some archs dev_t is 32bit, and on others 64bit. And sometimes
+ * it's 64bit on 32bit archs, and sometimes 32bit on 64bit archs. Yuck! */
+ #if SIZEOF_DEV_T != 8
+ void devt_hash_func(const void *p, struct siphash *state) _pure_;
+ int devt_compare_func(const void *a, const void *b) _pure_;
+ extern const struct hash_ops devt_hash_ops = {
+ .hash = devt_hash_func,
+ .compare = devt_compare_func
+ };
+ #else
+ #define devt_hash_func uint64_hash_func
+ #define devt_compare_func uint64_compare_func
+ #define devt_hash_ops uint64_hash_ops
+ #endif
+
+ /* Macros for type checking */
+ #define PTR_COMPATIBLE_WITH_HASHMAP_BASE(h) \
+ (__builtin_types_compatible_p(typeof(h), HashmapBase*) || \
+ __builtin_types_compatible_p(typeof(h), Hashmap*) || \
+ __builtin_types_compatible_p(typeof(h), OrderedHashmap*) || \
+ __builtin_types_compatible_p(typeof(h), Set*))
+
+ #define PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h) \
+ (__builtin_types_compatible_p(typeof(h), Hashmap*) || \
+ __builtin_types_compatible_p(typeof(h), OrderedHashmap*)) \
+
+ #define HASHMAP_BASE(h) \
+ __builtin_choose_expr(PTR_COMPATIBLE_WITH_HASHMAP_BASE(h), \
+ (HashmapBase*)(h), \
+ (void)0)
+
+ #define PLAIN_HASHMAP(h) \
+ __builtin_choose_expr(PTR_COMPATIBLE_WITH_PLAIN_HASHMAP(h), \
+ (Hashmap*)(h), \
+ (void)0)
+
+ #ifdef ENABLE_DEBUG_HASHMAP
+ # define HASHMAP_DEBUG_PARAMS , const char *func, const char *file, int line
+ # define HASHMAP_DEBUG_SRC_ARGS , __func__, __FILE__, __LINE__
+ # define HASHMAP_DEBUG_PASS_ARGS , func, file, line
+ #else
+ # define HASHMAP_DEBUG_PARAMS
+ # define HASHMAP_DEBUG_SRC_ARGS
+ # define HASHMAP_DEBUG_PASS_ARGS
+ #endif
+
+ Hashmap *internal_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+ OrderedHashmap *internal_ordered_hashmap_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+ #define hashmap_new(ops) internal_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS)
+ #define ordered_hashmap_new(ops) internal_ordered_hashmap_new(ops HASHMAP_DEBUG_SRC_ARGS)
+
+ HashmapBase *internal_hashmap_free(HashmapBase *h);
+ static inline Hashmap *hashmap_free(Hashmap *h) {
+ return (void*)internal_hashmap_free(HASHMAP_BASE(h));
+ }
+ static inline OrderedHashmap *ordered_hashmap_free(OrderedHashmap *h) {
+ return (void*)internal_hashmap_free(HASHMAP_BASE(h));
+ }
+
+ HashmapBase *internal_hashmap_free_free(HashmapBase *h);
+ static inline Hashmap *hashmap_free_free(Hashmap *h) {
+ return (void*)internal_hashmap_free_free(HASHMAP_BASE(h));
+ }
+ static inline OrderedHashmap *ordered_hashmap_free_free(OrderedHashmap *h) {
+ return (void*)internal_hashmap_free_free(HASHMAP_BASE(h));
+ }
+
+ Hashmap *hashmap_free_free_free(Hashmap *h);
+ static inline OrderedHashmap *ordered_hashmap_free_free_free(OrderedHashmap *h) {
+ return (void*)hashmap_free_free_free(PLAIN_HASHMAP(h));
+ }
+
+ HashmapBase *internal_hashmap_copy(HashmapBase *h);
+ static inline Hashmap *hashmap_copy(Hashmap *h) {
+ return (Hashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
+ }
+ static inline OrderedHashmap *ordered_hashmap_copy(OrderedHashmap *h) {
+ return (OrderedHashmap*) internal_hashmap_copy(HASHMAP_BASE(h));
+ }
+
+ int internal_hashmap_ensure_allocated(Hashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+ int internal_ordered_hashmap_ensure_allocated(OrderedHashmap **h, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+ #define hashmap_ensure_allocated(h, ops) internal_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
+ #define ordered_hashmap_ensure_allocated(h, ops) internal_ordered_hashmap_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
+
+ int hashmap_put(Hashmap *h, const void *key, void *value);
+ static inline int ordered_hashmap_put(OrderedHashmap *h, const void *key, void *value) {
+ return hashmap_put(PLAIN_HASHMAP(h), key, value);
+ }
+
+ int hashmap_update(Hashmap *h, const void *key, void *value);
+ static inline int ordered_hashmap_update(OrderedHashmap *h, const void *key, void *value) {
+ return hashmap_update(PLAIN_HASHMAP(h), key, value);
+ }
+
+ int hashmap_replace(Hashmap *h, const void *key, void *value);
+ static inline int ordered_hashmap_replace(OrderedHashmap *h, const void *key, void *value) {
+ return hashmap_replace(PLAIN_HASHMAP(h), key, value);
+ }
+
+ void *internal_hashmap_get(HashmapBase *h, const void *key);
+ static inline void *hashmap_get(Hashmap *h, const void *key) {
+ return internal_hashmap_get(HASHMAP_BASE(h), key);
+ }
+ static inline void *ordered_hashmap_get(OrderedHashmap *h, const void *key) {
+ return internal_hashmap_get(HASHMAP_BASE(h), key);
+ }
+
+ void *hashmap_get2(Hashmap *h, const void *key, void **rkey);
+ static inline void *ordered_hashmap_get2(OrderedHashmap *h, const void *key, void **rkey) {
+ return hashmap_get2(PLAIN_HASHMAP(h), key, rkey);
+ }
+
+ bool internal_hashmap_contains(HashmapBase *h, const void *key);
+ static inline bool hashmap_contains(Hashmap *h, const void *key) {
+ return internal_hashmap_contains(HASHMAP_BASE(h), key);
+ }
+ static inline bool ordered_hashmap_contains(OrderedHashmap *h, const void *key) {
+ return internal_hashmap_contains(HASHMAP_BASE(h), key);
+ }
+
+ void *internal_hashmap_remove(HashmapBase *h, const void *key);
+ static inline void *hashmap_remove(Hashmap *h, const void *key) {
+ return internal_hashmap_remove(HASHMAP_BASE(h), key);
+ }
+ static inline void *ordered_hashmap_remove(OrderedHashmap *h, const void *key) {
+ return internal_hashmap_remove(HASHMAP_BASE(h), key);
+ }
+
+ void *hashmap_remove2(Hashmap *h, const void *key, void **rkey);
+ static inline void *ordered_hashmap_remove2(OrderedHashmap *h, const void *key, void **rkey) {
+ return hashmap_remove2(PLAIN_HASHMAP(h), key, rkey);
+ }
+
+ void *hashmap_remove_value(Hashmap *h, const void *key, void *value);
+ static inline void *ordered_hashmap_remove_value(OrderedHashmap *h, const void *key, void *value) {
+ return hashmap_remove_value(PLAIN_HASHMAP(h), key, value);
+ }
+
+ int hashmap_remove_and_put(Hashmap *h, const void *old_key, const void *new_key, void *value);
+ static inline int ordered_hashmap_remove_and_put(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) {
+ return hashmap_remove_and_put(PLAIN_HASHMAP(h), old_key, new_key, value);
+ }
+
+ int hashmap_remove_and_replace(Hashmap *h, const void *old_key, const void *new_key, void *value);
+ static inline int ordered_hashmap_remove_and_replace(OrderedHashmap *h, const void *old_key, const void *new_key, void *value) {
+ return hashmap_remove_and_replace(PLAIN_HASHMAP(h), old_key, new_key, value);
+ }
+
+ /* Since merging data from a OrderedHashmap into a Hashmap or vice-versa
+ * should just work, allow this by having looser type-checking here. */
+ int internal_hashmap_merge(Hashmap *h, Hashmap *other);
+ #define hashmap_merge(h, other) internal_hashmap_merge(PLAIN_HASHMAP(h), PLAIN_HASHMAP(other))
+ #define ordered_hashmap_merge(h, other) hashmap_merge(h, other)
+
+ int internal_hashmap_reserve(HashmapBase *h, unsigned entries_add);
+ static inline int hashmap_reserve(Hashmap *h, unsigned entries_add) {
+ return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+ }
+ static inline int ordered_hashmap_reserve(OrderedHashmap *h, unsigned entries_add) {
+ return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+ }
+
+ int internal_hashmap_move(HashmapBase *h, HashmapBase *other);
+ /* Unlike hashmap_merge, hashmap_move does not allow mixing the types. */
+ static inline int hashmap_move(Hashmap *h, Hashmap *other) {
+ return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
+ }
+ static inline int ordered_hashmap_move(OrderedHashmap *h, OrderedHashmap *other) {
+ return internal_hashmap_move(HASHMAP_BASE(h), HASHMAP_BASE(other));
+ }
+
+ int internal_hashmap_move_one(HashmapBase *h, HashmapBase *other, const void *key);
+ static inline int hashmap_move_one(Hashmap *h, Hashmap *other, const void *key) {
+ return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
+ }
+ static inline int ordered_hashmap_move_one(OrderedHashmap *h, OrderedHashmap *other, const void *key) {
+ return internal_hashmap_move_one(HASHMAP_BASE(h), HASHMAP_BASE(other), key);
+ }
+
+ unsigned internal_hashmap_size(HashmapBase *h) _pure_;
+ static inline unsigned hashmap_size(Hashmap *h) {
+ return internal_hashmap_size(HASHMAP_BASE(h));
+ }
+ static inline unsigned ordered_hashmap_size(OrderedHashmap *h) {
+ return internal_hashmap_size(HASHMAP_BASE(h));
+ }
+
+ static inline bool hashmap_isempty(Hashmap *h) {
+ return hashmap_size(h) == 0;
+ }
+ static inline bool ordered_hashmap_isempty(OrderedHashmap *h) {
+ return ordered_hashmap_size(h) == 0;
+ }
+
+ unsigned internal_hashmap_buckets(HashmapBase *h) _pure_;
+ static inline unsigned hashmap_buckets(Hashmap *h) {
+ return internal_hashmap_buckets(HASHMAP_BASE(h));
+ }
+ static inline unsigned ordered_hashmap_buckets(OrderedHashmap *h) {
+ return internal_hashmap_buckets(HASHMAP_BASE(h));
+ }
+
+ bool internal_hashmap_iterate(HashmapBase *h, Iterator *i, void **value, const void **key);
+ static inline bool hashmap_iterate(Hashmap *h, Iterator *i, void **value, const void **key) {
+ return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
+ }
+ static inline bool ordered_hashmap_iterate(OrderedHashmap *h, Iterator *i, void **value, const void **key) {
+ return internal_hashmap_iterate(HASHMAP_BASE(h), i, value, key);
+ }
+
+ void internal_hashmap_clear(HashmapBase *h);
+ static inline void hashmap_clear(Hashmap *h) {
+ internal_hashmap_clear(HASHMAP_BASE(h));
+ }
+ static inline void ordered_hashmap_clear(OrderedHashmap *h) {
+ internal_hashmap_clear(HASHMAP_BASE(h));
+ }
+
+ void internal_hashmap_clear_free(HashmapBase *h);
+ static inline void hashmap_clear_free(Hashmap *h) {
+ internal_hashmap_clear_free(HASHMAP_BASE(h));
+ }
+ static inline void ordered_hashmap_clear_free(OrderedHashmap *h) {
+ internal_hashmap_clear_free(HASHMAP_BASE(h));
+ }
+
+ void hashmap_clear_free_free(Hashmap *h);
+ static inline void ordered_hashmap_clear_free_free(OrderedHashmap *h) {
+ hashmap_clear_free_free(PLAIN_HASHMAP(h));
+ }
+
+ /*
+ * Note about all *_first*() functions
+ *
+ * For plain Hashmaps and Sets the order of entries is undefined.
+ * The functions find whatever entry is first in the implementation
+ * internal order.
+ *
+ * Only for OrderedHashmaps the order is well defined and finding
+ * the first entry is O(1).
+ */
+
+ void *internal_hashmap_steal_first(HashmapBase *h);
+ static inline void *hashmap_steal_first(Hashmap *h) {
+ return internal_hashmap_steal_first(HASHMAP_BASE(h));
+ }
+ static inline void *ordered_hashmap_steal_first(OrderedHashmap *h) {
+ return internal_hashmap_steal_first(HASHMAP_BASE(h));
+ }
+
+ void *internal_hashmap_steal_first_key(HashmapBase *h);
+ static inline void *hashmap_steal_first_key(Hashmap *h) {
+ return internal_hashmap_steal_first_key(HASHMAP_BASE(h));
+ }
+ static inline void *ordered_hashmap_steal_first_key(OrderedHashmap *h) {
+ return internal_hashmap_steal_first_key(HASHMAP_BASE(h));
+ }
+
+ void *internal_hashmap_first_key(HashmapBase *h) _pure_;
+ static inline void *hashmap_first_key(Hashmap *h) {
+ return internal_hashmap_first_key(HASHMAP_BASE(h));
+ }
+ static inline void *ordered_hashmap_first_key(OrderedHashmap *h) {
+ return internal_hashmap_first_key(HASHMAP_BASE(h));
+ }
+
+ void *internal_hashmap_first(HashmapBase *h) _pure_;
+ static inline void *hashmap_first(Hashmap *h) {
+ return internal_hashmap_first(HASHMAP_BASE(h));
+ }
+ static inline void *ordered_hashmap_first(OrderedHashmap *h) {
+ return internal_hashmap_first(HASHMAP_BASE(h));
+ }
+
+ /* no hashmap_next */
+ void *ordered_hashmap_next(OrderedHashmap *h, const void *key);
+
+ char **internal_hashmap_get_strv(HashmapBase *h);
+ static inline char **hashmap_get_strv(Hashmap *h) {
+ return internal_hashmap_get_strv(HASHMAP_BASE(h));
+ }
+ static inline char **ordered_hashmap_get_strv(OrderedHashmap *h) {
+ return internal_hashmap_get_strv(HASHMAP_BASE(h));
+ }
+
+ /*
+ * Hashmaps are iterated in unpredictable order.
+ * OrderedHashmaps are an exception to this. They are iterated in the order
+ * the entries were inserted.
+ * It is safe to remove the current entry.
+ */
+ #define HASHMAP_FOREACH(e, h, i) \
+ for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), NULL); )
+
+ #define ORDERED_HASHMAP_FOREACH(e, h, i) \
+ for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), NULL); )
+
+ #define HASHMAP_FOREACH_KEY(e, k, h, i) \
+ for ((i) = ITERATOR_FIRST; hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); )
+
+ #define ORDERED_HASHMAP_FOREACH_KEY(e, k, h, i) \
+ for ((i) = ITERATOR_FIRST; ordered_hashmap_iterate((h), &(i), (void**)&(e), (const void**) &(k)); )
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(Hashmap*, hashmap_free_free_free);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(OrderedHashmap*, ordered_hashmap_free_free_free);
+
+ #define _cleanup_hashmap_free_ _cleanup_(hashmap_freep)
+ #define _cleanup_hashmap_free_free_ _cleanup_(hashmap_free_freep)
+ #define _cleanup_hashmap_free_free_free_ _cleanup_(hashmap_free_free_freep)
+ #define _cleanup_ordered_hashmap_free_ _cleanup_(ordered_hashmap_freep)
+ #define _cleanup_ordered_hashmap_free_free_ _cleanup_(ordered_hashmap_free_freep)
+ #define _cleanup_ordered_hashmap_free_free_free_ _cleanup_(ordered_hashmap_free_free_freep)
#define UID_INVALID ((uid_t) -1)
#define GID_INVALID ((gid_t) -1)
#define MODE_INVALID ((mode_t) -1)
+#endif /* NM_IGNORED */
- static inline bool UID_IS_INVALID(uid_t uid) {
- /* We consider both the old 16bit -1 user and the newer 32bit
- * -1 user invalid, since they are or used to be incompatible
- * with syscalls such as setresuid() or chown(). */
-
- return uid == (uid_t) ((uint32_t) -1) || uid == (uid_t) ((uint16_t) -1);
- }
-
- static inline bool GID_IS_INVALID(gid_t gid) {
- return gid == (gid_t) ((uint32_t) -1) || gid == (gid_t) ((uint16_t) -1);
- }
-
#define DEFINE_TRIVIAL_CLEANUP_FUNC(type, func) \
static inline void func##p(type *p) { \
if (*p) \
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2010-2014 Lennart Poettering
+ Copyright 2014 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include "mempool.h"
+ #include "macro.h"
+ #include "util.h"
+
+ struct pool {
+ struct pool *next;
+ unsigned n_tiles;
+ unsigned n_used;
+ };
+
+ void* mempool_alloc_tile(struct mempool *mp) {
+ unsigned i;
+
+ /* When a tile is released we add it to the list and simply
+ * place the next pointer at its offset 0. */
+
+ assert(mp->tile_size >= sizeof(void*));
+ assert(mp->at_least > 0);
+
+ if (mp->freelist) {
+ void *r;
+
+ r = mp->freelist;
+ mp->freelist = * (void**) mp->freelist;
+ return r;
+ }
+
+ if (_unlikely_(!mp->first_pool) ||
+ _unlikely_(mp->first_pool->n_used >= mp->first_pool->n_tiles)) {
+ unsigned n;
+ size_t size;
+ struct pool *p;
+
+ n = mp->first_pool ? mp->first_pool->n_tiles : 0;
+ n = MAX(mp->at_least, n * 2);
+ size = PAGE_ALIGN(ALIGN(sizeof(struct pool)) + n*mp->tile_size);
+ n = (size - ALIGN(sizeof(struct pool))) / mp->tile_size;
+
+ p = malloc(size);
+ if (!p)
+ return NULL;
+
+ p->next = mp->first_pool;
+ p->n_tiles = n;
+ p->n_used = 0;
+
+ mp->first_pool = p;
+ }
+
+ i = mp->first_pool->n_used++;
+
+ return ((uint8_t*) mp->first_pool) + ALIGN(sizeof(struct pool)) + i*mp->tile_size;
+ }
+
+ void* mempool_alloc0_tile(struct mempool *mp) {
+ void *p;
+
+ p = mempool_alloc_tile(mp);
+ if (p)
+ memzero(p, mp->tile_size);
+ return p;
+ }
+
+ void mempool_free_tile(struct mempool *mp, void *p) {
+ * (void**) p = mp->freelist;
+ mp->freelist = p;
+ }
+
+ #ifdef VALGRIND
+
+ void mempool_drop(struct mempool *mp) {
+ struct pool *p = mp->first_pool;
+ while (p) {
+ struct pool *n;
+ n = p->next;
+ free(p);
+ p = n;
+ }
+ }
+
+ #endif
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ #pragma once
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2011-2014 Lennart Poettering
+ Copyright 2014 Michal Schmidt
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <stddef.h>
+
+ struct pool;
+
+ struct mempool {
+ struct pool *first_pool;
+ void *freelist;
+ size_t tile_size;
+ unsigned at_least;
+ };
+
+ void* mempool_alloc_tile(struct mempool *mp);
+ void* mempool_alloc0_tile(struct mempool *mp);
+ void mempool_free_tile(struct mempool *mp, void *p);
+
+ #define DEFINE_MEMPOOL(pool_name, tile_type, alloc_at_least) \
+ struct mempool pool_name = { \
+ .tile_size = sizeof(tile_type), \
+ .at_least = alloc_at_least, \
+ }
+
+
+ #ifdef VALGRIND
+ void mempool_drop(struct mempool *mp);
+ #endif
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ /*
+ * Priority Queue
+ * The prioq object implements a priority queue. That is, it orders objects by
+ * their priority and allows O(1) access to the object with the highest
+ * priority. Insertion and removal are Θ(log n). Optionally, the caller can
+ * provide a pointer to an index which will be kept up-to-date by the prioq.
+ *
+ * The underlying algorithm used in this implementation is a Heap.
+ */
+
++#include "nm-sd-adapt.h"
++
+ #include "util.h"
+ #include "prioq.h"
+
+ struct prioq_item {
+ void *data;
+ unsigned *idx;
+ };
+
+ struct Prioq {
+ compare_func_t compare_func;
+ unsigned n_items, n_allocated;
+
+ struct prioq_item *items;
+ };
+
+ Prioq *prioq_new(compare_func_t compare_func) {
+ Prioq *q;
+
+ q = new0(Prioq, 1);
+ if (!q)
+ return q;
+
+ q->compare_func = compare_func;
+ return q;
+ }
+
+ Prioq* prioq_free(Prioq *q) {
+ if (!q)
+ return NULL;
+
+ free(q->items);
+ free(q);
+
+ return NULL;
+ }
+
+ int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func) {
+ assert(q);
+
+ if (*q)
+ return 0;
+
+ *q = prioq_new(compare_func);
+ if (!*q)
+ return -ENOMEM;
+
+ return 0;
+ }
+
+ static void swap(Prioq *q, unsigned j, unsigned k) {
+ void *saved_data;
+ unsigned *saved_idx;
+
+ assert(q);
+ assert(j < q->n_items);
+ assert(k < q->n_items);
+
+ assert(!q->items[j].idx || *(q->items[j].idx) == j);
+ assert(!q->items[k].idx || *(q->items[k].idx) == k);
+
+ saved_data = q->items[j].data;
+ saved_idx = q->items[j].idx;
+ q->items[j].data = q->items[k].data;
+ q->items[j].idx = q->items[k].idx;
+ q->items[k].data = saved_data;
+ q->items[k].idx = saved_idx;
+
+ if (q->items[j].idx)
+ *q->items[j].idx = j;
+
+ if (q->items[k].idx)
+ *q->items[k].idx = k;
+ }
+
+ static unsigned shuffle_up(Prioq *q, unsigned idx) {
+ assert(q);
+
+ while (idx > 0) {
+ unsigned k;
+
+ k = (idx-1)/2;
+
+ if (q->compare_func(q->items[k].data, q->items[idx].data) <= 0)
+ break;
+
+ swap(q, idx, k);
+ idx = k;
+ }
+
+ return idx;
+ }
+
+ static unsigned shuffle_down(Prioq *q, unsigned idx) {
+ assert(q);
+
+ for (;;) {
+ unsigned j, k, s;
+
+ k = (idx+1)*2; /* right child */
+ j = k-1; /* left child */
+
+ if (j >= q->n_items)
+ break;
+
+ if (q->compare_func(q->items[j].data, q->items[idx].data) < 0)
+
+ /* So our left child is smaller than we are, let's
+ * remember this fact */
+ s = j;
+ else
+ s = idx;
+
+ if (k < q->n_items &&
+ q->compare_func(q->items[k].data, q->items[s].data) < 0)
+
+ /* So our right child is smaller than we are, let's
+ * remember this fact */
+ s = k;
+
+ /* s now points to the smallest of the three items */
+
+ if (s == idx)
+ /* No swap necessary, we're done */
+ break;
+
+ swap(q, idx, s);
+ idx = s;
+ }
+
+ return idx;
+ }
+
+ int prioq_put(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+ unsigned k;
+
+ assert(q);
+
+ if (q->n_items >= q->n_allocated) {
+ unsigned n;
+ struct prioq_item *j;
+
+ n = MAX((q->n_items+1) * 2, 16u);
+ j = realloc(q->items, sizeof(struct prioq_item) * n);
+ if (!j)
+ return -ENOMEM;
+
+ q->items = j;
+ q->n_allocated = n;
+ }
+
+ k = q->n_items++;
+ i = q->items + k;
+ i->data = data;
+ i->idx = idx;
+
+ if (idx)
+ *idx = k;
+
+ shuffle_up(q, k);
+
+ return 0;
+ }
+
+ static void remove_item(Prioq *q, struct prioq_item *i) {
+ struct prioq_item *l;
+
+ assert(q);
+ assert(i);
+
+ l = q->items + q->n_items - 1;
+
+ if (i == l)
+ /* Last entry, let's just remove it */
+ q->n_items--;
+ else {
+ unsigned k;
+
+ /* Not last entry, let's replace the last entry with
+ * this one, and reshuffle */
+
+ k = i - q->items;
+
+ i->data = l->data;
+ i->idx = l->idx;
+ if (i->idx)
+ *i->idx = k;
+ q->n_items--;
+
+ k = shuffle_down(q, k);
+ shuffle_up(q, k);
+ }
+ }
+
+ _pure_ static struct prioq_item* find_item(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+
+ assert(q);
+
+ if (idx) {
+ if (*idx == PRIOQ_IDX_NULL ||
+ *idx > q->n_items)
+ return NULL;
+
+ i = q->items + *idx;
+ if (i->data != data)
+ return NULL;
+
+ return i;
+ } else {
+ for (i = q->items; i < q->items + q->n_items; i++)
+ if (i->data == data)
+ return i;
+ return NULL;
+ }
+ }
+
+ int prioq_remove(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+
+ if (!q)
+ return 0;
+
+ i = find_item(q, data, idx);
+ if (!i)
+ return 0;
+
+ remove_item(q, i);
+ return 1;
+ }
+
+ int prioq_reshuffle(Prioq *q, void *data, unsigned *idx) {
+ struct prioq_item *i;
+ unsigned k;
+
+ assert(q);
+
+ i = find_item(q, data, idx);
+ if (!i)
+ return 0;
+
+ k = i - q->items;
+ k = shuffle_down(q, k);
+ shuffle_up(q, k);
+ return 1;
+ }
+
+ void *prioq_peek(Prioq *q) {
+
+ if (!q)
+ return NULL;
+
+ if (q->n_items <= 0)
+ return NULL;
+
+ return q->items[0].data;
+ }
+
+ void *prioq_pop(Prioq *q) {
+ void *data;
+
+ if (!q)
+ return NULL;
+
+ if (q->n_items <= 0)
+ return NULL;
+
+ data = q->items[0].data;
+ remove_item(q, q->items);
+ return data;
+ }
+
+ unsigned prioq_size(Prioq *q) {
+
+ if (!q)
+ return 0;
+
+ return q->n_items;
+ }
+
+ bool prioq_isempty(Prioq *q) {
+
+ if (!q)
+ return true;
+
+ return q->n_items <= 0;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ #pragma once
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include "hashmap.h"
+
+ typedef struct Prioq Prioq;
+
+ #define PRIOQ_IDX_NULL ((unsigned) -1)
+
+ Prioq *prioq_new(compare_func_t compare);
+ Prioq *prioq_free(Prioq *q);
+ int prioq_ensure_allocated(Prioq **q, compare_func_t compare_func);
+
+ int prioq_put(Prioq *q, void *data, unsigned *idx);
+ int prioq_remove(Prioq *q, void *data, unsigned *idx);
+ int prioq_reshuffle(Prioq *q, void *data, unsigned *idx);
+
+ void *prioq_peek(Prioq *q) _pure_;
+ void *prioq_pop(Prioq *q);
+
+ unsigned prioq_size(Prioq *q) _pure_;
+ bool prioq_isempty(Prioq *q) _pure_;
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ #pragma once
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2010 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include "hashmap.h"
+ #include "macro.h"
+
+ Set *internal_set_new(const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+ #define set_new(ops) internal_set_new(ops HASHMAP_DEBUG_SRC_ARGS)
+
+
+ static inline Set *set_free(Set *s) {
+ internal_hashmap_free(HASHMAP_BASE(s));
+ return NULL;
+ }
+
+ static inline Set *set_free_free(Set *s) {
+ internal_hashmap_free_free(HASHMAP_BASE(s));
+ return NULL;
+ }
+
+ /* no set_free_free_free */
+
+ static inline Set *set_copy(Set *s) {
+ return (Set*) internal_hashmap_copy(HASHMAP_BASE(s));
+ }
+
+ int internal_set_ensure_allocated(Set **s, const struct hash_ops *hash_ops HASHMAP_DEBUG_PARAMS);
+ #define set_ensure_allocated(h, ops) internal_set_ensure_allocated(h, ops HASHMAP_DEBUG_SRC_ARGS)
+
+ int set_put(Set *s, const void *key);
+ /* no set_update */
+ /* no set_replace */
+ static inline void *set_get(Set *s, void *key) {
+ return internal_hashmap_get(HASHMAP_BASE(s), key);
+ }
+ /* no set_get2 */
+
+ static inline bool set_contains(Set *s, const void *key) {
+ return internal_hashmap_contains(HASHMAP_BASE(s), key);
+ }
+
+ static inline void *set_remove(Set *s, const void *key) {
+ return internal_hashmap_remove(HASHMAP_BASE(s), key);
+ }
+
+ /* no set_remove2 */
+ /* no set_remove_value */
+ int set_remove_and_put(Set *s, const void *old_key, const void *new_key);
+ /* no set_remove_and_replace */
+ int set_merge(Set *s, Set *other);
+
+ static inline int set_reserve(Set *h, unsigned entries_add) {
+ return internal_hashmap_reserve(HASHMAP_BASE(h), entries_add);
+ }
+
+ static inline int set_move(Set *s, Set *other) {
+ return internal_hashmap_move(HASHMAP_BASE(s), HASHMAP_BASE(other));
+ }
+
+ static inline int set_move_one(Set *s, Set *other, const void *key) {
+ return internal_hashmap_move_one(HASHMAP_BASE(s), HASHMAP_BASE(other), key);
+ }
+
+ static inline unsigned set_size(Set *s) {
+ return internal_hashmap_size(HASHMAP_BASE(s));
+ }
+
+ static inline bool set_isempty(Set *s) {
+ return set_size(s) == 0;
+ }
+
+ static inline unsigned set_buckets(Set *s) {
+ return internal_hashmap_buckets(HASHMAP_BASE(s));
+ }
+
+ bool set_iterate(Set *s, Iterator *i, void **value);
+
+ static inline void set_clear(Set *s) {
+ internal_hashmap_clear(HASHMAP_BASE(s));
+ }
+
+ static inline void set_clear_free(Set *s) {
+ internal_hashmap_clear_free(HASHMAP_BASE(s));
+ }
+
+ /* no set_clear_free_free */
+
+ static inline void *set_steal_first(Set *s) {
+ return internal_hashmap_steal_first(HASHMAP_BASE(s));
+ }
+
+ /* no set_steal_first_key */
+ /* no set_first_key */
+
+ static inline void *set_first(Set *s) {
+ return internal_hashmap_first(HASHMAP_BASE(s));
+ }
+
+ /* no set_next */
+
+ static inline char **set_get_strv(Set *s) {
+ return internal_hashmap_get_strv(HASHMAP_BASE(s));
+ }
+
+ int set_consume(Set *s, void *value);
+ int set_put_strdup(Set *s, const char *p);
+ int set_put_strdupv(Set *s, char **l);
+
+ #define SET_FOREACH(e, s, i) \
+ for ((i) = ITERATOR_FIRST; set_iterate((s), &(i), (void**)&(e)); )
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(Set*, set_free_free);
+
+ #define _cleanup_set_free_ _cleanup_(set_freep)
+ #define _cleanup_set_free_free_ _cleanup_(set_free_freep)
this software. If not, see <http://creativecommons.org/publicdomain/zero/1.0/>.
(Minimal changes made by Lennart Poettering, to make clean for inclusion in systemd)
+ (Refactored by Tom Gundersen to split up in several functions and follow systemd
+ coding style)
*/
- #include <stdint.h>
- #include <stdio.h>
- #include <string.h>
+#include "nm-sd-adapt.h"
+
+ #include "sparse-endian.h"
#include "siphash24.h"
+ #include "util.h"
- typedef uint64_t u64;
- typedef uint32_t u32;
- typedef uint8_t u8;
-
- #define ROTL(x,b) (u64)( ((x) << (b)) | ( (x) >> (64 - (b))) )
-
- #define U32TO8_LE(p, v) \
- (p)[0] = (u8)((v) ); (p)[1] = (u8)((v) >> 8); \
- (p)[2] = (u8)((v) >> 16); (p)[3] = (u8)((v) >> 24);
-
- #define U64TO8_LE(p, v) \
- U32TO8_LE((p), (u32)((v) )); \
- U32TO8_LE((p) + 4, (u32)((v) >> 32));
-
- #define U8TO64_LE(p) \
- (((u64)((p)[0]) ) | \
- ((u64)((p)[1]) << 8) | \
- ((u64)((p)[2]) << 16) | \
- ((u64)((p)[3]) << 24) | \
- ((u64)((p)[4]) << 32) | \
- ((u64)((p)[5]) << 40) | \
- ((u64)((p)[6]) << 48) | \
- ((u64)((p)[7]) << 56))
-
- #define SIPROUND \
- do { \
- v0 += v1; v1=ROTL(v1,13); v1 ^= v0; v0=ROTL(v0,32); \
- v2 += v3; v3=ROTL(v3,16); v3 ^= v2; \
- v0 += v3; v3=ROTL(v3,21); v3 ^= v0; \
- v2 += v1; v1=ROTL(v1,17); v1 ^= v2; v2=ROTL(v2,32); \
- } while(0)
+ static inline uint64_t rotate_left(uint64_t x, uint8_t b) {
+ assert(b < 64);
+
+ return (x << b) | (x >> (64 - b));
+ }
+
+ static inline void sipround(struct siphash *state) {
+ assert(state);
+
+ state->v0 += state->v1;
+ state->v1 = rotate_left(state->v1, 13);
+ state->v1 ^= state->v0;
+ state->v0 = rotate_left(state->v0, 32);
+ state->v2 += state->v3;
+ state->v3 = rotate_left(state->v3, 16);
+ state->v3 ^= state->v2;
+ state->v0 += state->v3;
+ state->v3 = rotate_left(state->v3, 21);
+ state->v3 ^= state->v0;
+ state->v2 += state->v1;
+ state->v1 = rotate_left(state->v1, 17);
+ state->v1 ^= state->v2;
+ state->v2 = rotate_left(state->v2, 32);
+ }
+
+ void siphash24_init(struct siphash *state, const uint8_t k[16]) {
+ uint64_t k0, k1;
+
+ assert(state);
+ assert(k);
+
+ k0 = le64toh(*(le64_t*) k);
+ k1 = le64toh(*(le64_t*) (k + 8));
+
+ /* "somepseudorandomlygeneratedbytes" */
+ state->v0 = 0x736f6d6570736575ULL ^ k0;
+ state->v1 = 0x646f72616e646f6dULL ^ k1;
+ state->v2 = 0x6c7967656e657261ULL ^ k0;
+ state->v3 = 0x7465646279746573ULL ^ k1;
+ state->padding = 0;
+ state->inlen = 0;
+ }
+
+ void siphash24_compress(const void *_in, size_t inlen, struct siphash *state) {
+ uint64_t m;
+ const uint8_t *in = _in;
+ const uint8_t *end = in + inlen;
+ unsigned left = state->inlen & 7;
+
+ assert(in);
+ assert(state);
+
+ /* update total length */
+ state->inlen += inlen;
+
+ /* if padding exists, fill it out */
+ if (left > 0) {
+ for ( ; in < end && left < 8; in ++, left ++ )
+ state->padding |= ( ( uint64_t )*in ) << (left * 8);
+
+ if (in == end && left < 8)
+ /* we did not have enough input to fill out the padding completely */
+ return;
- /* SipHash-2-4 */
- void siphash24(uint8_t out[8], const void *_in, size_t inlen, const uint8_t k[16])
- {
- /* "somepseudorandomlygeneratedbytes" */
- u64 v0 = 0x736f6d6570736575ULL;
- u64 v1 = 0x646f72616e646f6dULL;
- u64 v2 = 0x6c7967656e657261ULL;
- u64 v3 = 0x7465646279746573ULL;
- u64 b;
- u64 k0 = U8TO64_LE( k );
- u64 k1 = U8TO64_LE( k + 8 );
- u64 m;
- const u8 *in = _in;
- const u8 *end = in + inlen - ( inlen % sizeof( u64 ) );
- const int left = inlen & 7;
- b = ( ( u64 )inlen ) << 56;
- v3 ^= k1;
- v2 ^= k0;
- v1 ^= k1;
- v0 ^= k0;
-
- for ( ; in != end; in += 8 )
- {
- m = U8TO64_LE( in );
#ifdef DEBUG
- printf( "(%3d) v0 %08x %08x\n", ( int )inlen, ( u32 )( v0 >> 32 ), ( u32 )v0 );
- printf( "(%3d) v1 %08x %08x\n", ( int )inlen, ( u32 )( v1 >> 32 ), ( u32 )v1 );
- printf( "(%3d) v2 %08x %08x\n", ( int )inlen, ( u32 )( v2 >> 32 ), ( u32 )v2 );
- printf( "(%3d) v3 %08x %08x\n", ( int )inlen, ( u32 )( v3 >> 32 ), ( u32 )v3 );
- printf( "(%3d) compress %08x %08x\n", ( int )inlen, ( u32 )( m >> 32 ), ( u32 )m );
+ printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
+ printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
+ printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
+ printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
+ printf("(%3zu) compress padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t)state->padding);
#endif
- v3 ^= m;
- SIPROUND;
- SIPROUND;
- v0 ^= m;
- }
+ state->v3 ^= state->padding;
+ sipround(state);
+ sipround(state);
+ state->v0 ^= state->padding;
- switch( left )
- {
- case 7: b |= ( ( u64 )in[ 6] ) << 48;
+ state->padding = 0;
+ }
- case 6: b |= ( ( u64 )in[ 5] ) << 40;
+ end -= ( state->inlen % sizeof (uint64_t) );
- case 5: b |= ( ( u64 )in[ 4] ) << 32;
+ for ( ; in < end; in += 8 ) {
+ m = le64toh(*(le64_t*) in);
+ #ifdef DEBUG
+ printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
+ printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
+ printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
+ printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
+ printf("(%3zu) compress %08x %08x\n", state->inlen, (uint32_t) (m >> 32), (uint32_t) m);
+ #endif
+ state->v3 ^= m;
+ sipround(state);
+ sipround(state);
+ state->v0 ^= m;
+ }
+
+ left = state->inlen & 7;
+
+ switch(left)
+ {
+ case 7: state->padding |= ((uint64_t) in[6]) << 48;
- case 4: b |= ( ( u64 )in[ 3] ) << 24;
+ case 6: state->padding |= ((uint64_t) in[5]) << 40;
- case 3: b |= ( ( u64 )in[ 2] ) << 16;
+ case 5: state->padding |= ((uint64_t) in[4]) << 32;
- case 2: b |= ( ( u64 )in[ 1] ) << 8;
+ case 4: state->padding |= ((uint64_t) in[3]) << 24;
- case 1: b |= ( ( u64 )in[ 0] ); break;
+ case 3: state->padding |= ((uint64_t) in[2]) << 16;
+
+ case 2: state->padding |= ((uint64_t) in[1]) << 8;
+
+ case 1: state->padding |= ((uint64_t) in[0]); break;
+
+ case 0: break;
+ }
+ }
- case 0: break;
- }
+ void siphash24_finalize(uint8_t out[8], struct siphash *state) {
+ uint64_t b;
+ b = state->padding | (( ( uint64_t )state->inlen ) << 56);
#ifdef DEBUG
- printf( "(%3d) v0 %08x %08x\n", ( int )inlen, ( u32 )( v0 >> 32 ), ( u32 )v0 );
- printf( "(%3d) v1 %08x %08x\n", ( int )inlen, ( u32 )( v1 >> 32 ), ( u32 )v1 );
- printf( "(%3d) v2 %08x %08x\n", ( int )inlen, ( u32 )( v2 >> 32 ), ( u32 )v2 );
- printf( "(%3d) v3 %08x %08x\n", ( int )inlen, ( u32 )( v3 >> 32 ), ( u32 )v3 );
- printf( "(%3d) padding %08x %08x\n", ( int )inlen, ( u32 )( b >> 32 ), ( u32 )b );
+ printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t)state->v0);
+ printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t)state->v1);
+ printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t)state->v2);
+ printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t)state->v3);
+ printf("(%3zu) padding %08x %08x\n", state->inlen, (uint32_t) (state->padding >> 32), (uint32_t) state->padding);
#endif
- v3 ^= b;
- SIPROUND;
- SIPROUND;
- v0 ^= b;
+ state->v3 ^= b;
+ sipround(state);
+ sipround(state);
+ state->v0 ^= b;
+
#ifdef DEBUG
- printf( "(%3d) v0 %08x %08x\n", ( int )inlen, ( u32 )( v0 >> 32 ), ( u32 )v0 );
- printf( "(%3d) v1 %08x %08x\n", ( int )inlen, ( u32 )( v1 >> 32 ), ( u32 )v1 );
- printf( "(%3d) v2 %08x %08x\n", ( int )inlen, ( u32 )( v2 >> 32 ), ( u32 )v2 );
- printf( "(%3d) v3 %08x %08x\n", ( int )inlen, ( u32 )( v3 >> 32 ), ( u32 )v3 );
+ printf("(%3zu) v0 %08x %08x\n", state->inlen, (uint32_t) (state->v0 >> 32), (uint32_t) state->v0);
+ printf("(%3zu) v1 %08x %08x\n", state->inlen, (uint32_t) (state->v1 >> 32), (uint32_t) state->v1);
+ printf("(%3zu) v2 %08x %08x\n", state->inlen, (uint32_t) (state->v2 >> 32), (uint32_t) state->v2);
+ printf("(%3zu) v3 %08x %08x\n", state->inlen, (uint32_t) (state->v3 >> 32), (uint32_t) state->v3);
#endif
- v2 ^= 0xff;
- SIPROUND;
- SIPROUND;
- SIPROUND;
- SIPROUND;
- b = v0 ^ v1 ^ v2 ^ v3;
- U64TO8_LE( out, b );
+ state->v2 ^= 0xff;
+
+ sipround(state);
+ sipround(state);
+ sipround(state);
+ sipround(state);
+
+ *(le64_t*)out = htole64(state->v0 ^ state->v1 ^ state->v2 ^ state->v3);
+ }
+
+ /* SipHash-2-4 */
+ void siphash24(uint8_t out[8], const void *_in, size_t inlen, const uint8_t k[16]) {
+ struct siphash state;
+
+ siphash24_init(&state, k);
+ siphash24_compress(_in, inlen, &state);
+ siphash24_finalize(out, &state);
}
return l;
}
+#if 0 /* NM_IGNORED */
int strv_split_extract(char ***t, const char *s, const char *separators, ExtractFlags flags) {
- size_t n = 0, allocated = 0;
_cleanup_strv_free_ char **l = NULL;
+ size_t n = 0, allocated = 0;
int r;
assert(t);
*t = l;
l = NULL;
- return 0;
+ return (int) n;
}
+#endif /* NM_IGNORED */
char *strv_join(char **l, const char *separator) {
char *r, *e;
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
- #include <string.h>
- #include <unistd.h>
+#include "nm-sd-adapt.h"
+
+ #include <ctype.h>
+ #include <dirent.h>
#include <errno.h>
- #include <stdlib.h>
- #include <signal.h>
+ #include <fcntl.h>
+ #include <glob.h>
+ #include <grp.h>
+ #include <langinfo.h>
#include <libintl.h>
- #include <stdio.h>
- #include <syslog.h>
- #include <sched.h>
- #include <sys/resource.h>
+ #include <limits.h>
+ #include <linux/magic.h>
#include <linux/sched.h>
- #include <sys/types.h>
- #include <sys/stat.h>
- #include <fcntl.h>
- #include <dirent.h>
- #include <sys/ioctl.h>
- #include <stdarg.h>
+ #include <locale.h>
+ #include <netinet/ip.h>
#include <poll.h>
- #include <ctype.h>
- #include <sys/prctl.h>
- #include <sys/utsname.h>
#include <pwd.h>
- #include <netinet/ip.h>
- #include <sys/wait.h>
- #include <sys/time.h>
- #include <glob.h>
- #include <grp.h>
+ #include <sched.h>
+ #include <signal.h>
+ #include <stdarg.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+ #include <sys/file.h>
+ #include <sys/ioctl.h>
#include <sys/mman.h>
- #include <sys/vfs.h>
#include <sys/mount.h>
- #include <linux/magic.h>
- #include <limits.h>
- #include <langinfo.h>
- #include <locale.h>
#include <sys/personality.h>
- #include <sys/xattr.h>
+ #include <sys/prctl.h>
+ #include <sys/resource.h>
+ #include <sys/stat.h>
#include <sys/statvfs.h>
- #include <sys/file.h>
- #include <linux/fs.h>
+ #include <sys/time.h>
+ #include <sys/types.h>
+ #include <sys/utsname.h>
+ #include <sys/vfs.h>
+ #include <sys/wait.h>
+ #include <sys/xattr.h>
+ #include <syslog.h>
+ #include <unistd.h>
/* When we include libgen.h because we need dirname() we immediately
- * undefine basename() since libgen.h defines it as a macro to the POSIX
- * version which is really broken. We prefer GNU basename(). */
+ * undefine basename() since libgen.h defines it as a macro to the
+ * POSIX version which is really broken. We prefer GNU basename(). */
#include <libgen.h>
#undef basename
#include <sys/auxv.h>
#endif
- #include "config.h"
- #include "macro.h"
- #include "util.h"
+ /* We include linux/fs.h as last of the system headers, as it
+ * otherwise conflicts with sys/mount.h. Yay, Linux is great! */
+ #include <linux/fs.h>
+
+#if 0 /* NM_IGNORED */
+ #include "build.h"
+ #include "def.h"
+ #include "device-nodes.h"
+ #include "env-util.h"
+ #include "exit-status.h"
+ #include "fileio.h"
+ #include "formats-util.h"
+ #include "gunicode.h"
+ #include "hashmap.h"
+ #include "hostname-util.h"
#include "ioprio.h"
- #include "missing.h"
#include "log.h"
- #include "strv.h"
++#endif /* NM_IGNORED */
+ #include "macro.h"
++#if 0 /* NM_IGNORED */
+ #include "missing.h"
#include "mkdir.h"
+#endif /* NM_IGNORED */
#include "path-util.h"
- #include "exit-status.h"
- #include "hashmap.h"
- #include "env-util.h"
- #include "fileio.h"
- #include "device-nodes.h"
+#if 0 /* NM_IGNORED */
+ #include "process-util.h"
+ #include "random-util.h"
+ #include "signal-util.h"
+ #include "sparse-endian.h"
+ #include "strv.h"
+ #include "terminal-util.h"
+#endif /* NM_IGNORED */
#include "utf8.h"
- #include "gunicode.h"
+ #include "util.h"
+#if 0 /* NM_IGNORED */
#include "virt.h"
- #include "def.h"
- #include "sparse-endian.h"
- #include "formats-util.h"
- #include "process-util.h"
- #include "random-util.h"
- #include "terminal-util.h"
- #include "hostname-util.h"
- #include "signal-util.h"
+#endif /* NM_IGNORED */
/* Put this test here for a lack of better place */
assert_cc(EAGAIN == EWOULDBLOCK);
+#if 0 /* NM_IGNORED */
int saved_argc = 0;
char **saved_argv = NULL;
++#endif /* NM_IGNORED */
size_t page_size(void) {
static thread_local size_t pgsz = 0;
return -errno;
}
}
+
+ int send_one_fd(int transport_fd, int fd, int flags) {
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control = {};
+ struct msghdr mh = {
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg;
+
+ assert(transport_fd >= 0);
+ assert(fd >= 0);
+
+ cmsg = CMSG_FIRSTHDR(&mh);
+ cmsg->cmsg_level = SOL_SOCKET;
+ cmsg->cmsg_type = SCM_RIGHTS;
+ cmsg->cmsg_len = CMSG_LEN(sizeof(int));
+ memcpy(CMSG_DATA(cmsg), &fd, sizeof(int));
+
+ mh.msg_controllen = CMSG_SPACE(sizeof(int));
+ if (sendmsg(transport_fd, &mh, MSG_NOSIGNAL | flags) < 0)
+ return -errno;
+
+ return 0;
+ }
+
+ int receive_one_fd(int transport_fd, int flags) {
+ union {
+ struct cmsghdr cmsghdr;
+ uint8_t buf[CMSG_SPACE(sizeof(int))];
+ } control = {};
+ struct msghdr mh = {
+ .msg_control = &control,
+ .msg_controllen = sizeof(control),
+ };
+ struct cmsghdr *cmsg, *found = NULL;
+
+ assert(transport_fd >= 0);
+
+ /*
+ * Receive a single FD via @transport_fd. We don't care for
+ * the transport-type. We retrieve a single FD at most, so for
+ * packet-based transports, the caller must ensure to send
+ * only a single FD per packet. This is best used in
+ * combination with send_one_fd().
+ */
+
+ if (recvmsg(transport_fd, &mh, MSG_NOSIGNAL | MSG_CMSG_CLOEXEC | flags) < 0)
+ return -errno;
+
+ CMSG_FOREACH(cmsg, &mh) {
+ if (cmsg->cmsg_level == SOL_SOCKET &&
+ cmsg->cmsg_type == SCM_RIGHTS &&
+ cmsg->cmsg_len == CMSG_LEN(sizeof(int))) {
+ assert(!found);
+ found = cmsg;
+ break;
+ }
+ }
+
+ if (!found) {
+ cmsg_close_all(&mh);
+ return -EIO;
+ }
+
+ return *(int*) CMSG_DATA(found);
+ }
+
+ void nop_signal_handler(int sig) {
+ /* nothing here */
+ }
+
+ int version(void) {
+ puts(PACKAGE_STRING "\n"
+ SYSTEMD_FEATURES);
+ return 0;
+ }
+
+ bool fdname_is_valid(const char *s) {
+ const char *p;
+
+ /* Validates a name for $LISTEN_FDNAMES. We basically allow
+ * everything ASCII that's not a control character. Also, as
+ * special exception the ":" character is not allowed, as we
+ * use that as field separator in $LISTEN_FDNAMES.
+ *
+ * Note that the empty string is explicitly allowed
+ * here. However, we limit the length of the names to 255
+ * characters. */
+
+ if (!s)
+ return false;
+
+ for (p = s; *p; p++) {
+ if (*p < ' ')
+ return false;
+ if (*p >= 127)
+ return false;
+ if (*p == ':')
+ return false;
+ }
+
+ return p - s < 256;
+ }
+#endif /* NM_IGNORED */
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
+#include "nm-sd-adapt.h"
+
#include <alloca.h>
+ #include <dirent.h>
#include <fcntl.h>
#include <inttypes.h>
- #include <time.h>
+ #include <limits.h>
+ #include <locale.h>
+ #include <mntent.h>
#include <stdarg.h>
#include <stdbool.h>
- #include <stdlib.h>
+ #include <stddef.h>
#include <stdio.h>
- #include <sched.h>
- #include <limits.h>
- #include <sys/types.h>
+ #include <stdlib.h>
+ #include <sys/inotify.h>
#include <sys/socket.h>
#include <sys/stat.h>
- #include <dirent.h>
- #include <stddef.h>
- #include <unistd.h>
- #include <locale.h>
- #include <mntent.h>
- #include <sys/inotify.h>
#include <sys/statfs.h>
+ #include <sys/types.h>
+ #include <time.h>
+ #include <unistd.h>
++#if 0 /* NM_IGNORED */
+ #include "formats-util.h"
++#endif /* NM_IGNORED */
#include "macro.h"
+#if 0 /* NM_IGNORED */
#include "missing.h"
+#endif /* NM_IGNORED */
#include "time-util.h"
- #if 0 /* NM_IGNORED */
- #include "formats-util.h"
- #endif /* NM_IGNORED */
/* What is interpreted as whitespace? */
#define WHITESPACE " \t\n\r"
char *strjoin(const char *x, ...) _sentinel_;
++#if 0 /* NM_IGNORED */
bool is_main_thread(void);
++#endif /* NM_IGNORED */
static inline bool _pure_ in_charset(const char *s, const char* charset) {
assert(s);
--- /dev/null
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Axis Communications AB. All rights reserved.
+ Copyright (C) 2015 Tom Gundersen
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <linux/filter.h>
+ #include <arpa/inet.h>
+
+ #include "util.h"
+ #include "arp-util.h"
+
+ int arp_network_bind_raw_socket(int ifindex, be32_t address, const struct ether_addr *eth_mac) {
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_LEN, 0), /* A <- packet length */
+ BPF_JUMP(BPF_JMP + BPF_JGE + BPF_K, sizeof(struct ether_arp), 1, 0), /* packet >= arp packet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hrd)), /* A <- header */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPHRD_ETHER, 1, 0), /* header == ethernet ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pro)), /* A <- protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_IP, 1, 0), /* protocol == IP ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_hln)), /* A <- hardware address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct ether_addr), 1, 0), /* length == sizeof(ether_addr)? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_B + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_pln)), /* A <- protocol address length */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, sizeof(struct in_addr), 1, 0), /* length == sizeof(in_addr) ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, ea_hdr.ar_op)), /* A <- operation */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REQUEST, 2, 0), /* protocol == request ? */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ARPOP_REPLY, 1, 0), /* protocol == reply ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ /* Sender Hardware Address must be different from our own */
+ BPF_STMT(BPF_LD + BPF_IMM, htobe32(*((uint32_t *) eth_mac))), /* A <- 4 bytes of client's MAC */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_sha)), /* A <- 4 bytes of SHA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 6), /* A == 0 ? */
+ BPF_STMT(BPF_LD + BPF_IMM, htobe16(*((uint16_t *) (((char *) eth_mac) + 4)))), /* A <- remainder of client's MAC */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(struct ether_arp, arp_sha) + 4), /* A <- remainder of SHA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* A xor X */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ /* Sender Protocol Address or Target Protocol Address must be equal to the one we care about*/
+ BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_spa)), /* A <- SPA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ BPF_STMT(BPF_LD + BPF_IMM, htobe32(address)), /* A <- clients IP */
+ BPF_STMT(BPF_MISC + BPF_TAX, 0), /* X <- A */
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(struct ether_arp, arp_tpa)), /* A <- TPA */
+ BPF_STMT(BPF_ALU + BPF_XOR + BPF_X, 0), /* X xor A */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0, 0, 1), /* A == 0 ? */
+ BPF_STMT(BPF_RET + BPF_K, 65535), /* return all */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* ignore */
+ };
+ struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = (struct sock_filter*) filter
+ };
+ union sockaddr_union link = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htons(ETH_P_ARP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ };
+ _cleanup_close_ int s = -1;
+ int r;
+
+ assert(ifindex > 0);
+
+ s = socket(PF_PACKET, SOCK_DGRAM | SOCK_CLOEXEC | SOCK_NONBLOCK, 0);
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = bind(s, &link.sa, sizeof(link.ll));
+ if (r < 0)
+ return -errno;
+
+ r = s;
+ s = -1;
+
+ return r;
+ }
+
+ static int arp_send_packet(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha,
+ bool announce) {
+ union sockaddr_union link = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_protocol = htons(ETH_P_ARP),
+ .ll.sll_ifindex = ifindex,
+ .ll.sll_halen = ETH_ALEN,
+ .ll.sll_addr = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff },
+ };
+ struct ether_arp arp = {
+ .ea_hdr.ar_hrd = htons(ARPHRD_ETHER), /* HTYPE */
+ .ea_hdr.ar_pro = htons(ETHERTYPE_IP), /* PTYPE */
+ .ea_hdr.ar_hln = ETH_ALEN, /* HLEN */
+ .ea_hdr.ar_pln = sizeof(be32_t), /* PLEN */
+ .ea_hdr.ar_op = htons(ARPOP_REQUEST), /* REQUEST */
+ };
+ int r;
+
+ assert(fd >= 0);
+ assert(pa != 0);
+ assert(ha);
+
+ memcpy(&arp.arp_sha, ha, ETH_ALEN);
+ memcpy(&arp.arp_tpa, &pa, sizeof(pa));
+
+ if (announce)
+ memcpy(&arp.arp_spa, &pa, sizeof(pa));
+
+ r = sendto(fd, &arp, sizeof(struct ether_arp), 0, &link.sa, sizeof(link.ll));
+ if (r < 0)
+ return -errno;
+
+ return 0;
+ }
+
+ int arp_send_probe(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha) {
+ return arp_send_packet(fd, ifindex, pa, ha, false);
+ }
+
+ int arp_send_announcement(int fd, int ifindex,
+ be32_t pa, const struct ether_addr *ha) {
+ return arp_send_packet(fd, ifindex, pa, ha, true);
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include "lldp-internal.h"
+ #include "sd-lldp.h"
+
+ /* We store maximum 1K chassis entries */
+ #define LLDP_MIB_MAX_CHASSIS 1024
+
+ /* Maximum Ports can be attached to any chassis */
+ #define LLDP_MIB_MAX_PORT_PER_CHASSIS 32
+
+ /* 10.5.5.2.2 mibUpdateObjects ()
+ * The mibUpdateObjects () procedure updates the MIB objects corresponding to
+ * the TLVs contained in the received LLDPDU for the LLDP remote system
+ * indicated by the LLDP remote systems update process defined in 10.3.5 */
+
+ int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv) {
+ lldp_neighbour_port *p;
+ uint16_t length, ttl;
+ uint8_t *data;
+ uint8_t type;
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(tlv, -EINVAL);
+
+ r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length);
+ if (r < 0)
+ return r;
+
+ /* Update the packet if we already have */
+ LIST_FOREACH(port, p, c->ports) {
+
+ if ((p->type == type && p->length == length && !memcmp(p->data, data, p->length))) {
+
+ r = sd_lldp_packet_read_ttl(tlv, &ttl);
+ if (r < 0)
+ return r;
+
+ p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
+
+ sd_lldp_packet_unref(p->packet);
+ p->packet = tlv;
+
+ prioq_reshuffle(p->c->by_expiry, p, &p->prioq_idx);
+
+ return 0;
+ }
+ }
+
+ return -1;
+ }
+
+ int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv) {
+ lldp_neighbour_port *p, *q;
+ uint8_t *data;
+ uint16_t length;
+ uint8_t type;
+ int r;
+
+ assert_return(c, -EINVAL);
+ assert_return(tlv, -EINVAL);
+
+ r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length);
+ if (r < 0)
+ return r;
+
+ LIST_FOREACH_SAFE(port, p, q, c->ports) {
+
+ /* Find the port */
+ if (p->type == type && p->length == length && !memcmp(p->data, data, p->length)) {
+ lldp_neighbour_port_remove_and_free(p);
+ break;
+ }
+ }
+
+ return 0;
+ }
+
+ int lldp_mib_add_objects(Prioq *by_expiry,
+ Hashmap *neighbour_mib,
+ tlv_packet *tlv) {
+ _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL;
+ _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL;
+ lldp_chassis_id chassis_id;
+ bool new_chassis = false;
+ uint8_t subtype, *data;
+ uint16_t ttl, length;
+ int r;
+
+ assert_return(by_expiry, -EINVAL);
+ assert_return(neighbour_mib, -EINVAL);
+ assert_return(tlv, -EINVAL);
+
+ r = sd_lldp_packet_read_chassis_id(tlv, &subtype, &data, &length);
+ if (r < 0)
+ goto drop;
+
+ r = sd_lldp_packet_read_ttl(tlv, &ttl);
+ if (r < 0)
+ goto drop;
+
+ /* Make hash key */
+ chassis_id.type = subtype;
+ chassis_id.length = length;
+ chassis_id.data = data;
+
+ /* Try to find the Chassis */
+ c = hashmap_get(neighbour_mib, &chassis_id);
+ if (!c) {
+
+ /* Don't create chassis if ttl 0 is received . Silently drop it */
+ if (ttl == 0) {
+ log_lldp("TTL value 0 received. Skiping Chassis creation.");
+ goto drop;
+ }
+
+ /* Admission Control: Can we store this packet ? */
+ if (hashmap_size(neighbour_mib) >= LLDP_MIB_MAX_CHASSIS) {
+
+ log_lldp("Exceeding number of chassie: %d. Dropping ...",
+ hashmap_size(neighbour_mib));
+ goto drop;
+ }
+
+ r = lldp_chassis_new(tlv, by_expiry, neighbour_mib, &c);
+ if (r < 0)
+ goto drop;
+
+ new_chassis = true;
+
+ r = hashmap_put(neighbour_mib, &c->chassis_id, c);
+ if (r < 0)
+ goto drop;
+
+ } else {
+
+ /* When the TTL field is set to zero, the receiving LLDP agent is notified all
+ * system information associated with the LLDP agent/port is to be deleted */
+ if (ttl == 0) {
+ log_lldp("TTL value 0 received . Deleting associated Port ...");
+
+ lldp_mib_remove_objects(c, tlv);
+
+ c = NULL;
+ goto drop;
+ }
+
+ /* if we already have this port just update it */
+ r = lldp_mib_update_objects(c, tlv);
+ if (r >= 0) {
+ c = NULL;
+ return r;
+ }
+
+ /* Admission Control: Can this port attached to the existing chassis ? */
+ if (c->n_ref >= LLDP_MIB_MAX_PORT_PER_CHASSIS) {
+ log_lldp("Port limit reached. Chassis has: %d ports. Dropping ...", c->n_ref);
+
+ c = NULL;
+ goto drop;
+ }
+ }
+
+ /* This is a new port */
+ r = lldp_neighbour_port_new(c, tlv, &p);
+ if (r < 0)
+ goto drop;
+
+ r = prioq_put(c->by_expiry, p, &p->prioq_idx);
+ if (r < 0)
+ goto drop;
+
+ /* Attach new port to chassis */
+ LIST_PREPEND(port, c->ports, p);
+ c->n_ref ++;
+
+ p = NULL;
+ c = NULL;
+
+ return 0;
+
+ drop:
+ sd_lldp_packet_unref(tlv);
+
+ if (new_chassis)
+ hashmap_remove(neighbour_mib, &c->chassis_id);
+
+ return r;
+ }
+
+ void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p) {
+ lldp_chassis *c;
+
+ assert(p);
+ assert(p->c);
+
+ c = p->c;
+
+ prioq_remove(c->by_expiry, p, &p->prioq_idx);
+
+ LIST_REMOVE(port, c->ports, p);
+ lldp_neighbour_port_free(p);
+
+ /* Drop the Chassis if no port is attached */
+ c->n_ref --;
+ if (c->n_ref <= 1) {
+ hashmap_remove(c->neighbour_mib, &c->chassis_id);
+ lldp_chassis_free(c);
+ }
+ }
+
+ void lldp_neighbour_port_free(lldp_neighbour_port *p) {
+
+ if(!p)
+ return;
+
+ sd_lldp_packet_unref(p->packet);
+
+ free(p->data);
+ free(p);
+ }
+
+ int lldp_neighbour_port_new(lldp_chassis *c,
+ tlv_packet *tlv,
+ lldp_neighbour_port **ret) {
+ _cleanup_lldp_neighbour_port_free_ lldp_neighbour_port *p = NULL;
+ uint16_t length, ttl;
+ uint8_t *data;
+ uint8_t type;
+ int r;
+
+ assert(tlv);
+
+ r = sd_lldp_packet_read_port_id(tlv, &type, &data, &length);
+ if (r < 0)
+ return r;
+
+ r = sd_lldp_packet_read_ttl(tlv, &ttl);
+ if (r < 0)
+ return r;
+
+ p = new0(lldp_neighbour_port, 1);
+ if (!p)
+ return -ENOMEM;
+
+ p->c = c;
+ p->type = type;
+ p->length = length;
+ p->packet = tlv;
+ p->prioq_idx = PRIOQ_IDX_NULL;
+ p->until = ttl * USEC_PER_SEC + now(clock_boottime_or_monotonic());
+
+ p->data = memdup(data, length);
+ if (!p->data)
+ return -ENOMEM;
+
+ *ret = p;
+ p = NULL;
+
+ return 0;
+ }
+
+ void lldp_chassis_free(lldp_chassis *c) {
+
+ if (!c)
+ return;
+
+ if (c->n_ref > 1)
+ return;
+
+ free(c->chassis_id.data);
+ free(c);
+ }
+
+ int lldp_chassis_new(tlv_packet *tlv,
+ Prioq *by_expiry,
+ Hashmap *neighbour_mib,
+ lldp_chassis **ret) {
+ _cleanup_lldp_chassis_free_ lldp_chassis *c = NULL;
+ uint16_t length;
+ uint8_t *data;
+ uint8_t type;
+ int r;
+
+ assert(tlv);
+
+ r = sd_lldp_packet_read_chassis_id(tlv, &type, &data, &length);
+ if (r < 0)
+ return r;
+
+ c = new0(lldp_chassis, 1);
+ if (!c)
+ return -ENOMEM;
+
+ c->n_ref = 1;
+ c->chassis_id.type = type;
+ c->chassis_id.length = length;
+
+ c->chassis_id.data = memdup(data, length);
+ if (!c->chassis_id.data)
+ return -ENOMEM;
+
+ LIST_HEAD_INIT(c->ports);
+
+ c->by_expiry = by_expiry;
+ c->neighbour_mib = neighbour_mib;
+
+ *ret = c;
+ c = NULL;
+
+ return 0;
+ }
+
+ int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata) {
+ _cleanup_lldp_packet_unref_ tlv_packet *packet = NULL;
+ tlv_packet *p;
+ uint16_t length;
+ int r;
+
+ assert(fd);
+ assert(userdata);
+
+ r = tlv_packet_new(&packet);
+ if (r < 0)
+ return r;
+
+ length = read(fd, &packet->pdu, sizeof(packet->pdu));
+
+ /* Silently drop the packet */
+ if ((size_t) length > ETHER_MAX_LEN)
+ return 0;
+
+ packet->userdata = userdata;
+
+ p = packet;
+ packet = NULL;
+
+ return lldp_handle_packet(p, (uint16_t) length);
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ #include "log.h"
+ #include "list.h"
+ #include "lldp-tlv.h"
+ #include "prioq.h"
+ #include "sd-event.h"
+
+ typedef struct lldp_neighbour_port lldp_neighbour_port;
+ typedef struct lldp_chassis lldp_chassis;
+ typedef struct lldp_chassis_id lldp_chassis_id;
+ typedef struct lldp_agent_statistics lldp_agent_statistics;
+
+ struct lldp_neighbour_port {
+ uint8_t type;
+ uint8_t *data;
+
+ uint16_t length;
+ usec_t until;
+
+ unsigned prioq_idx;
+
+ lldp_chassis *c;
+ tlv_packet *packet;
+
+ LIST_FIELDS(lldp_neighbour_port, port);
+ };
+
+ int lldp_neighbour_port_new(lldp_chassis *c, tlv_packet *tlv, lldp_neighbour_port **ret);
+ void lldp_neighbour_port_free(lldp_neighbour_port *p);
+ void lldp_neighbour_port_remove_and_free(lldp_neighbour_port *p);
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_neighbour_port *, lldp_neighbour_port_free);
+ #define _cleanup_lldp_neighbour_port_free_ _cleanup_(lldp_neighbour_port_freep)
+
+ struct lldp_chassis_id {
+ uint8_t type;
+ uint16_t length;
+
+ uint8_t *data;
+ };
+
+ struct lldp_chassis {
+ unsigned n_ref;
+
+ lldp_chassis_id chassis_id;
+
+ Prioq *by_expiry;
+ Hashmap *neighbour_mib;
+
+ LIST_HEAD(lldp_neighbour_port, ports);
+ };
+
+ int lldp_chassis_new(tlv_packet *tlv,
+ Prioq *by_expiry,
+ Hashmap *neighbour_mib,
+ lldp_chassis **ret);
+
+ void lldp_chassis_free(lldp_chassis *c);
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_chassis *, lldp_chassis_free);
+ #define _cleanup_lldp_chassis_free_ _cleanup_(lldp_chassis_freep)
+
+ int lldp_mib_update_objects(lldp_chassis *c, tlv_packet *tlv);
+ int lldp_mib_add_objects(Prioq *by_expiry, Hashmap *neighbour_mib, tlv_packet *tlv);
+ int lldp_mib_remove_objects(lldp_chassis *c, tlv_packet *tlv);
+
+ int lldp_handle_packet(tlv_packet *m, uint16_t length);
+ int lldp_receive_packet(sd_event_source *s, int fd, uint32_t revents, void *userdata);
+ #define log_lldp(fmt, ...) log_internal(LOG_DEBUG, 0, __FILE__, __LINE__, __func__, "LLDP: " fmt, ##__VA_ARGS__)
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <linux/filter.h>
+ #include <linux/if_ether.h>
+
+ #include "socket-util.h"
+ #include "lldp-tlv.h"
+ #include "lldp-network.h"
+ #include "lldp-internal.h"
+
+ int lldp_network_bind_raw_socket(int ifindex) {
+ typedef struct LLDPFrame {
+ struct ethhdr hdr;
+ uint8_t tlvs[0];
+ } LLDPFrame;
+
+ struct sock_filter filter[] = {
+ BPF_STMT(BPF_LD + BPF_W + BPF_ABS, offsetof(LLDPFrame, hdr.h_dest)), /* A <- 4 bytes of destination MAC */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0180c200, 1, 0), /* A != 01:80:c2:00 */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(LLDPFrame, hdr.h_dest) + 4), /* A <- remaining 2 bytes of destination MAC */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0000, 3, 0), /* A != 00:00 */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x0003, 2, 0), /* A != 00:03 */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, 0x000e, 1, 0), /* A != 00:0e */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
+ BPF_STMT(BPF_LD + BPF_H + BPF_ABS, offsetof(LLDPFrame, hdr.h_proto)), /* A <- protocol */
+ BPF_JUMP(BPF_JMP + BPF_JEQ + BPF_K, ETHERTYPE_LLDP, 1, 0), /* A != ETHERTYPE_LLDP */
+ BPF_STMT(BPF_RET + BPF_K, 0), /* drop packet */
+ BPF_STMT(BPF_RET + BPF_K, (uint32_t) -1), /* accept packet */
+ };
+
+ struct sock_fprog fprog = {
+ .len = ELEMENTSOF(filter),
+ .filter = filter
+ };
+
+ _cleanup_close_ int s = -1;
+
+ union sockaddr_union saddrll = {
+ .ll.sll_family = AF_PACKET,
+ .ll.sll_ifindex = ifindex,
+ };
+
+ int r;
+
+ assert(ifindex > 0);
+
+ s = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (s < 0)
+ return -errno;
+
+ r = setsockopt(s, SOL_SOCKET, SO_ATTACH_FILTER, &fprog, sizeof(fprog));
+ if (r < 0)
+ return -errno;
+
+ r = bind(s, &saddrll.sa, sizeof(saddrll.ll));
+ if (r < 0)
+ return -errno;
+
+ r = s;
+ s = -1;
+
+ return r;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ #include "sd-event.h"
+
+ int lldp_network_bind_raw_socket(int ifindex);
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include "async.h"
+ #include "lldp-port.h"
+ #include "lldp-network.h"
+ #include "lldp-internal.h"
+
+ int lldp_port_start(lldp_port *p) {
+ int r;
+
+ assert_return(p, -EINVAL);
+
+ r = lldp_network_bind_raw_socket(p->ifindex);
+ if (r < 0)
+ return r;
+
+ p->rawfd = r;
+
+ r = sd_event_add_io(p->event, &p->lldp_port_rx,
+ p->rawfd, EPOLLIN, lldp_receive_packet, p);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to allocate event source: %m");
+ goto fail;
+ }
+
+ r = sd_event_source_set_priority(p->lldp_port_rx, p->event_priority);
+ if (r < 0) {
+ log_debug_errno(r, "Failed to set event priority: %m");
+ goto fail;
+ }
+
+ r = sd_event_source_set_description(p->lldp_port_rx, "lldp-port-rx");
+ if (r < 0) {
+ log_debug_errno(r, "Failed to set event name: %m");
+ goto fail;
+ }
+
+ return 0;
+
+ fail:
+ lldp_port_stop(p);
+
+ return r;
+ }
+
+ int lldp_port_stop(lldp_port *p) {
+
+ assert_return(p, -EINVAL);
+
+ p->rawfd = asynchronous_close(p->rawfd);
+ p->lldp_port_rx = sd_event_source_unref(p->lldp_port_rx);
+
+ return 0;
+ }
+
+ void lldp_port_free(lldp_port *p) {
+ if (!p)
+ return;
+
+ lldp_port_stop(p);
+
+ free(p->ifname);
+ free(p);
+ }
+
+ int lldp_port_new(int ifindex,
+ const char *ifname,
+ const struct ether_addr *addr,
+ void *userdata,
+ lldp_port **ret) {
+ _cleanup_free_ lldp_port *p = NULL;
+
+ assert_return(ifindex, -EINVAL);
+ assert_return(ifname, -EINVAL);
+ assert_return(addr, -EINVAL);
+
+ p = new0(lldp_port, 1);
+ if (!p)
+ return -ENOMEM;
+
+ p->rawfd = -1;
+ p->ifindex = ifindex;
+
+ p->ifname = strdup(ifname);
+ if (!p->ifname)
+ return -ENOMEM;
+
+ memcpy(&p->mac, addr, ETH_ALEN);
+
+ p->userdata = userdata;
+
+ *ret = p;
+
+ p = NULL;
+
+ return 0;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ #include <net/ethernet.h>
+
+ #include "sd-event.h"
+ #include "sd-lldp.h"
+
+ #include "util.h"
+
+ typedef struct lldp_port lldp_port;
+
+ typedef enum LLDPPortStatus {
+ LLDP_PORT_STATUS_NONE,
+ LLDP_PORT_STATUS_ENABLED,
+ LLDP_PORT_STATUS_DISABLED,
+ _LLDP_PORT_STATUS_MAX,
+ _LLDP_PORT_STATUS_INVALID = -1,
+ } LLDPPortStatus;
+
+ struct lldp_port {
+ LLDPPortStatus status;
+
+ int ifindex;
+ char *ifname;
+
+ struct ether_addr mac;
+
+ int rawfd;
+
+ sd_event *event;
+ sd_event_source *lldp_port_rx;
+
+ int event_priority;
+
+ void *userdata;
+ };
+
+ int lldp_port_new(int ifindex,
+ const char *ifname,
+ const struct ether_addr *addr,
+ void *userdata,
+ lldp_port **ret);
+ void lldp_port_free(lldp_port *p);
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(lldp_port*, lldp_port_free);
+ #define _cleanup_lldp_port_free_ _cleanup_(lldp_port_freep)
+
+ int lldp_port_start(lldp_port *p);
+ int lldp_port_stop(lldp_port *p);
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <net/ethernet.h>
+ #include <arpa/inet.h>
+
+ #include "macro.h"
+ #include "lldp-tlv.h"
+
+ int tlv_section_new(tlv_section **ret) {
+ tlv_section *s;
+
+ s = new0(tlv_section, 1);
+ if (!s)
+ return -ENOMEM;
+
+ *ret = s;
+
+ return 0;
+ }
+
+ void tlv_section_free(tlv_section *m) {
+
+ if (!m)
+ return;
+
+ free(m);
+ }
+
+ int tlv_packet_new(tlv_packet **ret) {
+ tlv_packet *m;
+
+ m = new0(tlv_packet, 1);
+ if (!m)
+ return -ENOMEM;
+
+ LIST_HEAD_INIT(m->sections);
+ m->n_ref = 1;
+
+ *ret = m;
+
+ return 0;
+ }
+
+ tlv_packet *sd_lldp_packet_ref(tlv_packet *m) {
+
+ if (!m)
+ return NULL;
+
+ assert(m->n_ref > 0);
+ m->n_ref++;
+
+ return m;
+ }
+
+ tlv_packet *sd_lldp_packet_unref(tlv_packet *m) {
+ tlv_section *s, *n;
+
+ if (!m)
+ return NULL;
+
+ assert(m->n_ref > 0);
+ m->n_ref--;
+
+ if (m->n_ref > 0)
+ return m;
+
+ LIST_FOREACH_SAFE(section, s, n, m->sections)
+ tlv_section_free(s);
+
+ free(m);
+ return NULL;
+ }
+
+ int tlv_packet_append_bytes(tlv_packet *m, const void *data, size_t data_length) {
+ uint8_t *p;
+
+ assert_return(m, -EINVAL);
+ assert_return(data, -EINVAL);
+ assert_return(data_length, -EINVAL);
+
+ if (m->length + data_length > ETHER_MAX_LEN)
+ return -ENOMEM;
+
+ p = m->pdu + m->length;
+ memcpy(p, data, data_length);
+ m->length += data_length;
+
+ return 0;
+ }
+
+ int tlv_packet_append_u8(tlv_packet *m, uint8_t data) {
+
+ assert_return(m, -EINVAL);
+
+ return tlv_packet_append_bytes(m, &data, sizeof(uint8_t));
+ }
+
+ int tlv_packet_append_u16(tlv_packet *m, uint16_t data) {
+ uint16_t type;
+
+ assert_return(m, -EINVAL);
+
+ type = htons(data);
+
+ return tlv_packet_append_bytes(m, &type, sizeof(uint16_t));
+ }
+
+ int tlv_packet_append_u32(tlv_packet *m, uint32_t data) {
+ uint32_t type;
+
+ assert_return(m, -EINVAL);
+
+ type = htonl(data);
+
+ return tlv_packet_append_bytes(m, &type, sizeof(uint32_t));
+ }
+
+ int tlv_packet_append_string(tlv_packet *m, char *data, uint16_t size) {
+
+ assert_return(m, -EINVAL);
+
+ return tlv_packet_append_bytes(m, data, size);
+ }
+
+ int lldp_tlv_packet_open_container(tlv_packet *m, uint16_t type) {
+
+ assert_return(m, -EINVAL);
+
+ m->container_pos = m->pdu + m->length;
+
+ return tlv_packet_append_u16(m, type << 9);
+ }
+
+ int lldp_tlv_packet_close_container(tlv_packet *m) {
+ uint16_t type;
+
+ assert_return(m, -EINVAL);
+ assert_return(m->container_pos, -EINVAL);
+
+ memcpy(&type, m->container_pos, sizeof(uint16_t));
+
+ type |= htons(((m->pdu + m->length) - (m->container_pos + 2)) & 0x01ff);
+ memcpy(m->container_pos, &type, sizeof(uint16_t));
+
+ return 0;
+ }
+
+ static inline int tlv_packet_read_internal(tlv_section *m, void **data) {
+
+ assert_return(m->read_pos, -EINVAL);
+
+ *data = m->read_pos;
+
+ return 0;
+ }
+
+ int tlv_packet_read_u8(tlv_packet *m, uint8_t *data) {
+ void *val = NULL;
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ r = tlv_packet_read_internal(m->container, &val);
+ if (r < 0)
+ return r;
+
+ memcpy(data, val, sizeof(uint8_t));
+
+ m->container->read_pos ++;
+
+ return 0;
+ }
+
+ int tlv_packet_read_u16(tlv_packet *m, uint16_t *data) {
+ uint16_t t;
+ void *val = NULL;
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ r = tlv_packet_read_internal(m->container, &val);
+ if (r < 0)
+ return r;
+
+ memcpy(&t, val, sizeof(uint16_t));
+ *data = ntohs(t);
+
+ m->container->read_pos += 2;
+
+ return 0;
+ }
+
+ int tlv_packet_read_u32(tlv_packet *m, uint32_t *data) {
+ uint32_t t;
+ void *val;
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ r = tlv_packet_read_internal(m->container, &val);
+ if (r < 0)
+ return r;
+
+ memcpy(&t, val, sizeof(uint32_t));
+ *data = ntohl(t);
+
+ m->container->read_pos += 4;
+
+ return r;
+ }
+
+ int tlv_packet_read_string(tlv_packet *m, char **data, uint16_t *data_length) {
+ void *val = NULL;
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ r = tlv_packet_read_internal(m->container, &val);
+ if (r < 0)
+ return r;
+
+ *data = (char *) val;
+ *data_length = m->container->data + m->container->length - m->container->read_pos;
+
+ m->container->read_pos += *data_length;
+
+ return 0;
+ }
+
+ int tlv_packet_read_bytes(tlv_packet *m, uint8_t **data, uint16_t *data_length) {
+ void *val = NULL;
+ int r;
+
+ assert_return(m, -EINVAL);
+
+ r = tlv_packet_read_internal(m->container, &val);
+ if (r < 0)
+ return r;
+
+ *data = (uint8_t *) val;
+ *data_length = m->container->data + m->container->length - m->container->read_pos;
+
+ m->container->read_pos += *data_length;
+
+ return 0;
+ }
+
+ /* parse raw TLV packet */
+ int tlv_packet_parse_pdu(tlv_packet *m, uint16_t size) {
+ tlv_section *section, *tail;
+ uint16_t t, l;
+ uint8_t *p;
+ int r;
+
+ assert_return(m, -EINVAL);
+ assert_return(size, -EINVAL);
+
+ p = m->pdu;
+
+ /* extract ethernet header */
+ memcpy(&m->mac, p, ETH_ALEN);
+ p += sizeof(struct ether_header);
+
+ for (l = 0; l <= size; ) {
+ r = tlv_section_new(§ion);
+ if (r < 0)
+ return r;
+
+ memcpy(&t, p, sizeof(uint16_t));
+
+ section->type = ntohs(t) >> 9;
+ section->length = ntohs(t) & 0x01ff;
+
+ if (section->type == LLDP_TYPE_END || section->type >=_LLDP_TYPE_MAX) {
+ tlv_section_free(section);
+ break;
+ }
+
+ p += 2;
+
+ if (section->type == LLDP_TYPE_PRIVATE &&
+ section->length >= LLDP_OUI_LEN + 1) {
+ section->oui = p;
+ p += LLDP_OUI_LEN;
+ section->subtype = *p++;
+
+ section->length -= LLDP_OUI_LEN + 1;
+ l += LLDP_OUI_LEN + 1;
+ }
+
+ section->data = p;
+
+ LIST_FIND_TAIL(section, m->sections, tail);
+ LIST_INSERT_AFTER(section, m->sections, tail, section);
+
+ p += section->length;
+ l += (section->length + 2);
+ }
+
+ return 0;
+ }
+
+ int lldp_tlv_packet_enter_container(tlv_packet *m, uint16_t type) {
+ tlv_section *s;
+
+ assert_return(m, -EINVAL);
+ assert_return(type != LLDP_TYPE_PRIVATE, -EINVAL);
+
+ LIST_FOREACH(section, s, m->sections)
+ if (s->type == type)
+ break;
+ if (!s)
+ return -1;
+
+ m->container = s;
+
+ m->container->read_pos = s->data;
+ if (!m->container->read_pos) {
+ m->container = NULL;
+ return -1;
+ }
+
+ return 0;
+ }
+
+ int lldp_tlv_packet_enter_container_oui(tlv_packet *m, const uint8_t *oui, uint8_t subtype) {
+ tlv_section *s;
+
+ assert_return(m, -EINVAL);
+ assert_return(oui, -EINVAL);
+
+ LIST_FOREACH(section, s, m->sections) {
+ if (s->type == LLDP_TYPE_PRIVATE &&
+ s->oui &&
+ s->subtype == subtype &&
+ !memcmp(s->oui, oui, LLDP_OUI_LEN))
+ break;
+ }
+
+ if (!s)
+ return -1;
+
+ m->container = s;
+
+ m->container->read_pos = s->data;
+ if (!m->container->read_pos) {
+ m->container = NULL;
+ return -1;
+ }
+
+ return 0;
+ }
+
+ int lldp_tlv_packet_exit_container(tlv_packet *m) {
+ assert_return(m, -EINVAL);
+
+ m->container = 0;
+
+ return 0;
+ }
+
+ static int lldp_tlv_packet_read_u16_tlv(tlv_packet *tlv, uint16_t type, uint16_t *value) {
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container(tlv, type);
+ if (r < 0)
+ goto out;
+
+ r = tlv_packet_read_u16(tlv, value);
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out:
+ return r < 0 ? r : r2;
+ }
+
+ static int lldp_tlv_packet_read_string_tlv(tlv_packet *tlv, uint16_t type, char **data, uint16_t *length) {
+ char *s;
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container(tlv, type);
+ if (r < 0)
+ return r;
+
+ r = tlv_packet_read_string(tlv, &s, length);
+ if (r < 0)
+ goto out;
+
+ *data = (char *) s;
+
+ out:
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_chassis_id(tlv_packet *tlv,
+ uint8_t *type,
+ uint8_t **data,
+ uint16_t *length) {
+ uint8_t subtype;
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_CHASSIS_ID);
+ if (r < 0)
+ goto out2;
+
+ r = tlv_packet_read_u8(tlv, &subtype);
+ if (r < 0)
+ goto out1;
+
+ switch (subtype) {
+ case LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS:
+
+ r = tlv_packet_read_bytes(tlv, data, length);
+ if (r < 0)
+ goto out1;
+
+ break;
+ default:
+ r = -EOPNOTSUPP;
+ break;
+ }
+
+ *type = subtype;
+
+ out1:
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out2:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_port_id(tlv_packet *tlv,
+ uint8_t *type,
+ uint8_t **data,
+ uint16_t *length) {
+ uint8_t subtype;
+ char *s;
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container(tlv, LLDP_TYPE_PORT_ID);
+ if (r < 0)
+ goto out2;
+
+ r = tlv_packet_read_u8(tlv, &subtype);
+ if (r < 0)
+ goto out1;
+
+ switch (subtype) {
+ case LLDP_PORT_SUBTYPE_PORT_COMPONENT:
+ case LLDP_PORT_SUBTYPE_INTERFACE_ALIAS:
+ case LLDP_PORT_SUBTYPE_INTERFACE_NAME:
+ case LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED:
+
+ r = tlv_packet_read_string(tlv, &s, length);
+ if (r < 0)
+ goto out1;
+
+ *data = (uint8_t *) s;
+
+ break;
+ case LLDP_PORT_SUBTYPE_MAC_ADDRESS:
+
+ r = tlv_packet_read_bytes(tlv, data, length);
+ if (r < 0)
+ goto out1;
+
+ break;
+ default:
+ r = -EOPNOTSUPP;
+ break;
+ }
+
+ *type = subtype;
+
+ out1:
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out2:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_ttl(tlv_packet *tlv, uint16_t *ttl) {
+ return lldp_tlv_packet_read_u16_tlv(tlv, LLDP_TYPE_TTL, ttl);
+ }
+
+ int sd_lldp_packet_read_system_name(tlv_packet *tlv,
+ char **data,
+ uint16_t *length) {
+ return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_SYSTEM_NAME, data, length);
+ }
+
+ int sd_lldp_packet_read_system_description(tlv_packet *tlv,
+ char **data,
+ uint16_t *length) {
+ return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_SYSTEM_DESCRIPTION, data, length);
+ }
+
+ int sd_lldp_packet_read_port_description(tlv_packet *tlv,
+ char **data,
+ uint16_t *length) {
+ return lldp_tlv_packet_read_string_tlv(tlv, LLDP_TYPE_PORT_DESCRIPTION, data, length);
+ }
+
+ int sd_lldp_packet_read_system_capability(tlv_packet *tlv, uint16_t *data) {
+ return lldp_tlv_packet_read_u16_tlv(tlv, LLDP_TYPE_SYSTEM_CAPABILITIES, data);
+ }
+
+ int sd_lldp_packet_read_port_vlan_id(tlv_packet *tlv, uint16_t *id) {
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_PORT_VLAN_ID);
+ if (r < 0)
+ goto out;
+
+ r = tlv_packet_read_u16(tlv, id);
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id) {
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_PORT_PROTOCOL_VLAN_ID);
+ if (r < 0)
+ goto out;
+
+ r = tlv_packet_read_u8(tlv, flags);
+ if (r >= 0)
+ r = tlv_packet_read_u16(tlv, id);
+
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_vlan_name(tlv_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length) {
+ int r, r2;
+ uint8_t len = 0;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_VLAN_NAME);
+ if (r < 0)
+ goto out;
+
+ r = tlv_packet_read_u16(tlv, vlan_id);
+ if (r >= 0)
+ r = tlv_packet_read_u8(tlv, &len);
+ if (r >= 0)
+ r = tlv_packet_read_string(tlv, name, length);
+
+ if (r >= 0 && len < *length)
+ *length = len;
+
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_management_vid(tlv_packet *tlv, uint16_t *id) {
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_MANAGEMENT_VID);
+ if (r < 0)
+ goto out;
+
+ r = tlv_packet_read_u16(tlv, id);
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id) {
+ int r, r2;
+
+ assert_return(tlv, -EINVAL);
+
+ r = lldp_tlv_packet_enter_container_oui(tlv, LLDP_OUI_802_1, LLDP_OUI_SUBTYPE_802_1_LINK_AGGREGATION);
+ if (r < 0)
+ goto out;
+
+ r = tlv_packet_read_u8(tlv, status);
+ if (r >= 0)
+ r = tlv_packet_read_u32(tlv, id);
+
+ r2 = lldp_tlv_packet_exit_container(tlv);
+
+ out:
+ return r < 0 ? r : r2;
+ }
+
+ int sd_lldp_packet_get_destination_type(tlv_packet *tlv, int *dest) {
+ assert_return(tlv, -EINVAL);
+ assert_return(dest, -EINVAL);
+
+ /* 802.1AB-2009, Table 7-1 */
+ if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_BRIDGE, ETH_ALEN))
+ *dest = SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE;
+ else if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_NON_TPMR_BRIDGE, ETH_ALEN))
+ *dest = SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE;
+ else if (!memcmp(&tlv->mac, LLDP_MAC_NEAREST_CUSTOMER_BRIDGE, ETH_ALEN))
+ *dest = SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE;
+ else
+ return -EINVAL;
+
+ return 0;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ #include <net/ethernet.h>
+
+ #include "util.h"
+ #include "lldp.h"
+ #include "list.h"
+
+ #include "sd-lldp.h"
+
+ typedef struct tlv_packet tlv_packet;
+ typedef struct tlv_section tlv_section;
+
+ #define LLDP_OUI_LEN 3
+
+ struct tlv_section {
+ uint16_t type;
+ uint16_t length;
+ uint8_t *oui;
+ uint8_t subtype;
+
+ uint8_t *read_pos;
+ uint8_t *data;
+
+ LIST_FIELDS(tlv_section, section);
+ };
+
+ #define LLDP_MAC_NEAREST_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }
+ #define LLDP_MAC_NEAREST_NON_TPMR_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x03 }
+ #define LLDP_MAC_NEAREST_CUSTOMER_BRIDGE (uint8_t[]) { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x00 }
+
+ int tlv_section_new(tlv_section **ret);
+ void tlv_section_free(tlv_section *ret);
+
+ struct tlv_packet {
+ unsigned n_ref;
+
+ uint16_t type;
+ uint16_t length;
+ usec_t ts;
+
+ uint8_t *container_pos;
+ uint8_t pdu[ETHER_MAX_LEN];
+
+ void *userdata;
+
+ struct ether_addr mac;
+ tlv_section *container;
+
+ LIST_HEAD(tlv_section, sections);
+ };
+
+ int tlv_packet_new(tlv_packet **ret);
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_lldp_packet*, sd_lldp_packet_unref);
+ #define _cleanup_lldp_packet_unref_ _cleanup_(sd_lldp_packet_unrefp)
+
+ int lldp_tlv_packet_open_container(tlv_packet *m, uint16_t type);
+ int lldp_tlv_packet_close_container(tlv_packet *m);
+
+ int tlv_packet_append_bytes(tlv_packet *m, const void *data, size_t data_length);
+ int tlv_packet_append_u8(tlv_packet *m, uint8_t data);
+ int tlv_packet_append_u16(tlv_packet *m, uint16_t data);
+ int tlv_packet_append_u32(tlv_packet *m, uint32_t data);
+ int tlv_packet_append_string(tlv_packet *m, char *data, uint16_t size);
+
+ int lldp_tlv_packet_enter_container(tlv_packet *m, uint16_t type);
+ int lldp_tlv_packet_enter_container_oui(tlv_packet *m, const uint8_t *oui, uint8_t subtype);
+ int lldp_tlv_packet_exit_container(tlv_packet *m);
+
+ int tlv_packet_read_bytes(tlv_packet *m, uint8_t **data, uint16_t *data_length);
+ int tlv_packet_read_string(tlv_packet *m, char **data, uint16_t *data_length);
+ int tlv_packet_read_u8(tlv_packet *m, uint8_t *data);
+ int tlv_packet_read_u16(tlv_packet *m, uint16_t *data);
+ int tlv_packet_read_u32(tlv_packet *m, uint32_t *data);
+
+ int tlv_packet_parse_pdu(tlv_packet *t, uint16_t size);
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_lldp *, sd_lldp_free);
+ #define _cleanup_lldp_free_ _cleanup_(sd_lldp_freep)
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ #define LLDP_MULTICAST_ADDR { 0x01, 0x80, 0xc2, 0x00, 0x00, 0x0e }
+
+ #define ETHERTYPE_LLDP 0x88cc
+
+ /* IEEE 802.3AB Clause 9: TLV Types */
+ typedef enum LLDPTypes {
+ LLDP_TYPE_END = 0,
+ LLDP_TYPE_CHASSIS_ID = 1,
+ LLDP_TYPE_PORT_ID = 2,
+ LLDP_TYPE_TTL = 3,
+ LLDP_TYPE_PORT_DESCRIPTION = 4,
+ LLDP_TYPE_SYSTEM_NAME = 5,
+ LLDP_TYPE_SYSTEM_DESCRIPTION = 6,
+ LLDP_TYPE_SYSTEM_CAPABILITIES = 7,
+ LLDP_TYPE_MGMT_ADDRESS = 8,
+ LLDP_TYPE_PRIVATE = 127,
+ _LLDP_TYPE_MAX,
+ _LLDP_TYPE_INVALID = -1,
+ } LLDPTypes;
+
+ /* IEEE 802.3AB Clause 9.5.2: Chassis subtypes */
+ typedef enum LLDPChassisSubtypes {
+ LLDP_CHASSIS_SUBTYPE_RESERVED = 0,
+ LLDP_CHASSIS_SUBTYPE_CHASSIS_COMPONENT = 1,
+ LLDP_CHASSIS_SUBTYPE_INTERFACE_ALIAS = 2,
+ LLDP_CHASSIS_SUBTYPE_PORT_COMPONENT = 3,
+ LLDP_CHASSIS_SUBTYPE_MAC_ADDRESS = 4,
+ LLDP_CHASSIS_SUBTYPE_NETWORK_ADDRESS = 5,
+ LLDP_CHASSIS_SUBTYPE_INTERFACE_NAME = 6,
+ LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED = 7,
+ _LLDP_CHASSIS_SUBTYPE_MAX,
+ _LLDP_CHASSIS_SUBTYPE_INVALID = -1,
+ } LLDPChassisSubtypes;
+
+ /* IEEE 802.3AB Clause 9.5.3: Port subtype */
+ typedef enum LLDPPortSubtypes {
+ LLDP_PORT_SUBTYPE_RESERVED = 0,
+ LLDP_PORT_SUBTYPE_INTERFACE_ALIAS = 1,
+ LLDP_PORT_SUBTYPE_PORT_COMPONENT = 2,
+ LLDP_PORT_SUBTYPE_MAC_ADDRESS = 3,
+ LLDP_PORT_SUBTYPE_NETWORK = 4,
+ LLDP_PORT_SUBTYPE_INTERFACE_NAME = 5,
+ LLDP_PORT_SUBTYPE_AGENT_CIRCUIT_ID = 6,
+ LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED = 7,
+ _LLDP_PORT_SUBTYPE_MAX,
+ _LLDP_PORT_SUBTYPE_INVALID = -1
+ } LLDPPortSubtypes;
+
+ typedef enum LLDPSystemCapabilities {
+ LLDP_SYSTEM_CAPABILITIES_OTHER = 1 << 0,
+ LLDP_SYSTEM_CAPABILITIES_REPEATER = 1 << 1,
+ LLDP_SYSTEM_CAPABILITIES_BRIDGE = 1 << 2,
+ LLDP_SYSTEM_CAPABILITIES_WLAN_AP = 1 << 3,
+ LLDP_SYSTEM_CAPABILITIES_ROUTER = 1 << 4,
+ LLDP_SYSTEM_CAPABILITIES_PHONE = 1 << 5,
+ LLDP_SYSTEM_CAPABILITIES_DOCSIS = 1 << 6,
+ LLDP_SYSTEM_CAPABILITIES_STATION = 1 << 7,
+ LLDP_SYSTEM_CAPABILITIES_CVLAN = 1 << 8,
+ LLDP_SYSTEM_CAPABILITIES_SVLAN = 1 << 9,
+ LLDP_SYSTEM_CAPABILITIES_TPMR = 1 << 10,
+ _LLDP_SYSTEM_CAPABILITIES_MAX,
+ _LLDP_SYSTEM_CAPABILITIES_INVALID = -1,
+ } LLDPSystemCapabilities;
+
+ typedef enum LLDPMedSubtype {
+ LLDP_MED_SUBTYPE_RESERVED = 0,
+ LLDP_MED_SUBTYPE_CAPABILITIES = 1,
+ LLDP_MED_SUBTYPE_NETWORK_POLICY = 2,
+ LLDP_MED_SUBTYPE_LOCATION_ID = 3,
+ LLDP_MED_SUBTYPE_EXTENDED_PVMDI = 4,
+ LLDP_MED_SUBTYPE_INV_HWREV = 5,
+ LLDP_MED_SUBTYPE_INV_FWREV = 6,
+ LLDP_MED_SUBTYPE_INV_SWREV = 7,
+ LLDP_MED_SUBTYPE_INV_SERIAL = 8,
+ LLDP_MED_SUBTYPE_INV_MANUFACTURER = 9,
+ LLDP_MED_SUBTYPE_INV_MODELNAME = 10,
+ LLDP_MED_SUBTYPE_INV_ASSETID = 11,
+ _LLDP_MED_SUBTYPE_MAX,
+ _LLDP_MED_SUBTYPE_INVALID = -1,
+ } LLDPMedSubtype;
+
+ typedef enum LLDPMedCapability {
+ LLDP_MED_CAPABILITY_CAPAPILITIES = 1 << 0,
+ LLDP_MED_CAPABILITY_NETWORK_POLICY = 1 << 1,
+ LLDP_MED_CAPABILITY_LOCATION_ID = 1 << 2,
+ LLDP_MED_CAPABILITY_EXTENDED_PSE = 1 << 3,
+ LLDP_MED_CAPABILITY_EXTENDED_PD = 1 << 4,
+ LLDP_MED_CAPABILITY_INVENTORY = 1 << 5,
+ LLDP_MED_CAPABILITY_MAX,
+ LLDP_MED_CAPABILITY_INVALID = -1,
+ } LLDPMedCapability;
+
+ #define LLDP_OUI_802_1 (uint8_t[]) { 0x00, 0x80, 0xc2 }
+ #define LLDP_OUI_802_3 (uint8_t[]) { 0x00, 0x12, 0x0f }
+
+ enum {
+ LLDP_OUI_SUBTYPE_802_1_PORT_VLAN_ID = 1,
+ LLDP_OUI_SUBTYPE_802_1_PORT_PROTOCOL_VLAN_ID = 2,
+ LLDP_OUI_SUBTYPE_802_1_VLAN_NAME = 3,
+ LLDP_OUI_SUBTYPE_802_1_PROTOCOL_IDENTITY = 4,
+ LLDP_OUI_SUBTYPE_802_1_VID_USAGE_DIGEST = 5,
+ LLDP_OUI_SUBTYPE_802_1_MANAGEMENT_VID = 6,
+ LLDP_OUI_SUBTYPE_802_1_LINK_AGGREGATION = 7,
+ };
--- /dev/null
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Axis Communications AB. All rights reserved.
+ Copyright (C) 2015 Tom Gundersen
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <arpa/inet.h>
+ #include <errno.h>
+ #include <stdio.h>
+ #include <stdlib.h>
+ #include <string.h>
+
+ #include "event-util.h"
+ #include "in-addr-util.h"
+ #include "list.h"
+ #include "refcnt.h"
+ #include "random-util.h"
+ #include "siphash24.h"
+ #include "util.h"
+
+ #include "arp-util.h"
+ #include "sd-ipv4acd.h"
+
+ /* Constants from the RFC */
+ #define PROBE_WAIT 1
+ #define PROBE_NUM 3
+ #define PROBE_MIN 1
+ #define PROBE_MAX 2
+ #define ANNOUNCE_WAIT 2
+ #define ANNOUNCE_NUM 2
+ #define ANNOUNCE_INTERVAL 2
+ #define MAX_CONFLICTS 10
+ #define RATE_LIMIT_INTERVAL 60
+ #define DEFEND_INTERVAL 10
+
+ #define IPV4ACD_NETWORK 0xA9FE0000L
+ #define IPV4ACD_NETMASK 0xFFFF0000L
+
+ #define log_ipv4acd_full(ll, level, error, fmt, ...) log_internal(level, error, __FILE__, __LINE__, __func__, "ACD: " fmt, ##__VA_ARGS__)
+
+ #define log_ipv4acd_debug(ll, ...) log_ipv4acd_full(ll, LOG_DEBUG, 0, ##__VA_ARGS__)
+ #define log_ipv4acd_info(ll, ...) log_ipv4acd_full(ll, LOG_INFO, 0, ##__VA_ARGS__)
+ #define log_ipv4acd_notice(ll, ...) log_ipv4acd_full(ll, LOG_NOTICE, 0, ##__VA_ARGS__)
+ #define log_ipv4acd_warning(ll, ...) log_ipv4acd_full(ll, LOG_WARNING, 0, ##__VA_ARGS__)
+ #define log_ipv4acd_error(ll, ...) log_ipv4acd_full(ll, LOG_ERR, 0, ##__VA_ARGS__)
+
+ #define log_ipv4acd_debug_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_DEBUG, error, ##__VA_ARGS__)
+ #define log_ipv4acd_info_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_INFO, error, ##__VA_ARGS__)
+ #define log_ipv4acd_notice_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_NOTICE, error, ##__VA_ARGS__)
+ #define log_ipv4acd_warning_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_WARNING, error, ##__VA_ARGS__)
+ #define log_ipv4acd_error_errno(ll, error, ...) log_ipv4acd_full(ll, LOG_ERR, error, ##__VA_ARGS__)
+
+ typedef enum IPv4ACDState {
+ IPV4ACD_STATE_INIT,
+ IPV4ACD_STATE_WAITING_PROBE,
+ IPV4ACD_STATE_PROBING,
+ IPV4ACD_STATE_WAITING_ANNOUNCE,
+ IPV4ACD_STATE_ANNOUNCING,
+ IPV4ACD_STATE_RUNNING,
+ _IPV4ACD_STATE_MAX,
+ _IPV4ACD_STATE_INVALID = -1
+ } IPv4ACDState;
+
+ struct sd_ipv4acd {
+ RefCount n_ref;
+
+ IPv4ACDState state;
+ int index;
+ int fd;
+ int iteration;
+ int conflict;
+ sd_event_source *receive_message;
+ sd_event_source *timer;
+ usec_t defend_window;
+ be32_t address;
+ /* External */
+ struct ether_addr mac_addr;
+ sd_event *event;
+ int event_priority;
+ sd_ipv4acd_cb_t cb;
+ void* userdata;
+ };
+
+ sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll) {
+ if (ll)
+ assert_se(REFCNT_INC(ll->n_ref) >= 2);
+
+ return ll;
+ }
+
+ sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll) {
+ if (!ll || REFCNT_DEC(ll->n_ref) > 0)
+ return NULL;
+
+ ll->receive_message = sd_event_source_unref(ll->receive_message);
+ ll->fd = safe_close(ll->fd);
+
+ ll->timer = sd_event_source_unref(ll->timer);
+
+ sd_ipv4acd_detach_event(ll);
+
+ free(ll);
+
+ return NULL;
+ }
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_ipv4acd*, sd_ipv4acd_unref);
+ #define _cleanup_ipv4acd_unref_ _cleanup_(sd_ipv4acd_unrefp)
+
+ int sd_ipv4acd_new(sd_ipv4acd **ret) {
+ _cleanup_ipv4acd_unref_ sd_ipv4acd *ll = NULL;
+
+ assert_return(ret, -EINVAL);
+
+ ll = new0(sd_ipv4acd, 1);
+ if (!ll)
+ return -ENOMEM;
+
+ ll->n_ref = REFCNT_INIT;
+ ll->state = IPV4ACD_STATE_INIT;
+ ll->index = -1;
+ ll->fd = -1;
+
+ *ret = ll;
+ ll = NULL;
+
+ return 0;
+ }
+
+ static void ipv4acd_set_state(sd_ipv4acd *ll, IPv4ACDState st, bool reset_counter) {
+
+ assert(ll);
+ assert(st < _IPV4ACD_STATE_MAX);
+
+ if (st == ll->state && !reset_counter)
+ ll->iteration++;
+ else {
+ ll->state = st;
+ ll->iteration = 0;
+ }
+ }
+
+ static void ipv4acd_client_notify(sd_ipv4acd *ll, int event) {
+ assert(ll);
+
+ if (ll->cb)
+ ll->cb(ll, event, ll->userdata);
+ }
+
+ static void ipv4acd_stop(sd_ipv4acd *ll) {
+ assert(ll);
+
+ ll->receive_message = sd_event_source_unref(ll->receive_message);
+ ll->fd = safe_close(ll->fd);
+
+ ll->timer = sd_event_source_unref(ll->timer);
+
+ log_ipv4acd_debug(ll, "STOPPED");
+
+ ipv4acd_set_state (ll, IPV4ACD_STATE_INIT, true);
+ }
+
+ int sd_ipv4acd_stop(sd_ipv4acd *ll) {
+ assert_return(ll, -EINVAL);
+
+ ipv4acd_stop(ll);
+
+ ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_STOP);
+
+ return 0;
+ }
+
+ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata);
+
+ static int ipv4acd_set_next_wakeup(sd_ipv4acd *ll, int sec, int random_sec) {
+ _cleanup_event_source_unref_ sd_event_source *timer = NULL;
+ usec_t next_timeout;
+ usec_t time_now;
+ int r;
+
+ assert(sec >= 0);
+ assert(random_sec >= 0);
+ assert(ll);
+
+ next_timeout = sec * USEC_PER_SEC;
+
+ if (random_sec)
+ next_timeout += random_u32() % (random_sec * USEC_PER_SEC);
+
+ assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &time_now) >= 0);
+
+ r = sd_event_add_time(ll->event, &timer, clock_boottime_or_monotonic(),
+ time_now + next_timeout, 0, ipv4acd_on_timeout, ll);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_priority(timer, ll->event_priority);
+ if (r < 0)
+ return r;
+
+ r = sd_event_source_set_description(timer, "ipv4acd-timer");
+ if (r < 0)
+ return r;
+
+ ll->timer = sd_event_source_unref(ll->timer);
+ ll->timer = timer;
+ timer = NULL;
+
+ return 0;
+ }
+
+ static bool ipv4acd_arp_conflict(sd_ipv4acd *ll, struct ether_arp *arp) {
+ assert(ll);
+ assert(arp);
+
+ /* see the BPF */
+ if (memcmp(arp->arp_spa, &ll->address, sizeof(ll->address)) == 0)
+ return true;
+
+ /* the TPA matched instead of the SPA, this is not a conflict */
+ return false;
+ }
+
+ static int ipv4acd_on_timeout(sd_event_source *s, uint64_t usec, void *userdata) {
+ sd_ipv4acd *ll = userdata;
+ int r = 0;
+
+ assert(ll);
+
+ switch (ll->state) {
+ case IPV4ACD_STATE_INIT:
+
+ ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_PROBE, true);
+
+ if (ll->conflict >= MAX_CONFLICTS) {
+ log_ipv4acd_notice(ll, "Max conflicts reached, delaying by %us", RATE_LIMIT_INTERVAL);
+ r = ipv4acd_set_next_wakeup(ll, RATE_LIMIT_INTERVAL, PROBE_WAIT);
+ if (r < 0)
+ goto out;
+
+ ll->conflict = 0;
+ } else {
+ r = ipv4acd_set_next_wakeup(ll, 0, PROBE_WAIT);
+ if (r < 0)
+ goto out;
+ }
+
+ break;
+ case IPV4ACD_STATE_WAITING_PROBE:
+ case IPV4ACD_STATE_PROBING:
+ /* Send a probe */
+ r = arp_send_probe(ll->fd, ll->index, ll->address, &ll->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_error_errno(ll, r, "Failed to send ARP probe: %m");
+ goto out;
+ } else {
+ _cleanup_free_ char *address = NULL;
+ union in_addr_union addr = { .in.s_addr = ll->address };
+
+ r = in_addr_to_string(AF_INET, &addr, &address);
+ if (r >= 0)
+ log_ipv4acd_debug(ll, "Probing %s", address);
+ }
+
+ if (ll->iteration < PROBE_NUM - 2) {
+ ipv4acd_set_state(ll, IPV4ACD_STATE_PROBING, false);
+
+ r = ipv4acd_set_next_wakeup(ll, PROBE_MIN, (PROBE_MAX-PROBE_MIN));
+ if (r < 0)
+ goto out;
+ } else {
+ ipv4acd_set_state(ll, IPV4ACD_STATE_WAITING_ANNOUNCE, true);
+
+ r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_WAIT, 0);
+ if (r < 0)
+ goto out;
+ }
+
+ break;
+
+ case IPV4ACD_STATE_ANNOUNCING:
+ if (ll->iteration >= ANNOUNCE_NUM - 1) {
+ ipv4acd_set_state(ll, IPV4ACD_STATE_RUNNING, false);
+
+ break;
+ }
+ case IPV4ACD_STATE_WAITING_ANNOUNCE:
+ /* Send announcement packet */
+ r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m");
+ goto out;
+ } else
+ log_ipv4acd_debug(ll, "ANNOUNCE");
+
+ ipv4acd_set_state(ll, IPV4ACD_STATE_ANNOUNCING, false);
+
+ r = ipv4acd_set_next_wakeup(ll, ANNOUNCE_INTERVAL, 0);
+ if (r < 0)
+ goto out;
+
+ if (ll->iteration == 0) {
+ ll->conflict = 0;
+ ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_BIND);
+ }
+
+ break;
+ default:
+ assert_not_reached("Invalid state.");
+ }
+
+ out:
+ if (r < 0)
+ sd_ipv4acd_stop(ll);
+
+ return 1;
+ }
+
+ static void ipv4acd_on_conflict(sd_ipv4acd *ll) {
+ _cleanup_free_ char *address = NULL;
+ union in_addr_union addr = { .in.s_addr = ll->address };
+ int r;
+
+ assert(ll);
+
+ ll->conflict++;
+
+ r = in_addr_to_string(AF_INET, &addr, &address);
+ if (r >= 0)
+ log_ipv4acd_debug(ll, "Conflict on %s (%u)", address, ll->conflict);
+
+ ipv4acd_stop(ll);
+
+ ipv4acd_client_notify(ll, SD_IPV4ACD_EVENT_CONFLICT);
+ }
+
+ static int ipv4acd_on_packet(sd_event_source *s, int fd,
+ uint32_t revents, void *userdata) {
+ sd_ipv4acd *ll = userdata;
+ struct ether_arp packet;
+ int r;
+
+ assert(ll);
+ assert(fd >= 0);
+
+ r = read(fd, &packet, sizeof(struct ether_arp));
+ if (r < (int) sizeof(struct ether_arp))
+ goto out;
+
+ switch (ll->state) {
+ case IPV4ACD_STATE_ANNOUNCING:
+ case IPV4ACD_STATE_RUNNING:
+ if (ipv4acd_arp_conflict(ll, &packet)) {
+ usec_t ts;
+
+ assert_se(sd_event_now(ll->event, clock_boottime_or_monotonic(), &ts) >= 0);
+
+ /* Defend address */
+ if (ts > ll->defend_window) {
+ ll->defend_window = ts + DEFEND_INTERVAL * USEC_PER_SEC;
+ r = arp_send_announcement(ll->fd, ll->index, ll->address, &ll->mac_addr);
+ if (r < 0) {
+ log_ipv4acd_error_errno(ll, r, "Failed to send ARP announcement: %m");
+ goto out;
+ } else
+ log_ipv4acd_debug(ll, "DEFEND");
+
+ } else
+ ipv4acd_on_conflict(ll);
+ }
+
+ break;
+ case IPV4ACD_STATE_WAITING_PROBE:
+ case IPV4ACD_STATE_PROBING:
+ case IPV4ACD_STATE_WAITING_ANNOUNCE:
+ /* BPF ensures this packet indicates a conflict */
+ ipv4acd_on_conflict(ll);
+
+ break;
+ default:
+ assert_not_reached("Invalid state.");
+ }
+
+ out:
+ if (r < 0)
+ sd_ipv4acd_stop(ll);
+
+ return 1;
+ }
+
+ int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index) {
+ assert_return(ll, -EINVAL);
+ assert_return(interface_index > 0, -EINVAL);
+ assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ ll->index = interface_index;
+
+ return 0;
+ }
+
+ int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr) {
+ assert_return(ll, -EINVAL);
+ assert_return(addr, -EINVAL);
+ assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ memcpy(&ll->mac_addr, addr, ETH_ALEN);
+
+ return 0;
+ }
+
+ int sd_ipv4acd_detach_event(sd_ipv4acd *ll) {
+ assert_return(ll, -EINVAL);
+
+ ll->event = sd_event_unref(ll->event);
+
+ return 0;
+ }
+
+ int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int priority) {
+ int r;
+
+ assert_return(ll, -EINVAL);
+ assert_return(!ll->event, -EBUSY);
+
+ if (event)
+ ll->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&ll->event);
+ if (r < 0)
+ return r;
+ }
+
+ ll->event_priority = priority;
+
+ return 0;
+ }
+
+ int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata) {
+ assert_return(ll, -EINVAL);
+
+ ll->cb = cb;
+ ll->userdata = userdata;
+
+ return 0;
+ }
+
+ int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address){
+ assert_return(ll, -EINVAL);
+ assert_return(address, -EINVAL);
+ assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ ll->address = address->s_addr;
+
+ return 0;
+ }
+
+ bool sd_ipv4acd_is_running(sd_ipv4acd *ll) {
+ assert_return(ll, false);
+
+ return ll->state != IPV4ACD_STATE_INIT;
+ }
+
+ static bool ether_addr_is_nul(const struct ether_addr *addr) {
+ const struct ether_addr nul_addr = {};
+
+ assert(addr);
+
+ return memcmp(addr, &nul_addr, sizeof(struct ether_addr)) == 0;
+ }
+
+ #define HASH_KEY SD_ID128_MAKE(df,04,22,98,3f,ad,14,52,f9,87,2e,d1,9c,70,e2,f2)
+
+ int sd_ipv4acd_start(sd_ipv4acd *ll) {
+ int r;
+
+ assert_return(ll, -EINVAL);
+ assert_return(ll->event, -EINVAL);
+ assert_return(ll->index > 0, -EINVAL);
+ assert_return(ll->address != 0, -EINVAL);
+ assert_return(!ether_addr_is_nul(&ll->mac_addr), -EINVAL);
+ assert_return(ll->state == IPV4ACD_STATE_INIT, -EBUSY);
+
+ ll->defend_window = 0;
+
+ r = arp_network_bind_raw_socket(ll->index, ll->address, &ll->mac_addr);
+ if (r < 0)
+ goto out;
+
+ ll->fd = safe_close(ll->fd);
+ ll->fd = r;
+
+ r = sd_event_add_io(ll->event, &ll->receive_message, ll->fd,
+ EPOLLIN, ipv4acd_on_packet, ll);
+ if (r < 0)
+ goto out;
+
+ r = sd_event_source_set_priority(ll->receive_message, ll->event_priority);
+ if (r < 0)
+ goto out;
+
+ r = sd_event_source_set_description(ll->receive_message, "ipv4acd-receive-message");
+ if (r < 0)
+ goto out;
+
+ r = ipv4acd_set_next_wakeup(ll, 0, 0);
+ if (r < 0)
+ goto out;
+ out:
+ if (r < 0) {
+ ipv4acd_stop(ll);
+ return r;
+ }
+
+ return 0;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <arpa/inet.h>
+
+ #include "siphash24.h"
+ #include "hashmap.h"
+
+ #include "lldp-tlv.h"
+ #include "lldp-port.h"
+ #include "sd-lldp.h"
+ #include "prioq.h"
+ #include "lldp-internal.h"
+ #include "lldp-util.h"
+
+ typedef enum LLDPAgentRXState {
+ LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL = 4,
+ LLDP_AGENT_RX_DELETE_AGED_INFO,
+ LLDP_AGENT_RX_LLDP_INITIALIZE,
+ LLDP_AGENT_RX_WAIT_FOR_FRAME,
+ LLDP_AGENT_RX_RX_FRAME,
+ LLDP_AGENT_RX_DELETE_INFO,
+ LLDP_AGENT_RX_UPDATE_INFO,
+ _LLDP_AGENT_RX_STATE_MAX,
+ _LLDP_AGENT_RX_INVALID = -1,
+ } LLDPAgentRXState;
+
+ /* Section 10.5.2.2 Reception counters */
+ struct lldp_agent_statistics {
+ uint64_t stats_ageouts_total;
+ uint64_t stats_frames_discarded_total;
+ uint64_t stats_frames_in_errors_total;
+ uint64_t stats_frames_in_total;
+ uint64_t stats_tlvs_discarded_total;
+ uint64_t stats_tlvs_unrecognized_total;
+ };
+
+ struct sd_lldp {
+ lldp_port *port;
+
+ Prioq *by_expiry;
+ Hashmap *neighbour_mib;
+
+ sd_lldp_cb_t cb;
+
+ void *userdata;
+
+ LLDPAgentRXState rx_state;
+ lldp_agent_statistics statistics;
+ };
+
+ static void chassis_id_hash_func(const void *p, struct siphash *state) {
+ const lldp_chassis_id *id = p;
+
+ assert(id);
+ assert(id->data);
+
+ siphash24_compress(&id->length, sizeof(id->length), state);
+ siphash24_compress(id->data, id->length, state);
+ }
+
+ static int chassis_id_compare_func(const void *_a, const void *_b) {
+ const lldp_chassis_id *a, *b;
+
+ a = _a;
+ b = _b;
+
+ assert(!a->length || a->data);
+ assert(!b->length || b->data);
+
+ if (a->type != b->type)
+ return -1;
+
+ if (a->length != b->length)
+ return a->length < b->length ? -1 : 1;
+
+ return memcmp(a->data, b->data, a->length);
+ }
+
+ static const struct hash_ops chassis_id_hash_ops = {
+ .hash = chassis_id_hash_func,
+ .compare = chassis_id_compare_func
+ };
+
+ static void lldp_mib_delete_objects(sd_lldp *lldp);
+ static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state);
+ static void lldp_run_state_machine(sd_lldp *ll);
+
+ static int lldp_receive_frame(sd_lldp *lldp, tlv_packet *tlv) {
+ int r;
+
+ assert(lldp);
+ assert(tlv);
+
+ /* Remove expired packets */
+ if (prioq_size(lldp->by_expiry) > 0) {
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_DELETE_INFO);
+
+ lldp_mib_delete_objects(lldp);
+ }
+
+ r = lldp_mib_add_objects(lldp->by_expiry, lldp->neighbour_mib, tlv);
+ if (r < 0)
+ goto out;
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_UPDATE_INFO);
+
+ log_lldp("Packet added. MIB size: %d , PQ size: %d",
+ hashmap_size(lldp->neighbour_mib),
+ prioq_size(lldp->by_expiry));
+
+ lldp->statistics.stats_frames_in_total ++;
+
+ out:
+ if (r < 0)
+ log_lldp("Receive frame failed: %s", strerror(-r));
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
+
+ return 0;
+ }
+
+ /* 10.3.2 LLDPDU validation: rxProcessFrame() */
+ int lldp_handle_packet(tlv_packet *tlv, uint16_t length) {
+ uint16_t type, len, i, l, t;
+ bool chassis_id = false;
+ bool malformed = false;
+ bool port_id = false;
+ bool ttl = false;
+ bool end = false;
+ lldp_port *port;
+ uint8_t *p, *q;
+ sd_lldp *lldp;
+ int r;
+
+ assert(tlv);
+ assert(length > 0);
+
+ port = (lldp_port *) tlv->userdata;
+ lldp = (sd_lldp *) port->userdata;
+
+ if (lldp->port->status == LLDP_PORT_STATUS_DISABLED) {
+ log_lldp("Port is disabled : %s . Dropping ...",
+ lldp->port->ifname);
+ goto out;
+ }
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_RX_FRAME);
+
+ p = tlv->pdu;
+ p += sizeof(struct ether_header);
+
+ for (i = 1, l = 0; l <= length; i++) {
+
+ memcpy(&t, p, sizeof(uint16_t));
+
+ type = ntohs(t) >> 9;
+ len = ntohs(t) & 0x01ff;
+
+ if (type == LLDP_TYPE_END) {
+ if (len != 0) {
+ log_lldp("TLV type end is not length 0. Length:%d received . Dropping ...",
+ len);
+
+ malformed = true;
+ goto out;
+ }
+
+ end = true;
+
+ break;
+ } else if (type >=_LLDP_TYPE_MAX) {
+ log_lldp("TLV type not recognized %d . Dropping ...",
+ type);
+
+ malformed = true;
+ goto out;
+ }
+
+ /* skip type and length encoding */
+ p += 2;
+ q = p;
+
+ p += len;
+ l += (len + 2);
+
+ if (i <= 3) {
+ if (i != type) {
+ log_lldp("TLV missing or out of order. Dropping ...");
+
+ malformed = true;
+ goto out;
+ }
+ }
+
+ switch(type) {
+ case LLDP_TYPE_CHASSIS_ID:
+
+ if (len < 2) {
+ log_lldp("Received malformed Chassis ID TLV len = %d. Dropping",
+ len);
+
+ malformed = true;
+ goto out;
+ }
+
+ if (chassis_id) {
+ log_lldp("Duplicate Chassis ID TLV found. Dropping ...");
+
+ malformed = true;
+ goto out;
+ }
+
+ /* Look what subtype it has */
+ if (*q == LLDP_CHASSIS_SUBTYPE_RESERVED ||
+ *q > LLDP_CHASSIS_SUBTYPE_LOCALLY_ASSIGNED) {
+ log_lldp("Unknown subtype: %d found in Chassis ID TLV . Dropping ...",
+ *q);
+
+ malformed = true;
+ goto out;
+
+ }
+
+ chassis_id = true;
+
+ break;
+ case LLDP_TYPE_PORT_ID:
+
+ if (len < 2) {
+ log_lldp("Received malformed Port ID TLV len = %d. Dropping",
+ len);
+
+ malformed = true;
+ goto out;
+ }
+
+ if (port_id) {
+ log_lldp("Duplicate Port ID TLV found. Dropping ...");
+
+ malformed = true;
+ goto out;
+ }
+
+ /* Look what subtype it has */
+ if (*q == LLDP_PORT_SUBTYPE_RESERVED ||
+ *q > LLDP_PORT_SUBTYPE_LOCALLY_ASSIGNED) {
+ log_lldp("Unknown subtype: %d found in Port ID TLV . Dropping ...",
+ *q);
+
+ malformed = true;
+ goto out;
+
+ }
+
+ port_id = true;
+
+ break;
+ case LLDP_TYPE_TTL:
+
+ if(len != 2) {
+ log_lldp(
+ "Received invalid lenth: %d TTL TLV. Dropping ...",
+ len);
+
+ malformed = true;
+ goto out;
+ }
+
+ if (ttl) {
+ log_lldp("Duplicate TTL TLV found. Dropping ...");
+
+ malformed = true;
+ goto out;
+ }
+
+ ttl = true;
+
+ break;
+ default:
+
+ if (len == 0) {
+ log_lldp("TLV type = %d's, length 0 received . Dropping ...",
+ type);
+
+ malformed = true;
+ goto out;
+ }
+ break;
+ }
+ }
+
+ if(!chassis_id || !port_id || !ttl || !end) {
+ log_lldp( "One or more mandotory TLV missing . Dropping ...");
+
+ malformed = true;
+ goto out;
+
+ }
+
+ r = tlv_packet_parse_pdu(tlv, length);
+ if (r < 0) {
+ log_lldp( "Failed to parse the TLV. Dropping ...");
+
+ malformed = true;
+ goto out;
+ }
+
+ return lldp_receive_frame(lldp, tlv);
+
+ out:
+ lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
+
+ if (malformed) {
+ lldp->statistics.stats_frames_discarded_total ++;
+ lldp->statistics.stats_frames_in_errors_total ++;
+ }
+
+ sd_lldp_packet_unref(tlv);
+
+ return 0;
+ }
+
+ static int ttl_expiry_item_prioq_compare_func(const void *a, const void *b) {
+ const lldp_neighbour_port *p = a, *q = b;
+
+ if (p->until < q->until)
+ return -1;
+
+ if (p->until > q->until)
+ return 1;
+
+ return 0;
+ }
+
+ static void lldp_set_state(sd_lldp *lldp, LLDPAgentRXState state) {
+
+ assert(lldp);
+ assert(state < _LLDP_AGENT_RX_STATE_MAX);
+
+ lldp->rx_state = state;
+
+ lldp_run_state_machine(lldp);
+ }
+
+ static void lldp_run_state_machine(sd_lldp *lldp) {
+ if (!lldp->cb)
+ return;
+
+ switch (lldp->rx_state) {
+ case LLDP_AGENT_RX_UPDATE_INFO:
+ lldp->cb(lldp, SD_LLDP_EVENT_UPDATE_INFO, lldp->userdata);
+ break;
+ default:
+ break;
+ }
+ }
+
+ /* 10.5.5.2.1 mibDeleteObjects ()
+ * The mibDeleteObjects () procedure deletes all information in the LLDP remote
+ * systems MIB associated with the MSAP identifier if an LLDPDU is received with
+ * an rxTTL value of zero (see 10.3.2) or the timing counter rxInfoTTL expires. */
+
+ static void lldp_mib_delete_objects(sd_lldp *lldp) {
+ lldp_neighbour_port *p;
+ usec_t t = 0;
+
+ /* Remove all entries that are past their TTL */
+ for (;;) {
+
+ if (prioq_size(lldp->by_expiry) <= 0)
+ break;
+
+ p = prioq_peek(lldp->by_expiry);
+ if (!p)
+ break;
+
+ if (t <= 0)
+ t = now(clock_boottime_or_monotonic());
+
+ if (p->until > t)
+ break;
+
+ lldp_neighbour_port_remove_and_free(p);
+
+ lldp->statistics.stats_ageouts_total ++;
+ }
+ }
+
+ static void lldp_mib_objects_flush(sd_lldp *lldp) {
+ lldp_neighbour_port *p, *q;
+ lldp_chassis *c;
+
+ assert(lldp);
+ assert(lldp->neighbour_mib);
+ assert(lldp->by_expiry);
+
+ /* Drop all packets */
+ while ((c = hashmap_steal_first(lldp->neighbour_mib))) {
+
+ LIST_FOREACH_SAFE(port, p, q, c->ports) {
+ lldp_neighbour_port_remove_and_free(p);
+ }
+ }
+
+ assert(hashmap_size(lldp->neighbour_mib) == 0);
+ assert(prioq_size(lldp->by_expiry) == 0);
+ }
+
+ int sd_lldp_save(sd_lldp *lldp, const char *lldp_file) {
+ _cleanup_free_ char *temp_path = NULL;
+ _cleanup_fclose_ FILE *f = NULL;
+ uint8_t *mac, *port_id, type;
+ lldp_neighbour_port *p;
+ uint16_t data = 0, length = 0;
+ char buf[LINE_MAX];
+ lldp_chassis *c;
+ usec_t time;
+ Iterator i;
+ int r;
+
+ assert(lldp);
+ assert(lldp_file);
+
+ r = fopen_temporary(lldp_file, &f, &temp_path);
+ if (r < 0)
+ goto fail;
+
+ fchmod(fileno(f), 0644);
+
+ HASHMAP_FOREACH(c, lldp->neighbour_mib, i) {
+ LIST_FOREACH(port, p, c->ports) {
+ _cleanup_free_ char *s = NULL;
+ char *k, *t;
+
+ r = sd_lldp_packet_read_chassis_id(p->packet, &type, &mac, &length);
+ if (r < 0)
+ continue;
+
+ sprintf(buf, "'_Chassis=%02x:%02x:%02x:%02x:%02x:%02x' '_CType=%d' ",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
+
+ s = strdup(buf);
+ if (!s) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ r = sd_lldp_packet_read_port_id(p->packet, &type, &port_id, &length);
+ if (r < 0)
+ continue;
+
+ if (type != LLDP_PORT_SUBTYPE_MAC_ADDRESS) {
+ k = strndup((char *) port_id, length -1);
+ if (!k) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ sprintf(buf, "'_Port=%s' '_PType=%d' ", k , type);
+ free(k);
+ } else {
+ mac = port_id;
+ sprintf(buf, "'_Port=%02x:%02x:%02x:%02x:%02x:%02x' '_PType=%d' ",
+ mac[0], mac[1], mac[2], mac[3], mac[4], mac[5], type);
+ }
+
+ k = strappend(s, buf);
+ if (!k) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ free(s);
+ s = k;
+
+ time = now(clock_boottime_or_monotonic());
+
+ /* Don't write expired packets */
+ if (time - p->until <= 0)
+ continue;
+
+ sprintf(buf, "'_TTL="USEC_FMT"' ", p->until);
+
+ k = strappend(s, buf);
+ if (!k) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ free(s);
+ s = k;
+
+ r = sd_lldp_packet_read_system_name(p->packet, &k, &length);
+ if (r < 0)
+ k = strappend(s, "'_NAME=N/A' ");
+ else {
+ t = strndup(k, length);
+ if (!t) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ k = strjoin(s, "'_NAME=", t, "' ", NULL);
+ free(t);
+ }
+
+ if (!k) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ free(s);
+ s = k;
+
+ (void) sd_lldp_packet_read_system_capability(p->packet, &data);
+
+ sprintf(buf, "'_CAP=%x'", data);
+
+ k = strappend(s, buf);
+ if (!k) {
+ r = -ENOMEM;
+ goto fail;
+ }
+
+ free(s);
+ s = k;
+
+ fprintf(f, "%s\n", s);
+ }
+ }
+
+ r = fflush_and_check(f);
+ if (r < 0)
+ goto fail;
+
+ if (rename(temp_path, lldp_file) < 0) {
+ r = -errno;
+ goto fail;
+ }
+
+ return 0;
+
+ fail:
+ if (temp_path)
+ (void) unlink(temp_path);
+
+ return log_error_errno(r, "Failed to save lldp data %s: %m", lldp_file);
+ }
+
+ int sd_lldp_start(sd_lldp *lldp) {
+ int r;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(lldp->port, -EINVAL);
+
+ lldp->port->status = LLDP_PORT_STATUS_ENABLED;
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_LLDP_INITIALIZE);
+
+ r = lldp_port_start(lldp->port);
+ if (r < 0) {
+ log_lldp("Failed to start Port : %s , %s",
+ lldp->port->ifname,
+ strerror(-r));
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL);
+
+ return r;
+ }
+
+ lldp_set_state(lldp, LLDP_AGENT_RX_WAIT_FOR_FRAME);
+
+ return 0;
+ }
+
+ int sd_lldp_stop(sd_lldp *lldp) {
+ int r;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(lldp->port, -EINVAL);
+
+ lldp->port->status = LLDP_PORT_STATUS_DISABLED;
+
+ r = lldp_port_stop(lldp->port);
+ if (r < 0)
+ return r;
+
+ lldp_mib_objects_flush(lldp);
+
+ return 0;
+ }
+
+ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority) {
+ int r;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(!lldp->port->event, -EBUSY);
+
+ if (event)
+ lldp->port->event = sd_event_ref(event);
+ else {
+ r = sd_event_default(&lldp->port->event);
+ if (r < 0)
+ return r;
+ }
+
+ lldp->port->event_priority = priority;
+
+ return 0;
+ }
+
+ int sd_lldp_detach_event(sd_lldp *lldp) {
+
+ assert_return(lldp, -EINVAL);
+
+ lldp->port->event = sd_event_unref(lldp->port->event);
+
+ return 0;
+ }
+
+ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata) {
+ assert_return(lldp, -EINVAL);
+
+ lldp->cb = cb;
+ lldp->userdata = userdata;
+
+ return 0;
+ }
+
+ void sd_lldp_free(sd_lldp *lldp) {
+
+ if (!lldp)
+ return;
+
+ /* Drop all packets */
+ lldp_mib_objects_flush(lldp);
+
+ lldp_port_free(lldp->port);
+
+ hashmap_free(lldp->neighbour_mib);
+ prioq_free(lldp->by_expiry);
+
+ free(lldp);
+ }
+
+ int sd_lldp_new(int ifindex,
+ const char *ifname,
+ const struct ether_addr *mac,
+ sd_lldp **ret) {
+ _cleanup_lldp_free_ sd_lldp *lldp = NULL;
+ int r;
+
+ assert_return(ret, -EINVAL);
+ assert_return(ifindex > 0, -EINVAL);
+ assert_return(ifname, -EINVAL);
+ assert_return(mac, -EINVAL);
+
+ lldp = new0(sd_lldp, 1);
+ if (!lldp)
+ return -ENOMEM;
+
+ r = lldp_port_new(ifindex, ifname, mac, lldp, &lldp->port);
+ if (r < 0)
+ return r;
+
+ lldp->neighbour_mib = hashmap_new(&chassis_id_hash_ops);
+ if (!lldp->neighbour_mib)
+ return -ENOMEM;
+
+ r = prioq_ensure_allocated(&lldp->by_expiry,
+ ttl_expiry_item_prioq_compare_func);
+ if (r < 0)
+ return r;
+
+ lldp->rx_state = LLDP_AGENT_RX_WAIT_PORT_OPERATIONAL;
+
+ *ret = lldp;
+ lldp = NULL;
+
+ return 0;
+ }
+
+ int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs) {
+ lldp_neighbour_port *p;
+ lldp_chassis *c;
+ Iterator iter;
+ unsigned count = 0, i;
+
+ assert_return(lldp, -EINVAL);
+ assert_return(tlvs, -EINVAL);
+
+ HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
+ LIST_FOREACH(port, p, c->ports)
+ count++;
+ }
+
+ if (!count) {
+ *tlvs = NULL;
+ return 0;
+ }
+
+ *tlvs = new(sd_lldp_packet *, count);
+ if (!*tlvs)
+ return -ENOMEM;
+
+ i = 0;
+ HASHMAP_FOREACH(c, lldp->neighbour_mib, iter) {
+ LIST_FOREACH(port, p, c->ports)
+ (*tlvs)[i++] = sd_lldp_packet_ref(p->packet);
+ }
+
+ return count;
+ }
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ #pragma once
+
+ /***
+ This file is part of systemd.
+
+ Copyright 2013 Lennart Poettering
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include "util.h"
+ #include "sd-event.h"
+
+ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_event*, sd_event_unref);
+ DEFINE_TRIVIAL_CLEANUP_FUNC(sd_event_source*, sd_event_source_unref);
+
+ #define _cleanup_event_unref_ _cleanup_(sd_event_unrefp)
+ #define _cleanup_event_source_unref_ _cleanup_(sd_event_source_unrefp)
#include "sd-id128.h"
#include "random-util.h"
- _public_ char *sd_id128_to_string(sd_id128_t id, char s[33]) {
+#if 0 /* NM_IGNORED */
+ _public_ char *sd_id128_to_string(sd_id128_t id, char s[SD_ID128_STRING_MAX]) {
unsigned n;
assert_return(s, NULL);
return 0;
}
- unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]) {
+#if 0 /* NM_IGNORED */
+ void dns_name_hash_func(const void *s, struct siphash *state) {
const char *p = s;
- unsigned long ul = hash_key[0];
int r;
assert(p);
return 1;
}
- unsigned long dns_name_hash_func(const void *s, const uint8_t hash_key[HASH_KEY_SIZE]);
+#if 0 /* NM_IGNORED */
+ void dns_name_hash_func(const void *s, struct siphash *state);
int dns_name_compare_func(const void *a, const void *b);
extern const struct hash_ops dns_name_hash_ops;
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ #ifndef foosdipv4acdfoo
+ #define foosdipv4acdfoo
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Axis Communications AB. All rights reserved.
+ Copyright (C) 2015 Tom Gundersen
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
++#include "nm-sd-adapt.h"
++
+ #include <stdbool.h>
+ #include <netinet/in.h>
+ #include <net/ethernet.h>
+
+ #include "sd-event.h"
+
+ enum {
+ SD_IPV4ACD_EVENT_STOP = 0,
+ SD_IPV4ACD_EVENT_BIND = 1,
+ SD_IPV4ACD_EVENT_CONFLICT = 2,
+ };
+
+ typedef struct sd_ipv4acd sd_ipv4acd;
+ typedef void (*sd_ipv4acd_cb_t)(sd_ipv4acd *ll, int event, void *userdata);
+
+ int sd_ipv4acd_detach_event(sd_ipv4acd *ll);
+ int sd_ipv4acd_attach_event(sd_ipv4acd *ll, sd_event *event, int priority);
+ int sd_ipv4acd_get_address(sd_ipv4acd *ll, struct in_addr *address);
+ int sd_ipv4acd_set_callback(sd_ipv4acd *ll, sd_ipv4acd_cb_t cb, void *userdata);
+ int sd_ipv4acd_set_mac(sd_ipv4acd *ll, const struct ether_addr *addr);
+ int sd_ipv4acd_set_index(sd_ipv4acd *ll, int interface_index);
+ int sd_ipv4acd_set_address(sd_ipv4acd *ll, const struct in_addr *address);
+ bool sd_ipv4acd_is_running(sd_ipv4acd *ll);
+ int sd_ipv4acd_start(sd_ipv4acd *ll);
+ int sd_ipv4acd_stop(sd_ipv4acd *ll);
+ sd_ipv4acd *sd_ipv4acd_ref(sd_ipv4acd *ll);
+ sd_ipv4acd *sd_ipv4acd_unref(sd_ipv4acd *ll);
+ int sd_ipv4acd_new (sd_ipv4acd **ret);
+
+ #endif
--- /dev/null
+ /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+ /***
+ This file is part of systemd.
+
+ Copyright (C) 2014 Tom Gundersen
+ Copyright (C) 2014 Susant Sahani
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd 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
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+ ***/
+
+ #pragma once
+
++#include "nm-sd-adapt.h"
++
+ #include "sd-event.h"
+
+ enum {
+ SD_LLDP_EVENT_UPDATE_INFO = 0,
+ };
+
+ enum {
+ SD_LLDP_DESTINATION_TYPE_NEAREST_BRIDGE,
+ SD_LLDP_DESTINATION_TYPE_NEAREST_NON_TPMR_BRIDGE,
+ SD_LLDP_DESTINATION_TYPE_NEAREST_CUSTOMER_BRIDGE,
+ };
+
+ typedef struct sd_lldp sd_lldp;
+ typedef struct tlv_packet sd_lldp_packet;
+
+ typedef void (*sd_lldp_cb_t)(sd_lldp *lldp, int event, void *userdata);
+
+ int sd_lldp_new(int ifindex, const char *ifname, const struct ether_addr *mac, sd_lldp **ret);
+ void sd_lldp_free(sd_lldp *lldp);
+
+ int sd_lldp_start(sd_lldp *lldp);
+ int sd_lldp_stop(sd_lldp *lldp);
+
+ int sd_lldp_attach_event(sd_lldp *lldp, sd_event *event, int priority);
+ int sd_lldp_detach_event(sd_lldp *lldp);
+
+ int sd_lldp_set_callback(sd_lldp *lldp, sd_lldp_cb_t cb, void *userdata);
+ int sd_lldp_save(sd_lldp *lldp, const char *file);
+
+ int sd_lldp_packet_read_chassis_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length);
+ int sd_lldp_packet_read_port_id(sd_lldp_packet *tlv, uint8_t *type, uint8_t **data, uint16_t *length);
+ int sd_lldp_packet_read_ttl(sd_lldp_packet *tlv, uint16_t *ttl);
+ int sd_lldp_packet_read_system_name(sd_lldp_packet *tlv, char **data, uint16_t *length);
+ int sd_lldp_packet_read_system_description(sd_lldp_packet *tlv, char **data, uint16_t *length);
+ int sd_lldp_packet_read_system_capability(sd_lldp_packet *tlv, uint16_t *data);
+ int sd_lldp_packet_read_port_description(sd_lldp_packet *tlv, char **data, uint16_t *length);
+
+ /* IEEE 802.1 organizationally specific TLVs */
+ int sd_lldp_packet_read_port_vlan_id(sd_lldp_packet *tlv, uint16_t *id);
+ int sd_lldp_packet_read_port_protocol_vlan_id(sd_lldp_packet *tlv, uint8_t *flags, uint16_t *id);
+ int sd_lldp_packet_read_vlan_name(sd_lldp_packet *tlv, uint16_t *vlan_id, char **name, uint16_t *length);
+ int sd_lldp_packet_read_management_vid(sd_lldp_packet *tlv, uint16_t *id);
+ int sd_lldp_packet_read_link_aggregation(sd_lldp_packet *tlv, uint8_t *status, uint32_t *id);
+
+ sd_lldp_packet *sd_lldp_packet_ref(sd_lldp_packet *tlv);
+ sd_lldp_packet *sd_lldp_packet_unref(sd_lldp_packet *tlv);
+
+ int sd_lldp_packet_get_destination_type(sd_lldp_packet *tlv, int *dest);
+
+ int sd_lldp_get_packets(sd_lldp *lldp, sd_lldp_packet ***tlvs);