ipsecctl(8)'s output is easily parsed by a program but not by human
brain, at least not by mine.  The programs currently dumps one flow
per line.  While this is perfect when you want to see the content of
the radix-tree, it is hard to compare what's in there with what you
specified in ipsec.conf(5).

That's why I'd like to introduce a new option "-c" for collapsing flows.

Here's an example with a single peer, before:

# ipsecctl -sf -vv 
@0 flow esp in from 192.168.100.0/24 to 10.10.10.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type use
@1 flow esp in from 192.168.100.0/24 to 10.10.11.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type use
@2 flow esp in from 192.168.200.0/24 to 10.10.10.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type use
@3 flow esp in from 192.168.200.0/24 to 10.10.11.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type use
@4 flow esp out from 10.10.10.0/24 to 192.168.100.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type require
@5 flow esp out from 10.10.10.0/24 to 192.168.200.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type require
@6 flow esp out from 10.10.11.0/24 to 192.168.100.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type require
@7 flow esp out from 10.10.11.0/24 to 192.168.200.0/24 peer 192.168.1.80 srcid 
192.168.1.102/32 dstid 192.168.1.80/32 type require

After:

# ipsecctl -sf -vv -c
@0,1,2,3 flow esp in from { 192.168.100.0/24, 192.168.200.0/24 } to { 
10.10.10.0/24, 10.10.11.0/24 } peer 192.168.1.80 srcid 192.168.1.102/32 dstid 
192.168.1.80/32 type use
@4,5,6,7 flow esp out from { 10.10.10.0/24, 10.10.11.0/24 } to { 
192.168.100.0/24, 192.168.200.0/24 } peer 192.168.1.80 srcid 192.168.1.102/32 
dstid 192.168.1.80/32 type require

Comments?  Ok?

Index: ipsecctl.8
===================================================================
RCS file: /cvs/src/sbin/ipsecctl/ipsecctl.8,v
retrieving revision 1.28
diff -u -p -r1.28 ipsecctl.8
--- ipsecctl.8  8 Nov 2011 16:49:32 -0000       1.28
+++ ipsecctl.8  7 Nov 2017 13:48:12 -0000
@@ -22,7 +22,7 @@
 .Nd control flows for IPsec
 .Sh SYNOPSIS
 .Nm ipsecctl
-.Op Fl dFkmnv
+.Op Fl cdFkmnv
 .Op Fl D Ar macro Ns = Ns Ar value
 .Op Fl f Ar file
 .Op Fl i Ar fifo
@@ -44,6 +44,10 @@ The ruleset grammar is described in
 .Pp
 The options are as follows:
 .Bl -tag -width Ds
+.It Fl c
+Use in combination with the
+.Fl s
+option to collapse flow output.
 .It Fl D Ar macro Ns = Ns Ar value
 Define
 .Ar macro
Index: ipsecctl.c
===================================================================
RCS file: /cvs/src/sbin/ipsecctl/ipsecctl.c,v
retrieving revision 1.82
diff -u -p -r1.82 ipsecctl.c
--- ipsecctl.c  19 Apr 2017 15:59:38 -0000      1.82
+++ ipsecctl.c  7 Nov 2017 13:42:13 -0000
@@ -25,6 +25,7 @@
 #include <netinet/ip_ipsp.h>
 #include <arpa/inet.h>
 
+#include <assert.h>
 #include <err.h>
 #include <errno.h>
 #include <fcntl.h>
