discover: Add network handling
authorJeremy Kerr <jk@ozlabs.org>
Thu, 30 May 2013 06:25:26 +0000 (16:25 +1000)
committerJeremy Kerr <jk@ozlabs.org>
Mon, 24 Jun 2013 05:07:57 +0000 (13:07 +0800)
Signed-off-by: Jeremy Kerr <jk@ozlabs.org>
configure.ac.in
discover/Makefile.am
discover/network.c [new file with mode: 0644]
discover/network.h [new file with mode: 0644]
discover/pb-discover.c
lib/system/system.c
lib/system/system.h

index bfc25720782e41763ca91fabe3ffde5dd086a2e0..1d6703b0c4f27e679fd0074b8d063b6dbb7dba3d 100644 (file)
@@ -195,6 +195,8 @@ DEFINE_HOST_PROG(SFTP, sftp, [/usr/bin/sftp])
 DEFINE_HOST_PROG(TFTP, tftp, [/usr/bin/tftp])
 DEFINE_HOST_PROG(UMOUNT, umount, [/bin/umount])
 DEFINE_HOST_PROG(WGET, wget, [/usr/bin/wget])
+DEFINE_HOST_PROG(IP, ip, [/sbin/ip])
+DEFINE_HOST_PROG(UDHCPC, udhcpc, [/sbin/udhcpc])
 
 default_cflags="--std=gnu99 -g \
        -Wall -W -Wunused -Wstrict-prototypes -Wmissing-prototypes \
index ddf37efc3c349e56491f05dde1c709ce103a7c4b..a4498187170fd66a6d2e1368093565c433623704 100644 (file)
@@ -58,6 +58,8 @@ pb_discover_SOURCES = \
        parser-utils.h \
        pb-discover.c \
        pb-discover.h \
+       network.c \
+       network.h \
        udev.c \
        udev.h \
        user-event.c \
