]> git.ozlabs.org Git - petitboot/blob - discover/network.c
discover/network: fix incorrect nameserver directive in resolv.conf
[petitboot] / discover / network.c
1
2 #include <stdbool.h>
3 #include <stdint.h>
4 #include <string.h>
5 #include <stdlib.h>
6 #include <errno.h>
7 #include <sys/socket.h>
8 #include <linux/if.h>
9 #include <linux/netlink.h>
10 #include <linux/rtnetlink.h>
11
12 #include <log/log.h>
13 #include <list/list.h>
14 #include <talloc/talloc.h>
15 #include <waiter/waiter.h>
16 #include <pb-config/pb-config.h>
17 #include <system/system.h>
18
19 #include "file.h"
20 #include "network.h"
21
22 #define HWADDR_SIZE     6
23 #define PIDFILE_BASE    (LOCAL_STATE_DIR "/petitboot/")
24
25 #define for_each_nlmsg(buf, nlmsg, len) \
26         for (nlmsg = (struct nlmsghdr *)buf; \
27                 NLMSG_OK(nlmsg, len) && nlmsg->nlmsg_type != NLMSG_DONE; \
28                 nlmsg = NLMSG_NEXT(nlmsg, len))
29
30 #define for_each_rta(buf, rta, attrlen) \
31         for (rta = (struct rtattr *)(buf); RTA_OK(rta, attrlen); \
32                         rta = RTA_NEXT(rta, attrlen))
33
34
35 struct interface {
36         int     ifindex;
37         char    name[IFNAMSIZ];
38         uint8_t hwaddr[HWADDR_SIZE];
39
40         enum {
41                 IFSTATE_NEW,
42                 IFSTATE_UP_WAITING_LINK,
43                 IFSTATE_CONFIGURED,
44                 IFSTATE_IGNORED,
45         } state;
46
47         struct list_item list;
48 };
49
50 struct network {
51         struct list     interfaces;
52         struct waiter   *waiter;
53         int             netlink_sd;
54         bool            manual_config;
55         bool            dry_run;
56 };
57
58 static const struct interface_config *find_config_by_hwaddr(
59                 uint8_t *hwaddr)
60 {
61         const struct config *config;
62         int i;
63
64         config = config_get();
65         if (!config)
66                 return NULL;
67
68         for (i = 0; i < config->network.n_interfaces; i++) {
69                 struct interface_config *ifconf = config->network.interfaces[i];
70
71                 if (!memcmp(ifconf->hwaddr, hwaddr, HWADDR_SIZE))
72                         return ifconf;
73         }
74
75         return NULL;
76 }
77
78 static struct interface *find_interface_by_ifindex(struct network *network,
79                 int ifindex)
80 {
81         struct interface *interface;
82
83         list_for_each_entry(&network->interfaces, interface, list)
84                 if (interface->ifindex == ifindex)
85                         return interface;
86
87         return NULL;
88 }
89
90 static int network_init_netlink(struct network *network)
91 {
92         struct sockaddr_nl addr;
93         int rc;
94
95         memset(&addr, 0, sizeof(addr));
96         addr.nl_family = AF_NETLINK;
97         addr.nl_groups = RTMGRP_LINK;
98
99         network->netlink_sd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
100         if (network->netlink_sd < 0) {
101                 perror("socket(AF_NETLINK)");
102                 return -1;
103         }
104
105         rc = bind(network->netlink_sd, (struct sockaddr *)&addr, sizeof(addr));
106         if (rc) {
107                 perror("bind(sockaddr_nl)");
108                 close(network->netlink_sd);
109                 return -1;
110         }
111
112         return 0;
113 }
114
115 static int network_send_link_query(struct network *network)
116 {
117         int rc;
118         struct {
119                 struct nlmsghdr nlmsg;
120                 struct rtgenmsg rtmsg;
121         } msg;
122
123         memset(&msg, 0, sizeof(msg));
124
125         msg.nlmsg.nlmsg_len = sizeof(msg);
126         msg.nlmsg.nlmsg_type = RTM_GETLINK;
127         msg.nlmsg.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
128         msg.nlmsg.nlmsg_seq = 0;
129         msg.nlmsg.nlmsg_pid = 0;
130         msg.rtmsg.rtgen_family = AF_UNSPEC;
131
132         rc = send(network->netlink_sd, &msg, sizeof(msg), MSG_NOSIGNAL);
133         if (rc != sizeof(msg))
134                 return -1;
135
136         return 0;
137 }
138
139 static int interface_change(struct network *network,
140                 struct interface *interface,
141                 bool up)
142 {
143         int rc;
144         const char *statestr = up ? "up" : "down";
145         const char *argv[] = {
146                 pb_system_apps.ip,
147                 "link",
148                 "set",
149                 interface->name,
150                 statestr,
151                 NULL,
152         };
153
154         rc = pb_run_cmd(argv, 1, network->dry_run);
155         if (rc) {
156                 pb_log("failed to bring interface %s %s\n", interface->name,
157                                 statestr);
158                 return -1;
159         }
160         return 0;
161 }
162
163 static int interface_up(struct network *network,
164                 struct interface *interface)
165 {
166         return interface_change(network, interface, true);
167 }
168
169 static int interface_down(struct network *network,
170                 struct interface *interface)
171 {
172         return interface_change(network, interface, false);
173 }
174
175 static void configure_interface_dhcp(struct network *network,
176                 struct interface *interface)
177 {
178         char pidfile[256];
179         const char *argv[] = {
180                 pb_system_apps.udhcpc,
181                 "-R",
182                 "-n",
183                 "-p", pidfile,
184                 "-i", interface->name,
185                 NULL,
186         };
187         snprintf(pidfile, sizeof(pidfile), "%s/udhcpc-%s.pid",
188                         PIDFILE_BASE, interface->name);
189
190         pb_run_cmd(argv, 0, network->dry_run);
191         return;
192 }
193
194 static void configure_interface_static(struct network *network,
195                 struct interface *interface,
196                 const struct interface_config *config)
197 {
198         const char *addr_argv[] = {
199                 pb_system_apps.ip,
200                 "address",
201                 "add",
202                 config->static_config.address,
203                 "dev",
204                 interface->name,
205                 NULL,
206         };
207         const char *route_argv[] = {
208                 pb_system_apps.ip,
209                 "route",
210                 "add",
211                 "default",
212                 "via",
213                 config->static_config.gateway,
214                 NULL,
215         };
216         int rc;
217
218
219         rc = pb_run_cmd(addr_argv, 1, network->dry_run);
220         if (rc) {
221                 pb_log("failed to add address %s to interface %s\n",
222                                 config->static_config.address,
223                                 interface->name);
224                 return;
225         }
226
227         /* we need the interface up before we can route through it */
228         rc = interface_up(network, interface);
229         if (rc)
230                 return;
231
232         if (config->static_config.gateway)
233                 rc = pb_run_cmd(route_argv, 1, network->dry_run);
234
235         if (rc) {
236                 pb_log("failed to add default route %s on interface %s\n",
237                                 config->static_config.gateway,
238                                 interface->name);
239         }
240
241         return;
242 }
243
244 static void configure_interface(struct network *network,
245                 struct interface *interface, bool up, bool link)
246 {
247         const struct interface_config *config = NULL;
248
249         if (interface->state == IFSTATE_IGNORED)
250                 return;
251
252         /* old interface? check that we're still up and running */
253         if (interface->state == IFSTATE_CONFIGURED) {
254                 if (!up)
255                         interface->state = IFSTATE_NEW;
256                 else if (!link)
257                         interface->state = IFSTATE_UP_WAITING_LINK;
258                 else
259                         return;
260         }
261
262         /* always up the lookback, no other handling required */
263         if (!strcmp(interface->name, "lo")) {
264                 if (interface->state == IFSTATE_NEW)
265                         interface_up(network, interface);
266                 interface->state = IFSTATE_CONFIGURED;
267                 return;
268         }
269
270         config = find_config_by_hwaddr(interface->hwaddr);
271         if (config && config->ignore) {
272                 pb_log("network: ignoring interface %s\n", interface->name);
273                 interface->state = IFSTATE_IGNORED;
274                 return;
275         }
276
277         /* if we're in manual config mode, we need an interface configuration */
278         if (network->manual_config && !config) {
279                 interface->state = IFSTATE_IGNORED;
280                 pb_log("network: skipping %s: manual config mode, "
281                                 "but no config for this interface\n",
282                                 interface->name);
283                 return;
284         }
285
286         /* new interface? bring up to the point so we can detect a link */
287         if (interface->state == IFSTATE_NEW) {
288                 if (!up) {
289                         interface_up(network, interface);
290                         pb_log("network: bringing up interface %s\n",
291                                         interface->name);
292                         return;
293
294                 } else if (!link) {
295                         interface->state = IFSTATE_UP_WAITING_LINK;
296                 }
297         }
298
299         /* no link? wait for a notification */
300         if (interface->state == IFSTATE_UP_WAITING_LINK && !link)
301                 return;
302
303         pb_log("network: configuring interface %s\n", interface->name);
304
305         if (!config || config->method == CONFIG_METHOD_DHCP) {
306                 configure_interface_dhcp(network, interface);
307
308         } else if (config->method == CONFIG_METHOD_STATIC) {
309                 configure_interface_static(network, interface, config);
310         }
311 }
312
313 static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
314 {
315         bool have_ifaddr, have_ifname;
316         struct interface *interface;
317         struct ifinfomsg *info;
318         struct rtattr *attr;
319         uint8_t ifaddr[6];
320         char ifname[IFNAMSIZ+1];
321         int attrlen, type;
322
323
324         /* we're only interested in NEWLINK messages */
325         type = nlmsg->nlmsg_type;
326         if (!(type == RTM_NEWLINK || type == RTM_DELLINK))
327                 return 0;
328
329         info = NLMSG_DATA(nlmsg);
330
331         have_ifaddr = have_ifname = false;
332
333         attrlen = nlmsg->nlmsg_len - sizeof(*info);
334
335         /* extract the interface name and hardware address attributes */
336         for_each_rta(info + 1, attr, attrlen) {
337                 void *data = RTA_DATA(attr);
338
339                 switch (attr->rta_type) {
340                 case IFLA_ADDRESS:
341                         memcpy(ifaddr, data, sizeof(ifaddr));
342                         have_ifaddr = true;
343                         break;
344
345                 case IFLA_IFNAME:
346                         strncpy(ifname, data, IFNAMSIZ);
347                         have_ifname = true;
348                         break;
349                 }
350         }
351
352         if (!have_ifaddr || !have_ifname)
353                 return -1;
354
355         if (type == RTM_DELLINK) {
356                 interface = find_interface_by_ifindex(network, info->ifi_index);
357                 if (!interface)
358                         return 0;
359                 pb_log("network: interface %s removed\n", interface->name);
360                 list_remove(&interface->list);
361                 talloc_free(interface);
362                 return 0;
363         }
364
365
366         interface = find_interface_by_ifindex(network, info->ifi_index);
367         if (!interface) {
368                 interface = talloc_zero(network, struct interface);
369                 interface->ifindex = info->ifi_index;
370                 interface->state = IFSTATE_NEW;
371                 memcpy(interface->hwaddr, ifaddr, sizeof(interface->hwaddr));
372                 strncpy(interface->name, ifname, sizeof(interface->name) - 1);
373         }
374
375         configure_interface(network, interface,
376                         info->ifi_flags & IFF_UP,
377                         info->ifi_flags & IFF_LOWER_UP);
378
379         return 0;
380 }
381
382 static int network_netlink_process(void *arg)
383 {
384         struct network *network = arg;
385         struct nlmsghdr *nlmsg;
386         unsigned int len;
387         char buf[4096];
388         int rc;
389
390         rc = recv(network->netlink_sd, buf, sizeof(buf), 0);
391         if (rc < 0) {
392                 perror("netlink recv");
393                 return -1;
394         }
395
396         len = rc;
397
398         for_each_nlmsg(buf, nlmsg, len)
399                 network_handle_nlmsg(network, nlmsg);
400
401         return 0;
402 }
403
404 static void network_init_dns(struct network *network)
405 {
406         const struct config *config;
407         int i, rc, len;
408         bool modified;
409         char *buf;
410
411         if (network->dry_run)
412                 return;
413
414         config = config_get();
415         if (!config || !config->network.n_dns_servers)
416                 return;
417
418         rc = read_file(network, "/etc/resolv.conf", &buf, &len);
419
420         if (rc) {
421                 buf = talloc_strdup(network, "");
422                 len = 0;
423         }
424
425         modified = false;
426
427         for (i = 0; i < config->network.n_dns_servers; i++) {
428                 int dns_conf_len;
429                 char *dns_conf;
430
431                 dns_conf = talloc_asprintf(network, "nameserver %s\n",
432                                 config->network.dns_servers[i]);
433
434                 if (strstr(buf, dns_conf)) {
435                         talloc_free(dns_conf);
436                         continue;
437                 }
438
439                 dns_conf_len = strlen(dns_conf);
440                 buf = talloc_realloc(network, buf, char, len + dns_conf_len);
441                 memcpy(buf + len, dns_conf, dns_conf_len);
442                 len += dns_conf_len;
443                 modified = true;
444         }
445
446         if (!modified)
447                 return;
448
449         rc = replace_file("/etc/resolv.conf", buf, len);
450         if (rc) {
451                 pb_log("error replacing resolv.conf: %s\n", strerror(errno));
452                 return;
453         }
454
455 }
456
457 struct network *network_init(void *ctx, struct waitset *waitset, bool dry_run)
458 {
459         struct network *network;
460         int rc;
461
462         network = talloc(ctx, struct network);
463         list_init(&network->interfaces);
464         network->manual_config = false;
465         network->dry_run = dry_run;
466
467         network_init_dns(network);
468
469         rc = network_init_netlink(network);
470         if (rc)
471                 goto err;
472
473         network->waiter = waiter_register_io(waitset, network->netlink_sd,
474                         WAIT_IN, network_netlink_process, network);
475
476         if (!network->waiter)
477                 goto err;
478
479         rc = network_send_link_query(network);
480         if (rc)
481                 goto err;
482
483         return network;
484
485 err:
486         network_shutdown(network);
487         return NULL;
488 }
489
490
491 int network_shutdown(struct network *network)
492 {
493         struct interface *interface;
494
495         if (network->waiter)
496                 waiter_remove(network->waiter);
497
498         list_for_each_entry(&network->interfaces, interface, list)
499                 interface_down(network, interface);
500
501         close(network->netlink_sd);
502         talloc_free(network);
503         return 0;
504 }