device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / NetworkManagerUtils.c
1 /* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */
2 /* NetworkManager -- Network link manager
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 of the License, or
7  * (at your option) 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 2004 - 2016 Red Hat, Inc.
19  * Copyright 2005 - 2008 Novell, Inc.
20  */
21
22 #include "nm-default.h"
23
24 #include "NetworkManagerUtils.h"
25
26 #include "nm-utils.h"
27 #include "nm-setting-connection.h"
28 #include "nm-setting-ip4-config.h"
29 #include "nm-setting-ip6-config.h"
30 #include "nm-core-internal.h"
31
32 #include "nm-platform.h"
33 #include "nm-exported-object.h"
34 #include "nm-auth-utils.h"
35
36 /******************************************************************************/
37
38 /**
39  * nm_utils_get_shared_wifi_permission:
40  * @connection: the NMConnection to lookup the permission.
41  *
42  * Returns: a static string of the wifi-permission (if any) or %NULL.
43  */
44 const char *
45 nm_utils_get_shared_wifi_permission (NMConnection *connection)
46 {
47         NMSettingWireless *s_wifi;
48         NMSettingWirelessSecurity *s_wsec;
49         const char *method = NULL;
50
51         method = nm_utils_get_ip_config_method (connection, NM_TYPE_SETTING_IP4_CONFIG);
52         if (strcmp (method, NM_SETTING_IP4_CONFIG_METHOD_SHARED) != 0)
53                 return NULL;  /* Not shared */
54
55         s_wifi = nm_connection_get_setting_wireless (connection);
56         if (s_wifi) {
57                 s_wsec = nm_connection_get_setting_wireless_security (connection);
58                 if (s_wsec)
59                         return NM_AUTH_PERMISSION_WIFI_SHARE_PROTECTED;
60                 else
61                         return NM_AUTH_PERMISSION_WIFI_SHARE_OPEN;
62         }
63
64         return NULL;
65 }
66
67 /******************************************************************************/
68
69 static char *
70 get_new_connection_name (const GSList *existing,
71                          const char *preferred,
72                          const char *fallback_prefix)
73 {
74         GSList *names = NULL;
75         const GSList *iter;
76         char *cname = NULL;
77         int i = 0;
78         gboolean preferred_found = FALSE;
79
80         g_assert (fallback_prefix);
81
82         for (iter = existing; iter; iter = g_slist_next (iter)) {
83                 NMConnection *candidate = NM_CONNECTION (iter->data);
84                 const char *id;
85
86                 id = nm_connection_get_id (candidate);
87                 g_assert (id);
88                 names = g_slist_append (names, (gpointer) id);
89
90                 if (preferred && !preferred_found && (strcmp (preferred, id) == 0))
91                         preferred_found = TRUE;
92         }
93
94         /* Return the preferred name if it was unique */
95         if (preferred && !preferred_found) {
96                 g_slist_free (names);
97                 return g_strdup (preferred);
98         }
99
100         /* Otherwise find the next available unique connection name using the given
101          * connection name template.
102          */
103         while (!cname && (i++ < 10000)) {
104                 char *temp;
105                 gboolean found = FALSE;
106
107                 /* Translators: the first %s is a prefix for the connection id, such
108                  * as "Wired Connection" or "VPN Connection". The %d is a number
109                  * that is combined with the first argument to create a unique
110                  * connection id. */
111                 temp = g_strdup_printf (C_("connection id fallback", "%s %d"),
112                                         fallback_prefix, i);
113                 for (iter = names; iter; iter = g_slist_next (iter)) {
114                         if (!strcmp (iter->data, temp)) {
115                                 found = TRUE;
116                                 break;
117                         }
118                 }
119                 if (!found)
120                         cname = temp;
121                 else
122                         g_free (temp);
123         }
124
125         g_slist_free (names);
126         return cname;
127 }
128
129 static char *
130 get_new_connection_ifname (NMPlatform *platform,
131                            const GSList *existing,
132                            const char *prefix)
133 {
134         int i;
135         char *name;
136         const GSList *iter;
137         gboolean found;
138
139         for (i = 0; i < 500; i++) {
140                 name = g_strdup_printf ("%s%d", prefix, i);
141
142                 if (nm_platform_link_get_by_ifname (platform, name))
143                         goto next;
144
145                 for (iter = existing, found = FALSE; iter; iter = g_slist_next (iter)) {
146                         NMConnection *candidate = iter->data;
147
148                         if (g_strcmp0 (nm_connection_get_interface_name (candidate), name) == 0) {
149                                 found = TRUE;
150                                 break;
151                         }
152                 }
153
154                 if (!found)
155                         return name;
156
157         next:
158                 g_free (name);
159         }
160
161         return NULL;
162 }
163
164 const char *
165 nm_utils_get_ip_config_method (NMConnection *connection,
166                                GType         ip_setting_type)
167 {
168         NMSettingConnection *s_con;
169         NMSettingIPConfig *s_ip4, *s_ip6;
170         const char *method;
171
172         s_con = nm_connection_get_setting_connection (connection);
173
174         if (ip_setting_type == NM_TYPE_SETTING_IP4_CONFIG) {
175                 g_return_val_if_fail (s_con != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
176
177                 if (nm_setting_connection_get_master (s_con))
178                         return NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
179                 else {
180                         s_ip4 = nm_connection_get_setting_ip4_config (connection);
181                         if (!s_ip4)
182                                 return NM_SETTING_IP4_CONFIG_METHOD_DISABLED;
183                         method = nm_setting_ip_config_get_method (s_ip4);
184                         g_return_val_if_fail (method != NULL, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
185
186                         return method;
187                 }
188
189         } else if (ip_setting_type == NM_TYPE_SETTING_IP6_CONFIG) {
190                 g_return_val_if_fail (s_con != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
191
192                 if (nm_setting_connection_get_master (s_con))
193                         return NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
194                 else {
195                         s_ip6 = nm_connection_get_setting_ip6_config (connection);
196                         if (!s_ip6)
197                                 return NM_SETTING_IP6_CONFIG_METHOD_IGNORE;
198                         method = nm_setting_ip_config_get_method (s_ip6);
199                         g_return_val_if_fail (method != NULL, NM_SETTING_IP6_CONFIG_METHOD_AUTO);
200
201                         return method;
202                 }
203
204         } else
205                 g_assert_not_reached ();
206 }
207
208 void
209 nm_utils_complete_generic (NMPlatform *platform,
210                            NMConnection *connection,
211                            const char *ctype,
212                            const GSList *existing,
213                            const char *preferred_id,
214                            const char *fallback_id_prefix,
215                            const char *ifname_prefix,
216                            gboolean default_enable_ipv6)
217 {
218         NMSettingConnection *s_con;
219         char *id, *uuid, *ifname;
220         GHashTable *parameters;
221
222         g_assert (fallback_id_prefix);
223
224         s_con = nm_connection_get_setting_connection (connection);
225         if (!s_con) {
226                 s_con = (NMSettingConnection *) nm_setting_connection_new ();
227                 nm_connection_add_setting (connection, NM_SETTING (s_con));
228         }
229         g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_TYPE, ctype, NULL);
230
231         if (!nm_setting_connection_get_uuid (s_con)) {
232                 uuid = nm_utils_uuid_generate ();
233                 g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_UUID, uuid, NULL);
234                 g_free (uuid);
235         }
236
237         /* Add a connection ID if absent */
238         if (!nm_setting_connection_get_id (s_con)) {
239                 id = get_new_connection_name (existing, preferred_id, fallback_id_prefix);
240                 g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_ID, id, NULL);
241                 g_free (id);
242         }
243
244         /* Add an interface name, if requested */
245         if (ifname_prefix && !nm_setting_connection_get_interface_name (s_con)) {
246                 ifname = get_new_connection_ifname (platform, existing, ifname_prefix);
247                 g_object_set (G_OBJECT (s_con), NM_SETTING_CONNECTION_INTERFACE_NAME, ifname, NULL);
248                 g_free (ifname);
249         }
250
251         /* Normalize */
252         parameters = g_hash_table_new (g_str_hash, g_str_equal);
253         g_hash_table_insert (parameters, NM_CONNECTION_NORMALIZE_PARAM_IP6_CONFIG_METHOD,
254                              default_enable_ipv6 ? NM_SETTING_IP6_CONFIG_METHOD_AUTO : NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
255         nm_connection_normalize (connection, parameters, NULL, NULL);
256         g_hash_table_destroy (parameters);
257 }
258
259 /*****************************************************************************/
260
261 static GHashTable *
262 check_property_in_hash (GHashTable *hash,
263                         const char *s_name,
264                         const char *p_name)
265 {
266         GHashTable *props;
267
268         props = g_hash_table_lookup (hash, s_name);
269         if (   !props
270             || !g_hash_table_lookup (props, p_name)) {
271                 return NULL;
272         }
273         return props;
274 }
275
276 static void
277 remove_from_hash (GHashTable *s_hash,
278                   GHashTable *p_hash,
279                   const char *s_name,
280                   const char *p_name)
281 {
282         if (!p_hash)
283                 return;
284
285         g_hash_table_remove (p_hash, p_name);
286         if (g_hash_table_size (p_hash) == 0)
287                 g_hash_table_remove (s_hash, s_name);
288 }
289
290 static gboolean
291 check_ip6_method (NMConnection *orig,
292                   NMConnection *candidate,
293                   GHashTable *settings)
294 {
295         GHashTable *props;
296         const char *orig_ip6_method, *candidate_ip6_method;
297         NMSettingIPConfig *candidate_ip6;
298         gboolean allow = FALSE;
299
300         props = check_property_in_hash (settings,
301                                         NM_SETTING_IP6_CONFIG_SETTING_NAME,
302                                         NM_SETTING_IP_CONFIG_METHOD);
303         if (!props)
304                 return TRUE;
305
306         /* If the generated connection is 'link-local' and the candidate is both 'auto'
307          * and may-fail=TRUE, then the candidate is OK to use.  may-fail is included
308          * in the decision because if the candidate is 'auto' but may-fail=FALSE, then
309          * the connection could not possibly have been previously activated on the
310          * device if the device has no non-link-local IPv6 address.
311          */
312         orig_ip6_method = nm_utils_get_ip_config_method (orig, NM_TYPE_SETTING_IP6_CONFIG);
313         candidate_ip6_method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP6_CONFIG);
314         candidate_ip6 = nm_connection_get_setting_ip6_config (candidate);
315
316         if (   strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
317             && strcmp (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0
318             && (!candidate_ip6 || nm_setting_ip_config_get_may_fail (candidate_ip6))) {
319                 allow = TRUE;
320         }
321
322         /* If the generated connection method is 'link-local' or 'auto' and the candidate
323          * method is 'ignore' we can take the connection, because NM didn't simply take care
324          * of IPv6.
325          */
326         if (  (   strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL) == 0
327                || strcmp (orig_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO) == 0)
328             && strcmp (candidate_ip6_method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE) == 0) {
329                 allow = TRUE;
330         }
331
332         if (allow) {
333                 remove_from_hash (settings, props,
334                                   NM_SETTING_IP6_CONFIG_SETTING_NAME,
335                                   NM_SETTING_IP_CONFIG_METHOD);
336         }
337         return allow;
338 }
339
340 static int
341 route_compare (NMIPRoute *route1, NMIPRoute *route2, gint64 default_metric)
342 {
343         gint64 r, metric1, metric2;
344
345         r = g_strcmp0 (nm_ip_route_get_dest (route1), nm_ip_route_get_dest (route2));
346         if (r)
347                 return r;
348
349         r = nm_ip_route_get_prefix (route1) - nm_ip_route_get_prefix (route2);
350         if (r)
351                 return r > 0 ? 1 : -1;
352
353         r = g_strcmp0 (nm_ip_route_get_next_hop (route1), nm_ip_route_get_next_hop (route2));
354         if (r)
355                 return r;
356
357         metric1 = nm_ip_route_get_metric (route1) == -1 ? default_metric : nm_ip_route_get_metric (route1);
358         metric2 = nm_ip_route_get_metric (route2) == -1 ? default_metric : nm_ip_route_get_metric (route2);
359
360         r = metric1 - metric2;
361         if (r)
362                 return r > 0 ? 1 : -1;
363
364         r = nm_ip_route_get_family (route1) - nm_ip_route_get_family (route2);
365         if (r)
366                 return r > 0 ? 1 : -1;
367
368         return 0;
369 }
370
371 static int
372 route_ptr_compare (const void *a, const void *b)
373 {
374         return route_compare (*(NMIPRoute **) a, *(NMIPRoute **) b, -1);
375 }
376
377 static gboolean
378 check_ip_routes (NMConnection *orig,
379                  NMConnection *candidate,
380                  GHashTable *settings,
381                  gint64 default_metric,
382                  gboolean v4)
383 {
384         gs_free NMIPRoute **routes1 = NULL, **routes2 = NULL;
385         NMSettingIPConfig *s_ip1, *s_ip2;
386         const char *s_name;
387         GHashTable *props;
388         guint i, num;
389
390         s_name = v4 ? NM_SETTING_IP4_CONFIG_SETTING_NAME :
391                       NM_SETTING_IP6_CONFIG_SETTING_NAME;
392
393         props = check_property_in_hash (settings,
394                                         s_name,
395                                         NM_SETTING_IP_CONFIG_ROUTES);
396         if (!props)
397                 return TRUE;
398
399         s_ip1 = (NMSettingIPConfig *) nm_connection_get_setting_by_name (orig, s_name);
400         s_ip2 = (NMSettingIPConfig *) nm_connection_get_setting_by_name (candidate, s_name);
401
402         if (!s_ip1 || !s_ip2)
403                 return FALSE;
404
405         num = nm_setting_ip_config_get_num_routes (s_ip1);
406         if (num != nm_setting_ip_config_get_num_routes (s_ip2))
407                 return FALSE;
408
409         routes1 = g_new (NMIPRoute *, num);
410         routes2 = g_new (NMIPRoute *, num);
411
412         for (i = 0; i < num; i++) {
413                 routes1[i] = nm_setting_ip_config_get_route (s_ip1, i);
414                 routes2[i] = nm_setting_ip_config_get_route (s_ip2, i);
415         }
416
417         qsort (routes1, num, sizeof (NMIPRoute *), route_ptr_compare);
418         qsort (routes2, num, sizeof (NMIPRoute *), route_ptr_compare);
419
420         for (i = 0; i < num; i++) {
421                 if (route_compare (routes1[i], routes2[i], default_metric))
422                         return FALSE;
423         }
424
425         remove_from_hash (settings, props, s_name, NM_SETTING_IP_CONFIG_ROUTES);
426         return TRUE;
427 }
428
429 static gboolean
430 check_ip4_method (NMConnection *orig,
431                   NMConnection *candidate,
432                   GHashTable *settings,
433                   gboolean device_has_carrier)
434 {
435         GHashTable *props;
436         const char *orig_ip4_method, *candidate_ip4_method;
437         NMSettingIPConfig *candidate_ip4;
438
439         props = check_property_in_hash (settings,
440                                         NM_SETTING_IP4_CONFIG_SETTING_NAME,
441                                         NM_SETTING_IP_CONFIG_METHOD);
442         if (!props)
443                 return TRUE;
444
445         /* If the generated connection is 'disabled' (device had no IP addresses)
446          * but it has no carrier, that most likely means that IP addressing could
447          * not complete and thus no IP addresses were assigned.  In that case, allow
448          * matching to the "auto" method.
449          */
450         orig_ip4_method = nm_utils_get_ip_config_method (orig, NM_TYPE_SETTING_IP4_CONFIG);
451         candidate_ip4_method = nm_utils_get_ip_config_method (candidate, NM_TYPE_SETTING_IP4_CONFIG);
452         candidate_ip4 = nm_connection_get_setting_ip4_config (candidate);
453
454         if (   strcmp (orig_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED) == 0
455             && strcmp (candidate_ip4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO) == 0
456             && (!candidate_ip4 || nm_setting_ip_config_get_may_fail (candidate_ip4))
457             && (device_has_carrier == FALSE)) {
458                 remove_from_hash (settings, props,
459                                   NM_SETTING_IP4_CONFIG_SETTING_NAME,
460                                   NM_SETTING_IP_CONFIG_METHOD);
461                 return TRUE;
462         }
463         return FALSE;
464 }
465
466 static gboolean
467 check_connection_interface_name (NMConnection *orig,
468                                  NMConnection *candidate,
469                                  GHashTable *settings)
470 {
471         GHashTable *props;
472         const char *orig_ifname, *cand_ifname;
473         NMSettingConnection *s_con_orig, *s_con_cand;
474
475         props = check_property_in_hash (settings,
476                                         NM_SETTING_CONNECTION_SETTING_NAME,
477                                         NM_SETTING_CONNECTION_INTERFACE_NAME);
478         if (!props)
479                 return TRUE;
480
481         /* If one of the interface names is NULL, we accept that connection */
482         s_con_orig = nm_connection_get_setting_connection (orig);
483         s_con_cand = nm_connection_get_setting_connection (candidate);
484         orig_ifname = nm_setting_connection_get_interface_name (s_con_orig);
485         cand_ifname = nm_setting_connection_get_interface_name (s_con_cand);
486
487         if (!orig_ifname || !cand_ifname) {
488                 remove_from_hash (settings, props,
489                                   NM_SETTING_CONNECTION_SETTING_NAME,
490                                   NM_SETTING_CONNECTION_INTERFACE_NAME);
491                 return TRUE;
492         }
493         return FALSE;
494 }
495
496 static gboolean
497 check_connection_mac_address (NMConnection *orig,
498                               NMConnection *candidate,
499                               GHashTable *settings)
500 {
501         GHashTable *props;
502         const char *orig_mac = NULL, *cand_mac = NULL;
503         NMSettingWired *s_wired_orig, *s_wired_cand;
504
505         props = check_property_in_hash (settings,
506                                         NM_SETTING_WIRED_SETTING_NAME,
507                                         NM_SETTING_WIRED_MAC_ADDRESS);
508         if (!props)
509                 return TRUE;
510
511         /* If one of the MAC addresses is NULL, we accept that connection */
512         s_wired_orig = nm_connection_get_setting_wired (orig);
513         if (s_wired_orig)
514                 orig_mac = nm_setting_wired_get_mac_address (s_wired_orig);
515
516         s_wired_cand = nm_connection_get_setting_wired (candidate);
517         if (s_wired_cand)
518                 cand_mac = nm_setting_wired_get_mac_address (s_wired_cand);
519
520         if (!orig_mac || !cand_mac) {
521                 remove_from_hash (settings, props,
522                                   NM_SETTING_WIRED_SETTING_NAME,
523                                   NM_SETTING_WIRED_MAC_ADDRESS);
524                 return TRUE;
525         }
526         return FALSE;
527 }
528
529 static gboolean
530 check_connection_cloned_mac_address (NMConnection *orig,
531                               NMConnection *candidate,
532                               GHashTable *settings)
533 {
534         GHashTable *props;
535         const char *orig_mac = NULL, *cand_mac = NULL;
536         NMSettingWired *s_wired_orig, *s_wired_cand;
537
538         props = check_property_in_hash (settings,
539                                         NM_SETTING_WIRED_SETTING_NAME,
540                                         NM_SETTING_WIRED_CLONED_MAC_ADDRESS);
541         if (!props)
542                 return TRUE;
543
544         /* If one of the MAC addresses is NULL, we accept that connection */
545         s_wired_orig = nm_connection_get_setting_wired (orig);
546         if (s_wired_orig)
547                 orig_mac = nm_setting_wired_get_cloned_mac_address (s_wired_orig);
548
549         s_wired_cand = nm_connection_get_setting_wired (candidate);
550         if (s_wired_cand)
551                 cand_mac = nm_setting_wired_get_cloned_mac_address (s_wired_cand);
552
553         if (!orig_mac || !cand_mac) {
554                 remove_from_hash (settings, props,
555                                   NM_SETTING_WIRED_SETTING_NAME,
556                                   NM_SETTING_WIRED_CLONED_MAC_ADDRESS);
557                 return TRUE;
558         }
559         return FALSE;
560 }
561
562 static gboolean
563 check_connection_s390_props (NMConnection *orig,
564                              NMConnection *candidate,
565                              GHashTable *settings)
566 {
567         GHashTable *props1, *props2, *props3;
568         NMSettingWired *s_wired_orig, *s_wired_cand;
569
570         props1 = check_property_in_hash (settings,
571                                          NM_SETTING_WIRED_SETTING_NAME,
572                                          NM_SETTING_WIRED_S390_SUBCHANNELS);
573         props2 = check_property_in_hash (settings,
574                                          NM_SETTING_WIRED_SETTING_NAME,
575                                          NM_SETTING_WIRED_S390_NETTYPE);
576         props3 = check_property_in_hash (settings,
577                                          NM_SETTING_WIRED_SETTING_NAME,
578                                          NM_SETTING_WIRED_S390_OPTIONS);
579         if (!props1 && !props2 && !props3)
580                 return TRUE;
581
582         /* If the generated connection did not contain wired setting,
583          * allow it to match to a connection with a wired setting,
584          * but default (empty) s390-* properties */
585         s_wired_orig = nm_connection_get_setting_wired (orig);
586         s_wired_cand = nm_connection_get_setting_wired (candidate);
587         if (!s_wired_orig && s_wired_cand) {
588                 const char * const *subchans = nm_setting_wired_get_s390_subchannels (s_wired_cand);
589                 const char *nettype = nm_setting_wired_get_s390_nettype (s_wired_cand);
590                 guint32 num_options = nm_setting_wired_get_num_s390_options (s_wired_cand);
591
592                 if ((!subchans || !*subchans) && !nettype && num_options == 0) {
593                         remove_from_hash (settings, props1,
594                                           NM_SETTING_WIRED_SETTING_NAME,
595                                           NM_SETTING_WIRED_S390_SUBCHANNELS);
596                         remove_from_hash (settings, props2,
597                                           NM_SETTING_WIRED_SETTING_NAME,
598                                           NM_SETTING_WIRED_S390_NETTYPE);
599                         remove_from_hash (settings, props3,
600                                           NM_SETTING_WIRED_SETTING_NAME,
601                                           NM_SETTING_WIRED_S390_OPTIONS);
602                         return TRUE;
603                 }
604         }
605         return FALSE;
606 }
607
608 static NMConnection *
609 check_possible_match (NMConnection *orig,
610                       NMConnection *candidate,
611                       GHashTable *settings,
612                       gboolean device_has_carrier,
613                       gint64 default_v4_metric,
614                       gint64 default_v6_metric)
615 {
616         g_return_val_if_fail (settings != NULL, NULL);
617
618         if (!check_ip6_method (orig, candidate, settings))
619                 return NULL;
620
621         if (!check_ip4_method (orig, candidate, settings, device_has_carrier))
622                 return NULL;
623
624         if (!check_ip_routes (orig, candidate, settings, default_v4_metric, TRUE))
625                 return NULL;
626
627         if (!check_ip_routes (orig, candidate, settings, default_v6_metric, FALSE))
628                 return NULL;
629
630         if (!check_connection_interface_name (orig, candidate, settings))
631                 return NULL;
632
633         if (!check_connection_mac_address (orig, candidate, settings))
634                 return NULL;
635
636         if (!check_connection_cloned_mac_address (orig, candidate, settings))
637                 return NULL;
638
639         if (!check_connection_s390_props (orig, candidate, settings))
640                 return NULL;
641
642         if (g_hash_table_size (settings) == 0)
643                 return candidate;
644         else
645                 return NULL;
646 }
647
648 /**
649  * nm_utils_match_connection:
650  * @connections: a (optionally pre-sorted) list of connections from which to
651  * find a matching connection to @original based on "inferrable" properties
652  * @original: the #NMConnection to find a match for from @connections
653  * @device_has_carrier: pass %TRUE if the device that generated @original has
654  * a carrier, %FALSE if not
655  * @match_filter_func: a function to check whether each connection from @connections
656  * should be considered for matching.  This function should return %TRUE if the
657  * connection should be considered, %FALSE if the connection should be ignored
658  * @match_compat_data: data pointer passed to @match_filter_func
659  *
660  * Checks each connection from @connections until a matching connection is found
661  * considering only setting properties marked with %NM_SETTING_PARAM_INFERRABLE
662  * and checking a few other characteristics like IPv6 method.  If the caller
663  * desires some priority order of the connections, @connections should be
664  * sorted before calling this function.
665  *
666  * Returns: the best #NMConnection matching @original, or %NULL if no connection
667  * matches well enough.
668  */
669 NMConnection *
670 nm_utils_match_connection (GSList *connections,
671                            NMConnection *original,
672                            gboolean device_has_carrier,
673                            gint64 default_v4_metric,
674                            gint64 default_v6_metric,
675                            NMUtilsMatchFilterFunc match_filter_func,
676                            gpointer match_filter_data)
677 {
678         NMConnection *best_match = NULL;
679         GSList *iter;
680
681         for (iter = connections; iter; iter = iter->next) {
682                 NMConnection *candidate = NM_CONNECTION (iter->data);
683                 GHashTable *diffs = NULL;
684
685                 if (match_filter_func) {
686                         if (!match_filter_func (candidate, match_filter_data))
687                                 continue;
688                 }
689
690                 if (!nm_connection_diff (original, candidate, NM_SETTING_COMPARE_FLAG_INFERRABLE, &diffs)) {
691                         if (!best_match) {
692                                 best_match = check_possible_match (original, candidate, diffs, device_has_carrier,
693                                                                    default_v4_metric, default_v6_metric);
694                         }
695
696                         if (!best_match && nm_logging_enabled (LOGL_DEBUG, LOGD_CORE)) {
697                                 GString *diff_string;
698                                 GHashTableIter s_iter, p_iter;
699                                 gpointer setting_name, setting;
700                                 gpointer property_name, value;
701
702                                 diff_string = g_string_new (NULL);
703                                 g_hash_table_iter_init (&s_iter, diffs);
704                                 while (g_hash_table_iter_next (&s_iter, &setting_name, &setting)) {
705                                         g_hash_table_iter_init (&p_iter, setting);
706                                         while (g_hash_table_iter_next (&p_iter, &property_name, &value)) {
707                                                 if (diff_string->len)
708                                                         g_string_append (diff_string, ", ");
709                                                 g_string_append_printf (diff_string, "%s.%s",
710                                                                         (char *) setting_name,
711                                                                         (char *) property_name);
712                                         }
713                                 }
714
715                                 nm_log_dbg (LOGD_CORE, "Connection '%s' differs from candidate '%s' in %s",
716                                             nm_connection_get_id (original),
717                                             nm_connection_get_id (candidate),
718                                             diff_string->str);
719                                 g_string_free (diff_string, TRUE);
720                         }
721
722                         g_hash_table_unref (diffs);
723                         continue;
724                 }
725
726                 /* Exact match */
727                 return candidate;
728         }
729
730         /* Best match (if any) */
731         return best_match;
732 }
733
734 /******************************************************************************/
735
736 /**
737  * nm_utils_g_value_set_object_path:
738  * @value: a #GValue, initialized to store an object path
739  * @object: (allow-none): an #NMExportedObject
740  *
741  * Sets @value to @object's object path. If @object is %NULL, or not
742  * exported, @value is set to "/".
743  */
744 void
745 nm_utils_g_value_set_object_path (GValue *value, gpointer object)
746 {
747         g_return_if_fail (!object || NM_IS_EXPORTED_OBJECT (object));
748
749         if (object && nm_exported_object_is_exported (object))
750                 g_value_set_string (value, nm_exported_object_get_path (object));
751         else
752                 g_value_set_string (value, "/");
753 }
754
755 /**
756  * nm_utils_g_value_set_object_path_array:
757  * @value: a #GValue, initialized to store an object path
758  * @objects: a #GSList of #NMExportedObjects
759  * @filter_func: (allow-none): function to call on each object in @objects
760  * @user_data: data to pass to @filter_func
761  *
762  * Sets @value to an array of object paths of the objects in @objects.
763  */
764 void
765 nm_utils_g_value_set_object_path_array (GValue *value,
766                                         GSList *objects,
767                                         NMUtilsObjectFunc filter_func,
768                                         gpointer user_data)
769 {
770         char **paths;
771         guint i;
772         GSList *iter;
773
774         paths = g_new (char *, g_slist_length (objects) + 1);
775         for (i = 0, iter = objects; iter; iter = iter->next) {
776                 NMExportedObject *object = iter->data;
777                 const char *path;
778
779                 path = nm_exported_object_get_path (object);
780                 if (!path)
781                         continue;
782                 if (filter_func && !filter_func ((GObject *) object, user_data))
783                         continue;
784                 paths[i++] = g_strdup (path);
785         }
786         paths[i] = NULL;
787         g_value_take_boxed (value, paths);
788 }
789
790 /******************************************************************************/