diff --git a/discover/network.c b/discover/network.c
new file mode 100644 (file)
index 0000000..4a4f932
--- /dev/null
@@ -0,0 +1,426 @@
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <stdlib.h>
+#include <sys/socket.h>
+#include <linux/if.h>
+#include <linux/netlink.h>
+#include <linux/rtnetlink.h>
+
+#include <log/log.h>
+#include <list/list.h>
+#include <talloc/talloc.h>
+#include <waiter/waiter.h>
+#include <pb-config/pb-config.h>
+#include <system/system.h>
+
+#include "network.h"
+
+#define HWADDR_SIZE    6
+#define PIDFILE_BASE   (LOCAL_STATE_DIR "/petitboot/")
+
+#define for_each_nlmsg(buf, nlmsg, len) \
+       for (nlmsg = (struct nlmsghdr *)buf; \
+               NLMSG_OK(nlmsg, len) && nlmsg->nlmsg_type != NLMSG_DONE; \
+               nlmsg = NLMSG_NEXT(nlmsg, len))
+
+#define for_each_rta(buf, rta, attrlen) \
+       for (rta = (struct rtattr *)(buf); RTA_OK(rta, attrlen); \
+                       rta = RTA_NEXT(rta, attrlen))
+
+
+struct interface {
+       int     ifindex;
+       char    name[IFNAMSIZ];
+       uint8_t hwaddr[HWADDR_SIZE];
+
+       enum {
+               IFSTATE_NEW,
+               IFSTATE_UP_WAITING_LINK,
+               IFSTATE_CONFIGURED,
+               IFSTATE_IGNORED,
+       } state;
+
+       struct list_item list;
+};
+
+struct network {
+       struct list     interfaces;
+       struct waiter   *waiter;
+       int             netlink_sd;
+       bool            manual_config;
+       bool            dry_run;
+};
+
+static const struct network_config *find_config_by_hwaddr(
+               uint8_t *hwaddr)
+{
+       const struct config *config;
+       int i;
+
+       config = config_get();
+       if (!config)
+               return NULL;
+
+       for (i = 0; i < config->n_network_configs; i++) {
+               struct network_config *netconf = config->network_configs[i];
+
+               if (!memcmp(netconf->hwaddr, hwaddr, HWADDR_SIZE))
+                       return netconf;
+       }
+
+       return NULL;
+}
+
+static struct interface *find_interface_by_ifindex(struct network *network,
+               int ifindex)
+{
+       struct interface *interface;
+
+       list_for_each_entry(&network->interfaces, interface, list)
+               if (interface->ifindex == ifindex)
+                       return interface;
+
+       return NULL;
+}
+
+static int network_init_netlink(struct network *network)
+{
+       struct sockaddr_nl addr;
+       int rc;
+
+       memset(&addr, 0, sizeof(addr));
+       addr.nl_family = AF_NETLINK;
+       addr.nl_groups = RTMGRP_LINK;
+
+       network->netlink_sd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_ROUTE);
+       if (network->netlink_sd < 0) {
+               perror("socket(AF_NETLINK)");
+               return -1;
+       }
+
+       rc = bind(network->netlink_sd, (struct sockaddr *)&addr, sizeof(addr));
+       if (rc) {
+               perror("bind(sockaddr_nl)");
+               close(network->netlink_sd);
+               return -1;
+       }
+
+       return 0;
+}
+
+static int network_send_link_query(struct network *network)
+{
+       int rc;
+       struct {
+               struct nlmsghdr nlmsg;
+               struct rtgenmsg rtmsg;
+       } msg;
+
+       memset(&msg, 0, sizeof(msg));
+
+       msg.nlmsg.nlmsg_len = sizeof(msg);
+       msg.nlmsg.nlmsg_type = RTM_GETLINK;
+       msg.nlmsg.nlmsg_flags = NLM_F_REQUEST | NLM_F_ROOT;
+       msg.nlmsg.nlmsg_seq = 0;
+       msg.nlmsg.nlmsg_pid = 0;
+       msg.rtmsg.rtgen_family = AF_UNSPEC;
+
+       rc = send(network->netlink_sd, &msg, sizeof(msg), MSG_NOSIGNAL);
+       if (rc != sizeof(msg))
+               return -1;
+
+       return 0;
+}
+
+static int interface_up(struct network *network, struct interface *interface)
+{
+       int rc;
+       const char *argv[] = {
+               pb_system_apps.ip,
+               "link",
+               "set",
+               interface->name,
+               "up",
+               NULL,
+       };
+
+       rc = pb_run_cmd(argv, 1, network->dry_run);
+       if (rc) {
+               pb_log("failed to bring interface %s up\n", interface->name);
+               return -1;
+       }
+       return 0;
+}
+
+static void configure_interface_dhcp(struct network *network,
+               struct interface *interface)
+{
+       char pidfile[256];
+       const char *argv[] = {
+               pb_system_apps.udhcpc,
+               "-R",
+               "-n",
+               "-p", pidfile,
+               "-i", interface->name,
+               NULL,
+       };
+       snprintf(pidfile, sizeof(pidfile), "%s/udhcpc-%s.pid",
+                       PIDFILE_BASE, interface->name);
+
+       pb_run_cmd(argv, 0, network->dry_run);
+       return;
+}
+
+static void configure_interface_static(struct network *network,
+               struct interface *interface,
+               const struct network_config *config)
+{
+       const char *addr_argv[] = {
+               pb_system_apps.ip,
+               "address",
+               "add",
+               config->static_config.address,
+               "dev",
+               interface->name,
+               NULL,
+       };
+       const char *route_argv[] = {
+               pb_system_apps.ip,
+               "route",
+               "add",
+               "default",
+               "via",
+               config->static_config.gateway,
+               NULL,
+       };
+       int rc;
+
+
+       rc = pb_run_cmd(addr_argv, 1, network->dry_run);
+       if (rc) {
+               pb_log("failed to add address %s to interface %s\n",
+                               config->static_config.address,
+                               interface->name);
+               return;
+       }
+
+       /* we need the interface up before we can route through it */
+       rc = interface_up(network, interface);
+       if (rc)
+               return;
+
+       if (config->static_config.gateway)
+               rc = pb_run_cmd(route_argv, 1, network->dry_run);
+
+       if (rc) {
+               pb_log("failed to add default route %s on interface %s\n",
+                               config->static_config.gateway,
+                               interface->name);
+       }
+
+       return;
+}
+
+static void configure_interface(struct network *network,
+               struct interface *interface, bool up, bool link)
+{
+       const struct network_config *config = NULL;
+
+       if (interface->state == IFSTATE_IGNORED)
+               return;
+
+       /* old interface? check that we're still up and running */
+       if (interface->state == IFSTATE_CONFIGURED) {
+               if (!up)
+                       interface->state = IFSTATE_NEW;
+               else if (!link)
+                       interface->state = IFSTATE_UP_WAITING_LINK;
+               else
+                       return;
+       }
+
+       /* always up the lookback, no other handling required */
+       if (!strcmp(interface->name, "lo")) {
+               if (interface->state == IFSTATE_NEW)
+                       interface_up(network, interface);
+               interface->state = IFSTATE_CONFIGURED;
+               return;
+       }
+
+       config = find_config_by_hwaddr(interface->hwaddr);
+       if (config && config->ignore) {
+               pb_log("network: ignoring interface %s\n", interface->name);
+               interface->state = IFSTATE_IGNORED;
+               return;
+       }
+
+       /* if we're in manual config mode, we need an interface configuration */
+       if (network->manual_config && !config) {
+               interface->state = IFSTATE_IGNORED;
+               pb_log("network: skipping %s: manual config mode, "
+                               "but no config for this interface\n",
+                               interface->name);
+               return;
+       }
+
+       /* new interface? bring up to the point so we can detect a link */
+       if (interface->state == IFSTATE_NEW) {
+               if (!up) {
+                       interface_up(network, interface);
+                       pb_log("network: bringing up interface %s\n",
+                                       interface->name);
+                       return;
+
+               } else if (!link) {
+                       interface->state = IFSTATE_UP_WAITING_LINK;
+               }
+       }
+
+       /* no link? wait for a notification */
+       if (interface->state == IFSTATE_UP_WAITING_LINK && !link)
+               return;
+
+       pb_log("network: configuring interface %s\n", interface->name);
+
+       if (!config || config->method == CONFIG_METHOD_DHCP) {
+               configure_interface_dhcp(network, interface);
+
+       } else if (config->method == CONFIG_METHOD_STATIC) {
+               configure_interface_static(network, interface, config);
+       }
+}
+
+static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
+{
+       bool have_ifaddr, have_ifname;
+       struct interface *interface;
+       struct ifinfomsg *info;
+       struct rtattr *attr;
+       uint8_t ifaddr[6];
+       char ifname[IFNAMSIZ+1];
+       int attrlen, type;
+
+
+       /* we're only interested in NEWLINK messages */
+       type = nlmsg->nlmsg_type;
+       if (!(type == RTM_NEWLINK || type == RTM_DELLINK))
+               return 0;
+
+       info = NLMSG_DATA(nlmsg);
+
+       have_ifaddr = have_ifname = false;
+
+       attrlen = nlmsg->nlmsg_len - sizeof(*info);
+
+       /* extract the interface name and hardware address attributes */
+       for_each_rta(info + 1, attr, attrlen) {
+               void *data = RTA_DATA(attr);
+
+               switch (attr->rta_type) {
+               case IFLA_ADDRESS:
+                       memcpy(ifaddr, data, sizeof(ifaddr));
+                       have_ifaddr = true;
+                       break;
+
+               case IFLA_IFNAME:
+                       strncpy(ifname, data, IFNAMSIZ);
+                       have_ifname = true;
+                       break;
+               }
+       }
+
+       if (!have_ifaddr || !have_ifname)
+               return -1;
+
+       if (type == RTM_DELLINK) {
+               interface = find_interface_by_ifindex(network, info->ifi_index);
+               if (!interface)
+                       return 0;
+               pb_log("network: interface %s removed\n", interface->name);
+               list_remove(&interface->list);
+               talloc_free(interface);
+               return 0;
+       }
+
+
+       interface = find_interface_by_ifindex(network, info->ifi_index);
+       if (!interface) {
+               interface = talloc_zero(network, struct interface);
+               interface->ifindex = info->ifi_index;
+               interface->state = IFSTATE_NEW;
+               memcpy(interface->hwaddr, ifaddr, sizeof(interface->hwaddr));
+               strncpy(interface->name, ifname, sizeof(interface->name) - 1);
+       }
+
+       configure_interface(network, interface,
+                       info->ifi_flags & IFF_UP,
+                       info->ifi_flags & IFF_LOWER_UP);
+
+       return 0;
+}
+
+static int network_netlink_process(void *arg)
+{
+       struct network *network = arg;
+       struct nlmsghdr *nlmsg;
+       unsigned int len;
+       char buf[4096];
+       int rc;
+
+       rc = recv(network->netlink_sd, buf, sizeof(buf), 0);
+       if (rc < 0) {
+               perror("netlink recv");
+               return -1;
+       }
+
+       len = rc;
+
+       for_each_nlmsg(buf, nlmsg, len)
+               network_handle_nlmsg(network, nlmsg);
+
+       return 0;
+}
+
+struct network *network_init(void *ctx, struct waitset *waitset, bool dry_run)
+{
+       struct network *network;
+       int rc;
+
+       network = talloc(ctx, struct network);
+       list_init(&network->interfaces);
+       network->manual_config = false;
+       network->dry_run = dry_run;
+
+       rc = network_init_netlink(network);
+       if (rc)
+               goto err;
+
+       network->waiter = waiter_register_io(waitset, network->netlink_sd,
+                       WAIT_IN, network_netlink_process, network);
+
+       if (!network->waiter)
+               goto err;
+
+       rc = network_send_link_query(network);
+       if (rc)
+               goto err;
+
+       return network;
+
+err:
+       network_shutdown(network);
+       return NULL;
+}
+
+
+int network_shutdown(struct network *network)
+{
+       if (network->waiter)
+               waiter_remove(network->waiter);
+
+       close(network->netlink_sd);
+       talloc_free(network);
+       return 0;
+}
diff --git a/discover/network.h b/discover/network.h
new file mode 100644 (file)
index 0000000..c90af40
--- /dev/null
@@ -0,0 +1,11 @@
+#ifndef NETWORK_H
+#define NETWORK_H
+
+struct network;
+struct waitset;
+
+struct network *network_init(void *ctx, struct waitset *waitset, bool dry_run);
+int network_shutdown(struct network *network);
+
+#endif /* NETWORK_H */
+
index fac1c9deb261301e9927b9dc7c095339a6d83370..986401d9fff6f434bff7c9c007f4e50548961341 100644 (file)
@@ -17,6 +17,7 @@
 #include "user-event.h"
 #include "discover-server.h"
 #include "device-handler.h"
