ip link: add support for "address ETHADDR". Closes 4862
[oweals/busybox.git] / networking / ifenslave.c
index 0aa232028f5f5b0f529f836d2eaad724ae50063b..c3be8180b6f75716c324011bf1e7590b2cfbcaba 100644 (file)
  *         set version to 1.1.0
  */
 
+//usage:#define ifenslave_trivial_usage
+//usage:       "[-cdf] MASTER_IFACE SLAVE_IFACE..."
+//usage:#define ifenslave_full_usage "\n\n"
+//usage:       "Configure network interfaces for parallel routing\n"
+//usage:     "\n       -c,--change-active      Change active slave"
+//usage:     "\n       -d,--detach             Remove slave interface from bonding device"
+//usage:     "\n       -f,--force              Force, even if interface is not Ethernet"
+/* //usage:  "\n       -r,--receive-slave      Create a receive-only slave" */
+//usage:
+//usage:#define ifenslave_example_usage
+//usage:       "To create a bond device, simply follow these three steps:\n"
+//usage:       "- ensure that the required drivers are properly loaded:\n"
+//usage:       "  # modprobe bonding ; modprobe <3c59x|eepro100|pcnet32|tulip|...>\n"
+//usage:       "- assign an IP address to the bond device:\n"
+//usage:       "  # ifconfig bond0 <addr> netmask <mask> broadcast <bcast>\n"
+//usage:       "- attach all the interfaces you need to the bond device:\n"
+//usage:       "  # ifenslave bond0 eth0 eth1 eth2\n"
+//usage:       "  If bond0 didn't have a MAC address, it will take eth0's. Then, all\n"
+//usage:       "  interfaces attached AFTER this assignment will get the same MAC addr.\n\n"
+//usage:       "  To detach a dead interface without setting the bond device down:\n"
+//usage:       "  # ifenslave -d bond0 eth1\n\n"
+//usage:       "  To set the bond device down and automatically release all the slaves:\n"
+//usage:       "  # ifconfig bond0 down\n\n"
+//usage:       "  To change active slave:\n"
+//usage:       "  # ifenslave -c bond0 eth0\n"
+
 #include "libbb.h"
 
-#include <net/if_arp.h>
+/* #include <net/if.h> - no. linux/if_bonding.h pulls in linux/if.h */
+#include <linux/if.h>
+//#include <net/if_arp.h> - not needed?
 #include <linux/if_bonding.h>
 #include <linux/sockios.h>
-
-typedef unsigned long long u64; /* hack, so we may include kernel's ethtool.h */
-typedef uint32_t u32;           /* ditto */
-typedef uint16_t u16;           /* ditto */
-typedef uint8_t u8;             /* ditto */
+#include "fix_u32.h" /* hack, so we may include kernel's ethtool.h */
 #include <linux/ethtool.h>
