discover: extend URL in UI to support auto-discovery
[petitboot] / discover / network.c
index 910649eeb742690851a11fd6c51bf17fb9d7c07f..c072eece9627408ac7bbebab2d227d968a8f9fbf 100644 (file)
@@ -3,6 +3,7 @@
 #include <stdint.h>
 #include <string.h>
 #include <stdlib.h>
+#include <errno.h>
 #include <sys/socket.h>
 #include <linux/if.h>
 #include <linux/netlink.h>
 
 #include <log/log.h>
 #include <list/list.h>
+#include <file/file.h>
+#include <types/types.h>
 #include <talloc/talloc.h>
 #include <waiter/waiter.h>
-#include <pb-config/pb-config.h>
+#include <process/process.h>
 #include <system/system.h>
 
 #include "network.h"
+#include "sysinfo.h"
+#include "platform.h"
+#include "device-handler.h"
 
 #define HWADDR_SIZE    6
 #define PIDFILE_BASE   (LOCAL_STATE_DIR "/petitboot/")
+#define INITIAL_BUFSIZE        4096
 
 #define for_each_nlmsg(buf, nlmsg, len) \
        for (nlmsg = (struct nlmsghdr *)buf; \
@@ -43,21 +50,26 @@ struct interface {
        } state;
 
        struct list_item list;
+       struct process *udhcpc_process;
+       struct discover_device *dev;
 };
 
 struct network {
-       struct list     interfaces;
-       struct waiter   *waiter;
-       int             netlink_sd;
-       bool            manual_config;
-       bool            dry_run;
+       struct list             interfaces;
+       struct device_handler   *handler;
+       struct waiter           *waiter;
+       int                     netlink_sd;
+       void                    *netlink_buf;
+       unsigned int            netlink_buf_size;
+       bool                    manual_config;
+       bool                    dry_run;
 };
 
 static const struct interface_config *find_config_by_hwaddr(
                uint8_t *hwaddr)
 {
        const struct config *config;
-       int i;
+       unsigned int i;
 
        config = config_get();
        if (!config)
@@ -85,6 +97,31 @@ static struct interface *find_interface_by_ifindex(struct network *network,
        return NULL;
 }
 
+static struct interface *find_interface_by_name(struct network *network,
+               const char *name)
+{
+       struct interface *interface;
+
+       list_for_each_entry(&network->interfaces, interface, list)
+               if (!strcmp(interface->name, name))
+                       return interface;
+
+       return NULL;
+}
+
+uint8_t *find_mac_by_name(void *ctx, struct network *network,
+               const char *name)
+{
+       struct interface *interface;
+
+       interface = find_interface_by_name(network, name);
+       if (!interface)
+               return NULL;
+
+       return talloc_memdup(ctx, &interface->hwaddr,
+                            sizeof(uint8_t) * HWADDR_SIZE);
+}
+
 static int network_init_netlink(struct network *network)
 {
        struct sockaddr_nl addr;
@@ -107,6 +144,10 @@ static int network_init_netlink(struct network *network)
                return -1;
        }
 
+       network->netlink_buf_size = INITIAL_BUFSIZE;
+       network->netlink_buf = talloc_array(network, char,
+                               network->netlink_buf_size);
+
        return 0;
 }
 
@@ -134,22 +175,96 @@ static int network_send_link_query(struct network *network)
        return 0;
 }
 
