diff options
author | Dan Williams <dcbw@redhat.com> | 2014-07-22 12:38:20 -0500 |
---|---|---|
committer | Dan Williams <dcbw@redhat.com> | 2014-11-06 09:58:47 -0600 |
commit | 6a35562024aec3ab85e3ecb9b6dbaeba76e9aab3 (patch) | |
tree | 5d07d1c1496819aa750b7533e12d5befd1a21427 | |
parent | 98845f9b7b7884300dd982a33e03279de242ec03 (diff) | |
download | NetworkManager-6a35562024aec3ab85e3ecb9b6dbaeba76e9aab3.tar.gz |
dhcp: add systemd-based "internal" DHCP client
We must also remove -Waggregate-return from m4/compiler-warnings.m4 because systemd
uses aggregate return (correctly) in a couple cases, and we cannot keep single-level
makefiles and override aggregate-return only for the systemd sub-library.
This client currently only supports DHCPv4 because the base systemd code
does not yet fully support DHCPv6.
-rw-r--r-- | m4/compiler_warnings.m4 | 2 | ||||
-rw-r--r-- | src/Makefile.am | 76 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-manager.c | 4 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-systemd.c | 798 | ||||
-rw-r--r-- | src/dhcp-manager/nm-dhcp-systemd.h | 49 |
5 files changed, 926 insertions, 3 deletions
diff --git a/m4/compiler_warnings.m4 b/m4/compiler_warnings.m4 index 3fde795504..c05a26d098 100644 --- a/m4/compiler_warnings.m4 +++ b/m4/compiler_warnings.m4 @@ -29,7 +29,7 @@ if test "$GCC" = "yes" -a "$set_more_warnings" != "no"; then -fno-strict-aliasing -Wno-unused-but-set-variable \ -Wundef -Wimplicit-function-declaration \ -Wpointer-arith -Winit-self \ - -Wmissing-include-dirs -Waggregate-return; do + -Wmissing-include-dirs; do CFLAGS="$CFLAGS_MORE_WARNINGS $CFLAGS_EXTRA $option $CFLAGS_SAVED" AC_MSG_CHECKING([whether gcc understands $option]) AC_TRY_COMPILE([], [], diff --git a/src/Makefile.am b/src/Makefile.am index d568c00a46..60f7ef6a30 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -48,6 +48,74 @@ AM_CPPFLAGS = \ # primarily for its side effect of removing duplicates. AM_CPPFLAGS += $(foreach d,$(sort $(dir $(libNetworkManager_la_SOURCES))),-I$(top_srcdir)/src/$d) +noinst_LTLIBRARIES = libNetworkManager.la libsystemd-dhcp.la + +###################### +# libsystemd-dhcp +###################### + +SYSTEMD_DHCP_CFLAGS = \ + -I$(top_srcdir)/src/dhcp-manager/systemd-dhcp/src/systemd \ + -I$(top_srcdir)/src/dhcp-manager/systemd-dhcp/src/libsystemd-network \ + -I$(top_srcdir)/src/dhcp-manager/systemd-dhcp/src/shared \ + -I$(top_srcdir)/src/dhcp-manager/systemd-dhcp + +libsystemd_dhcp_la_SOURCES = \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-network.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-packet.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-internal.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp6-network.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp6-lease-internal.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp6-option.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/network-internal.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp-lease.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp-client.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-option.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/network-internal.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp6-lease.c \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-protocol.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp6-internal.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp6-protocol.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/dhcp-lease-internal.h \ + dhcp-manager/systemd-dhcp/src/libsystemd-network/sd-dhcp6-client.c \ + dhcp-manager/systemd-dhcp/src/shared/async.h \ + dhcp-manager/systemd-dhcp/src/shared/time-util.h \ + dhcp-manager/systemd-dhcp/src/shared/siphash24.h \ + dhcp-manager/systemd-dhcp/src/shared/time-util.c \ + dhcp-manager/systemd-dhcp/src/shared/socket-util.h \ + dhcp-manager/systemd-dhcp/src/shared/sparse-endian.h \ + dhcp-manager/systemd-dhcp/src/shared/macro.h \ + dhcp-manager/systemd-dhcp/src/shared/refcnt.h \ + dhcp-manager/systemd-dhcp/src/shared/util.c \ + dhcp-manager/systemd-dhcp/src/shared/in-addr-util.c \ + dhcp-manager/systemd-dhcp/src/shared/siphash24.c \ + dhcp-manager/systemd-dhcp/src/shared/util.h \ + dhcp-manager/systemd-dhcp/src/shared/in-addr-util.h \ + dhcp-manager/systemd-dhcp/src/shared/list.h \ + dhcp-manager/systemd-dhcp/src/shared/fileio.h \ + dhcp-manager/systemd-dhcp/src/shared/fileio.c \ + dhcp-manager/systemd-dhcp/src/shared/strv.h \ + dhcp-manager/systemd-dhcp/src/shared/strv.c \ + dhcp-manager/systemd-dhcp/src/shared/utf8.h \ + dhcp-manager/systemd-dhcp/src/shared/utf8.c \ + dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp-lease.h \ + dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp-client.h \ + dhcp-manager/systemd-dhcp/src/systemd/sd-id128.h \ + dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp6-lease.h \ + dhcp-manager/systemd-dhcp/src/systemd/sd-dhcp6-client.h \ + dhcp-manager/systemd-dhcp/src/systemd/sd-event.h \ + dhcp-manager/systemd-dhcp/src/systemd/_sd-common.h \ + dhcp-manager/systemd-dhcp/nm-sd-adapt.h \ + dhcp-manager/systemd-dhcp/nm-sd-adapt.c + +libsystemd_dhcp_la_CPPFLAGS = \ + -I$(top_srcdir)/include \ + $(SYSTEMD_DHCP_CFLAGS) \ + $(GLIB_CFLAGS) + +libsystemd_dhcp_la_LIBADD = \ + $(GLIB_LIBS) + ########################################### # NetworkManager ########################################### @@ -60,8 +128,6 @@ NetworkManager_SOURCES = \ NetworkManager_LDADD = libNetworkManager.la -noinst_LTLIBRARIES = libNetworkManager.la - nm_device_sources = \ devices/nm-device-bond.c \ devices/nm-device-bridge.c \ @@ -110,6 +176,8 @@ nm_sources = \ dhcp-manager/nm-dhcp-dhclient-utils.h \ dhcp-manager/nm-dhcp-dhcpcd.c \ dhcp-manager/nm-dhcp-dhcpcd.h \ + dhcp-manager/nm-dhcp-systemd.h \ + dhcp-manager/nm-dhcp-systemd.c \ dhcp-manager/nm-dhcp-manager.c \ dhcp-manager/nm-dhcp-manager.h \ \ @@ -328,6 +396,7 @@ AM_CPPFLAGS += \ $(LIBNDP_CFLAGS) \ $(LIBSOUP_CFLAGS) \ $(SYSTEMD_LOGIN_CFLAGS) \ + $(SYSTEMD_DHCP_CFLAGS) \ \ -DBINDIR=\"$(bindir)\" \ -DDATADIR=\"$(datadir)\" \ @@ -359,6 +428,7 @@ libNetworkManager_la_SOURCES = \ libNetworkManager_la_LIBADD = \ $(top_builddir)/libnm-core/libnm-core.la \ + libsystemd-dhcp.la \ $(DBUS_LIBS) \ $(GLIB_LIBS) \ $(GUDEV_LIBS) \ @@ -374,6 +444,8 @@ endif NetworkManager_LDFLAGS = -rdynamic +###################### + dbusservicedir = $(DBUS_SYS_DIR) dbusservice_DATA = org.freedesktop.NetworkManager.conf diff --git a/src/dhcp-manager/nm-dhcp-manager.c b/src/dhcp-manager/nm-dhcp-manager.c index b19590a69b..8d4b1228ba 100644 --- a/src/dhcp-manager/nm-dhcp-manager.c +++ b/src/dhcp-manager/nm-dhcp-manager.c @@ -37,6 +37,7 @@ #include "nm-dhcp-manager.h" #include "nm-dhcp-dhclient.h" #include "nm-dhcp-dhcpcd.h" +#include "nm-dhcp-systemd.h" #include "nm-logging.h" #include "nm-dbus-manager.h" #include "nm-config.h" @@ -313,6 +314,9 @@ get_client_type (const char *client, GError **error) return NM_TYPE_DHCP_DHCPCD; } + if (!strcmp (client, "internal")) + return NM_TYPE_DHCP_SYSTEMD; + g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, _("unsupported DHCP client '%s'"), client); diff --git a/src/dhcp-manager/nm-dhcp-systemd.c b/src/dhcp-manager/nm-dhcp-systemd.c new file mode 100644 index 0000000000..8a1fb7fe11 --- /dev/null +++ b/src/dhcp-manager/nm-dhcp-systemd.c @@ -0,0 +1,798 @@ +/* -*- 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 <glib.h> +#include <glib/gi18n.h> +#include <gio/gio.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-dhcp-systemd.h" +#include "nm-utils.h" +#include "nm-logging.h" +#include "nm-dhcp-utils.h" +#include "NetworkManagerUtils.h" + +#include "nm-sd-adapt.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 *desc; + 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].desc; i++) { + if (requests[i].num == option) { + g_hash_table_insert (options, + (gpointer) (requests[i].desc + STRLEN (REQPREFIX)), + value); + break; + } + } + /* Option should always be found */ + g_assert (requests[i].desc); +} + +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_requests_to_options (GHashTable *options, const ReqOption *requests) +{ + guint i; + + for (i = 0; options && requests[i].desc; i++) { + if (requests[i].include) + g_hash_table_insert (options, (gpointer) requests[i].desc, 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 (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, plen = 0, i; + NMPlatformIP4Address address; + GString *l; + struct sd_dhcp_route *routes; + guint16 mtu; + int r; + + r = sd_dhcp_lease_get_address (lease, &tmp_addr); + if (r < 0) { + g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED, + "failed to read address from lease"); + return NULL; + } + + ip4_config = nm_ip4_config_new (); + + /* Address */ + 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 */ + r = sd_dhcp_lease_get_netmask (lease, &tmp_addr); + if (r < 0) { + /* Get default netmask for the IP according to appropriate class. */ + plen = nm_utils_ip4_get_default_prefix (address.address); + LOG_LEASE (LOGD_DHCP4, " plen %d (default)", plen); + } else { + plen = nm_utils_ip4_netmask_to_prefix (tmp_addr.s_addr); + LOG_LEASE (LOGD_DHCP4, " plen %d", plen); + } + address.plen = plen; + tmp_addr.s_addr = nm_utils_ip4_prefix_to_netmask (plen); + add_option (options, + dhcp4_requests, + DHCP_OPTION_SUBNET_MASK, + nm_utils_inet4_ntop (tmp_addr.s_addr, NULL)); + + /* Lease time */ + r = sd_dhcp_lease_get_lifetime (lease, &lifetime); + if (r < 0) + lifetime = 3600; /* one hour */ + address.timestamp = nm_utils_get_monotonic_timestamp_s (); + address.lifetime = address.preferred = lifetime; + add_option_u32 (options, + dhcp4_requests, + DHCP_OPTION_IP_ADDRESS_LEASE_TIME, + (guint) MIN (time (NULL) + lifetime, G_MAXUINT32)); + + 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 */ + r = sd_dhcp_lease_get_dns (lease, &addr_list); + if (r > 0) { + l = g_string_sized_new (30); + for (i = 0; i < r; 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); + } + + r = sd_dhcp_lease_get_routes (lease, &routes); + if (r > 0) { + l = g_string_sized_new (30); + for (i = 0; i < r; 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); + } + + 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); + } + + r = sd_dhcp_lease_get_ntp(lease, &addr_list); + if (r > 0) { + l = g_string_sized_new (30); + for (i = 0; i < r; i++) { + str = nm_utils_inet4_ntop (addr_list[i].s_addr, buf); + 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); + } + + 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); +} + +GSList * +nm_dhcp_systemd_get_lease_ip_configs (const char *iface, + const char *uuid, + gboolean ipv6) +{ + GSList *leases = NULL; + gs_free char *path = NULL; + sd_dhcp_lease *lease = NULL; + NMIP4Config *ip4_config; + int r; + + path = get_leasefile_path (iface, uuid, ipv6); + r = sd_dhcp_lease_load (path, &lease); + if (r == 0) { + ip4_config = lease_to_ip4_config (lease, NULL, 0, FALSE, NULL); + if (ip4_config) + leases = g_slist_append (leases, ip4_config); + } + + return leases; +} + +/************************************************************/ + +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 (lease, + options, + nm_dhcp_client_get_priority (NM_DHCP_CLIENT (self)), + TRUE, + &error); + if (ip4_config) { + add_requests_to_options (options, dhcp4_requests); + sd_dhcp_lease_save (lease, priv->lease_file); + + 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); + } + + sd_dhcp_lease_unref (lease); + 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_EXPIRED: + case DHCP_EVENT_STOP: + nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_FAIL, NULL, NULL); + break; + case DHCP_EVENT_RENEW: + case DHCP_EVENT_IP_CHANGE: + case DHCP_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_client_id, + const char *dhcp_anycast_addr, + const char *hostname) +{ + 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; + const uint8_t *client_id = NULL; + size_t client_id_len = 0; + struct in_addr last_addr; + int r, i; + + 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; + } + + sd_dhcp_lease_load (priv->lease_file, &lease); + + if (lease) { + r = sd_dhcp_lease_get_address (lease, &last_addr); + if (r == 0) { + 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; + } + } + } + + if (dhcp_client_id) { + gs_unref_bytes GBytes *b = NULL; + + b = nm_dhcp_utils_client_id_string_to_bytes (dhcp_client_id); + if (b) { + client_id = (const guint8 *) g_bytes_get_data (b, &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 { + r = sd_dhcp_lease_get_client_id (lease, &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); + } + } + + if (lease) + sd_dhcp_lease_unref (lease); + + /* Add requested options */ + for (i = 0; dhcp4_requests[i].desc; i++) { + if (dhcp4_requests[i].include) + sd_dhcp_client_set_request_option (priv->client4, dhcp4_requests[i].num); + } + + 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; + } + + return TRUE; + +error: + sd_dhcp_client_unref (priv->client4); + priv->client4 = NULL; + return FALSE; +} + +static void +bound6_handle (NMDhcpSystemd *self) +{ + /* not yet supported... */ + 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_RETRANS_MAX: + nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_TIMEOUT, NULL, NULL); + break; + case DHCP6_EVENT_RESEND_EXPIRE: + case DHCP6_EVENT_STOP: + nm_dhcp_client_set_state (NM_DHCP_CLIENT (user_data), NM_DHCP_STATE_FAIL, NULL, NULL); + break; + case DHCP6_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, + const char *hostname, + 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_ifname (priv->client6, iface); + if (r < 0) { + nm_log_warn (LOGD_DHCP6, "(%s): failed to set DHCP ifname (%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].desc; 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; +} + diff --git a/src/dhcp-manager/nm-dhcp-systemd.h b/src/dhcp-manager/nm-dhcp-systemd.h new file mode 100644 index 0000000000..05e8e8da33 --- /dev/null +++ b/src/dhcp-manager/nm-dhcp-systemd.h @@ -0,0 +1,49 @@ +/* -*- 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. + */ + +#ifndef NM_DHCP_SYSTEMD_H +#define NM_DHCP_SYSTEMD_H + +#include <glib.h> +#include <glib-object.h> + +#include "nm-dhcp-client.h" + +#define NM_TYPE_DHCP_SYSTEMD (nm_dhcp_systemd_get_type ()) +#define NM_DHCP_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemd)) +#define NM_DHCP_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass)) +#define NM_IS_DHCP_SYSTEMD(obj) (G_TYPE_CHECK_INSTANCE_TYPE ((obj), NM_TYPE_DHCP_SYSTEMD)) +#define NM_IS_DHCP_SYSTEMD_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), NM_TYPE_DHCP_SYSTEMD)) +#define NM_DHCP_SYSTEMD_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), NM_TYPE_DHCP_SYSTEMD, NMDhcpSystemdClass)) + +typedef struct { + NMDhcpClient parent; +} NMDhcpSystemd; + +typedef struct { + NMDhcpClientClass parent; +} NMDhcpSystemdClass; + +GType nm_dhcp_systemd_get_type (void); + +GSList *nm_dhcp_systemd_get_lease_ip_configs (const char *iface, + const char *uuid, + gboolean ipv6); + +#endif /* NM_DHCP_SYSTEMD_H */ + |