+#ifndef BOND_ABI_VERSION
+# define BOND_ABI_VERSION 2
+#endif
+#ifndef IFNAMSIZ
+# define IFNAMSIZ 16
+#endif
 
 
 struct dev_data {
@@ -116,7 +146,7 @@ struct dev_data {
 };
 
 
-enum { skfd = 3 };      /* AF_INET socket for ioctl() calls.*/
+enum { skfd = 3 };      /* AF_INET socket for ioctl() calls. */
 struct globals {
        unsigned abi_ver;       /* userland - kernel ABI version */
        smallint hwaddr_set;    /* Master's hwaddr is set */
@@ -133,197 +163,122 @@ struct globals {
 } while (0)
 
 
-static void get_drv_info(char *master_ifname);
-static int get_if_settings(char *ifname, struct dev_data *dd);
-static int get_slave_flags(char *slave_ifname);
-static int set_master_hwaddr(char *master_ifname, struct sockaddr *hwaddr);
-static int set_slave_hwaddr(char *slave_ifname, struct sockaddr *hwaddr);
-static int set_slave_mtu(char *slave_ifname, int mtu);
-static int set_if_flags(char *ifname, short flags);
-static int set_if_up(char *ifname, short flags);
-static int set_if_down(char *ifname, short flags);
-static int clear_if_addr(char *ifname);
-static int set_if_addr(char *master_ifname, char *slave_ifname);
-static void change_active(char *master_ifname, char *slave_ifname);
-static int enslave(char *master_ifname, char *slave_ifname);
-static int release(char *master_ifname, char *slave_ifname);
-
-static void strncpy_IFNAMSIZ(char *dst, const char *src)
+/* NOINLINEs are placed where it results in smaller code (gcc 4.3.1) */
+
+static int ioctl_on_skfd(unsigned request, struct ifreq *ifr)
 {
-       strncpy(dst, src, IFNAMSIZ);
+       return ioctl(skfd, request, ifr);
 }
 
-int ifenslave_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
-int ifenslave_main(int argc ATTRIBUTE_UNUSED, char **argv)
+static int set_ifrname_and_do_ioctl(unsigned request, struct ifreq *ifr, const char *ifname)
 {
-       char *master_ifname, *slave_ifname;
-       int rv;
-       int res;
-       unsigned opt;
-       enum {
-               OPT_c = (1 << 0),
-               OPT_d = (1 << 1),
-               OPT_f = (1 << 2),
-       };
-#if ENABLE_GETOPT_LONG
-       static const char ifenslave_longopts[] ALIGN1 =
-               "change-active" No_argument "c"
-               "detach"        No_argument "d"
-               "force"         No_argument "f"
-       ;
-
-       applet_long_options = ifenslave_longopts;
-#endif
-       opt = getopt32(argv, "cdf");
-       argv += optind;
-       if (opt & (opt-1)) /* options check */
-               bb_show_usage();
-
-       /* Copy the interface name */
-       master_ifname = *argv++;
-
-       /* No remaining args means show all interfaces. */
-       if (!master_ifname) {
-               display_interfaces(NULL);
-               return EXIT_SUCCESS;
-       }
+       strncpy_IFNAMSIZ(ifr->ifr_name, ifname);
+       return ioctl_on_skfd(request, ifr);
+}
 
-       /* Open a basic socket */
-       xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), skfd);
+static int get_if_settings(char *ifname, struct dev_data *dd)
+{
+       int res;
 
-       /* exchange abi version with bonding module */
-       get_drv_info(master_ifname);
+       res = set_ifrname_and_do_ioctl(SIOCGIFMTU, &dd->mtu, ifname);
+       res |= set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &dd->flags, ifname);
+       res |= set_ifrname_and_do_ioctl(SIOCGIFHWADDR, &dd->hwaddr, ifname);
 
-       slave_ifname = *argv++;
-       if (!slave_ifname) {
-               if (opt & (OPT_d|OPT_c)) {
-                       display_interfaces(slave_ifname);
-                       return 2; /* why? */
-               }
-               /* A single arg means show the
-                * configuration for this interface
-                */
-               display_interfaces(master_ifname);
-               return EXIT_SUCCESS;
-       }
+       return res;
+}
 
-       res = get_if_settings(master_ifname, &master);
-       if (res) {
-               /* Probably a good reason not to go on */
-               bb_perror_msg_and_die("%s: can't get settings", master_ifname);
-       }
+static int get_slave_flags(char *slave_ifname)
+{
+       return set_ifrname_and_do_ioctl(SIOCGIFFLAGS, &slave.flags, slave_ifname);
+}
 
-       /* check if master is indeed a master;
-        * if not then fail any operation
-        */
-       if (!(master.flags.ifr_flags & IFF_MASTER))
-               bb_error_msg_and_die("%s is not a master", master_ifname);
+static int set_hwaddr(char *ifname, struct sockaddr *hwaddr)
+{
+       struct ifreq ifr;
 
-       /* check if master is up; if not then fail any operation */
-       if (!(master.flags.ifr_flags & IFF_UP))
-               bb_error_msg_and_die("%s is not up", master_ifname);
+       memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(*hwaddr));
+       return set_ifrname_and_do_ioctl(SIOCSIFHWADDR, &ifr, ifname);
+}
 
