device: renew dhcp leases on awake for software devices
[NetworkManager.git] / src / nm-dcb.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 (C) 2013 Red Hat, Inc.
19  */
20
21 #include "nm-default.h"
22
23 #include <sys/wait.h>
24 #include <string.h>
25
26 #include "nm-dcb.h"
27 #include "nm-platform.h"
28 #include "NetworkManagerUtils.h"
29
30 static const char *helper_names[] = { "dcbtool", "fcoeadm" };
31
32 gboolean
33 do_helper (const char *iface,
34            guint which,
35            DcbFunc run_func,
36            gpointer user_data,
37            GError **error,
38            const char *fmt,
39            ...)
40 {
41         char **argv = NULL, **split = NULL, *cmdline, *errmsg = NULL;
42         gboolean success = FALSE;
43         guint i, u;
44         va_list args;
45
46         g_return_val_if_fail (fmt != NULL, FALSE);
47
48         va_start (args, fmt);
49         cmdline = g_strdup_vprintf (fmt, args);
50         va_end (args);
51
52         split = g_strsplit_set (cmdline, " ", 0);
53         if (!split) {
54                 g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
55                              "failure parsing %s command line", helper_names[which]);
56                 goto out;
57         }
58
59         /* Allocate space for path, custom arg, interface name, arguments, and NULL */
60         i = u = 0;
61         argv = g_new0 (char *, g_strv_length (split) + 4);
62         argv[i++] = NULL;  /* Placeholder for dcbtool path */
63         if (which == DCBTOOL) {
64                 argv[i++] = "sc";
65                 argv[i++] = (char *) iface;
66         }
67         while (u < g_strv_length (split))
68                 argv[i++] = split[u++];
69         argv[i++] = NULL;
70         success = run_func (argv, which, user_data, error);
71         if (!success && error)
72                 g_assert (*error);
73
74 out:
75         if (split)
76                 g_strfreev (split);
77         g_free (argv);
78         g_free (cmdline);
79         g_free (errmsg);
80         return success;
81 }
82
83 gboolean
84 _dcb_enable (const char *iface,
85              gboolean enable,
86              DcbFunc run_func,
87              gpointer user_data,
88              GError **error)
89 {
90         if (enable)
91                 return do_helper (iface, DCBTOOL, run_func, user_data, error, "dcb on");
92         else
93                 return do_helper (iface, DCBTOOL, run_func, user_data, error, "dcb off");
94 }
95
96 #define SET_FLAGS(f, tag) \
97 G_STMT_START { \
98         if (!do_helper (iface, DCBTOOL, run_func, user_data, error, tag " e:%c a:%c w:%c", \
99                          f & NM_SETTING_DCB_FLAG_ENABLE ? '1' : '0', \
100                          f & NM_SETTING_DCB_FLAG_ADVERTISE ? '1' : '0', \
101                          f & NM_SETTING_DCB_FLAG_WILLING ? '1' : '0')) \
102                 return FALSE; \
103 } G_STMT_END
104
105 #define SET_APP(f, s, tag) \
106 G_STMT_START { \
107         gint prio = nm_setting_dcb_get_app_##tag##_priority (s); \
108  \
109         SET_FLAGS (f, "app:" #tag); \
110         if ((f & NM_SETTING_DCB_FLAG_ENABLE) && (prio >= 0)) { \
111                 if (!do_helper (iface, DCBTOOL, run_func, user_data, error, "app:" #tag " appcfg:%02x", (1 << prio))) \
112                         return FALSE; \
113         } \
114 } G_STMT_END
115
116 gboolean
117 _dcb_setup (const char *iface,
118             NMSettingDcb *s_dcb,
119             DcbFunc run_func,
120             gpointer user_data,
121             GError **error)
122 {
123         NMSettingDcbFlags flags;
124         guint i;
125
126         g_assert (s_dcb);
127
128         /* FCoE */
129         flags = nm_setting_dcb_get_app_fcoe_flags (s_dcb);
130         SET_APP (flags, s_dcb, fcoe);
131
132         /* iSCSI */
133         flags = nm_setting_dcb_get_app_iscsi_flags (s_dcb);
134         SET_APP (flags, s_dcb, iscsi);
135
136         /* FIP */
137         flags = nm_setting_dcb_get_app_fip_flags (s_dcb);
138         SET_APP (flags, s_dcb, fip);
139
140         /* Priority Flow Control */
141         flags = nm_setting_dcb_get_priority_flow_control_flags (s_dcb);
142         SET_FLAGS (flags, "pfc");
143         if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
144                 char buf[10];
145
146                 for (i = 0; i < 8; i++)
147                         buf[i] = nm_setting_dcb_get_priority_flow_control (s_dcb, i) ? '1' : '0';
148                 buf[i] = 0;
149                 if (!do_helper (iface, DCBTOOL, run_func, user_data, error, "pfc pfcup:%s", buf))
150                         return FALSE;
151         }
152
153         /* Priority Groups */
154         flags = nm_setting_dcb_get_priority_group_flags (s_dcb);
155         if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
156                 GString *s;
157                 gboolean success;
158                 guint id;
159
160                 s = g_string_sized_new (150);
161
162                 g_string_append_printf (s, "pg e:1 a:%c w:%c",
163                                         flags & NM_SETTING_DCB_FLAG_ADVERTISE ? '1' : '0',
164                                         flags & NM_SETTING_DCB_FLAG_WILLING ? '1' : '0');
165
166                 /* Priority Groups */
167                 g_string_append (s, " pgid:");
168                 for (i = 0; i < 8; i++) {
169                         id = nm_setting_dcb_get_priority_group_id (s_dcb, i);
170                         g_assert (id < 8 || id == 15);
171                         g_string_append_c (s, (id < 8) ? ('0' + id) : 'f');
172                 }
173
174                 /* Priority Group Bandwidth */
175                 g_string_append_printf (s, " pgpct:%u,%u,%u,%u,%u,%u,%u,%u",
176                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 0),
177                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 1),
178                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 2),
179                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 3),
180                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 4),
181                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 5),
182                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 6),
183                                         nm_setting_dcb_get_priority_group_bandwidth (s_dcb, 7));
184
185                 /* Priority Bandwidth */
186                 g_string_append_printf (s, " uppct:%u,%u,%u,%u,%u,%u,%u,%u",
187                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 0),
188                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 1),
189                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 2),
190                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 3),
191                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 4),
192                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 5),
193                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 6),
194                                         nm_setting_dcb_get_priority_bandwidth (s_dcb, 7));
195
196                 /* Strict Bandwidth */
197                 g_string_append (s, " strict:");
198                 for (i = 0; i < 8; i++)
199                         g_string_append_c (s, nm_setting_dcb_get_priority_strict_bandwidth (s_dcb, i) ? '1' : '0');
200
201                 /* Priority Traffic Class */
202                 g_string_append (s, " up2tc:");
203                 for (i = 0; i < 8; i++) {
204                         id = nm_setting_dcb_get_priority_traffic_class (s_dcb, i);
205                         g_assert (id < 8);
206                         g_string_append_c (s, '0' + id);
207                 }
208
209                 success = do_helper (iface, DCBTOOL, run_func, user_data, error, "%s", s->str);
210                 g_string_free (s, TRUE);
211                 if (!success)
212                         return FALSE;
213         } else {
214                 /* Ignore disable failure since lldpad <= 0.9.46 does not support disabling
215                  * priority groups without specifying an entire PG config.
216                  */
217                 (void) do_helper (iface, DCBTOOL, run_func, user_data, error, "pg e:0");
218         }
219
220         return TRUE;
221 }
222
223 gboolean
224 _dcb_cleanup (const char *iface,
225               DcbFunc run_func,
226               gpointer user_data,
227               GError **error)
228 {
229         const char *cmds[] = {
230                 "app:fcoe e:0",
231                 "app:iscsi e:0",
232                 "app:fip e:0",
233                 "pfc e:0",
234                 "pg e:0",
235                 NULL
236         };
237         const char **iter = cmds;
238         gboolean success = TRUE;
239
240         /* Turn everything off and return first error we get (if any) */
241         while (iter && *iter) {
242                 if (!do_helper (iface, DCBTOOL, run_func, user_data, success ? error : NULL, "%s", *iter))
243                         success = FALSE;
244                 iter++;
245         }
246
247         if (!_dcb_enable (iface, FALSE, run_func, user_data, success ? error : NULL))
248                 success = FALSE;
249
250         return success;
251 }
252
253 gboolean
254 _fcoe_setup (const char *iface,
255              NMSettingDcb *s_dcb,
256              DcbFunc run_func,
257              gpointer user_data,
258              GError **error)
259 {
260         NMSettingDcbFlags flags;
261
262         g_assert (s_dcb);
263
264         flags = nm_setting_dcb_get_app_fcoe_flags (s_dcb);
265         if (flags & NM_SETTING_DCB_FLAG_ENABLE) {
266                 const char *mode = nm_setting_dcb_get_app_fcoe_mode (s_dcb);
267
268                 if (!do_helper (NULL, FCOEADM, run_func, user_data, error, "-m %s -c %s", mode, iface))
269                         return FALSE;
270         } else {
271                 if (!do_helper (NULL, FCOEADM, run_func, user_data, error, "-d %s", iface))
272                         return FALSE;
273         }
274
275         return TRUE;
276 }
277
278 gboolean
279 _fcoe_cleanup (const char *iface,
280                DcbFunc run_func,
281                gpointer user_data,
282                GError **error)
283 {
284         return do_helper (NULL, FCOEADM, run_func, user_data, error, "-d %s", iface);
285 }
286
287 static gboolean
288 run_helper (char **argv, guint which, gpointer user_data, GError **error)
289 {
290         const char *helper_path;
291         int exit_status = 0;
292         gboolean success;
293         char *errmsg = NULL, *outmsg = NULL;
294         char *cmdline;
295
296         helper_path = nm_utils_find_helper ((which == DCBTOOL) ? "dcbtool" : "fcoeadm", NULL, error);
297         if (!helper_path)
298                 return FALSE;
299
300         argv[0] = (char *) helper_path;
301         cmdline = g_strjoinv (" ", argv);
302         nm_log_dbg (LOGD_DCB, "%s", cmdline);
303
304         success = g_spawn_sync ("/", argv, NULL, 0 /*G_SPAWN_DEFAULT*/,
305                                 NULL, NULL,
306                                 &outmsg, &errmsg, &exit_status, error);
307         /* Log any stderr output */
308         if (success && WIFEXITED (exit_status) && WEXITSTATUS (exit_status) && (errmsg || outmsg)) {
309                 gboolean ignore_error = FALSE;
310
311                 /* Ignore fcoeadm "success" errors like when FCoE is already set up */
312                 if (errmsg && strstr (errmsg, "Connection already created"))
313                         ignore_error = TRUE;
314
315                 if (ignore_error == FALSE) {
316                         nm_log_warn (LOGD_DCB, "'%s' failed: '%s'",
317                                      cmdline, (errmsg && strlen (errmsg)) ? errmsg : outmsg);
318                         g_set_error (error, NM_MANAGER_ERROR, NM_MANAGER_ERROR_FAILED,
319                                      "Failed to run '%s'", cmdline);
320                         success = FALSE;
321                 }
322         }
323
324         g_free (outmsg);
325         g_free (errmsg);
326         g_free (cmdline);
327         return success;
328 }
329
330 gboolean
331 nm_dcb_enable (const char *iface, gboolean enable, GError **error)
332 {
333         return _dcb_enable (iface, enable, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
334 }
335
336 gboolean
337 nm_dcb_setup (const char *iface, NMSettingDcb *s_dcb, GError **error)
338 {
339         gboolean success;
340
341         success = _dcb_setup (iface, s_dcb, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
342         if (success)
343                 success = _fcoe_setup (iface, s_dcb, run_helper, GUINT_TO_POINTER (FCOEADM), error);
344
345         return success;
346 }
347
348 static void
349 carrier_wait (const char *iface, guint secs, gboolean up)
350 {
351         int ifindex, count = secs * 10;
352
353         g_return_if_fail (iface != NULL);
354
355         ifindex = nm_platform_link_get_ifindex (NM_PLATFORM_GET, iface);
356         if (ifindex > 0) {
357                 /* To work around driver quirks and lldpad handling of carrier status,
358                  * we must wait a short period of time to see if the carrier goes
359                  * down, and then wait for the carrier to come back up again.  Otherwise
360                  * subsequent lldpad calls may fail with "Device not found, link down
361                  * or DCB not enabled" errors.
362                  */
363                 nm_log_dbg (LOGD_DCB, "(%s): cleanup waiting for carrier %s",
364                             iface, up ? "up" : "down");
365                 g_usleep (G_USEC_PER_SEC / 4);
366                 while (nm_platform_link_is_connected (NM_PLATFORM_GET, ifindex) != up && count-- > 0) {
367                         g_usleep (G_USEC_PER_SEC / 10);
368                         nm_platform_link_refresh (NM_PLATFORM_GET, ifindex);
369                 }
370         }
371 }
372
373 gboolean
374 nm_dcb_cleanup (const char *iface, GError **error)
375 {
376         /* Ignore FCoE cleanup errors */
377         _fcoe_cleanup (iface, run_helper, GUINT_TO_POINTER (FCOEADM), NULL);
378
379         /* Must pause a bit to wait for carrier-up since disabling FCoE may
380          * cause the device to take the link down, making lldpad return errors.
381          */
382         carrier_wait (iface, 2, FALSE);
383         carrier_wait (iface, 4, TRUE);
384
385         return _dcb_cleanup (iface, run_helper, GUINT_TO_POINTER (DCBTOOL), error);
386 }
387