chiark / gitweb /
networkctl: make networkctl status without link name show all local IP addresses
[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         r = sd_rtnl_open(&rtnl, 0);
400         if (r < 0) {
401                 log_error("Failed to connect to netlink: %s", strerror(-r));
402                 return r;
403         }
404
405         udev = udev_new();
406         if (!udev) {
407                 log_error("Failed to connect to udev: %m");
408                 return -errno;
409         }
410
411         if (n <= 1 && !arg_all) {
412                 _cleanup_free_ char *operational_state = NULL;
413                 _cleanup_strv_free_ char **dns = NULL, **ntp = NULL;
414                 _cleanup_free_ struct local_address *addresses = NULL;
415                 int i, c;
416
417                 sd_network_get_operational_state(&operational_state);
418                 if (operational_state)
419                         printf("       State: %s\n", operational_state);
420
421                 c = local_addresses(rtnl, 0, &addresses);
422                 for (i = 0; i < c; i++) {
423                         _cleanup_free_ char *pretty = NULL;
424
425                         r = in_addr_to_string(addresses[i].family, &addresses[i].address, &pretty);
426                         if (r < 0)
427                                 return log_oom();
428
429                         printf("%13s %s\n",
430                                i > 0 ? "" : "Address:", pretty);
431                 }
432
433                 sd_network_get_dns(&dns);
434                 if (!strv_isempty(dns))
435                         dump_list("         DNS: ", dns);
436
437                 sd_network_get_dns(&ntp);
438                 if (!strv_isempty(ntp))
439                         dump_list("         NTP: ", ntp);
440
441                 return 0;
442         }
443
444         pager_open_if_enabled();
445
446         if (arg_all) {
447                 _cleanup_rtnl_message_unref_ sd_rtnl_message *req = NULL, *reply = NULL;
448                 _cleanup_free_ LinkInfo *links = NULL;
449                 int c, i;
450
451                 r = sd_rtnl_message_new_link(rtnl, &req, RTM_GETLINK, 0);
452                 if (r < 0)
453                         return rtnl_log_create_error(r);
454
455                 r = sd_rtnl_message_request_dump(req, true);
456                 if (r < 0)
457                         return rtnl_log_create_error(r);
458
459                 r = sd_rtnl_call(rtnl, req, 0, &reply);
460                 if (r < 0) {
461                         log_error("Failed to enumerate links: %s", strerror(-r));
462                         return r;
463                 }
464
465                 c = decode_and_sort_links(reply, &links);
466                 if (c < 0)
467                         return rtnl_log_parse_error(c);
468
469                 for (i = 0; i < c; i++) {
470                         if (i > 0)
471                                 fputc('\n', stdout);
472
473                         link_status_one(rtnl, udev, links[i].name);
474                 }
475         }
476
477         STRV_FOREACH(name, args + 1) {
478                 if (name != args+1)
479                         fputc('\n', stdout);
480
481                 link_status_one(rtnl, udev, *name);
482         }
483
484         return 0;
485 }
486
487 static void help(void) {
488         printf("%s [OPTIONS...]\n\n"
489                "Query and control the networking subsystem.\n\n"
490                "  -h --help             Show this help\n"
491                "     --version          Show package version\n"
492                "     --no-pager         Do not pipe output into a pager\n"
493                "     --no-legend        Do not show the headers and footers\n"
494                "  -a --all              Show status for all links\n\n"
495                "Commands:\n"
496                "  list                  List links\n"
497                "  status LINK           Show link status\n"
498                , program_invocation_short_name);
499 }
500
501 static int parse_argv(int argc, char *argv[]) {
502
503         enum {
504                 ARG_VERSION = 0x100,
505                 ARG_NO_PAGER,
506                 ARG_NO_LEGEND,
507         };
508
509         static const struct option options[] = {
510                 { "help",      no_argument,       NULL, 'h'           },
511                 { "version",   no_argument,       NULL, ARG_VERSION   },
512                 { "no-pager",  no_argument,       NULL, ARG_NO_PAGER  },
513                 { "no-legend", no_argument,       NULL, ARG_NO_LEGEND },
514                 { "all",       no_argument,       NULL, 'a'           },
515                 {}
516         };
517
518         int c;
519
520         assert(argc >= 0);
521         assert(argv);
522
523         while ((c = getopt_long(argc, argv, "ha", options, NULL)) >= 0) {
524
525                 switch (c) {
526
527                 case 'h':
528                         help();
529                         return 0;
530
531                 case ARG_VERSION:
532                         puts(PACKAGE_STRING);
533                         puts(SYSTEMD_FEATURES);
534                         return 0;
535
536                 case ARG_NO_PAGER:
537                         arg_no_pager = true;
538                         break;
539
540                 case ARG_NO_LEGEND:
541                         arg_legend = false;
542                         break;
543
544                 case 'a':
545                         arg_all = true;
546                         break;
547
548                 case '?':
549                         return -EINVAL;
550
551                 default:
552                         assert_not_reached("Unhandled option");
553                 }
554         }
555
556         return 1;
557 }
558
559 static int networkctl_main(int argc, char *argv[]) {
560
561         static const struct {
562                 const char* verb;
563                 const enum {
564                         MORE,
565                         LESS,
566                         EQUAL
567                 } argc_cmp;
568                 const int argc;
569                 int (* const dispatch)(char **args, unsigned n);
570         } verbs[] = {
571                 { "list",   LESS, 1, list_links  },
572                 { "status", MORE, 1, link_status },
573         };
574
575         int left;
576         unsigned i;
577
578         assert(argc >= 0);
579         assert(argv);
580
581         left = argc - optind;
582
583         if (left <= 0)
584                 /* Special rule: no arguments means "list" */
585                 i = 0;
586         else {
587                 if (streq(argv[optind], "help")) {
588                         help();
589                         return 0;
590                 }
591
592                 for (i = 0; i < ELEMENTSOF(verbs); i++)
593                         if (streq(argv[optind], verbs[i].verb))
594                                 break;
595
596                 if (i >= ELEMENTSOF(verbs)) {
597                         log_error("Unknown operation %s", argv[optind]);
598                         return -EINVAL;
599                 }
600         }
601
602         switch (verbs[i].argc_cmp) {
603
604         case EQUAL:
605                 if (left != verbs[i].argc) {
606                         log_error("Invalid number of arguments.");
607                         return -EINVAL;
608                 }
609
610                 break;
611
612         case MORE:
613                 if (left < verbs[i].argc) {
614                         log_error("Too few arguments.");
615                         return -EINVAL;
616                 }
617
618                 break;
619
620         case LESS:
621                 if (left > verbs[i].argc) {
622                         log_error("Too many arguments.");
623                         return -EINVAL;
624                 }
625
626                 break;
627
628         default:
629                 assert_not_reached("Unknown comparison operator.");
630         }
631
632         return verbs[i].dispatch(argv + optind, left);
633 }
634
635 int main(int argc, char* argv[]) {
636         int r;
637
638         log_parse_environment();
639         log_open();
640
641         r = parse_argv(argc, argv);
642         if (r <= 0)
643                 goto finish;
644
645         r = networkctl_main(argc, argv);
646
647 finish:
648         pager_close();
649
650         return r < 0 ? EXIT_FAILURE : EXIT_SUCCESS;
651 }