-       /* Only for enslaving */
-       if (!(opt & (OPT_c|OPT_d))) {
-               sa_family_t master_family = master.hwaddr.ifr_hwaddr.sa_family;
+static int set_mtu(char *ifname, int mtu)
+{
+       struct ifreq ifr;
 
-               /* The family '1' is ARPHRD_ETHER for ethernet. */
-               if (master_family != 1 && !(opt & OPT_f)) {
-                       bb_error_msg_and_die(
-                               "%s is not ethernet-like (-f overrides)",
-                               master_ifname);
-               }
-       }
+       ifr.ifr_mtu = mtu;
+       return set_ifrname_and_do_ioctl(SIOCSIFMTU, &ifr, ifname);
+}
 
-       /* Accepts only one slave */
-       if (opt & OPT_c) {
-               /* change active slave */
-               res = get_slave_flags(slave_ifname);
-               if (res) {
-                       bb_perror_msg_and_die(
-                               "%s: can't get flags", slave_ifname);
-               }
-               change_active(master_ifname, slave_ifname);
-               return EXIT_SUCCESS;
-       }
+static int set_if_flags(char *ifname, int flags)
+{
+       struct ifreq ifr;
 
-       /* Accept multiple slaves */
-       res = 0;
-       do {
-               if (opt & OPT_d) {
-                       /* detach a slave interface from the master */
-                       rv = get_slave_flags(slave_ifname);
-                       if (rv) {
-                               /* Can't work with this slave. */
-                               /* remember the error and skip it*/
-                               bb_perror_msg(
-                                       "skipping %s: can't get flags",
-                                       slave_ifname);
-                               res = rv;
-                               continue;
-                       }
-                       rv = release(master_ifname, slave_ifname);
-                       if (rv) {
-                               bb_perror_msg(
-                                       "master %s, slave %s: "
-                                       "can't release",
-                                       master_ifname, slave_ifname);
-                               res = rv;
-                       }
-               } else {
-                       /* attach a slave interface to the master */
-                       rv = get_if_settings(slave_ifname, &slave);
-                       if (rv) {
-                               /* Can't work with this slave. */
-                               /* remember the error and skip it*/
-                               bb_perror_msg(
-                                       "skipping %s: can't get settings",
-                                       slave_ifname);
-                               res = rv;
-                               continue;
-                       }
-                       rv = enslave(master_ifname, slave_ifname);
-                       if (rv) {
-                               bb_perror_msg(
-                                       "master %s, slave %s: "
-                                       "can't enslave",
-                                       master_ifname, slave_ifname);
-                               res = rv;
-                       }
-               }
-       } while ((slave_ifname = *argv++) != NULL);
+       ifr.ifr_flags = flags;
+       return set_ifrname_and_do_ioctl(SIOCSIFFLAGS, &ifr, ifname);
+}
 
-       if (ENABLE_FEATURE_CLEAN_UP) {
-               close(skfd);
-       }
+static int set_if_up(char *ifname, int flags)
+{
+       int res = set_if_flags(ifname, flags | IFF_UP);
+       if (res)
+               bb_perror_msg("%s: can't up", ifname);
+       return res;
+}
 
+static int set_if_down(char *ifname, int flags)
+{
+       int res = set_if_flags(ifname, flags & ~IFF_UP);
+       if (res)
+               bb_perror_msg("%s: can't down", ifname);
        return res;
 }
 
