discover: Wait for net interfaces to be marked ready
authorSamuel Mendoza-Jonas <sam@mendozajonas.com>
Thu, 15 Jun 2017 05:23:06 +0000 (15:23 +1000)
committerSamuel Mendoza-Jonas <sam@mendozajonas.com>
Tue, 11 Jul 2017 04:50:00 +0000 (14:50 +1000)
If pb-discover is started before udev has settled there is a race
between Petitboot configuring interfaces and udev renaming them. If an
interface is set "up" the name change will fail and interfaces can be
inconsistently named, eg:

  Device:        (*) eth0 [0c:c4:7a:f4:1c:50, link up]
                 ( ) enP1p9s0f1 [0c:c4:7a:f4:1c:51, link down]
                 ( ) enP1p9s0f2 [0c:c4:7a:f4:1c:52, link down]
                 ( ) enP1p9s0f3 [0c:c4:7a:f4:1c:53, link down]

Add "net" devices to the udev filter and wait for them to be announced
by udev before configuring them.
udev_enumerate_add_match_is_initialized() ensures that by the time an
interface appears via udev its name will be consistent.

This also swaps the network and udev init order, but since interfaces
now will not be configured until after udev is ready this should not
have a user-visible effect.

Signed-off-by: Samuel Mendoza-Jonas <sam@mendozajonas.com>
discover/device-handler.c
discover/network.c
discover/network.h
discover/udev.c

index 38584440801963e193a7aee2ad9c7a62cb89f9d3..778cc8d8e3bffea0d4ffb623f1c1a97e11240b11 100644 (file)
@@ -1424,15 +1424,15 @@ static void device_handler_update_lang(const char *lang)
 static int device_handler_init_sources(struct device_handler *handler)
 {
        /* init our device sources: udev, network and user events */
-       handler->udev = udev_init(handler, handler->waitset);
-       if (!handler->udev)
-               return -1;
-
        handler->network = network_init(handler, handler->waitset,
                        handler->dry_run);
        if (!handler->network)
                return -1;
 
+       handler->udev = udev_init(handler, handler->waitset);
+       if (!handler->udev)
+               return -1;
+
        handler->user_event = user_event_init(handler, handler->waitset);
        if (!handler->user_event)
                return -1;
@@ -1451,11 +1451,11 @@ static void device_handler_reinit_sources(struct device_handler *handler)
 
        system_info_reinit();
 
-       udev_reinit(handler->udev);
-
        network_shutdown(handler->network);
        handler->network = network_init(handler, handler->waitset,
                        handler->dry_run);
+
+       udev_reinit(handler->udev);
 }
 
 static inline const char *get_device_path(struct discover_device *dev)
index 5035b7ce0895cfe0f2a3c12992ca0d1d57c06f24..e2cae912182265eef225c86b130afc4fffafaea2 100644 (file)
@@ -54,6 +54,7 @@ struct interface {
        struct list_item list;
        struct process *udhcpc_process;
        struct discover_device *dev;
+       bool ready;
 };
 
 struct network {
@@ -598,6 +599,11 @@ static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
        if (!interface->dev)
                create_interface_dev(network, interface);
 
+       if (!interface->ready && strncmp(interface->name, "lo", strlen("lo"))) {
+               pb_log("%s not marked ready yet\n", interface->name);
+               return 0;
+       }
+
        configure_interface(network, interface,
                        info->ifi_flags & IFF_UP,
                        info->ifi_flags & IFF_LOWER_UP);
@@ -605,6 +611,72 @@ static int network_handle_nlmsg(struct network *network, struct nlmsghdr *nlmsg)
        return 0;
 }
 
