chiark / gitweb /
networkctl: when outputing a list fo links, sort them by ifindex
[elogind.git] / src / network / networkctl.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2014 Lennart Poettering
7
8   systemd is free software; you can redistribute it and/or modify it
9   under the terms of the GNU Lesser General Public License as published by
10   the Free Software Foundation; either version 2.1 of the License, or
11   (at your option) any later version.
12
13   systemd is distributed in the hope that it will be useful, but
14   WITHOUT ANY WARRANTY; without even the implied warranty of
15   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16   Lesser General Public License for more details.
17
18   You should have received a copy of the GNU Lesser General Public License
19   along with systemd; If not, see <http://www.gnu.org/licenses/>.
20 ***/
21
22 #include <stdbool.h>
23 #include <getopt.h>
24
25 #include "sd-network.h"
26 #include "sd-rtnl.h"
27 #include "libudev.h"
28
29 #include "build.h"
30 #include "util.h"
31 #include "pager.h"
32 #include "rtnl-util.h"
33 #include "udev-util.h"
34 #include "arphrd-list.h"
35 #include "local-addresses.h"
36
37 static bool arg_no_pager = false;
38 static bool arg_legend = true;
39 static bool arg_all = false;
40
41 static void pager_open_if_enabled(void) {
42
43         if (arg_no_pager)
44                 return;
45
46         pager_open(false);
47 }
48
49 static int link_get_type_string(int iftype, struct udev_device *d, char **ret) {
50         const char *t;
51         char *p;
52
53         if (iftype == ARPHRD_ETHER && d) {
54                 const char *devtype, *id = NULL;
55                 /* WLANs have iftype ARPHRD_ETHER, but we want
56                  * to show a more useful type string for
57                  * them */
58
59                 devtype = udev_device_get_devtype(d);
60                 if (streq_ptr(devtype, "wlan"))
61                         id = "wlan";
62                 else if (streq_ptr(devtype, "wwan"))
63                         id = "wwan";
64
65                 if (id) {
66                         p = strdup(id);
67                         if (!p)
68                                 return -ENOMEM;
69
70                         *ret = p;
71                         return 1;
72                 }
73         }
74
75         t = arphrd_to_name(iftype);
76         if (!t) {
77                 *ret = NULL;
78                 return 0;
79         }
80
81         p = strdup(t);
82         if (!p)
83                 return -ENOMEM;
84
85         ascii_strlower(p);
86         *ret = p;
87
88         return 0;
89 }
90
91 typedef struct LinkInfo {
92         const char *name;
93         int ifindex;
94         unsigned iftype;
95 } LinkInfo;
96
97 static int link_info_compare(const void *a, const void *b) {
98         const LinkInfo *x = a, *y = b;
99
100         return x->ifindex - y->ifindex;
101 }
102
103 static int decode_and_sort_links(sd_rtnl_message *m, LinkInfo **ret) {
104         _cleanup_free_ LinkInfo *links = NULL;
105         size_t size = 0, c = 0;
106         sd_rtnl_message *i;
107         int r;
108
109         for (i = m; i; i = sd_rtnl_message_next(i)) {
110                 const char *name;
111                 unsigned iftype;
112                 uint16_t type;
113                 int ifindex;
114
115                 r = sd_rtnl_message_get_type(i, &type);
116                 if (r < 0)
117                         return r;
118
119                 if (type != RTM_NEWLINK)
120                         continue;
121
122                 r = sd_rtnl_message_link_get_ifindex(i, &ifindex);
123                 if (r < 0)
124                         return r;
125
126                 r = sd_rtnl_message_read_string(i, IFLA_IFNAME, &name);
127                 if (r < 0)
128                         return r;
129
130                 r = sd_rtnl_message_link_get_type(i, &iftype);
131                 if (r < 0)
132                         return r;
133
134                 if (!GREEDY_REALLOC(links, size, c+1))
135                         return -ENOMEM;
136
137                 links[c].name = name;
138                 links[c].ifindex = ifindex;
139                 links[c].iftype = iftype;
140                 c++;
141         }
142
143         qsort(links, c, sizeof(LinkInfo), link_info_compare);
144
145         *ret = links;
146         links = NULL;
147
148         return (int) c;
149 }
150
151 static int list_links(char **args, unsigned n) {
152         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
153         _cleanup_udev_unref_ struct udev *udev = NULL;
154         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
155         _cleanup_free_ LinkInfo *links = NULL;
156         int r, c, i;
157
158         pager_open_if_enabled();
159
160         r = sd_rtnl_open(&rtnl, 0);
161         if (r < 0) {
162                 log_error("Failed to connect to netlink: %s", strerror(-r));
163                 return r;
164         }
165
166         udev = udev_new();
167         if (!udev) {
168                 log_error("Failed to connect to udev: %m");
169                 return -errno;
170         }
171
172         r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
173         if (r < 0)
174                 return rtnl_log_create_error(r);
175
176         r = sd_rtnl_message_request_dump(req, true);
177         if (r < 0)
178                 return rtnl_log_create_error(r);
179
180         r = sd_rtnl_call(rtnl, req, 0, &reply);
181         if (r < 0) {
182                 log_error("Failed to enumerate links: %s", strerror(-r));
183                 return r;
184         }
185
186         if (arg_legend)
187                 printf("%3s %-16s %-10s %-10s %-10s\n", "IDX", "LINK", "TYPE", "ADMIN", "OPERATIONAL");
188
189         c = decode_and_sort_links(reply, &links);
190         if (c < 0)
191                 return rtnl_log_parse_error(c);
192
193         for (i = 0; i < c; i++) {
194                 _cleanup_free_ char *state = NULL, *operational_state = NULL;
195                 _cleanup_udev_device_unref_ struct udev_device *d = NULL;
196                 const char *on_color = "", *off_color = "";
197                  char devid[2 + DECIMAL_STR_MAX(int)];
198                 _cleanup_free_ char *t = NULL;
199
200                 sd_network_get_link_state(links[i].ifindex, &state);
201                 sd_network_get_link_operational_state(links[i].ifindex, &operational_state);
202
203                 sprintf(devid, "n%i", links[i].ifindex);
204                 d = udev_device_new_from_device_id(udev, devid);
205
206                 link_get_type_string(links[i].iftype, d, &t);
207
208                 if (streq_ptr(operational_state, "routable")) {
209                         on_color = ansi_highlight_green();
210                         off_color = ansi_highlight_off();
211                 } else if (streq_ptr(operational_state, "degraded")) {
212                         on_color = ansi_highlight_yellow();
213                         off_color = ansi_highlight_off();
214                 }
215
216                 printf("%3i %-16s %-10s %-10s %s%-10s%s\n", links[i].ifindex, links[i].name, strna(t), strna(state), on_color, strna(operational_state), off_color);
217         }
218
219         if (arg_legend)
220                 printf("\n%i links listed.\n", c);
221
222         return 0;
223 }
224
225 static int dump_addresses(sd_rtnl *rtnl, const char *prefix, int ifindex) {
226         _cleanup_free_ struct local_address *local = NULL;
227         int r, n, i;
228
229         n = local_addresses(rtnl, ifindex, &local);
230         if (n < 0)
231                 return n;
232
233         for (i = 0; i < n; i++) {
234                 _cleanup_free_ char *pretty = NULL;
235
236                 r = in_addr_to_string(local[i].family, &local[i].address, &pretty);
237                 if (r < 0)
238                         return r;
239
240                 printf("%*s%s\n",
241                        (int) strlen(prefix),
242                        i == 0 ? prefix : "",
243                        pretty);
244         }
245
246         return 0;
247 }
248
249 static void dump_list(const char *prefix, char **l) {
250         char **i;
251
252         STRV_FOREACH(i, l) {
253                 printf("%*s%s\n",
254                        (int) strlen(prefix),
255                        i == l ? prefix : "",
256                        *i);
257         }
258 }
259
260 static int link_status_one(sd_rtnl *rtnl, struct udev *udev, const char *name) {
261         _cleanup_strv_free_ char **dns = NULL, **ntp = NULL;
262         _cleanup_free_ char *state = NULL, *operational_state = NULL;
263         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
264         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
265         char devid[2 + DECIMAL_STR_MAX(int)];
266         _cleanup_free_ char *t = NULL;
267         const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL;
268         const char *on_color = "", *off_color = "";
269         struct ether_addr e;
270         unsigned iftype;
271         int r, ifindex;
272         bool have_mac;
273         uint32_t mtu;
274
275         assert(rtnl);
276         assert(udev);
277         assert(name);
278
279         if (safe_atoi(name, &ifindex) >= 0 && ifindex > 0)
280                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, ifindex);
281         else {
282                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
283                 if (r < 0)
284                         return rtnl_log_create_error(r);
285
286                 r = sd_rtnl_message_append_string(req, IFLA_IFNAME, name);
287         }
288
289         if (r < 0)
290                 return rtnl_log_create_error(r);
291
292         r = sd_rtnl_call(rtnl, req, 0, &reply);
293         if (r < 0) {
294                 log_error("Failed to query link: %s", strerror(-r));
295                 return r;
296         }
297
298         r = sd_rtnl_message_link_get_ifindex(reply, &ifindex);
299         if (r < 0)
300                 return rtnl_log_parse_error(r);
301
302         r = sd_rtnl_message_read_string(reply, IFLA_IFNAME, &name);
303         if (r < 0)
304                 return rtnl_log_parse_error(r);
305
306         r = sd_rtnl_message_link_get_type(reply, &iftype);
307         if (r < 0)
308                 return rtnl_log_parse_error(r);
309
310         have_mac = sd_rtnl_message_read_ether_addr(reply, IFLA_ADDRESS, &e) >= 0;
311
312         if (have_mac) {
313                 const uint8_t *p;
314                 bool all_zeroes = true;
315
316                 for (p = (uint8_t*) &e; p < (uint8_t*) &e + sizeof(e); p++)
317                         if (*p != 0) {
318                                 all_zeroes = false;
319                                 break;
320                         }
321
322                 if (all_zeroes)
323                         have_mac = false;
324         }
325
326         sd_rtnl_message_read_u32(reply, IFLA_MTU, &mtu);
327
328         sd_network_get_link_state(ifindex, &state);
329         sd_network_get_link_operational_state(ifindex, &operational_state);
330
331         sd_network_get_link_dns(ifindex, &dns);
332         sd_network_get_link_ntp(ifindex, &ntp);
333
334         sprintf(devid, "n%i", ifindex);
335         d = udev_device_new_from_device_id(udev, devid);
336
337         link_get_type_string(iftype, d, &t);
338
339         if (d) {
340                 driver = udev_device_get_property_value(d, "ID_NET_DRIVER");
341                 path = udev_device_get_property_value(d, "ID_PATH");
342
343                 vendor = udev_device_get_property_value(d, "ID_VENDOR_FROM_DATABASE");
344                 if (!vendor)
345                         vendor = udev_device_get_property_value(d, "ID_VENDOR");
346
347                 model = udev_device_get_property_value(d, "ID_MODEL_FROM_DATABASE");
348                 if (!model)
349                         model = udev_device_get_property_value(d, "ID_MODEL");
350         }
351
352         if (streq_ptr(operational_state, "routable")) {
353                 on_color = ansi_highlight_green();
354                 off_color = ansi_highlight_off();
355         } else if (streq_ptr(operational_state, "degraded")) {
356                 on_color = ansi_highlight_yellow();
357                 off_color = ansi_highlight_off();
358         }
359
360         printf("%s%s%s %i: %s\n", on_color, draw_special_char(DRAW_BLACK_CIRCLE), off_color, ifindex, name);
361
362         printf("        Type: %s\n"
363                "       State: %s%s%s (%s)\n",
364                strna(t),
365                on_color, strna(operational_state), off_color,
366                strna(state));
367
368         if (path)
369                 printf("        Path: %s\n", path);
370         if (driver)
371                 printf("      Driver: %s\n", driver);
372         if (vendor)
373                 printf("      Vendor: %s\n", vendor);
374         if (model)
375                 printf("       Model: %s\n", model);
376
377         if (have_mac)
378                 printf("  HW Address: %s\n", ether_ntoa(&e));
379
380         if (mtu > 0)
381                 printf("         MTU: %u\n", mtu);
382
383         dump_addresses(rtnl, "     Address: ", ifindex);
384
385         if (!strv_isempty(dns))
386                 dump_list("         DNS: ", dns);
387         if (!strv_isempty(ntp))
388                 dump_list("         NTP: ", ntp);
389
390         return 0;
391 }
392
393 static int link_status(char **args, unsigned n) {
394         _cleanup_udev_unref_ struct udev *udev = NULL;
395         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
396         char **name;
397         int r;
398
399         if (n <= 1 && !arg_all) {
400                 _cleanup_free_ char *operational_state = NULL;
401                 _cleanup_strv_free_ char **dns = NULL, **ntp = NULL;
402
403                 sd_network_get_operational_state(&operational_state);
404                 if (operational_state)
405                         printf("       State: %s\n", operational_state);
406
407                 sd_network_get_dns(&dns);
408                 if (!strv_isempty(dns))
409                         dump_list("         DNS: ", dns);
410
411                 sd_network_get_dns(&ntp);
412                 if (!strv_isempty(ntp))
413                         dump_list("         NTP: ", ntp);
414
415                 return 0;
416         }
417
418         pager_open_if_enabled();
419
420         r = sd_rtnl_open(&rtnl, 0);
421         if (r < 0) {
422                 log_error("Failed to connect to netlink: %s", strerror(-r));
423                 return r;
424         }
425
426         udev = udev_new();
427         if (!udev) {
428                 log_error("Failed to connect to udev: %m");
429                 return -errno;
430         }
431
432         if (arg_all) {
433                 _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
434                 _cleanup_free_ LinkInfo *links = NULL;
435                 int c, i;
436
437                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
438                 if (r < 0)
439                         return rtnl_log_create_error(r);
440
441                 r = sd_rtnl_message_request_dump(req, true);
442                 if (r < 0)
443                         return rtnl_log_create_error(r);
444
445                 r = sd_rtnl_call(rtnl, req, 0, &reply);
446                 if (r < 0) {
447                         log_error("Failed to enumerate links: %s", strerror(-r));
448                         return r;
449                 }
450
451                 c = decode_and_sort_links(reply, &links);
452                 if (c < 0)
453                         return rtnl_log_parse_error(c);
454
455                 for (i = 0; i < c; i++) {
456                         if (i > 0)
457                                 fputc('\n', stdout);
458
459                         link_status_one(rtnl, udev, links[i].name);
460                 }
461         }
462
463         STRV_FOREACH(name, args + 1) {
464                 if (name != args+1)
465                         fputc('\n', stdout);
466
467                 link_status_one(rtnl, udev, *name);
468         }
469
470         return 0;
471 }
472
473 static void help(void) {
474         printf("%s [OPTIONS...]\n\n"
475                "Query and control the networking subsystem.\n\n"
476                "  -h --help             Show this help\n"
477                "     --version          Show package version\n"
478                "     --no-pager         Do not pipe output into a pager\n"
479                "     --no-legend        Do not show the headers and footers\n"
480                "  -a --all              Show status for all links\n\n"
481                "Commands:\n"
482                "  list                  List links\n"
483                "  status LINK           Show link status\n"
484                , program_invocation_short_name);
485 }
486
487 static int parse_argv(int argc, char *argv[]) {
488
489         enum {
490                 ARG_VERSION = 0x100,
491                 ARG_NO_PAGER,
492                 ARG_NO_LEGEND,
493         };
494
495         static const struct option options[] = {
496                 { "help",      no_argument,       NULL, 'h'           },
497                 { "version",   no_argument,       NULL, ARG_VERSION   },
498                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
499                 { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
500                 { "all",       no_argument,       NULL, 'a'           },
501                 {}
502         };
503
504         int c;
505
506         assert(argc >= 0);
507         assert(argv);
508
509         while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) {
510
511                 switch (c) {
512
513                 case 'h':
514                         help();
515                         return 0;
516
517                 case ARG_VERSION:
518                         puts(PACKAGE_STRING);
519                         puts(SYSTEMD_FEATURES);
520                         return 0;
521
522                 case ARG_NO_PAGER:
523                         arg_no_pager = true;
524                         break;
525
526                 case ARG_NO_LEGEND:
527                         arg_legend = false;
528                         break;
529
530                 case 'a':
531                         arg_all = true;
532                         break;
533
534                 case '?':
535                         return -EINVAL;
536
537                 default:
538                         assert_not_reached("Unhandled option");
539                 }
540         }
541
542         return 1;
543 }
544
545 static int networkctl_main(int argc, char *argv[]) {
546
547         static const struct {
548                 const char* verb;
549                 const enum {
550                         MORE,
551                         LESS,
552                         EQUAL
553                 } argc_cmp;
554                 const int argc;
555                 int (* const dispatch)(char **args, unsigned n);
556         } verbs[] = {
557                 { "list",   LESS, 1, list_links  },
558                 { "status", MORE, 1, link_status },
559         };
560
561         int left;
562         unsigned i;
563
564         assert(argc >= 0);
565         assert(argv);
566
567         left = argc - optind;
568
569         if (left <= 0)
570                 /* Special rule: no arguments means "list" */
571                 i = 0;
572         else {
573                 if (streq(argv[optind], "help")) {
574                         help();
575                         return 0;
576                 }
577
578                 for (i = 0; i < ELEMENTSOF(verbs); i++)
579                         if (streq(argv[optind], verbs[i].verb))
580                                 break;
581
582                 if (i >= ELEMENTSOF(verbs)) {
583                         log_error("Unknown operation %s", argv[optind]);
584                         return -EINVAL;
585                 }
586         }
587
588         switch (verbs[i].argc_cmp) {
589
590         case EQUAL:
591                 if (left != verbs[i].argc) {
592                         log_error("Invalid number of arguments.");
593                         return -EINVAL;
594                 }
595
596                 break;
597
598         case MORE:
599                 if (left < verbs[i].argc) {
600                         log_error("Too few arguments.");
601                         return -EINVAL;
602                 }
603
604                 break;
605
606         case LESS:
607                 if (left > verbs[i].argc) {
608                         log_error("Too many arguments.");
609                         return -EINVAL;
610                 }
611
612                 break;
613
614         default:
615                 assert_not_reached("Unknown comparison operator.");
616         }
617
618         return verbs[i].dispatch(argv + optind, left);
619 }
620
621 int main(int argc, char* argv[]) {
622         int r;
623
624         log_parse_environment();
625         log_open();
626
627         r = parse_argv(argc, argv);
628         if (r <= 0)
629                 goto finish;
630
631         r = networkctl_main(argc, argv);
632
633 finish:
634         pager_close();
635
636         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
637 }