-static void get_drv_info(char *master_ifname)
+static int clear_if_addr(char *ifname)
 {
        struct ifreq ifr;
-       struct ethtool_drvinfo info;
 
-       memset(&ifr, 0, sizeof(ifr));
-       strncpy_IFNAMSIZ(ifr.ifr_name, master_ifname);
-       ifr.ifr_data = (caddr_t)&info;
+       ifr.ifr_addr.sa_family = AF_INET;
+       memset(ifr.ifr_addr.sa_data, 0, sizeof(ifr.ifr_addr.sa_data));
+       return set_ifrname_and_do_ioctl(SIOCSIFADDR, &ifr, ifname);
+}
 
-       info.cmd = ETHTOOL_GDRVINFO;
-       strncpy(info.driver, "ifenslave", 32);
-       snprintf(info.fw_version, 32, "%d", BOND_ABI_VERSION);
+static int set_if_addr(char *master_ifname, char *slave_ifname)
+{
+#if (SIOCGIFADDR | SIOCSIFADDR \
+  | SIOCGIFDSTADDR | SIOCSIFDSTADDR \
+  | SIOCGIFBRDADDR | SIOCSIFBRDADDR \
+  | SIOCGIFNETMASK | SIOCSIFNETMASK) <= 0xffff
+#define INT uint16_t
+#else
+#define INT int
+#endif
+       static const struct {
+               INT g_ioctl;
+               INT s_ioctl;
+       } ifra[] = {
+               { SIOCGIFADDR,    SIOCSIFADDR    },
+               { SIOCGIFDSTADDR, SIOCSIFDSTADDR },
+               { SIOCGIFBRDADDR, SIOCSIFBRDADDR },
+               { SIOCGIFNETMASK, SIOCSIFNETMASK },
+       };
 
-       if (ioctl(skfd, SIOCETHTOOL, &ifr) < 0) {
-               if (errno == EOPNOTSUPP)
-                       return;
-               bb_perror_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
-       }
+       struct ifreq ifr;
+       int res;
+       unsigned i;
 
-       abi_ver = bb_strtou(info.fw_version, NULL, 0);
-       if (errno)
-               bb_error_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
+       for (i = 0; i < ARRAY_SIZE(ifra); i++) {
+               res = set_ifrname_and_do_ioctl(ifra[i].g_ioctl, &ifr, master_ifname);
+               if (res < 0) {
+                       ifr.ifr_addr.sa_family = AF_INET;
+                       memset(ifr.ifr_addr.sa_data, 0,
+                               sizeof(ifr.ifr_addr.sa_data));
+               }
 
-       return;
+               res = set_ifrname_and_do_ioctl(ifra[i].s_ioctl, &ifr, slave_ifname);
+               if (res < 0)
+                       return res;
+       }
+
+       return 0;
 }
 
 static void change_active(char *master_ifname, char *slave_ifname)
@@ -331,15 +286,12 @@ static void change_active(char *master_ifname, char *slave_ifname)
        struct ifreq ifr;
 
        if (!(slave.flags.ifr_flags & IFF_SLAVE)) {
-               bb_error_msg_and_die(
-                       "%s is not a slave",
-                       slave_ifname);
+               bb_error_msg_and_die("%s is not a slave", slave_ifname);
        }
 
-       strncpy_IFNAMSIZ(ifr.ifr_name, master_ifname);
        strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
