lguest: Support assigning a MAC address
Mark McLoughlin [Tue, 29 Jul 2008 14:58:33 +0000 (09:58 -0500)]
If you've got a nice DHCP configuration which maps MAC
addresses to specific IP addresses, then you're going to
want to start your guest with one of those MAC addresses.

Also, in Fedora, we have persistent network interface naming
based on the MAC address, so with randomly assigned
addresses you're soon going to hit eth13. Who knows what
will happen then!

Allow assigning a MAC address to the network interface with
e.g.

  --tunnet=bridge:eth0:00:FF:95:6B:DA:3D

or:

  --tunnet=192.168.121.1:00:FF:95:6B:DA:3D

which is pretty unintelligable, but ...

(includes Rusty's minor rework)

Signed-off-by: Mark McLoughlin <markmc@redhat.com>
Signed-off-by: Rusty Russell <rusty@rustcorp.com.au>

Documentation/lguest/lguest.c

index 686e2d4..684d611 100644 (file)
@@ -1265,10 +1265,25 @@ static void setup_console(void)
 
 static u32 str2ip(const char *ipaddr)
 {
-       unsigned int byte[4];
+       unsigned int b[4];
 
-       sscanf(ipaddr, "%u.%u.%u.%u", &byte[0], &byte[1], &byte[2], &byte[3]);
-       return (byte[0] << 24) | (byte[1] << 16) | (byte[2] << 8) | byte[3];
+       if (sscanf(ipaddr, "%u.%u.%u.%u", &b[0], &b[1], &b[2], &b[3]) != 4)
+               errx(1, "Failed to parse IP address '%s'", ipaddr);
+       return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+}
+
+static void str2mac(const char *macaddr, unsigned char mac[6])
+{
+       unsigned int m[6];
+       if (sscanf(macaddr, "%02x:%02x:%02x:%02x:%02x:%02x",
+                  &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) != 6)
+               errx(1, "Failed to parse mac address '%s'", macaddr);
+       mac[0] = m[0];
+       mac[1] = m[1];
+       mac[2] = m[2];
+       mac[3] = m[3];
+       mac[4] = m[4];
+       mac[5] = m[5];
 }
 
 /* This code is "adapted" from libbridge: it attaches the Host end of the
@@ -1289,6 +1304,7 @@ static void add_to_bridge(int fd, const char *if_name, const char *br_name)
                errx(1, "interface %s does not exist!", if_name);
 
        strncpy(ifr.ifr_name, br_name, IFNAMSIZ);
+       ifr.ifr_name[IFNAMSIZ-1] = '\0';
        ifr.ifr_ifindex = ifidx;
        if (ioctl(fd, SIOCBRADDIF, &ifr) < 0)
                err(1, "can't add %s to bridge %s", if_name, br_name);
@@ -1297,58 +1313,80 @@ static void add_to_bridge(int fd, const char *if_name, const char *br_name)
 /* This sets up the Host end of the network device with an IP address, brings
  * it up so packets will flow, the copies the MAC address into the hwaddr
  * pointer. */
-static void configure_device(int fd, const char *devname, u32 ipaddr,
-                            unsigned char hwaddr[6])
+static void configure_device(int fd, const char *tapif, u32 ipaddr)
 {
        struct ifreq ifr;
        struct sockaddr_in *sin = (struct sockaddr_in *)&ifr.ifr_addr;
 
-       /* Don't read these incantations.  Just cut & paste them like I did! */
        memset(&ifr, 0, sizeof(ifr));
-       strcpy(ifr.ifr_name, devname);
+       strcpy(ifr.ifr_name, tapif);
+
+       /* Don't read these incantations.  Just cut & paste them like I did! */
        sin->sin_family = AF_INET;
        sin->sin_addr.s_addr = htonl(ipaddr);
        if (ioctl(fd, SIOCSIFADDR, &ifr) != 0)
-               err(1, "Setting %s interface address", devname);
+               err(1, "Setting %s interface address", tapif);
        ifr.ifr_flags = IFF_UP;
        if (ioctl(fd, SIOCSIFFLAGS, &ifr) != 0)
-               err(1, "Bringing interface %s up", devname);
+               err(1, "Bringing interface %s up", tapif);
+}
+
+static void get_mac(int fd, const char *tapif, unsigned char hwaddr[6])
+{
+       struct ifreq ifr;
+
+       memset(&ifr, 0, sizeof(ifr));
+       strcpy(ifr.ifr_name, tapif);
 
        /* SIOC stands for Socket I/O Control.  G means Get (vs S for Set
         * above).  IF means Interface, and HWADDR is hardware address.
         * Simple! */
        if (ioctl(fd, SIOCGIFHWADDR, &ifr) != 0)
-               err(1, "getting hw address for %s", devname);
+               err(1, "getting hw address for %s", tapif);
        memcpy(hwaddr, ifr.ifr_hwaddr.sa_data, 6);
 }
 
-/*L:195 Our network is a Host<->Guest network.  This can either use bridging or
- * routing, but the principle is the same: it uses the "tun" device to inject
- * packets into the Host as if they came in from a normal network card.  We
- * just shunt packets between the Guest and the tun device. */
-static void setup_tun_net(const char *arg)
+static int get_tun_device(char tapif[IFNAMSIZ])
 {
-       struct device *dev;
        struct ifreq ifr;
-       int netfd, ipfd;
-       u32 ip;
-       const char *br_name = NULL;
-       struct virtio_net_config conf;
+       int netfd;
+
+       /* Start with this zeroed.  Messy but sure. */
+       memset(&ifr, 0, sizeof(ifr));
 
        /* We open the /dev/net/tun device and tell it we want a tap device.  A
         * tap device is like a tun device, only somehow different.  To tell
         * the truth, I completely blundered my way through this code, but it
         * works now! */
        netfd = open_or_die("/dev/net/tun", O_RDWR);
-       memset(&ifr, 0, sizeof(ifr));
        ifr.ifr_flags = IFF_TAP | IFF_NO_PI;
        strcpy(ifr.ifr_name, "tap%d");
        if (ioctl(netfd, TUNSETIFF, &ifr) != 0)
                err(1, "configuring /dev/net/tun");
+
        /* We don't need checksums calculated for packets coming in this
         * device: trust us! */
        ioctl(netfd, TUNSETNOCSUM, 1);
 
+       memcpy(tapif, ifr.ifr_name, IFNAMSIZ);
+       return netfd;
+}
+
+/*L:195 Our network is a Host<->Guest network.  This can either use bridging or
+ * routing, but the principle is the same: it uses the "tun" device to inject
+ * packets into the Host as if they came in from a normal network card.  We
+ * just shunt packets between the Guest and the tun device. */
+static void setup_tun_net(char *arg)
+{
+       struct device *dev;
+       int netfd, ipfd;
+       u32 ip = INADDR_ANY;
+       bool bridging = false;
+       char tapif[IFNAMSIZ], *p;
+       struct virtio_net_config conf;
+
+       netfd = get_tun_device(tapif);
+
        /* First we create a new network device. */
        dev = new_device("net", VIRTIO_ID_NET, netfd, handle_tun_input);
 
@@ -1365,14 +1403,29 @@ static void setup_tun_net(const char *arg)
 
        /* If the command line was --tunnet=bridge:<name> do bridging. */
        if (!strncmp(BRIDGE_PFX, arg, strlen(BRIDGE_PFX))) {
-               ip = INADDR_ANY;
-               br_name = arg + strlen(BRIDGE_PFX);
-               add_to_bridge(ipfd, ifr.ifr_name, br_name);
-       } else /* It is an IP address to set up the device with */
+               arg += strlen(BRIDGE_PFX);
+               bridging = true;
+       }
+
+       /* A mac address may follow the bridge name or IP address */
+       p = strchr(arg, ':');
+       if (p) {
+               str2mac(p+1, conf.mac);
+               *p = '\0';
+       } else {
+               p = arg + strlen(arg);
+               /* None supplied; query the randomly assigned mac. */
+               get_mac(ipfd, tapif, conf.mac);
+       }
+
+       /* arg is now either an IP address or a bridge name */
+       if (bridging)
+               add_to_bridge(ipfd, tapif, arg);
+       else
                ip = str2ip(arg);
 
-       /* Set up the tun device, and get the mac address for the interface. */
-       configure_device(ipfd, ifr.ifr_name, ip, conf.mac);
+       /* Set up the tun device. */
+       configure_device(ipfd, tapif, ip);
 
        /* Tell Guest what MAC address to use. */
        add_feature(dev, VIRTIO_NET_F_MAC);
@@ -1382,11 +1435,14 @@ static void setup_tun_net(const char *arg)
        /* We don't need the socket any more; setup is done. */
        close(ipfd);
 
-       verbose("device %u: tun net %u.%u.%u.%u\n",
-               devices.device_num++,
-               (u8)(ip>>24),(u8)(ip>>16),(u8)(ip>>8),(u8)ip);
-       if (br_name)
-               verbose("attached to bridge: %s\n", br_name);
+       devices.device_num++;
+
+       if (bridging)
+               verbose("device %u: tun %s attached to bridge: %s\n",
+                       devices.device_num, tapif, arg);
+       else
+               verbose("device %u: tun %s: %s\n",
+                       devices.device_num, tapif, arg);
 }
 
 /* Our block (disk) device should be really simple: the Guest asks for a block
@@ -1698,7 +1754,7 @@ static struct option opts[] = {
 static void usage(void)
 {
        errx(1, "Usage: lguest [--verbose] "
-            "[--tunnet=(<ipaddr>|bridge:<bridgename>)\n"
+            "[--tunnet=(<ipaddr>:<macaddr>|bridge:<bridgename>:<macaddr>)\n"
             "|--block=<filename>|--initrd=<filename>]...\n"
             "<mem-in-mb> vmlinux [args...]");
 }