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;