44c3365d2aacca96965e14677de12857a218b25b
[NetworkManager.git] / src / dhcp-manager / nm-dhcp-manager.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* nm-dhcp-manager.c - Handle the DHCP daemon for NetworkManager
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; either version 2, or (at your option)
7  * any later version.
8  *
9  * This program is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program; if not, write to the Free Software Foundation, Inc.,
16  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
17  *
18  * Copyright (C) 2005 - 2013 Red Hat, Inc.
19  * Copyright (C) 2006 - 2008 Novell, Inc.
20  *
21  */
22
23 #include "nm-default.h"
24
25 #include <sys/socket.h>
26 #include <sys/wait.h>
27 #include <signal.h>
28 #include <string.h>
29 #include <stdlib.h>
30 #include <errno.h>
31 #include <unistd.h>
32 #include <fcntl.h>
33 #include <stdio.h>
34
35 #include "nm-dhcp-manager.h"
36 #include "nm-dhcp-dhclient.h"
37 #include "nm-dhcp-dhcpcd.h"
38 #include "nm-dhcp-systemd.h"
39 #include "nm-config.h"
40 #include "NetworkManagerUtils.h"
41
42 #define DHCP_TIMEOUT 45 /* default DHCP timeout, in seconds */
43
44 /* default to installed helper, but can be modified for testing */
45 const char *nm_dhcp_helper_path = LIBEXECDIR "/nm-dhcp-helper";
46
47 typedef GSList * (*GetLeaseConfigFunc) (const char *iface, const char *uuid, gboolean ipv6);
48
49 typedef struct {
50         GType               client_type;
51         GHashTable *        clients;
52         char *              default_hostname;
53 } NMDhcpManagerPrivate;
54
55 #define NM_DHCP_MANAGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), NM_TYPE_DHCP_MANAGER, NMDhcpManagerPrivate))
56
57 G_DEFINE_TYPE (NMDhcpManager, nm_dhcp_manager, G_TYPE_OBJECT)
58
59 /***************************************************/
60
61 typedef struct {
62         GType gtype;
63         const char *name;
64         NMDhcpClientGetPathFunc get_path_func;
65         NMDhcpClientGetLeaseConfigsFunc get_lease_configs_func;
66 } ClientDesc;
67
68 static GSList *client_descs = NULL;
69
70 void
71 _nm_dhcp_client_register (GType gtype,
72                           const char *name,
73                           NMDhcpClientGetPathFunc get_path_func,
74                           NMDhcpClientGetLeaseConfigsFunc get_lease_configs_func)
75 {
76         ClientDesc *desc;
77         GSList *iter;
78
79         g_return_if_fail (gtype != G_TYPE_INVALID);
80         g_return_if_fail (name != NULL);
81
82         for (iter = client_descs; iter; iter = iter->next) {
83                 desc = iter->data;
84                 g_return_if_fail (desc->gtype != gtype);
85                 g_return_if_fail (strcmp (desc->name, name) != 0);
86         }
87
88         desc = g_slice_new0 (ClientDesc);
89         desc->gtype = gtype;
90         desc->name = name;
91         desc->get_path_func = get_path_func;
92         desc->get_lease_configs_func = get_lease_configs_func;
93         client_descs = g_slist_prepend (client_descs, desc);
94 }
95
96 static ClientDesc *
97 find_client_desc (const char *name, GType gtype)
98 {
99         GSList *iter;
100
101         g_return_val_if_fail (name || gtype, NULL);
102
103         for (iter = client_descs; iter; iter = iter->next) {
104                 ClientDesc *desc = iter->data;
105
106                 if (name && strcmp (desc->name, name) != 0)
107                         continue;
108                 if (gtype && desc->gtype != gtype)
109                         continue;
110                 return desc;
111         }
112         return NULL;
113 }
114
115 static GType
116 is_client_enabled (const char *name, GError **error)
117 {
118         ClientDesc *desc;
119
120         desc = find_client_desc (name, G_TYPE_INVALID);
121         if (desc && (!desc->get_path_func || desc->get_path_func()))
122                 return desc->gtype;
123
124         g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
125                      _("'%s' support not found or not enabled."),
126                      name);
127         return G_TYPE_INVALID;
128 }
129
130 /***************************************************/
131
132 static NMDhcpClient *
133 get_client_for_ifindex (NMDhcpManager *manager, int ifindex, gboolean ip6)
134 {
135         NMDhcpManagerPrivate *priv;
136         GHashTableIter iter;
137         gpointer value;
138
139         g_return_val_if_fail (NM_IS_DHCP_MANAGER (manager), NULL);
140         g_return_val_if_fail (ifindex > 0, NULL);
141
142         priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
143
144         g_hash_table_iter_init (&iter, priv->clients);
145         while (g_hash_table_iter_next (&iter, NULL, &value)) {
146                 NMDhcpClient *candidate = NM_DHCP_CLIENT (value);
147
148                 if (   nm_dhcp_client_get_ifindex (candidate) == ifindex
149                     && nm_dhcp_client_get_ipv6 (candidate) == ip6)
150                         return candidate;
151         }
152
153         return NULL;
154 }
155
156 static GType
157 get_client_type (const char *client, GError **error)
158 {
159         GType client_gtype;
160
161         if (client)
162                 client_gtype = is_client_enabled (client, error);
163         else {
164                 /* Fallbacks */
165                 client_gtype = is_client_enabled ("dhclient", NULL);
166                 if (client_gtype == G_TYPE_INVALID)
167                         client_gtype = is_client_enabled ("dhcpcd", NULL);
168                 if (client_gtype == G_TYPE_INVALID)
169                         client_gtype = is_client_enabled ("internal", NULL);
170                 if (client_gtype == G_TYPE_INVALID) {
171                         g_set_error_literal (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
172                                                  _("no usable DHCP client could be found."));
173                 }
174         }
175         return client_gtype;
176 }
177
178 static void client_state_changed (NMDhcpClient *client,
179                                   NMDhcpState state,
180                                   GObject *ip_config,
181                                   GVariant *options,
182                                   const char *event_id,
183                                   NMDhcpManager *self);
184
185 static void
186 remove_client (NMDhcpManager *self, NMDhcpClient *client)
187 {
188         g_signal_handlers_disconnect_by_func (client, client_state_changed, self);
189
190         /* Stopping the client is left up to the controlling device
191          * explicitly since we may want to quit NetworkManager but not terminate
192          * the DHCP client.
193          */
194
195         g_hash_table_remove (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client);
196 }
197
198 static void
199 client_state_changed (NMDhcpClient *client,
200                       NMDhcpState state,
201                       GObject *ip_config,
202                       GVariant *options,
203                       const char *event_id,
204                       NMDhcpManager *self)
205 {
206         if (state >= NM_DHCP_STATE_TIMEOUT)
207                 remove_client (self, client);
208 }
209
210 static NMDhcpClient *
211 client_start (NMDhcpManager *self,
212               const char *iface,
213               int ifindex,
214               const GByteArray *hwaddr,
215               const char *uuid,
216               guint32 priority,
217               gboolean ipv6,
218               const struct in6_addr *ipv6_ll_addr,
219               const char *dhcp_client_id,
220               guint32 timeout,
221               const char *dhcp_anycast_addr,
222               const char *hostname,
223               const char *fqdn,
224               gboolean info_only,
225               NMSettingIP6ConfigPrivacy privacy,
226               const char *last_ip4_address)
227 {
228         NMDhcpManagerPrivate *priv;
229         NMDhcpClient *client;
230         gboolean success = FALSE;
231
232         g_return_val_if_fail (self, NULL);
233         g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
234         g_return_val_if_fail (ifindex > 0, NULL);
235         g_return_val_if_fail (uuid != NULL, NULL);
236
237         priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
238
239         /* Ensure we have a usable DHCP client */
240         g_return_val_if_fail (priv->client_type != 0, NULL);
241
242         /* Kill any old client instance */
243         client = get_client_for_ifindex (self, ifindex, ipv6);
244         if (client) {
245                 g_object_ref (client);
246                 remove_client (self, client);
247                 nm_dhcp_client_stop (client, FALSE);
248                 g_object_unref (client);
249         }
250
251         /* And make a new one */
252         client = g_object_new (priv->client_type,
253                                NM_DHCP_CLIENT_INTERFACE, iface,
254                                NM_DHCP_CLIENT_IFINDEX, ifindex,
255                                NM_DHCP_CLIENT_HWADDR, hwaddr,
256                                NM_DHCP_CLIENT_IPV6, ipv6,
257                                NM_DHCP_CLIENT_UUID, uuid,
258                                NM_DHCP_CLIENT_PRIORITY, priority,
259                                NM_DHCP_CLIENT_TIMEOUT, timeout ? timeout : DHCP_TIMEOUT,
260                                NULL);
261         g_hash_table_insert (NM_DHCP_MANAGER_GET_PRIVATE (self)->clients, client, g_object_ref (client));
262         g_signal_connect (client, NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED, G_CALLBACK (client_state_changed), self);
263
264         if (ipv6)
265                 success = nm_dhcp_client_start_ip6 (client, dhcp_anycast_addr, ipv6_ll_addr, hostname, info_only, privacy);
266         else
267                 success = nm_dhcp_client_start_ip4 (client, dhcp_client_id, dhcp_anycast_addr, hostname, fqdn, last_ip4_address);
268
269         if (!success) {
270                 remove_client (self, client);
271                 client = NULL;
272         }
273
274         return client;
275 }
276
277 static const char *
278 get_send_hostname (NMDhcpManager *self, const char *setting_hostname)
279 {
280         NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
281
282         /* Always prefer the explicit dhcp-send-hostname if given */
283         return setting_hostname ? setting_hostname : priv->default_hostname;
284 }
285
286 /* Caller owns a reference to the NMDhcpClient on return */
287 NMDhcpClient *
288 nm_dhcp_manager_start_ip4 (NMDhcpManager *self,
289                            const char *iface,
290                            int ifindex,
291                            const GByteArray *hwaddr,
292                            const char *uuid,
293                            guint32 priority,
294                            gboolean send_hostname,
295                            const char *dhcp_hostname,
296                            const char *dhcp_fqdn,
297                            const char *dhcp_client_id,
298                            guint32 timeout,
299                            const char *dhcp_anycast_addr,
300                            const char *last_ip_address)
301 {
302         const char *hostname = NULL;
303         const char *fqdn = NULL;
304
305         g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
306
307         if (send_hostname) {
308                 hostname = get_send_hostname (self, dhcp_hostname);
309                 fqdn = dhcp_fqdn;
310         }
311         return client_start (self, iface, ifindex, hwaddr, uuid, priority, FALSE, NULL,
312                              dhcp_client_id, timeout, dhcp_anycast_addr, hostname,
313                              fqdn, FALSE, 0, last_ip_address);
314 }
315
316 /* Caller owns a reference to the NMDhcpClient on return */
317 NMDhcpClient *
318 nm_dhcp_manager_start_ip6 (NMDhcpManager *self,
319                            const char *iface,
320                            int ifindex,
321                            const GByteArray *hwaddr,
322                            const struct in6_addr *ll_addr,
323                            const char *uuid,
324                            guint32 priority,
325                            gboolean send_hostname,
326                            const char *dhcp_hostname,
327                            guint32 timeout,
328                            const char *dhcp_anycast_addr,
329                            gboolean info_only,
330                            NMSettingIP6ConfigPrivacy privacy)
331 {
332         const char *hostname = NULL;
333
334         g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
335
336         if (send_hostname)
337                 hostname = get_send_hostname (self, dhcp_hostname);
338         return client_start (self, iface, ifindex, hwaddr, uuid, priority, TRUE,
339                              ll_addr, NULL, timeout, dhcp_anycast_addr, hostname, NULL, info_only,
340                              privacy, NULL);
341 }
342
343 void
344 nm_dhcp_manager_set_default_hostname (NMDhcpManager *manager, const char *hostname)
345 {
346         NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (manager);
347
348         g_clear_pointer (&priv->default_hostname, g_free);
349
350         /* Never send 'localhost'-type names to the DHCP server */
351         if (!nm_utils_is_specific_hostname (hostname))
352                 return;
353
354         priv->default_hostname = g_strdup (hostname);
355 }
356
357 GSList *
358 nm_dhcp_manager_get_lease_ip_configs (NMDhcpManager *self,
359                                       const char *iface,
360                                       int ifindex,
361                                       const char *uuid,
362                                       gboolean ipv6,
363                                       guint32 default_route_metric)
364 {
365         ClientDesc *desc;
366
367         g_return_val_if_fail (NM_IS_DHCP_MANAGER (self), NULL);
368         g_return_val_if_fail (iface != NULL, NULL);
369         g_return_val_if_fail (ifindex >= -1, NULL);
370         g_return_val_if_fail (uuid != NULL, NULL);
371
372         desc = find_client_desc (NULL, NM_DHCP_MANAGER_GET_PRIVATE (self)->client_type);
373         if (desc && desc->get_lease_configs_func)
374                 return desc->get_lease_configs_func (iface, ifindex, uuid, ipv6, default_route_metric);
375         return NULL;
376 }
377
378 /***************************************************/
379
380 NM_DEFINE_SINGLETON_GETTER (NMDhcpManager, nm_dhcp_manager_get, NM_TYPE_DHCP_MANAGER);
381
382 static void
383 nm_dhcp_manager_init (NMDhcpManager *self)
384 {
385         NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (self);
386         NMConfig *config = nm_config_get ();
387         const char *client;
388         GError *error = NULL;
389         GSList *iter;
390
391         for (iter = client_descs; iter; iter = iter->next) {
392                 ClientDesc *desc = iter->data;
393
394                 nm_log_dbg (LOGD_DHCP, "Registered DHCP client '%s' (%s)",
395                             desc->name, g_type_name (desc->gtype));
396         }
397
398         /* Client-specific setup */
399         client = nm_config_get_dhcp_client (config);
400         if (nm_config_get_configure_and_quit (config)) {
401                 if (g_strcmp0 (client, "internal") != 0)
402                         nm_log_warn (LOGD_DHCP, "Using internal DHCP client since configure-and-quit is set.");
403                 client = "internal";
404         }
405
406         priv->client_type = get_client_type (client, &error);
407         if (priv->client_type == G_TYPE_INVALID) {
408                 nm_log_warn (LOGD_DHCP, "No usable DHCP client found (%s)! DHCP configurations will fail.",
409                              error->message);
410         } else {
411                 nm_log_dbg (LOGD_DHCP, "Using DHCP client '%s'", find_client_desc (NULL, priv->client_type)->name);
412
413         }
414         g_clear_error (&error);
415
416         priv->clients = g_hash_table_new_full (g_direct_hash, g_direct_equal,
417                                                NULL,
418                                                (GDestroyNotify) g_object_unref);
419 }
420
421 static void
422 dispose (GObject *object)
423 {
424         NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (object);
425         GList *values, *iter;
426
427         if (priv->clients) {
428                 values = g_hash_table_get_values (priv->clients);
429                 for (iter = values; iter; iter = g_list_next (iter))
430                         remove_client (NM_DHCP_MANAGER (object), NM_DHCP_CLIENT (iter->data));
431                 g_list_free (values);
432         }
433
434         G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->dispose (object);
435 }
436
437 static void
438 finalize (GObject *object)
439 {
440         NMDhcpManagerPrivate *priv = NM_DHCP_MANAGER_GET_PRIVATE (object);
441
442         g_free (priv->default_hostname);
443
444         if (priv->clients)
445                 g_hash_table_destroy (priv->clients);
446
447         G_OBJECT_CLASS (nm_dhcp_manager_parent_class)->finalize (object);
448 }
449
450 static void
451 nm_dhcp_manager_class_init (NMDhcpManagerClass *manager_class)
452 {
453         GObjectClass *object_class = G_OBJECT_CLASS (manager_class);
454
455         g_type_class_add_private (manager_class, sizeof (NMDhcpManagerPrivate));
456
457         /* virtual methods */
458         object_class->finalize = finalize;
459         object_class->dispose = dispose;
460 }