-       if (ioctl(skfd, SIOCBONDCHANGEACTIVE, &ifr) < 0
-        && ioctl(skfd, BOND_CHANGE_ACTIVE_OLD, &ifr) < 0
+       if (set_ifrname_and_do_ioctl(SIOCBONDCHANGEACTIVE, &ifr, master_ifname)
+        && ioctl_on_skfd(BOND_CHANGE_ACTIVE_OLD, &ifr)
        ) {
                bb_perror_msg_and_die(
                        "master %s, slave %s: can't "
@@ -348,7 +300,7 @@ static void change_active(char *master_ifname, char *slave_ifname)
        }
 }
 
-static int enslave(char *master_ifname, char *slave_ifname)
+static NOINLINE int enslave(char *master_ifname, char *slave_ifname)
 {
        struct ifreq ifr;
        int res;
@@ -382,7 +334,7 @@ static int enslave(char *master_ifname, char *slave_ifname)
        }
 
        if (master.mtu.ifr_mtu != slave.mtu.ifr_mtu) {
-               res = set_slave_mtu(slave_ifname, master.mtu.ifr_mtu);
+               res = set_mtu(slave_ifname, master.mtu.ifr_mtu);
                if (res) {
                        bb_perror_msg("%s: can't set MTU", slave_ifname);
                        return res;
@@ -398,9 +350,7 @@ static int enslave(char *master_ifname, char *slave_ifname)
                         * the application sets the slave's
                         * hwaddr
                         */
-                       res = set_slave_hwaddr(slave_ifname,
-                                              &(master.hwaddr.ifr_hwaddr));
-                       if (res) {
+                       if (set_hwaddr(slave_ifname, &(master.hwaddr.ifr_hwaddr))) {
                                bb_perror_msg("%s: can't set hw address",
                                                slave_ifname);
                                goto undo_mtu;
@@ -409,8 +359,7 @@ static int enslave(char *master_ifname, char *slave_ifname)
                        /* For old ABI the application needs to bring the
                         * slave back up
                         */
-                       res = set_if_up(slave_ifname, slave.flags.ifr_flags);
-                       if (res)
+                       if (set_if_up(slave_ifname, slave.flags.ifr_flags))
                                goto undo_slave_mac;
                }
                /* The driver is using a new ABI,
@@ -426,14 +375,11 @@ static int enslave(char *master_ifname, char *slave_ifname)
                        /* For old ABI, the master needs to be
                         * down before setting it's hwaddr
                         */
-                       res = set_if_down(master_ifname, master.flags.ifr_flags);
-                       if (res)
+                       if (set_if_down(master_ifname, master.flags.ifr_flags))
                                goto undo_mtu;
                }
 
-               res = set_master_hwaddr(master_ifname,
-                                       &(slave.hwaddr.ifr_hwaddr));
-               if (res) {
+               if (set_hwaddr(master_ifname, &(slave.hwaddr.ifr_hwaddr))) {
                        bb_error_msg("%s: can't set hw address",
                                master_ifname);
                        goto undo_mtu;
@@ -443,8 +389,7 @@ static int enslave(char *master_ifname, char *slave_ifname)
                        /* For old ABI, bring the master
                         * back up
                         */
-                       res = set_if_up(master_ifname, master.flags.ifr_flags);
-                       if (res)
+                       if (set_if_up(master_ifname, master.flags.ifr_flags))
                                goto undo_master_mac;
                }
 
@@ -452,29 +397,26 @@ static int enslave(char *master_ifname, char *slave_ifname)
        }
 
        /* Do the real thing */
-       strncpy_IFNAMSIZ(ifr.ifr_name, master_ifname);
        strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
-       if (ioctl(skfd, SIOCBONDENSLAVE, &ifr) < 0
-        && ioctl(skfd, BOND_ENSLAVE_OLD, &ifr) < 0
+       if (set_ifrname_and_do_ioctl(SIOCBONDENSLAVE, &ifr, master_ifname)
+        && ioctl_on_skfd(BOND_ENSLAVE_OLD, &ifr)
        ) {
-               res = 1;
-       }
-
-       if (res)
                goto undo_master_mac;
+       }
 
        return 0;
 
 /* rollback (best effort) */
  undo_master_mac:
-       set_master_hwaddr(master_ifname, &(master.hwaddr.ifr_hwaddr));
+       set_hwaddr(master_ifname, &(master.hwaddr.ifr_hwaddr));
        hwaddr_set = 0;
        goto undo_mtu;
+
  undo_slave_mac:
-       set_slave_hwaddr(slave_ifname, &(slave.hwaddr.ifr_hwaddr));
+       set_hwaddr(slave_ifname, &(slave.hwaddr.ifr_hwaddr));
  undo_mtu:
-       set_slave_mtu(slave_ifname, slave.mtu.ifr_mtu);
-       return res;
+       set_mtu(slave_ifname, slave.mtu.ifr_mtu);
+       return 1;
 }
 
 static int release(char *master_ifname, char *slave_ifname)
@@ -483,15 +425,13 @@ static int release(char *master_ifname, char *slave_ifname)
        int res = 0;
 
        if (!(slave.flags.ifr_flags & IFF_SLAVE)) {
-               bb_error_msg("%s is not a slave",
-                       slave_ifname);
+               bb_error_msg("%s is not a slave", slave_ifname);
                return 1;
        }
 
-       strncpy_IFNAMSIZ(ifr.ifr_name, master_ifname);
        strncpy_IFNAMSIZ(ifr.ifr_slave, slave_ifname);
-       if (ioctl(skfd, SIOCBONDRELEASE, &ifr) < 0
-        && ioctl(skfd, BOND_RELEASE_OLD, &ifr) < 0
+       if (set_ifrname_and_do_ioctl(SIOCBONDRELEASE, &ifr, master_ifname) < 0
+        && ioctl_on_skfd(BOND_RELEASE_OLD, &ifr) < 0
        ) {
                return 1;
        }
@@ -503,123 +443,175 @@ static int release(char *master_ifname, char *slave_ifname)
        }
 
        /* set to default mtu */
-       set_slave_mtu(slave_ifname, 1500);
+       set_mtu(slave_ifname, 1500);
 
        return res;
 }
 
-static int get_if_settings(char *ifname, struct dev_data *dd)
+static NOINLINE void get_drv_info(char *master_ifname)
 {
-       int res;
-
-       strncpy_IFNAMSIZ(dd->mtu.ifr_name, ifname);
-       res = ioctl(skfd, SIOCGIFMTU, &dd->mtu);
-       strncpy_IFNAMSIZ(dd->flags.ifr_name, ifname);
-       res |= ioctl(skfd, SIOCGIFFLAGS, &dd->flags);
-       strncpy_IFNAMSIZ(dd->hwaddr.ifr_name, ifname);
-       res |= ioctl(skfd, SIOCGIFHWADDR, &dd->hwaddr);
+       struct ifreq ifr;
+       struct ethtool_drvinfo info;
 
-       return res;
-}
+       memset(&ifr, 0, sizeof(ifr));
+       ifr.ifr_data = (caddr_t)&info;
+       info.cmd = ETHTOOL_GDRVINFO;
+       /* both fields are 32 bytes long (long enough) */
+       strcpy(info.driver, "ifenslave");
+       strcpy(info.fw_version, utoa(BOND_ABI_VERSION));
+       if (set_ifrname_and_do_ioctl(SIOCETHTOOL, &ifr, master_ifname) < 0) {
+               if (errno == EOPNOTSUPP)
+                       return;
+               bb_perror_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
+       }
 
-static int get_slave_flags(char *slave_ifname)
-{
-       strncpy_IFNAMSIZ(slave.flags.ifr_name, slave_ifname);
-       return ioctl(skfd, SIOCGIFFLAGS, &slave.flags);
+       abi_ver = bb_strtou(info.fw_version, NULL, 0);
+       if (errno)
+               bb_error_msg_and_die("%s: SIOCETHTOOL error", master_ifname);
 }
 
-static int set_master_hwaddr(char *master_ifname, struct sockaddr *hwaddr)
+int ifenslave_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
+int ifenslave_main(int argc UNUSED_PARAM, char **argv)
 {
-       struct ifreq ifr;
-
-       strncpy_IFNAMSIZ(ifr.ifr_name, master_ifname);
-       memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(struct sockaddr));
-       return ioctl(skfd, SIOCSIFHWADDR, &ifr);
-}
+       char *master_ifname, *slave_ifname;
+       int rv;
+       int res;
+       unsigned opt;
+       enum {
+               OPT_c = (1 << 0),
+               OPT_d = (1 << 1),
+               OPT_f = (1 << 2),
+       };
+#if ENABLE_LONG_OPTS
+       static const char ifenslave_longopts[] ALIGN1 =
+               "change-active\0"  No_argument "c"
+               "detach\0"         No_argument "d"
+               "force\0"          No_argument "f"
+               /* "all-interfaces\0" No_argument "a" */
+               ;
 