-static int interface_change(struct network *network,
-               struct interface *interface,
-               bool up)
+static char *mac_bytes_to_string(void *ctx, uint8_t *addr, int len)
+{
+       const int l = strlen("xx:");
+       char *buf;
+       int i;
+
+       if (len <= 0)
+               return talloc_strdup(ctx, "");
+
+       buf = talloc_array(ctx, char, (len * l) + 1);
+
+       for (i = 0; i < len; i++)
+               sprintf(buf + (l * i), "%02x:", addr[i]);
+
+       *(buf + (l * len) - 1) = '\0';
+
+       return buf;
+}
+
+static void add_interface(struct network *network,
+               struct interface *interface)
+{
+       list_add(&network->interfaces, &interface->list);
+       interface->dev = discover_device_create(network->handler,
+                                       interface->name);
+       interface->dev->device->type = DEVICE_TYPE_NETWORK;
+       interface->dev->uuid = mac_bytes_to_string(interface->dev,
+                       interface->hwaddr, sizeof(interface->hwaddr));
+       device_handler_add_device(network->handler, interface->dev);
+}
+
+static void remove_interface(struct network *network,
+               struct interface *interface)
+{
+       if (interface->dev)
+               device_handler_remove(network->handler, interface->dev);
+       list_remove(&interface->list);
+       talloc_free(interface);
+}
+
+void network_register_device(struct network *network,
+               struct discover_device *dev)
+{
+       struct interface *iface;
+
+       iface = find_interface_by_name(network, dev->device->id);
+       if (!iface)
+               return;
+
+       iface->dev = dev;
+       dev->uuid = mac_bytes_to_string(iface->dev, iface->hwaddr,
+                       sizeof(iface->hwaddr));
+}
+
+void network_unregister_device(struct network *network,
+               struct discover_device *dev)
+{
+       struct interface *iface;
+
+       iface = find_interface_by_name(network, dev->device->id);
+       if (!iface)
+               return;
+
+       iface->dev = NULL;
+}
+
+static int interface_change(struct interface *interface, bool up)
 {
-       int rc;
        const char *statestr = up ? "up" : "down";
-       const char *argv[] = {
-               pb_system_apps.ip,
-               "link",
-               "set",
-               interface->name,
-               statestr,
-               NULL,
-       };
+       int rc;
 
-       rc = pb_run_cmd(argv, 1, network->dry_run);
+       if (!up && interface->udhcpc_process) {
+               /* we don't care about the callback from here */
+               interface->udhcpc_process->exit_cb = NULL;
+               interface->udhcpc_process->data = NULL;
+               process_stop_async(interface->udhcpc_process);
+               process_release(interface->udhcpc_process);
+       }
+
+       if (!up) {
+               rc = process_run_simple(interface, pb_system_apps.ip,
+                               "address", "flush", "dev", interface->name,
+                               NULL);
+               if (rc)
+                       pb_log("failed to flush addresses from interface %s\n",
+                               interface->name);
+       }
+
+       rc = process_run_simple(interface, pb_system_apps.ip,
+                       "link", "set", interface->name, statestr, NULL);
        if (rc) {
                pb_log("failed to bring interface %s %s\n", interface->name,
                                statestr);
@@ -158,34 +273,66 @@ static int interface_change(struct network *network,
        return 0;
 }
 
-static int interface_up(struct network *network,
-               struct interface *interface)
+static int interface_up(struct interface *interface)
 {
-       return interface_change(network, interface, true);
+       return interface_change(interface, true);
 }
 
-static int interface_down(struct network *network,
-               struct interface *interface)
+static int interface_down(struct interface *interface)
 {
-       return interface_change(network, interface, false);
+       return interface_change(interface, false);
 }
 
-static void configure_interface_dhcp(struct network *network,
-               struct interface *interface)
+static void udhcpc_process_exit(struct process *process)
 {
-       char pidfile[256];
+       struct interface *interface = process->data;
+       pb_debug("udhcp client [pid %d] for interface %s exited, rc %d\n",
+                       process->pid, interface->name, process->exit_status);
+       interface->udhcpc_process = NULL;
+       process_release(process);
+}
+
+static void configure_interface_dhcp(struct interface *interface)
+{
+       const struct platform *platform;
+       char pidfile[256], id[10];
+       struct process *process;
+       int rc;
        const char *argv[] = {
                pb_system_apps.udhcpc,
                "-R",
-               "-n",
+               "-f",
+               "-O", "pxeconffile",
+               "-O", "pxepathprefix",
                "-p", pidfile,
                "-i", interface->name,
+               "-x", id, /* [11,12] - dhcp client identifier */
                NULL,
        };
+
        snprintf(pidfile, sizeof(pidfile), "%s/udhcpc-%s.pid",
                        PIDFILE_BASE, interface->name);
 
-       pb_run_cmd(argv, 0, network->dry_run);
+       platform = platform_get();
+       if (platform && platform->dhcp_arch_id != 0xffff)
+               snprintf(id, sizeof(id), "0x5d:%04x", platform->dhcp_arch_id);
+       else
+               argv[11] = NULL;
+
+       process = process_create(interface);
+
+       process->path = pb_system_apps.udhcpc;
+       process->argv = argv;
+       process->exit_cb = udhcpc_process_exit;
+       process->data = interface;
+
+       rc = process_run_async(process);
+
+       if (rc)
+               process_release(process);
+       else
+               interface->udhcpc_process = process;
+
        return;
 }
 
@@ -193,28 +340,13 @@ static void configure_interface_static(struct network *network,
                struct interface *interface,
                const struct interface_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 = process_run_simple(interface, pb_system_apps.ip,
+                       "address", "add", config->static_config.address,
+                       "dev", interface->name, NULL);
+
 
-       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,
@@ -223,12 +355,15 @@ static void configure_interface_static(struct network *network,
        }
 
        /* we need the interface up before we can route through it */
-       rc = interface_up(network, interface);
+       rc = interface_up(interface);
        if (rc)
                return;
 
        if (config->static_config.gateway)
-               rc = pb_run_cmd(route_argv, 1, network->dry_run);
+               rc = process_run_simple(interface, pb_system_apps.ip,
+                               "route", "add", "default",
+                               "via", config->static_config.gateway,
+                               NULL);
 
        if (rc) {
                pb_log("failed to add default route %s on interface %s\n",
@@ -236,6 +371,16 @@ static void configure_interface_static(struct network *network,
                                interface->name);
        }
 
+       if (config->static_config.url) {
+               pb_log("config URL %s\n", config->static_config.url);
+               device_handler_process_url(network->handler,
+                               config->static_config.url,
+                               mac_bytes_to_string(interface->dev,
+                                               interface->hwaddr,
+                                               sizeof(interface->hwaddr)),
+                               config->static_config.address);
+       }
+
        return;
 }
 
@@ -260,7 +405,7 @@ static void configure_interface(struct network *network,
        /* always up the lookback, no other handling required */
        if (!strcmp(interface->name, "lo")) {
                if (interface->state == IFSTATE_NEW)
-                       interface_up(network, interface);
+                       interface_up(interface);
                interface->state = IFSTATE_CONFIGURED;
                return;
        }
@@ -284,7 +429,7 @@ static void configure_interface(struct network *network,
        /* new interface? bring up to the point so we can detect a link */
        if (interface->state == IFSTATE_NEW) {
                if (!up) {
-                       interface_up(network, interface);
+                       interface_up(interface);
                        pb_log("network: bringing up interface %s\n",
                                        interface->name);
                        return;
@@ -301,7 +446,7 @@ static void configure_interface(struct network *network,
        pb_log("network: configuring interface %s\n", interface->name);
 
        if (!config || config->method == CONFIG_METHOD_DHCP) {
-               configure_interface_dhcp(network, interface);
+               configure_interface_dhcp(interface);
 
        } else if (config->method == CONFIG_METHOD_STATIC) {
                configure_interface_static(network, interface, config);
@@ -314,6 +459,7 @@ static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
        struct interface *interface;
        struct ifinfomsg *info;
        struct rtattr *attr;
+       unsigned int mtu;
        uint8_t ifaddr[6];
        char ifname[IFNAMSIZ+1];
        int attrlen, type;
@@ -327,6 +473,7 @@ static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
        info = NLMSG_DATA(nlmsg);
 
        have_ifaddr = have_ifname = false;
+       mtu = 1;
 
        attrlen = nlmsg->nlmsg_len - sizeof(*info);
 
@@ -344,19 +491,22 @@ static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
                        strncpy(ifname, data, IFNAMSIZ);
                        have_ifname = true;
                        break;
+
+               case IFLA_MTU:
+                       mtu = *(unsigned int *)data;
+                       break;
                }
        }
 
        if (!have_ifaddr || !have_ifname)
                return -1;
 
-       if (type == RTM_DELLINK) {
+       if (type == RTM_DELLINK || mtu == 0) {
                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);
+               remove_interface(network, interface);
                return 0;
        }
 
@@ -368,8 +518,25 @@ static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
                interface->state = IFSTATE_NEW;
                memcpy(interface->hwaddr, ifaddr, sizeof(interface->hwaddr));
                strncpy(interface->name, ifname, sizeof(interface->name) - 1);
+               add_interface(network, interface);
        }
 
+       /* A repeated RTM_NEWLINK can represent an interface name change */
+       if (strncmp(interface->name, ifname, IFNAMSIZ)) {
+               pb_debug("ifname update: %s -> %s\n", interface->name, ifname);
+               strncpy(interface->name, ifname, sizeof(interface->name) - 1);
+               talloc_free(interface->dev->device->id);
+               interface->dev->device->id =
+                       talloc_strdup(interface->dev->device, ifname);
+       }
+
+       /* notify the sysinfo code about changes to this interface */
+       if (strcmp(interface->name, "lo"))
+               system_info_register_interface(
+                               sizeof(interface->hwaddr),
+                               interface->hwaddr, interface->name,
+                               info->ifi_flags & IFF_LOWER_UP);
+
        configure_interface(network, interface,
                        info->ifi_flags & IFF_UP,
                        info->ifi_flags & IFF_LOWER_UP);
@@ -381,33 +548,122 @@ static int network_netlink_process(void *arg)
 {
        struct network *network = arg;
        struct nlmsghdr *nlmsg;
+       struct msghdr msg;
+       struct iovec iov;
        unsigned int len;
-       char buf[4096];
-       int rc;
+       int rc, flags;
+
+       memset(&msg, 0, sizeof(msg));
+       msg.msg_iov = &iov;
+       msg.msg_iovlen = 1;
+
+       flags = MSG_PEEK;
+
+retry:
+       iov.iov_len = network->netlink_buf_size;
+       iov.iov_base = network->netlink_buf;
+
+       rc = recvmsg(network->netlink_sd, &msg, flags);
 
-       rc = recv(network->netlink_sd, buf, sizeof(buf), 0);
        if (rc < 0) {
-               perror("netlink recv");
+               perror("netlink recv header");
                return -1;
        }
 
        len = rc;
 
-       for_each_nlmsg(buf, nlmsg, len)
+       /* if the netlink message was larger than our buffer, realloc
+        * before reading again */
+       if (len > network->netlink_buf_size || msg.msg_flags & MSG_TRUNC) {
+               network->netlink_buf_size *= 2;
+               network->netlink_buf = talloc_realloc(network,
+                                       network->netlink_buf,
+                                       char *,
+                                       network->netlink_buf_size);
+               goto retry;
+       }
+
+       /* otherwise, we're good to read the entire message without PEEK */
+       if (flags == MSG_PEEK) {
+               flags = 0;
+               goto retry;
+       }
+
+       for_each_nlmsg(network->netlink_buf, nlmsg, len)
                network_handle_nlmsg(network, nlmsg);
 
        return 0;
 }
 
-struct network *network_init(void *ctx, struct waitset *waitset, bool dry_run)
+static void network_init_dns(struct network *network)
+{
+       const struct config *config;
+       unsigned int i;
+       int rc, len;
+       bool modified;
+       char *buf;
+
+       if (network->dry_run)
+               return;
+
+       config = config_get();
+       if (!config || !config->network.n_dns_servers)
+               return;
+
+       rc = read_file(network, "/etc/resolv.conf", &buf, &len);
+
+       if (rc) {
+               buf = talloc_strdup(network, "");
+               len = 0;
+       }
+
+       modified = false;
+
+       for (i = 0; i < config->network.n_dns_servers; i++) {
+               int dns_conf_len;
+               char *dns_conf;
+
+               dns_conf = talloc_asprintf(network, "nameserver %s\n",
+                               config->network.dns_servers[i]);
+
+               if (strstr(buf, dns_conf)) {
+                       talloc_free(dns_conf);
+                       continue;
+               }
+
+               dns_conf_len = strlen(dns_conf);
+               buf = talloc_realloc(network, buf, char, len + dns_conf_len + 1);
+               memcpy(buf + len, dns_conf, dns_conf_len);
+               len += dns_conf_len;
+               buf[len] = '\0';
+               modified = true;
+
+               talloc_free(dns_conf);
+       }
+
+       if (modified) {
+               rc = replace_file("/etc/resolv.conf", buf, len);
+               if (rc)
+                       pb_log("error replacing resolv.conf: %s\n",
+                                       strerror(errno));
+       }
+
+       talloc_free(buf);
+}
+
+struct network *network_init(struct device_handler *handler,
+               struct waitset *waitset, bool dry_run)
 {
        struct network *network;
        int rc;
 
-       network = talloc(ctx, struct network);
+       network = talloc(handler, struct network);
        list_init(&network->interfaces);
-       network->manual_config = false;
+       network->handler = handler;
        network->dry_run = dry_run;
+       network->manual_config = config_get()->network.n_interfaces != 0;
+
+       network_init_dns(network);
 
        rc = network_init_netlink(network);
        if (rc)
@@ -430,7 +686,6 @@ err:
        return NULL;
 }
 
-
 int network_shutdown(struct network *network)
 {
        struct interface *interface;
@@ -438,8 +693,13 @@ int network_shutdown(struct network *network)
        if (network->waiter)
                waiter_remove(network->waiter);
 
-       list_for_each_entry(&network->interfaces, interface, list)
-               interface_down(network, interface);
+       list_for_each_entry(&network->interfaces, interface, list) {
+               if (interface->state == IFSTATE_IGNORED)
+                       continue;
+               if (!strcmp(interface->name, "lo"))
+                       continue;
+               interface_down(interface);
+       }
 
        close(network->netlink_sd);
        talloc_free(network);