+void network_mark_interface_ready(struct device_handler *handler,
+               int ifindex, const char *ifname, uint8_t *mac, int hwsize)
+{
+       struct network *network = device_handler_get_network(handler);
+       struct interface *interface, *tmp = NULL;
+       char *macstr;
+
+       if (!network) {
+               pb_log("Network not ready - can not mark interface ready\n");
+               return;
+       }
+
+       if (hwsize != HWADDR_SIZE)
+               return;
+
+       if (strncmp(ifname, "lo", strlen("lo")) == 0)
+               return;
+
+       interface = find_interface_by_ifindex(network, ifindex);
+       if (!interface) {
+               pb_debug("Creating ready interface %d - %s\n",
+                               ifindex, ifname);
+               interface = talloc_zero(network, struct interface);
+               interface->ifindex = ifindex;
+               interface->state = IFSTATE_NEW;
+               memcpy(interface->hwaddr, mac, HWADDR_SIZE);
+               strncpy(interface->name, ifname, sizeof(interface->name) - 1);
+
+               list_for_each_entry(&network->interfaces, tmp, list)
+                       if (memcmp(interface->hwaddr, tmp->hwaddr,
+                                  sizeof(interface->hwaddr)) == 0) {
+                               pb_log("%s: %s has duplicate MAC address, ignoring\n",
+                                      __func__, interface->name);
+                               talloc_free(interface);
+                               return;
+                       }
+
+               list_add(&network->interfaces, &interface->list);
+               create_interface_dev(network, interface);
+       }
+
+       if (interface->ready) {
+               pb_log("%s already ready\n", interface->name);
+               return;
+       }
+
+       if (strncmp(interface->name, ifname, strlen(ifname)) != 0) {
+               pb_debug("ifname update from udev: %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);
+       }
+
+       if (memcmp(interface->hwaddr, mac, HWADDR_SIZE) != 0) {
+               macstr = mac_bytes_to_string(interface, mac, hwsize);
+               pb_log("Warning - new MAC for interface %d does not match: %s\n",
+                               ifindex, macstr);
+               talloc_free(macstr);
+       }
+
+       pb_log("Interface %s ready\n", ifname);
+       interface->ready = true;
+       configure_interface(network, interface, false, false);
+}
+
 static int network_netlink_process(void *arg)
 {
        struct network *network = arg;
index e5e05d5f8081ff3047cf7e7655318215c082483c..bf1f2de2ccba192d6852d4c9d941d1f103184808 100644 (file)
@@ -18,5 +18,8 @@ void network_unregister_device(struct network *network,
 uint8_t *find_mac_by_name(void *ctx, struct network *network,
                const char *name);
 
+void network_mark_interface_ready(struct device_handler *handler,
+               int ifindex, const char *ifname, uint8_t *mac, int hwsize);
+
 #endif /* NETWORK_H */
 
index f4754b1662b89517f159dbfd0dd265a696501d31..45a8b56b05dd3847b9face8e6dec0f401e3081f6 100644 (file)
@@ -27,6 +27,7 @@
 #include "device-handler.h"
 #include "cdrom.h"
 #include "devmapper.h"
+#include "network.h"
 
 /* We set a default monitor buffer size, as we may not process monitor
  * events while performing device discvoery. systemd uses a 128M buffer, so
@@ -230,6 +231,69 @@ static int udev_handle_block_add(struct pb_udev *udev, struct udev_device *dev,
        return 0;
 }
 
+/*
+ * Mark valid interfaces as 'ready'.
+ * The udev_enumerate_add_match_is_initialized() filter in udev_enumerate()
+ * ensures that any device we see is properly initialized by udev (eg. interface
+ * names); here we check that the properties are sane and mark the interface
+ * as ready for configuration in discover/network.
+ */
+static int udev_check_interface_ready(struct device_handler *handler,
+               struct udev_device *dev)
+{
+       const char *name, *name_path, *ifindex, *interface, *mac_name;
+       uint8_t *mac;
+       char byte[3];
+       unsigned int i, j;
+
+
+       name = udev_device_get_sysname(dev);
+       if (!name) {
+               pb_debug("udev_device_get_sysname failed\n");
+               return -1;
+       }
+
+       name_path = udev_device_get_property_value(dev, "ID_NET_NAME_PATH");
+       ifindex = udev_device_get_property_value(dev, "IFINDEX");
+       interface = udev_device_get_property_value(dev, "INTERFACE");
+       mac_name = udev_device_get_property_value(dev, "ID_NET_NAME_MAC");
+
+       /* Physical interfaces should have all of these properties */
+       if (!name_path || !ifindex || !interface || !mac_name) {
+               pb_debug("%s: interface %s missing properties\n",
+                               __func__, name);
+               return -1;
+       }
+
+       /* ID_NET_NAME_MAC format is enxMACADDR */
+       if (strlen(mac_name) < 15) {
+               pb_debug("%s: Unexpected MAC format: %s\n",
+                               __func__, mac_name);
+               return -1;
+       }
+
+       mac = talloc_array(handler, uint8_t, HWADDR_SIZE);
+       if (!mac)
+               return -1;
+
+       /*
+        * ID_NET_NAME_MAC is not a conventionally formatted MAC
+        * string - convert it before passing it to network.c
+        */
+       byte[2] = '\0';
+       for (i = strlen("enx"), j = 0;
+                       i < strlen(mac_name) && j < HWADDR_SIZE; i += 2) {
+               memcpy(byte, &mac_name[i], 2);
+               mac[j++] = strtoul(byte, NULL, 16);
+       }
+
+       network_mark_interface_ready(handler,
+                       atoi(ifindex), interface, mac, HWADDR_SIZE);
+
+       talloc_free(mac);
+       return 0;
+}
+
 static int udev_handle_dev_add(struct pb_udev *udev, struct udev_device *dev)
 {
        const char *subsys;
@@ -247,6 +311,10 @@ static int udev_handle_dev_add(struct pb_udev *udev, struct udev_device *dev)
                return -1;
        }
 
+       /* If we see a net device, check if it is ready to be used */
+       if (!strncmp(subsys, "net", strlen("net")))
+               return udev_check_interface_ready(udev->handler, dev);
+
        if (device_lookup_by_id(udev->handler, name)) {
                pb_debug("device %s is already present?\n", name);
                return -1;
@@ -324,10 +392,16 @@ static bool udev_handle_cdrom_events(struct pb_udev *udev,
 static int udev_handle_dev_change(struct pb_udev *udev, struct udev_device *dev)
 {
        struct discover_device *ddev;
+       const char *subsys;
        const char *name;
        int rc = 0;
 
        name = udev_device_get_sysname(dev);
+       subsys = udev_device_get_subsystem(dev);
+
+       /* If we see a net device, check if it is ready to be used */
+       if (!strncmp(subsys, "net", strlen("net")))
+               return udev_check_interface_ready(udev->handler, dev);
 
        ddev = device_lookup_by_id(udev->handler, name);
 
@@ -392,6 +466,12 @@ static int udev_enumerate(struct udev *udev)
                goto fail;
        }
 
+       result = udev_enumerate_add_match_subsystem(enumerate, "net");
+       if (result) {
+               pb_log("udev_enumerate_add_match_subsystem failed\n");
+               goto fail;
+       }
+
        result = udev_enumerate_add_match_is_initialized(enumerate);
        if (result) {
                pb_log("udev_enumerate_add_match_is_initialised failed\n");
@@ -449,6 +529,14 @@ static int udev_setup_monitor(struct udev *udev, struct udev_monitor **monitor)
                goto out_err;
        }
 
+       result = udev_monitor_filter_add_match_subsystem_devtype(m, "net",
+               NULL);
+
+       if (result) {
+               pb_log("udev_monitor_filter_add_match_subsystem_devtype failed\n");
+               goto out_err;
+       }
+
        result = udev_monitor_enable_receiving(m);
 
        if (result) {