-static int set_slave_hwaddr(char *slave_ifname, struct sockaddr *hwaddr)
-{
-       struct ifreq ifr;
+       applet_long_options = ifenslave_longopts;
+#endif
+       INIT_G();
 
-       strncpy_IFNAMSIZ(ifr.ifr_name, slave_ifname);
-       memcpy(&(ifr.ifr_hwaddr), hwaddr, sizeof(struct sockaddr));
-       return ioctl(skfd, SIOCSIFHWADDR, &ifr);
-}
+       opt = getopt32(argv, "cdfa");
+       argv += optind;
+       if (opt & (opt-1)) /* Only one option can be given */
+               bb_show_usage();
 
-static int set_slave_mtu(char *slave_ifname, int mtu)
-{
-       struct ifreq ifr;
+       master_ifname = *argv++;
 
-       ifr.ifr_mtu = mtu;
-       strncpy_IFNAMSIZ(ifr.ifr_name, slave_ifname);
-       return ioctl(skfd, SIOCSIFMTU, &ifr);
-}
+       /* No interface names - show all interfaces. */
+       if (!master_ifname) {
+               display_interfaces(NULL);
+               return EXIT_SUCCESS;
+       }
 
-static int set_if_flags(char *ifname, short flags)
-{
-       struct ifreq ifr;
+       /* Open a basic socket */
+       xmove_fd(xsocket(AF_INET, SOCK_DGRAM, 0), skfd);
 
-       ifr.ifr_flags = flags;
-       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
-       return ioctl(skfd, SIOCSIFFLAGS, &ifr);
-}
+       /* Exchange abi version with bonding module */
+       get_drv_info(master_ifname);
 