@@ -42,6 +43,12 @@ FILE         *ipsecctl_fopen(const char *, cons
 int             ipsecctl_commit(int, struct ipsecctl *);
 int             ipsecctl_add_rule(struct ipsecctl *, struct ipsec_rule *);
 void            ipsecctl_free_rule(struct ipsec_rule *);
+int             ipsecctl_merge_rules(struct ipsec_rule *, struct ipsec_rule *);
+int             ipsecctl_cmp_ident(struct ipsec_rule *, struct ipsec_rule *);
+int             ipsecctl_rule_matchsrc(struct ipsec_rule *,
+                    struct ipsec_addr_wrap *);
+int             ipsecctl_rule_matchdst(struct ipsec_rule *,
+                    struct ipsec_addr_wrap *);
 void            ipsecctl_print_addr(struct ipsec_addr_wrap *);
 void            ipsecctl_print_proto(u_int8_t);
 void            ipsecctl_print_port(u_int16_t, const char *);
@@ -246,6 +253,138 @@ ipsecctl_free_rule(struct ipsec_rule *rp
        free(rp);
 }
 
+/*
+ * Merge two flow rules if they match.
+ *
+ * Return 0 if ``from'' has been merged into ``to'', -1 otherwise.
+ */
+int
+ipsecctl_merge_rules(struct ipsec_rule *to, struct ipsec_rule *from)
+{
+       int match = 0;
+
+       assert((to->type & RULE_FLOW) && (from->type & RULE_FLOW));
+
+       if ((to->satype != from->satype) ||
+           (to->direction != from->direction) ||
+           (to->sport != from->sport) ||
+           (to->dport != from->dport) ||
+           (to->proto != from->proto))
+               return (-1);
+
+       if (to->local != NULL || from->local != NULL) {
+               if ((to->local == NULL) || (from->local == NULL) ||
+                   memcmp(to->local, from->local, sizeof(*to->local)))
+                       return (-1);
+       }
+
+       if (to->peer != NULL || from->peer != NULL) {
+               if ((to->peer == NULL) || (from->peer == NULL) ||
+                   memcmp(to->peer, from->peer, sizeof(*to->peer)))
+                       return (-1);
+       }
+
+       if (ipsecctl_cmp_ident(to, from))
+               return (-1);
+
+       if (ipsecctl_rule_matchsrc(to, from->src)) {
+               free(from->src->name);
+               free(from->src);
+               from->src = NULL;
+               match = 1;
+       }
+       if (ipsecctl_rule_matchdst(to, from->dst)) {
+               free(from->dst->name);
+               free(from->dst);
+               from->dst = NULL;
+               match = 1;
+       }
+
+       if (!match)
+               return (-1);
+
+       TAILQ_INSERT_TAIL(&to->collapsed_rules, from, bundle_entry);
+
+       return (0);
+}
+
+/*
+ * Return 0 if ``r1'' and ``r2'' IDENTITY match, -1 otherwise.
+ */
+int
+ipsecctl_cmp_ident(struct ipsec_rule *r1, struct ipsec_rule *r2)
+{
+       if ((r1->auth == NULL) && (r2->auth == NULL))
+               return (0) ;
+
+       if ((r1->auth == NULL) || (r2->auth == NULL))
+               return (-1);
+
+       if (r1->auth->type != r2->auth->type)
+               return (-1);
+
+       if (r1->auth->srcid != NULL) {
+               if (r2->auth->srcid == NULL)
+                       return (-1);
+
+               if (strcmp(r1->auth->srcid, r2->auth->srcid))
+                       return (-1);
+       }
+
+       if (r1->auth->dstid) {
+               if (r2->auth->dstid == NULL)
+                       return (-1);
+
+               if (strcmp(r1->auth->dstid, r2->auth->dstid))
+                       return (-1);
+       }
+
+       return (0);
+}
+
+
+/*
+ * Return 0 if ``r'' or its merged entries contain ``src'', -1 otherwise.
+ */
+int
+ipsecctl_rule_matchsrc(struct ipsec_rule *r, struct ipsec_addr_wrap *src)
+{
+       struct ipsec_rule *r2;
+
+       if (memcmp(r->src, src, sizeof(*r->src)) == 0)
+               return (-1);
+
+       TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) {
+               if (r2->src == NULL)
+                       continue;
+               if (memcmp(r2->src, src, sizeof(*r->src)) == 0)
+                       return (-1);
+       }
+
+       return (0);
+}
+
+/*
+ * Return 0 if ``r'' or its merged entries contain ``dst'', -1 otherwise.
+ */
+int
+ipsecctl_rule_matchdst(struct ipsec_rule *r, struct ipsec_addr_wrap *dst)
+{
+       struct ipsec_rule *r2;
+
+       if (memcmp(r->dst, dst, sizeof(*r->dst)) == 0)
+               return (-1);
+
+       TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) {
+               if (r2->dst == NULL)
+                       continue;
+               if (memcmp(r2->dst, dst, sizeof(*r->dst)) == 0)
+                       return (-1);
+       }
+
+       return (0);
+}
+
 void
 ipsecctl_print_addr(struct ipsec_addr_wrap *ipa)
 {
@@ -268,7 +407,7 @@ ipsecctl_print_addr(struct ipsec_addr_wr
 
 void
 ipsecctl_print_proto(u_int8_t proto)
-{
+ {
        struct protoent *p;
 
        if ((p = getprotobynumber(proto)) != NULL)
@@ -300,6 +439,8 @@ ipsecctl_print_key(struct ipsec_key *key
 void
 ipsecctl_print_flow(struct ipsec_rule *r, int opts)
 {
+       struct ipsec_rule *r2;
+
        printf("flow %s %s", satype[r->satype], direction[r->direction]);
 
        if (r->proto) {
@@ -307,14 +448,36 @@ ipsecctl_print_flow(struct ipsec_rule *r
                ipsecctl_print_proto(r->proto);
        }
        printf(" from ");
-       ipsecctl_print_addr(r->src);
+       if (opts & IPSECCTL_OPT_COLLAPSE) {
+               printf("{ ");
+               ipsecctl_print_addr(r->src);
+               TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) {
+                       if (r2->src == NULL)
+                               continue;
+                       printf(", ");
+                       ipsecctl_print_addr(r2->src);
+               }
+               printf(" }");
+       } else
+               ipsecctl_print_addr(r->src);
        if (r->sport) {
                printf(" port ");
                ipsecctl_print_port(r->sport,
                    r->proto == IPPROTO_TCP ? "tcp" : "udp");
        }
        printf(" to ");
-       ipsecctl_print_addr(r->dst);
+       if (opts & IPSECCTL_OPT_COLLAPSE) {
+               printf("{ ");
+               ipsecctl_print_addr(r->dst);
+               TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) {
+                       if (r2->dst == NULL)
+                               continue;
+                       printf(", ");
+                       ipsecctl_print_addr(r2->dst);
+               }
+               printf(" }");
+       } else
+               ipsecctl_print_addr(r->dst);
        if (r->dport) {
                printf(" port ");
                ipsecctl_print_port(r->dport,
@@ -396,8 +559,17 @@ ipsecctl_print_sabundle(struct ipsec_rul
 void
 ipsecctl_print_rule(struct ipsec_rule *r, int opts)
 {
-       if (opts & IPSECCTL_OPT_VERBOSE2)
-               printf("@%d ", r->nr);
+       struct ipsec_rule *r2;
+
+       if (opts & IPSECCTL_OPT_VERBOSE2) {
+               printf("@%d", r->nr);
+               if (opts & IPSECCTL_OPT_COLLAPSE) {
+                       TAILQ_FOREACH(r2, &r->collapsed_rules, bundle_entry) {
+                               printf(",%d", r2->nr);
+                       }
+               }
+               printf(" ");
+       }
 
        if (r->type & RULE_FLOW)
                ipsecctl_print_flow(r, opts);
@@ -428,7 +600,7 @@ void
 ipsecctl_get_rules(struct ipsecctl *ipsec)
 {
        struct sadb_msg *msg;
-       struct ipsec_rule *rule;
+       struct ipsec_rule *rule, *last = NULL;
        int              mib[4];
        size_t           need;
        char            *buf, *lim, *next;
@@ -459,12 +631,24 @@ ipsecctl_get_rules(struct ipsecctl *ipse
                        err(1, "ipsecctl_get_rules: calloc");
                rule->nr = ipsec->rule_nr++;
                rule->type |= RULE_FLOW;
+               TAILQ_INIT(&rule->collapsed_rules);
 
                if (pfkey_parse(msg, rule))
                        errx(1, "ipsecctl_get_rules: "
                            "failed to parse PF_KEY message");
 
+               /*
+                * Try to collapse ``rule'' with the last enqueued rule.
+                *
+                * Note that comparing only the last entry works only if
+                * the dump is sorted.
+                */
+               if ((ipsec->opts & IPSECCTL_OPT_COLLAPSE) && (last != NULL) &&
+                   (ipsecctl_merge_rules(last, rule) == 0))
+                       continue;
+
                ipsecctl_add_rule(ipsec, rule);
+               last = rule;
        }
 
        free(buf);
@@ -595,7 +779,7 @@ usage(void)
 {
        extern char     *__progname;
 
-       fprintf(stderr, "usage: %s [-dFkmnv] [-D macro=value] [-f file]"
+       fprintf(stderr, "usage: %s [-cdFkmnv] [-D macro=value] [-f file]"
            " [-i fifo] [-s modifier]\n", __progname);
        exit(1);
 }
@@ -621,8 +805,12 @@ main(int argc, char *argv[])
        if (argc < 2)
                usage();
 
-       while ((ch = getopt(argc, argv, "D:df:Fi:kmnvs:")) != -1) {
+       while ((ch = getopt(argc, argv, "cD:df:Fi:kmnvs:")) != -1) {
                switch (ch) {
+               case 'c':
+                       opts |= IPSECCTL_OPT_COLLAPSE;
+                       break;
+
                case 'D':
                        if (cmdline_symset(optarg) < 0)
                                warnx("could not parse macro definition %s",
Index: ipsecctl.h
===================================================================
RCS file: /cvs/src/sbin/ipsecctl/ipsecctl.h,v
retrieving revision 1.72
diff -u -p -r1.72 ipsecctl.h
--- ipsecctl.h  27 Oct 2017 08:29:32 -0000      1.72
+++ ipsecctl.h  7 Nov 2017 13:33:36 -0000
@@ -29,6 +29,7 @@
 #define IPSECCTL_OPT_DELETE            0x0200
 #define IPSECCTL_OPT_MONITOR           0x0400
 #define IPSECCTL_OPT_SHOWKEY           0x0800
+#define IPSECCTL_OPT_COLLAPSE          0x1000
 
 enum {
        ACTION_ADD, ACTION_DELETE
@@ -216,6 +217,8 @@ struct ipsec_rule {
        TAILQ_ENTRY(ipsec_rule) rule_entry;
        TAILQ_ENTRY(ipsec_rule) bundle_entry;
        TAILQ_ENTRY(ipsec_rule) dst_bundle_entry;
+
+       TAILQ_HEAD(, ipsec_rule) collapsed_rules;
 
        struct dst_bundle_queue dst_bundle_queue;
        char                    *bundle;

Reply via email to