chiark / gitweb /
networkctl: also draw a nice unicode cirlce when "networkctl status" is run without...
[elogind.git] / src / network / networkd-network.c
1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3 /***
4   This file is part of systemd.
5
6   Copyright 2013 Tom Gundersen <teg@jklm.no>
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 <ctype.h>
23 #include <net/if.h>
24
25 #include "networkd.h"
26 #include "networkd-netdev.h"
27 #include "networkd-link.h"
28 #include "network-internal.h"
29 #include "path-util.h"
30 #include "conf-files.h"
31 #include "conf-parser.h"
32 #include "util.h"
33
34 static int network_load_one(Manager *manager, const char *filename) {
35         _cleanup_network_free_ Network *network = NULL;
36         _cleanup_fclose_ FILE *file = NULL;
37         Route *route;
38         Address *address;
39         int r;
40
41         assert(manager);
42         assert(filename);
43
44         file = fopen(filename, "re");
45         if (!file) {
46                 if (errno == ENOENT)
47                         return 0;
48                 else
49                         return -errno;
50         }
51
52         if (null_or_empty_fd(fileno(file))) {
53                 log_debug("Skipping empty file: %s", filename);
54                 return 0;
55         }
56
57         network = new0(Network, 1);
58         if (!network)
59                 return log_oom();
60
61         network->manager = manager;
62
63         LIST_HEAD_INIT(network->static_addresses);
64         LIST_HEAD_INIT(network->static_routes);
65
66         network->stacked_netdevs = hashmap_new(&string_hash_ops);
67         if (!network->stacked_netdevs)
68                 return log_oom();
69
70         network->addresses_by_section = hashmap_new(NULL);
71         if (!network->addresses_by_section)
72                 return log_oom();
73
74         network->routes_by_section = hashmap_new(NULL);
75         if (!network->routes_by_section)
76                 return log_oom();
77
78         network->filename = strdup(filename);
79         if (!network->filename)
80                 return log_oom();
81
82         network->dhcp = DHCP_SUPPORT_NONE;
83         network->dhcp_ntp = true;
84         network->dhcp_dns = true;
85         network->dhcp_hostname = true;
86         network->dhcp_routes = true;
87         network->dhcp_sendhost = true;
88         network->dhcp_route_metric = DHCP_ROUTE_METRIC;
89
90         network->llmnr = LLMNR_SUPPORT_YES;
91
92         r = config_parse(NULL, filename, file,
93                          "Match\0"
94                          "Link\0"
95                          "Network\0"
96                          "Address\0"
97                          "Route\0"
98                          "DHCP\0"
99                          "DHCPv4\0"
100                          "Bridge\0",
101                          config_item_perf_lookup, network_network_gperf_lookup,
102                          false, false, true, network);
103         if (r < 0)
104                 return r;
105
106         LIST_PREPEND(networks, manager->networks, network);
107
108         LIST_FOREACH(routes, route, network->static_routes) {
109                 if (!route->family) {
110                         log_warning("Route section without Gateway field configured in %s. "
111                                     "Ignoring", filename);
112                         return 0;
113                 }
114         }
115
116         LIST_FOREACH(addresses, address, network->static_addresses) {
117                 if (!address->family) {
118                         log_warning("Address section without Address field configured in %s. "
119                                     "Ignoring", filename);
120                         return 0;
121                 }
122         }
123
124         network = NULL;
125
126         return 0;
127 }
128
129 int network_load(Manager *manager) {
130         Network *network;
131         _cleanup_strv_free_ char **files = NULL;
132         char **f;
133         int r;
134
135         assert(manager);
136
137         while ((network = manager->networks))
138                 network_free(network);
139
140         r = conf_files_list_strv(&files, ".network", NULL, network_dirs);
141         if (r < 0)
142                 return log_error_errno(r, "Failed to enumerate network files: %m");
143
144         STRV_FOREACH_BACKWARDS(f, files) {
145                 r = network_load_one(manager, *f);
146                 if (r < 0)
147                         return r;
148         }
149
150         return 0;
151 }
152
153 void network_free(Network *network) {
154         NetDev *netdev;
155         Route *route;
156         Address *address;
157         Iterator i;
158
159         if (!network)
160                 return;
161
162         free(network->filename);
163
164         free(network->match_mac);
165         free(network->match_path);
166         free(network->match_driver);
167         free(network->match_type);
168         free(network->match_name);
169
170         free(network->description);
171         free(network->dhcp_vendor_class_identifier);
172
173         free(network->mac);
174
175         strv_free(network->ntp);
176         strv_free(network->dns);
177         strv_free(network->domains);
178
179         netdev_unref(network->bridge);
180
181         netdev_unref(network->bond);
182
183         HASHMAP_FOREACH(netdev, network->stacked_netdevs, i) {
184                 hashmap_remove(network->stacked_netdevs, netdev->ifname);
185                 netdev_unref(netdev);
186         }
187         hashmap_free(network->stacked_netdevs);
188
189         while ((route = network->static_routes))
190                 route_free(route);
191
192         while ((address = network->static_addresses))
193                 address_free(address);
194
195         hashmap_free(network->addresses_by_section);
196         hashmap_free(network->routes_by_section);
197
198         if (network->manager && network->manager->networks)
199                 LIST_REMOVE(networks, network->manager->networks, network);
200
201         condition_free_list(network->match_host);
202         condition_free_list(network->match_virt);
203         condition_free_list(network->match_kernel);
204         condition_free_list(network->match_arch);
205
206         free(network);
207 }
208
209 int network_get(Manager *manager, struct udev_device *device,
210                 const char *ifname, const struct ether_addr *address,
211                 Network **ret) {
212         Network *network;
213
214         assert(manager);
215         assert(ret);
216
217         LIST_FOREACH(networks, network, manager->networks) {
218                 if (net_match_config(network->match_mac, network->match_path,
219                                      network->match_driver, network->match_type,
220                                      network->match_name, network->match_host,
221                                      network->match_virt, network->match_kernel,
222                                      network->match_arch,
223                                      address,
224                                      udev_device_get_property_value(device, "ID_PATH"),
225                                      udev_device_get_driver(udev_device_get_parent(device)),
226                                      udev_device_get_property_value(device, "ID_NET_DRIVER"),
227                                      udev_device_get_devtype(device),
228                                      ifname)) {
229                         if (network->match_name) {
230                                 const char *attr;
231                                 uint8_t name_assign_type = NET_NAME_UNKNOWN;
232
233                                 attr = udev_device_get_sysattr_value(device, "name_assign_type");
234                                 if (attr)
235                                         (void)safe_atou8(attr, &name_assign_type);
236
237                                 if (name_assign_type == NET_NAME_ENUM)
238                                         log_warning("%-*s: found matching network '%s', based on potentially unpredictable ifname",
239                                                     IFNAMSIZ, ifname, network->filename);
240                                 else
241                                         log_debug("%-*s: found matching network '%s'", IFNAMSIZ, ifname, network->filename);
242                         } else
243                                 log_debug("%-*s: found matching network '%s'", IFNAMSIZ, ifname, network->filename);
244
245                         *ret = network;
246                         return 0;
247                 }
248         }
249
250         *ret = NULL;
251
252         return -ENOENT;
253 }
254
255 int network_apply(Manager *manager, Network *network, Link *link) {
256         int r;
257
258         link->network = network;
259
260         if (network->ipv4ll_route) {
261                 Route *route;
262
263                 r = route_new_static(network, 0, &route);
264                 if (r < 0)
265                         return r;
266
267                 r = inet_pton(AF_INET, "169.254.0.0", &route->dst_addr.in);
268                 if (r == 0)
269                         return -EINVAL;
270                 if (r < 0)
271                         return -errno;
272
273                 route->family = AF_INET;
274                 route->dst_prefixlen = 16;
275                 route->scope = RT_SCOPE_LINK;
276                 route->metrics = IPV4LL_ROUTE_METRIC;
277                 route->protocol = RTPROT_STATIC;
278         }
279
280         if (network->dns || network->ntp) {
281                 r = link_save(link);
282                 if (r < 0)
283                         return r;
284         }
285
286         return 0;
287 }
288
289 int config_parse_netdev(const char *unit,
290                 const char *filename,
291                 unsigned line,
292                 const char *section,
293                 unsigned section_line,
294                 const char *lvalue,
295                 int ltype,
296                 const char *rvalue,
297                 void *data,
298                 void *userdata) {
299         Network *network = userdata;
300         _cleanup_free_ char *kind_string = NULL;
301         char *p;
302         NetDev *netdev;
303         NetDevKind kind;
304         int r;
305
306         assert(filename);
307         assert(lvalue);
308         assert(rvalue);
309         assert(data);
310
311         kind_string = strdup(lvalue);
312         if (!kind_string)
313                 return log_oom();
314
315         /* the keys are CamelCase versions of the kind */
316         for (p = kind_string; *p; p++)
317                 *p = tolower(*p);
318
319         kind = netdev_kind_from_string(kind_string);
320         if (kind == _NETDEV_KIND_INVALID) {
321                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
322                            "Invalid NetDev kind: %s", lvalue);
323                 return 0;
324         }
325
326         r = netdev_get(network->manager, rvalue, &netdev);
327         if (r < 0) {
328                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
329                            "%s could not be found, ignoring assignment: %s", lvalue, rvalue);
330                 return 0;
331         }
332
333         if (netdev->kind != kind) {
334                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
335                            "NetDev is not a %s, ignoring assignment: %s", lvalue, rvalue);
336                 return 0;
337         }
338
339         switch (kind) {
340         case NETDEV_KIND_BRIDGE:
341                 network->bridge = netdev;
342
343                 break;
344         case NETDEV_KIND_BOND:
345                 network->bond = netdev;
346
347                 break;
348         case NETDEV_KIND_VLAN:
349         case NETDEV_KIND_MACVLAN:
350         case NETDEV_KIND_VXLAN:
351                 r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev);
352                 if (r < 0) {
353                         log_syntax(unit, LOG_ERR, filename, line, EINVAL,
354                                    "Can not add VLAN '%s' to network: %s",
355                                    rvalue, strerror(-r));
356                         return 0;
357                 }
358
359                 break;
360         default:
361                 assert_not_reached("Can not parse NetDev");
362         }
363
364         netdev_ref(netdev);
365
366         return 0;
367 }
368
369 int config_parse_domains(const char *unit,
370                          const char *filename,
371                          unsigned line,
372                          const char *section,
373                          unsigned section_line,
374                          const char *lvalue,
375                          int ltype,
376                          const char *rvalue,
377                          void *data,
378                          void *userdata) {
379         Network *network = userdata;
380         char ***domains = data;
381         char **domain;
382         int r;
383
384         r = config_parse_strv(unit, filename, line, section, section_line,
385                               lvalue, ltype, rvalue, domains, userdata);
386         if (r < 0)
387                 return r;
388
389         strv_uniq(*domains);
390         network->wildcard_domain = !!strv_find(*domains, "*");
391
392         STRV_FOREACH(domain, *domains) {
393                 if (is_localhost(*domain))
394                         log_syntax(unit, LOG_ERR, filename, line, EINVAL, "'localhost' domain names may not be configured, ignoring assignment: %s", *domain);
395                 else if (!hostname_is_valid(*domain)) {
396                         if (!streq(*domain, "*"))
397                                 log_syntax(unit, LOG_ERR, filename, line, EINVAL, "domain name is not valid, ignoring assignment: %s", *domain);
398                 } else
399                         continue;
400
401                 strv_remove(*domains, *domain);
402
403                 /* We removed one entry, make sure we don't skip the next one */
404                 domain--;
405         }
406
407         return 0;
408 }
409
410 int config_parse_tunnel(const char *unit,
411                         const char *filename,
412                         unsigned line,
413                         const char *section,
414                         unsigned section_line,
415                         const char *lvalue,
416                         int ltype,
417                         const char *rvalue,
418                         void *data,
419                         void *userdata) {
420         Network *network = userdata;
421         NetDev *netdev;
422         int r;
423
424         assert(filename);
425         assert(lvalue);
426         assert(rvalue);
427         assert(data);
428
429         r = netdev_get(network->manager, rvalue, &netdev);
430         if (r < 0) {
431                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
432                            "Tunnel is invalid, ignoring assignment: %s", rvalue);
433                 return 0;
434         }
435
436         if (netdev->kind != NETDEV_KIND_IPIP &&
437             netdev->kind != NETDEV_KIND_SIT &&
438             netdev->kind != NETDEV_KIND_GRE &&
439             netdev->kind != NETDEV_KIND_VTI) {
440                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
441                            "NetDev is not a tunnel, ignoring assignment: %s", rvalue);
442                 return 0;
443         }
444
445         r = hashmap_put(network->stacked_netdevs, netdev->ifname, netdev);
446         if (r < 0) {
447                 log_syntax(unit, LOG_ERR, filename, line, EINVAL,
448                            "Can not add VLAN '%s' to network: %s",
449                            rvalue, strerror(-r));
450                 return 0;
451         }
452
453         netdev_ref(netdev);
454
455         return 0;
456 }
457
458 static const char* const dhcp_support_table[_DHCP_SUPPORT_MAX] = {
459         [DHCP_SUPPORT_NONE] = "none",
460         [DHCP_SUPPORT_BOTH] = "both",
461         [DHCP_SUPPORT_V4] = "v4",
462         [DHCP_SUPPORT_V6] = "v6",
463 };
464
465 DEFINE_STRING_TABLE_LOOKUP(dhcp_support, DHCPSupport);
466
467 int config_parse_dhcp(
468                 const char* unit,
469                 const char *filename,
470                 unsigned line,
471                 const char *section,
472                 unsigned section_line,
473                 const char *lvalue,
474                 int ltype,
475                 const char *rvalue,
476                 void *data,
477                 void *userdata) {
478
479         DHCPSupport *dhcp = data;
480         int k;
481
482         assert(filename);
483         assert(lvalue);
484         assert(rvalue);
485         assert(data);
486
487         /* Our enum shall be a superset of booleans, hence first try
488          * to parse as boolean, and then as enum */
489
490         k = parse_boolean(rvalue);
491         if (k > 0)
492                 *dhcp = DHCP_SUPPORT_BOTH;
493         else if (k == 0)
494                 *dhcp = DHCP_SUPPORT_NONE;
495         else {
496                 DHCPSupport s;
497
498                 s = dhcp_support_from_string(rvalue);
499                 if (s < 0){
500                         log_syntax(unit, LOG_ERR, filename, line, -s, "Failed to parse DHCP option, ignoring: %s", rvalue);
501                         return 0;
502                 }
503
504                 *dhcp = s;
505         }
506
507         return 0;
508 }
509
510 static const char* const llmnr_support_table[_LLMNR_SUPPORT_MAX] = {
511         [LLMNR_SUPPORT_NO] = "no",
512         [LLMNR_SUPPORT_YES] = "yes",
513         [LLMNR_SUPPORT_RESOLVE] = "resolve",
514 };
515
516 DEFINE_STRING_TABLE_LOOKUP(llmnr_support, LLMNRSupport);
517
518 int config_parse_llmnr(
519                 const char* unit,
520                 const char *filename,
521                 unsigned line,
522                 const char *section,
523                 unsigned section_line,
524                 const char *lvalue,
525                 int ltype,
526                 const char *rvalue,
527                 void *data,
528                 void *userdata) {
529
530         LLMNRSupport *llmnr = data;
531         int k;
532
533         assert(filename);
534         assert(lvalue);
535         assert(rvalue);
536         assert(data);
537
538         /* Our enum shall be a superset of booleans, hence first try
539          * to parse as boolean, and then as enum */
540
541         k = parse_boolean(rvalue);
542         if (k > 0)
543                 *llmnr = LLMNR_SUPPORT_YES;
544         else if (k == 0)
545                 *llmnr = LLMNR_SUPPORT_NO;
546         else {
547                 LLMNRSupport s;
548
549                 s = llmnr_support_from_string(rvalue);
550                 if (s < 0){
551                         log_syntax(unit, LOG_ERR, filename, line, -s, "Failed to parse LLMNR option, ignoring: %s", rvalue);
552                         return 0;
553                 }
554
555                 *llmnr = s;
556         }
557
558         return 0;
559 }