-static int set_if_up(char *ifname, short flags)
-{
-       int res = set_if_flags(ifname, flags | IFF_UP);
-       if (res)
-               bb_perror_msg("%s: can't up", ifname);
-       return res;
-}
+       slave_ifname = *argv++;
+       if (!slave_ifname) {
+               if (opt & (OPT_d|OPT_c)) {
+                       /* --change or --detach, and no slaves given -
+                        * show all interfaces. */
+                       display_interfaces(slave_ifname /* == NULL */);
+                       return 2; /* why 2? */
+               }
+               /* A single arg means show the
+                * configuration for this interface
+                */
+               display_interfaces(master_ifname);
+               return EXIT_SUCCESS;
+       }
 
-static int set_if_down(char *ifname, short flags)
-{
-       int res = set_if_flags(ifname, flags & ~IFF_UP);
-       if (res)
-               bb_perror_msg("%s: can't down", ifname);
-       return res;
-}
+       if (get_if_settings(master_ifname, &master)) {
+               /* Probably a good reason not to go on */
+               bb_perror_msg_and_die("%s: can't get settings", master_ifname);
+       }
 
-static int clear_if_addr(char *ifname)
-{
-       struct ifreq ifr;
+       /* Check if master is indeed a master;
+        * if not then fail any operation
+        */
+       if (!(master.flags.ifr_flags & IFF_MASTER))
+               bb_error_msg_and_die("%s is not a master", master_ifname);
 
-       strncpy_IFNAMSIZ(ifr.ifr_name, ifname);
-       ifr.ifr_addr.sa_family = AF_INET;
-       memset(ifr.ifr_addr.sa_data, 0, sizeof(ifr.ifr_addr.sa_data));
-       return ioctl(skfd, SIOCSIFADDR, &ifr);
-}
+       /* Check if master is up; if not then fail any operation */
+       if (!(master.flags.ifr_flags & IFF_UP))
+               bb_error_msg_and_die("%s is not up", master_ifname);
 
