chiark / gitweb /
networkctl: add colors to admin state and fix alignment
[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 %-11s %-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_oper = "", *off_color_oper = "",
197                            *on_color = "", *off_color = "";
198                  char devid[2 + DECIMAL_STR_MAX(int)];
199                 _cleanup_free_ char *t = NULL;
200
201                 sd_network_get_link_state(links[i].ifindex, &state);
202                 sd_network_get_link_operational_state(links[i].ifindex, &operational_state);
203
204                 sprintf(devid, "n%i", links[i].ifindex);
205                 d = udev_device_new_from_device_id(udev, devid);
206
207                 link_get_type_string(links[i].iftype, d, &t);
208
209                 if (streq_ptr(operational_state, "routable")) {
210                         on_color_oper = ansi_highlight_green();
211                         off_color_oper = ansi_highlight_off();
212                 } else if (streq_ptr(operational_state, "degraded")) {
213                         on_color_oper = ansi_highlight_yellow();
214                         off_color_oper = ansi_highlight_off();
215                 }
216
217                 if (streq_ptr(state, "configured")) {
218                         on_color = ansi_highlight_green();
219                         off_color = ansi_highlight_off();
220                 } else if (streq_ptr(state, "configuring")) {
221                         on_color = ansi_highlight_yellow();
222                         off_color = ansi_highlight_off();
223                 } else if (streq_ptr(state, "failed") ||
224                            streq_ptr(state, "linger")) {
225                         on_color = ansi_highlight_red();
226                         off_color = ansi_highlight_off();
227                 }
228
229                 printf("%3i %-16s %-10s %s%-11s%s %s%-10s%s\n", links[i].ifindex,
230                        links[i].name, strna(t), on_color, strna(state), off_color,
231                        on_color_oper, strna(operational_state), off_color_oper);
232         }
233
234         if (arg_legend)
235                 printf("\n%i links listed.\n", c);
236
237         return 0;
238 }
239
240 static int dump_addresses(sd_rtnl *rtnl, const char *prefix, int ifindex) {
241         _cleanup_free_ struct local_address *local = NULL;
242         int r, n, i;
243
244         n = local_addresses(rtnl, ifindex, &local);
245         if (n < 0)
246                 return n;
247
248         for (i = 0; i < n; i++) {
249                 _cleanup_free_ char *pretty = NULL;
250
251                 r = in_addr_to_string(local[i].family, &local[i].address, &pretty);
252                 if (r < 0)
253                         return r;
254
255                 printf("%*s%s\n",
256                        (int) strlen(prefix),
257                        i == 0 ? prefix : "",
258                        pretty);
259         }
260
261         return 0;
262 }
263
264 static void dump_list(const char *prefix, char **l) {
265         char **i;
266
267         STRV_FOREACH(i, l) {
268                 printf("%*s%s\n",
269                        (int) strlen(prefix),
270                        i == l ? prefix : "",
271                        *i);
272         }
273 }
274
275 static int link_status_one(sd_rtnl *rtnl, struct udev *udev, const char *name) {
276         _cleanup_strv_free_ char **dns = NULL, **ntp = NULL;
277         _cleanup_free_ char *state = NULL, *operational_state = NULL;
278         _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
279         _cleanup_udev_device_unref_ struct udev_device *d = NULL;
280         char devid[2 + DECIMAL_STR_MAX(int)];
281         _cleanup_free_ char *t = NULL;
282         const char *driver = NULL, *path = NULL, *vendor = NULL, *model = NULL;
283         const char *on_color = "", *off_color = "";
284         struct ether_addr e;
285         unsigned iftype;
286         int r, ifindex;
287         bool have_mac;
288         uint32_t mtu;
289
290         assert(rtnl);
291         assert(udev);
292         assert(name);
293
294         if (safe_atoi(name, &ifindex) >= 0 && ifindex > 0)
295                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, ifindex);
296         else {
297                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
298                 if (r < 0)
299                         return rtnl_log_create_error(r);
300
301                 r = sd_rtnl_message_append_string(req, IFLA_IFNAME, name);
302         }
303
304         if (r < 0)
305                 return rtnl_log_create_error(r);
306
307         r = sd_rtnl_call(rtnl, req, 0, &reply);
308         if (r < 0) {
309                 log_error("Failed to query link: %s", strerror(-r));
310                 return r;
311         }
312
313         r = sd_rtnl_message_link_get_ifindex(reply, &ifindex);
314         if (r < 0)
315                 return rtnl_log_parse_error(r);
316
317         r = sd_rtnl_message_read_string(reply, IFLA_IFNAME, &name);
318         if (r < 0)
319                 return rtnl_log_parse_error(r);
320
321         r = sd_rtnl_message_link_get_type(reply, &iftype);
322         if (r < 0)
323                 return rtnl_log_parse_error(r);
324
325         have_mac = sd_rtnl_message_read_ether_addr(reply, IFLA_ADDRESS, &e) >= 0;
326
327         if (have_mac) {
328                 const uint8_t *p;
329                 bool all_zeroes = true;
330
331                 for (p = (uint8_t*) &e; p < (uint8_t*) &e + sizeof(e); p++)
332                         if (*p != 0) {
333                                 all_zeroes = false;
334                                 break;
335                         }
336
337                 if (all_zeroes)
338                         have_mac = false;
339         }
340
341         sd_rtnl_message_read_u32(reply, IFLA_MTU, &mtu);
342
343         sd_network_get_link_state(ifindex, &state);
344         sd_network_get_link_operational_state(ifindex, &operational_state);
345
346         sd_network_get_link_dns(ifindex, &dns);
347         sd_network_get_link_ntp(ifindex, &ntp);
348
349         sprintf(devid, "n%i", ifindex);
350         d = udev_device_new_from_device_id(udev, devid);
351
352         link_get_type_string(iftype, d, &t);
353
354         if (d) {
355                 driver = udev_device_get_property_value(d, "ID_NET_DRIVER");
356                 path = udev_device_get_property_value(d, "ID_PATH");
357
358                 vendor = udev_device_get_property_value(d, "ID_VENDOR_FROM_DATABASE");
359                 if (!vendor)
360                         vendor = udev_device_get_property_value(d, "ID_VENDOR");
361
362                 model = udev_device_get_property_value(d, "ID_MODEL_FROM_DATABASE");
363                 if (!model)
364                         model = udev_device_get_property_value(d, "ID_MODEL");
365         }
366
367         if (streq_ptr(operational_state, "routable")) {
368                 on_color = ansi_highlight_green();
369                 off_color = ansi_highlight_off();
370         } else if (streq_ptr(operational_state, "degraded")) {
371                 on_color = ansi_highlight_yellow();
372                 off_color = ansi_highlight_off();
373         }
374
375         printf("%s%s%s %i: %s\n", on_color, draw_special_char(DRAW_BLACK_CIRCLE), off_color, ifindex, name);
376
377         printf("        Type: %s\n"
378                "       State: %s%s%s (%s)\n",
379                strna(t),
380                on_color, strna(operational_state), off_color,
381                strna(state));
382
383         if (path)
384                 printf("        Path: %s\n", path);
385         if (driver)
386                 printf("      Driver: %s\n", driver);
387         if (vendor)
388                 printf("      Vendor: %s\n", vendor);
389         if (model)
390                 printf("       Model: %s\n", model);
391
392         if (have_mac)
393                 printf("  HW Address: %s\n", ether_ntoa(&e));
394
395         if (mtu > 0)
396                 printf("         MTU: %u\n", mtu);
397
398         dump_addresses(rtnl, "     Address: ", ifindex);
399
400         if (!strv_isempty(dns))
401                 dump_list("         DNS: ", dns);
402         if (!strv_isempty(ntp))
403                 dump_list("         NTP: ", ntp);
404
405         return 0;
406 }
407
408 static int link_status(char **args, unsigned n) {
409         _cleanup_udev_unref_ struct udev *udev = NULL;
410         _cleanup_rtnl_unref_ sd_rtnl *rtnl = NULL;
411         char **name;
412         int r;
413
414         r = sd_rtnl_open(&rtnl, 0);
415         if (r < 0) {
416                 log_error("Failed to connect to netlink: %s", strerror(-r));
417                 return r;
418         }
419
420         udev = udev_new();
421         if (!udev) {
422                 log_error("Failed to connect to udev: %m");
423                 return -errno;
424         }
425
426         if (n <= 1 && !arg_all) {
427                 _cleanup_free_ char *operational_state = NULL;
428                 _cleanup_strv_free_ char **dns = NULL, **ntp = NULL;
429                 _cleanup_free_ struct local_address *addresses = NULL;
430                 int i, c;
431
432                 sd_network_get_operational_state(&operational_state);
433                 if (operational_state)
434                         printf("       State: %s\n", operational_state);
435
436                 c = local_addresses(rtnl, 0, &addresses);
437                 for (i = 0; i < c; i++) {
438                         _cleanup_free_ char *pretty = NULL;
439
440                         r = in_addr_to_string(addresses[i].family, &addresses[i].address, &pretty);
441                         if (r < 0)
442                                 return log_oom();
443
444                         printf("%13s %s\n",
445                                i > 0 ? "" : "Address:", pretty);
446                 }
447
448                 sd_network_get_dns(&dns);
449                 if (!strv_isempty(dns))
450                         dump_list("         DNS: ", dns);
451
452                 sd_network_get_dns(&ntp);
453                 if (!strv_isempty(ntp))
454                         dump_list("         NTP: ", ntp);
455
456                 return 0;
457         }
458
459         pager_open_if_enabled();
460
461         if (arg_all) {
462                 _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
463                 _cleanup_free_ LinkInfo *links = NULL;
464                 int c, i;
465
466                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
467                 if (r < 0)
468                         return rtnl_log_create_error(r);
469
470                 r = sd_rtnl_message_request_dump(req, true);
471                 if (r < 0)
472                         return rtnl_log_create_error(r);
473
474                 r = sd_rtnl_call(rtnl, req, 0, &reply);
475                 if (r < 0) {
476                         log_error("Failed to enumerate links: %s", strerror(-r));
477                         return r;
478                 }
479
480                 c = decode_and_sort_links(reply, &links);
481                 if (c < 0)
482                         return rtnl_log_parse_error(c);
483
484                 for (i = 0; i < c; i++) {
485                         if (i > 0)
486                                 fputc('\n', stdout);
487
488                         link_status_one(rtnl, udev, links[i].name);
489                 }
490         }
491
492         STRV_FOREACH(name, args + 1) {
493                 if (name != args+1)
494                         fputc('\n', stdout);
495
496                 link_status_one(rtnl, udev, *name);
497         }
498
499         return 0;
500 }
501
502 static void help(void) {
503         printf("%s [OPTIONS...]\n\n"
504                "Query and control the networking subsystem.\n\n"
505                "  -h --help             Show this help\n"
506                "     --version          Show package version\n"
507                "     --no-pager         Do not pipe output into a pager\n"
508                "     --no-legend        Do not show the headers and footers\n"
509                "  -a --all              Show status for all links\n\n"
510                "Commands:\n"
511                "  list                  List links\n"
512                "  status LINK           Show link status\n"
513                , program_invocation_short_name);
514 }
515
516 static int parse_argv(int argc, char *argv[]) {
517
518         enum {
519                 ARG_VERSION = 0x100,
520                 ARG_NO_PAGER,
521                 ARG_NO_LEGEND,
522         };
523
524         static const struct option options[] = {
525                 { "help",      no_argument,       NULL, 'h'           },
526                 { "version",   no_argument,       NULL, ARG_VERSION   },
527                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
528                 { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
529                 { "all",       no_argument,       NULL, 'a'           },
530                 {}
531         };
532
533         int c;
534
535         assert(argc >= 0);
536         assert(argv);
537
538         while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) {
539
540                 switch (c) {
541
542                 case 'h':
543                         help();
544                         return 0;
545
546                 case ARG_VERSION:
547                         puts(PACKAGE_STRING);
548                         puts(SYSTEMD_FEATURES);
549                         return 0;
550
551                 case ARG_NO_PAGER:
552                         arg_no_pager = true;
553                         break;
554
555                 case ARG_NO_LEGEND:
556                         arg_legend = false;
557                         break;
558
559                 case 'a':
560                         arg_all = true;
561                         break;
562
563                 case '?':
564                         return -EINVAL;
565
566                 default:
567                         assert_not_reached("Unhandled option");
568                 }
569         }
570
571         return 1;
572 }
573
574 static int networkctl_main(int argc, char *argv[]) {
575
576         static const struct {
577                 const char* verb;
578                 const enum {
579                         MORE,
580                         LESS,
581                         EQUAL
582                 } argc_cmp;
583                 const int argc;
584                 int (* const dispatch)(char **args, unsigned n);
585         } verbs[] = {
586                 { "list",   LESS, 1, list_links  },
587                 { "status", MORE, 1, link_status },
588         };
589
590         int left;
591         unsigned i;
592
593         assert(argc >= 0);
594         assert(argv);
595
596         left = argc - optind;
597
598         if (left <= 0)
599                 /* Special rule: no arguments means "list" */
600                 i = 0;
601         else {
602                 if (streq(argv[optind], "help")) {
603                         help();
604                         return 0;
605                 }
606
607                 for (i = 0; i < ELEMENTSOF(verbs); i++)
608                         if (streq(argv[optind], verbs[i].verb))
609                                 break;
610
611                 if (i >= ELEMENTSOF(verbs)) {
612                         log_error("Unknown operation %s", argv[optind]);
613                         return -EINVAL;
614                 }
615         }
616
617         switch (verbs[i].argc_cmp) {
618
619         case EQUAL:
620                 if (left != verbs[i].argc) {
621                         log_error("Invalid number of arguments.");
622                         return -EINVAL;
623                 }
624
625                 break;
626
627         case MORE:
628                 if (left < verbs[i].argc) {
629                         log_error("Too few arguments.");
630                         return -EINVAL;
631                 }
632
633                 break;
634
635         case LESS:
636                 if (left > verbs[i].argc) {
637                         log_error("Too many arguments.");
638                         return -EINVAL;
639                 }
640
641                 break;
642
643         default:
644                 assert_not_reached("Unknown comparison operator.");
645         }
646
647         return verbs[i].dispatch(argv + optind, left);
648 }
649
650 int main(int argc, char* argv[]) {
651         int r;
652
653         log_parse_environment();
654         log_open();
655
656         r = parse_argv(argc, argv);
657         if (r <= 0)
658                 goto finish;
659
660         r = networkctl_main(argc, argv);
661
662 finish:
663         pager_close();
664
665         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
666 }