ifconfig(8): Support to filter output by interface groups
authorAaron LI <aly@aaronly.me>
Fri, 26 Jun 2020 14:34:43 +0000 (22:34 +0800)
committerAaron LI <aly@aaronly.me>
Fri, 26 Jun 2020 14:53:00 +0000 (22:53 +0800)
Add options '-g <match_group>' and '-G <no_group>' to select and
unselect interfaces by groups in the output of 'ifconfig -a', just
like the existing '-d' and '-u' options to select only interfaces
that are down or up, respectively.  Note that '-g' and '-G' options
can be used at the same time to apply both conditions and their
arguments may contain shell patterns.

Examples:
* To exclude loopback from the list:
      ifconfig -a -G lo
* To list interfaces whose group names begin with 't':
      ifconfig -a -g 't*'

Obtained from FreeBSD (revision 361790; review D25029)

sbin/ifconfig/ifconfig.8
sbin/ifconfig/ifconfig.c

index 78df0d9..bf6dcae 100644 (file)
 .Cm destroy
 .Nm
 .Fl a
+.Op Fl G Ar nogroup
 .Op Fl L
 .Oo
 .Fl d |
 .Fl u
 .Oc
+.Op Fl g Ar matchgroup
 .Op Fl m
 .Op Fl v
 .Op Ar address_family
@@ -2367,9 +2369,29 @@ This flag instructs
 to display information about all interfaces in the system.
 The
 .Fl d
-flag limits this to interfaces that are down, and
+flag limits this to interfaces that are down,
 .Fl u
-limits this to interfaces that are up.
+limits this to interfaces that are up,
+.Fl g
+limits this to members of the specified group of interfaces, and
+.Fl G
+excludes members of the specified group from the list.
+Both
+.Fl g
+and
+.Fl G
+flags may be specified to apply both conditions.
+Only one
+.Fl g
+flag should be specified, as the later one overrides previous ones
+(same for the
+.Fl G
+flag).
+The argument of
+.Fl g
+or
+.Fl G
+flag may contain shell patterns but should be quoted in that case.
 When no arguments are given,
 .Fl a
 is implied.
index 483e36e..0274a1c 100644 (file)
@@ -56,7 +56,9 @@
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
+#include <fnmatch.h>
 #include <ifaddrs.h>
+#include <stdbool.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -88,6 +90,8 @@ int   exit_code = 0;
 /* Formatter strings */
 char   *f_inet, *f_inet6, *f_ether, *f_addr;
 
+static bool group_member(const char *ifname, const char *match,
+                         const char *nomatch);
 static int ifconfig(int argc, char *const *argv, int iscreate,
                     const struct afswtch *afp);
 static void status(const struct afswtch *afp, const struct sockaddr_dl *sdl,
@@ -146,7 +150,7 @@ usage(void)
        "                [address [dest_address]] [parameters]\n"
        "       ifconfig [-n] interface create\n"
        "       ifconfig [-n] interface destroy\n"
-       "       ifconfig -a %s[-d | -u] [-m] [-v] [address_family]\n"
+       "       ifconfig -a %s[-G nogroup] [-d | -u] [-m] [-v] [address_family]\n"
        "       ifconfig -l [-d | -u] [address_family]\n"
        "       ifconfig %s[-d | -u] [-m] [-v]\n",
                options, options, options);
@@ -355,7 +359,7 @@ main(int argc, char *argv[])
        int ifindex, flags;
        const struct afswtch *afp = NULL;
        const struct sockaddr_dl *sdl;
-       const char *ifname;
+       const char *ifname, *matchgroup, *nogroup;
        struct ifa_order_elt *cur, *tmp;
        struct ifa_queue q = TAILQ_HEAD_INITIALIZER(q);
        struct ifaddrs *ifap, *sifap, *ifa;
@@ -367,6 +371,7 @@ main(int argc, char *argv[])
 
        all = downonly = uponly = namesonly = verbose = noload = 0;
        f_inet = f_inet6 = f_ether = f_addr = NULL;
+       matchgroup = nogroup = NULL;
 
        /*
         * Ensure we print interface name when expected to,
@@ -379,7 +384,7 @@ main(int argc, char *argv[])
                setformat(envformat);
 
        /* Parse leading line options */