-static int set_if_addr(char *master_ifname, char *slave_ifname)
-{
-       static const struct {
-               int g_ioctl;
-               int s_ioctl;
-       } ifra[] = {
-               { SIOCGIFADDR,    SIOCSIFADDR    },
-               { SIOCGIFDSTADDR, SIOCSIFDSTADDR },
-               { SIOCGIFBRDADDR, SIOCSIFBRDADDR },
-               { SIOCGIFNETMASK, SIOCSIFNETMASK },
-       };
+#ifdef WHY_BOTHER
+       /* Neither -c[hange] nor -d[etach] -> it's "enslave" then;
+        * and -f[orce] is not there too. Check that it's ethernet. */
+       if (!(opt & (OPT_d|OPT_c|OPT_f))) {
+               /* The family '1' is ARPHRD_ETHER for ethernet. */
+               if (master.hwaddr.ifr_hwaddr.sa_family != 1) {
+                       bb_error_msg_and_die(
+                               "%s is not ethernet-like (-f overrides)",
+                               master_ifname);
+               }
+       }
+#endif
 
-       struct ifreq ifr;
-       int res;
-       int i;
+       /* Accepts only one slave */
+       if (opt & OPT_c) {
+               /* Change active slave */
+               if (get_slave_flags(slave_ifname)) {
+                       bb_perror_msg_and_die(
+                               "%s: can't get flags", slave_ifname);
+               }
+               change_active(master_ifname, slave_ifname);
+               return EXIT_SUCCESS;
+       }
 
-       for (i = 0; i < ARRAY_SIZE(ifra); i++) {
-               strncpy_IFNAMSIZ(ifr.ifr_name, master_ifname);
-               res = ioctl(skfd, ifra[i].g_ioctl, &ifr);
-               if (res < 0) {
-                       ifr.ifr_addr.sa_family = AF_INET;
-                       memset(ifr.ifr_addr.sa_data, 0,
-                              sizeof(ifr.ifr_addr.sa_data));
+       /* Accepts multiple slaves */
+       res = 0;
+       do {
+               if (opt & OPT_d) {
+                       /* Detach a slave interface from the master */
+                       rv = get_slave_flags(slave_ifname);
+                       if (rv) {
+                               /* Can't work with this slave, */
+                               /* remember the error and skip it */
+                               bb_perror_msg(
+                                       "skipping %s: can't get flags",
+                                       slave_ifname);
+                               res = rv;
+                               continue;
+                       }
+                       rv = release(master_ifname, slave_ifname);
+                       if (rv) {
+                               bb_perror_msg("can't release %s from %s",
+                                       slave_ifname, master_ifname);
+                               res = rv;
+                       }
+               } else {
+                       /* Attach a slave interface to the master */
+                       rv = get_if_settings(slave_ifname, &slave);
+                       if (rv) {
+                               /* Can't work with this slave, */
+                               /* remember the error and skip it */
+                               bb_perror_msg(
+                                       "skipping %s: can't get settings",
+                                       slave_ifname);
+                               res = rv;
+                               continue;
+                       }
+                       rv = enslave(master_ifname, slave_ifname);
+                       if (rv) {
+                               bb_perror_msg("can't enslave %s to %s",
+                                       slave_ifname, master_ifname);
+                               res = rv;
+                       }
                }
+       } while ((slave_ifname = *argv++) != NULL);
 
-               strncpy_IFNAMSIZ(ifr.ifr_name, slave_ifname);
-               res = ioctl(skfd, ifra[i].s_ioctl, &ifr);
-               if (res < 0)
-                       return res;
+       if (ENABLE_FEATURE_CLEAN_UP) {
+               close(skfd);
        }
 
-       return 0;
+       return res;
 }