+#include "network.h"
 
 static void print_version(void)
 {
@@ -108,6 +109,7 @@ int main(int argc, char *argv[])
 {
        struct device_handler *handler;
        struct discover_server *server;
+       struct network *network;
        struct waitset *waitset;
        struct opts opts;
        struct pb_udev *udev;
@@ -154,6 +156,10 @@ int main(int argc, char *argv[])
        if (!server)
                return EXIT_FAILURE;
 
+       network = network_init(server, waitset, opts.dry_run == opt_yes);
+       if (!network)
+               return EXIT_FAILURE;
+
        handler = device_handler_init(server, waitset, opts.dry_run == opt_yes);
        if (!handler)
                return EXIT_FAILURE;
index 13dc146821958561699598040948654b7a8cc78a..dbdef46bdd9da775a8f02955df27d1325a49b8d5 100644 (file)
@@ -26,6 +26,8 @@ const struct pb_system_apps pb_system_apps = {
        .tftp           = HOST_PROG_TFTP,
        .umount         = HOST_PROG_UMOUNT,
        .wget           = HOST_PROG_WGET,
+       .ip             = HOST_PROG_IP,
+       .udhcpc         = HOST_PROG_UDHCPC,
 };
 
 int pb_mkdir_recursive(const char *dir)
index f8f18a3cdbe0e8cbee8e5b12b7819b988e9617e1..271c4355d3066cb46c10fe224abd65fa277f82b8 100644 (file)
@@ -11,6 +11,8 @@ struct pb_system_apps {
        const char *tftp;
        const char *umount;
        const char *wget;
+       const char *ip;
+       const char *udhcpc;
 };
 
 extern const struct pb_system_apps pb_system_apps;