-       strlcpy(options, "adf:klmnuv", sizeof(options));
+       strlcpy(options, "adf:G:klmnuv", sizeof(options));
        for (p = opts; p != NULL; p = p->next)
                strlcat(options, p->opt, sizeof(options));
        while ((c = getopt(argc, argv, options)) != -1) {
@@ -393,6 +398,11 @@ main(int argc, char *argv[])
                case 'f':
                        setformat(optarg);
                        break;
+               case 'G':
+                       if (!all)
+                               usage();
+                       nogroup = optarg;
+                       break;
                case 'k':
                        printkeys++;
                        break;
@@ -411,6 +421,12 @@ main(int argc, char *argv[])
                case 'v':
                        verbose++;
                        break;
+               case 'g':
+                       if (all) {
+                               matchgroup = optarg;
+                               break;
+                       }
+                       /* FALLTHROUGH (for ifgroup) */
                default:
                        for (p = opts; p != NULL; p = p->next)
                                if (p->opt[0] == c) {
@@ -556,6 +572,8 @@ main(int argc, char *argv[])
                        continue;
                if (uponly && (ifa->ifa_flags & IFF_UP) == 0)
                        continue;
+               if (!group_member(ifa->ifa_name, matchgroup, nogroup))
+                       continue;
 
                if (ifa->ifa_addr->sa_family == AF_LINK)
                        sdl = (const struct sockaddr_dl *)ifa->ifa_addr;
@@ -589,6 +607,73 @@ main(int argc, char *argv[])
 }
 
 
+/*
+ * Returns true if an interface should be listed because any its groups
+ * matches shell pattern "match" and none of groups matches pattern "nomatch".
+ * If any pattern is NULL, corresponding condition is skipped.
+ */
+static bool
+group_member(const char *ifname, const char *match, const char *nomatch)
+{
+       static int               sock = -1;
+
+       struct ifgroupreq        ifgr;
+       struct ifg_req          *ifg;
+       size_t                   len;
+       bool                     matched, nomatched;
+
+       /* Sanity checks. */
+       if (match == NULL && nomatch == NULL)
+               return (true);
+       if (ifname == NULL)
+               return (false);
+
+       memset(&ifgr, 0, sizeof(ifgr));
+       strlcpy(ifgr.ifgr_name, ifname, sizeof(ifgr.ifgr_name));
+
+       /* The socket is opened once. Let _exit() close it. */
+       if (sock == -1) {
+               sock = socket(AF_LOCAL, SOCK_DGRAM, 0);
+               if (sock == -1)
+                       errx(1, "%s: socket(AF_LOCAL,SOCK_DGRAM)", __func__);
+       }
+
+       /* Determine amount of memory for the list of groups. */
+       if (ioctl(sock, SIOCGIFGROUP, (caddr_t)&ifgr) == -1) {
+               if (errno == EINVAL || errno == ENOTTY)
+                       return (false);
+               else
+                       errx(1, "%s: SIOCGIFGROUP", __func__);
+       }
+
+       /* Obtain the list of groups. */
+       len = ifgr.ifgr_len;
+       ifgr.ifgr_groups =
+               (struct ifg_req *)calloc(len / sizeof(*ifg), sizeof(*ifg));
+       if (ifgr.ifgr_groups == NULL)
+               errx(1, "%s: no memory", __func__);
+       if (ioctl(sock, SIOCGIFGROUP, (caddr_t)&ifgr) == -1)
+               errx(1, "%s: SIOCGIFGROUP", __func__);
+
+       /* Perform matching. */
+       matched = false;
+       nomatched = true;
+       for (ifg = ifgr.ifgr_groups; ifg && len >= sizeof(*ifg); ifg++) {
+               len -= sizeof(struct ifg_req);
+               if (match)
+                       matched |= !fnmatch(match, ifg->ifgrq_group, 0);
+               if (nomatch)
+                       nomatched &= fnmatch(nomatch, ifg->ifgrq_group, 0);
+       }
+
+       if (match && !nomatch)
+               return (matched);
+       if (!match && nomatch)
+               return (nomatched);
+       return (matched && nomatched);
+}
+
+
 static struct afswtch *afs = NULL;
 
 void