From 563a9d379cfd330c61d33ef1465109222f360446 Mon Sep 17 00:00:00 2001
From: Claudio Jeker <claudio@cvs.openbsd.org>
Date: Tue, 31 May 2022 09:45:34 +0000
Subject: Implement a max communities filter match

When max-communities X is set on a filterrule the filter will match when
more than X communities are present in the path. In other words
max-communities 0 means no communities are allowed and max-communities 3
limits it up to 3 communities.
There is max-communities, max-ext-communities and max-large-communities
for each of the 3 community attributes. These three max checks can be used
together.
OK tb@ job@
---
 usr.sbin/bgpd/bgpd.conf.5     | 17 ++++++++++++++--
 usr.sbin/bgpd/bgpd.h          |  5 ++++-
 usr.sbin/bgpd/parse.y         | 46 ++++++++++++++++++++++++++++++++++++++++++-
 usr.sbin/bgpd/printconf.c     |  9 ++++++++-
 usr.sbin/bgpd/rde.h           |  3 ++-
 usr.sbin/bgpd/rde_community.c | 41 +++++++++++++++++++++++++++++++++++++-
 usr.sbin/bgpd/rde_filter.c    | 18 ++++++++++++++++-
 7 files changed, 131 insertions(+), 8 deletions(-)

(limited to 'usr.sbin')

diff --git a/usr.sbin/bgpd/bgpd.conf.5 b/usr.sbin/bgpd/bgpd.conf.5
index 530e2e9182a..a4073681694 100644
--- a/usr.sbin/bgpd/bgpd.conf.5
+++ b/usr.sbin/bgpd/bgpd.conf.5
@@ -1,4 +1,4 @@
-.\" $OpenBSD: bgpd.conf.5,v 1.219 2022/03/31 17:27:29 naddy Exp $
+.\" $OpenBSD: bgpd.conf.5,v 1.220 2022/05/31 09:45:33 claudio Exp $
 .\"
 .\" Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>
 .\" Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -16,7 +16,7 @@
 .\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 .\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 .\"
-.Dd $Mdocdate: March 31 2022 $
+.Dd $Mdocdate: May 31 2022 $
 .Dt BGPD.CONF 5
 .Os
 .Sh NAME
@@ -1609,6 +1609,19 @@ is repeated more than
 .Ar len
 times.
 .Pp
+.It Ic max-communities Ns | Ns Ic max-large-communities Ns | \
+Ns Ic max-ext-communities Ar num
+This rule applies only to
+.Em UPDATES
+where the
+.Em Basic, 
+.Em Large ,
+or
+.Em Extended Community
+attribute has more than
+.Ar num
+elements.
+.Pp
 .It Ic nexthop Ar address
 This rule applies only to
 .Em UPDATES
diff --git a/usr.sbin/bgpd/bgpd.h b/usr.sbin/bgpd/bgpd.h
index faa97db8554..c52d1a47629 100644
--- a/usr.sbin/bgpd/bgpd.h
+++ b/usr.sbin/bgpd/bgpd.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: bgpd.h,v 1.424 2022/05/25 16:03:34 claudio Exp $ */
+/*	$OpenBSD: bgpd.h,v 1.425 2022/05/31 09:45:33 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -1053,6 +1053,9 @@ struct filter_match {
 	struct filter_prefixset		prefixset;
 	struct filter_originset		originset;
 	struct filter_ovs		ovs;
+	int				maxcomm;
+	int				maxextcomm;
+	int				maxlargecomm;
 };
 
 struct filter_rule {
diff --git a/usr.sbin/bgpd/parse.y b/usr.sbin/bgpd/parse.y
index 5dd4eb55795..a0cd7b30cfa 100644
--- a/usr.sbin/bgpd/parse.y
+++ b/usr.sbin/bgpd/parse.y
@@ -1,4 +1,4 @@
-/*	$OpenBSD: parse.y,v 1.424 2022/05/23 13:40:12 deraadt Exp $ */
+/*	$OpenBSD: parse.y,v 1.425 2022/05/31 09:45:33 claudio Exp $ */
 
 /*
  * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -220,6 +220,7 @@ typedef struct {
 %token	FROM TO ANY
 %token	CONNECTED STATIC
 %token	COMMUNITY EXTCOMMUNITY LARGECOMMUNITY DELETE
+%token	MAXCOMMUNITIES MAXEXTCOMMUNITIES MAXLARGECOMMUNITIES
 %token	PREFIX PREFIXLEN PREFIXSET
 %token	ROASET ORIGINSET OVS EXPIRES
 %token	ASSET SOURCEAS TRANSITAS PEERAS MAXASLEN MAXASSEQ
@@ -2338,6 +2339,46 @@ filter_elm	: filter_prefix_h	{
 			}
 			free($3);
 		}
+		| MAXCOMMUNITIES NUMBER {
+			if ($2 < 0 || $2 > INT16_MAX) {
+				yyerror("bad max-comunities %lld", $2);
+				YYERROR;
+			}
+			if (fmopts.m.maxcomm != 0) {
+				yyerror("%s already specified",
+				    "max-communities");
+				YYERROR;
+			}
+			/*
+			 * Offset by 1 since 0 means not used.
+			 * The match function then uses >= to compensate.
+			 */
+			fmopts.m.maxcomm = $2 + 1;
+		}
+		| MAXEXTCOMMUNITIES NUMBER {
+			if ($2 < 0 || $2 > INT16_MAX) {
+				yyerror("bad max-ext-communities %lld", $2);
+				YYERROR;
+			}
+			if (fmopts.m.maxextcomm != 0) {
+				yyerror("%s already specified",
+				    "max-ext-communities");
+				YYERROR;
+			}
+			fmopts.m.maxextcomm = $2 + 1;
+		}
+		| MAXLARGECOMMUNITIES NUMBER {
+			if ($2 < 0 || $2 > INT16_MAX) {
+				yyerror("bad max-large-communities %lld", $2);
+				YYERROR;
+			}
+			if (fmopts.m.maxlargecomm != 0) {
+				yyerror("%s already specified",
+				    "max-large-communities");
+				YYERROR;
+			}
+			fmopts.m.maxlargecomm = $2 + 1;
+		}
 		| NEXTHOP address	{
 			if (fmopts.m.nexthop.flags) {
 				yyerror("nexthop already specified");
@@ -2999,6 +3040,9 @@ lookup(char *s)
 		{ "match",		MATCH},
 		{ "max-as-len",		MAXASLEN},
 		{ "max-as-seq",		MAXASSEQ},
+		{ "max-communities",	MAXCOMMUNITIES},
+		{ "max-ext-communities",	MAXEXTCOMMUNITIES},
+		{ "max-large-communities",	MAXLARGECOMMUNITIES},
 		{ "max-prefix",		MAXPREFIX},
 		{ "maxlen",		MAXLEN},
 		{ "md5sig",		MD5SIG},
diff --git a/usr.sbin/bgpd/printconf.c b/usr.sbin/bgpd/printconf.c
index 1ad98e4ea49..3a20405ef82 100644
--- a/usr.sbin/bgpd/printconf.c
+++ b/usr.sbin/bgpd/printconf.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: printconf.c,v 1.151 2022/05/25 16:03:34 claudio Exp $	*/
+/*	$OpenBSD: printconf.c,v 1.152 2022/05/31 09:45:33 claudio Exp $	*/
 
 /*
  * Copyright (c) 2003, 2004 Henning Brauer <henning@openbsd.org>
@@ -925,6 +925,13 @@ print_rule(struct bgpd_config *conf, struct filter_rule *r)
 		}
 	}
 
+	if (r->match.maxcomm != 0)
+		printf("max-communities %d ", r->match.maxcomm - 1);
+	if (r->match.maxextcomm != 0)
+		printf("max-ext-communities %d ", r->match.maxextcomm - 1);
+	if (r->match.maxlargecomm != 0)
+		printf("max-large-communities %d ", r->match.maxlargecomm - 1);
+
 	print_set(&r->set);
 
 	printf("\n");
diff --git a/usr.sbin/bgpd/rde.h b/usr.sbin/bgpd/rde.h
index f77e9899bb2..d78711d6b99 100644
--- a/usr.sbin/bgpd/rde.h
+++ b/usr.sbin/bgpd/rde.h
@@ -1,4 +1,4 @@
-/*	$OpenBSD: rde.h,v 1.252 2022/05/25 16:03:34 claudio Exp $ */
+/*	$OpenBSD: rde.h,v 1.253 2022/05/31 09:45:33 claudio Exp $ */
 
 /*
  * Copyright (c) 2003, 2004 Claudio Jeker <claudio@openbsd.org> and
@@ -452,6 +452,7 @@ int		 aspath_lenmatch(struct aspath *, enum aslen_spec, u_int);
 /* rde_community.c */
 int	community_match(struct rde_community *, struct community *,
 	    struct rde_peer *);
+int	community_count(struct rde_community *, uint8_t type);
 int	community_set(struct rde_community *, struct community *,
 	    struct rde_peer *);
 void	community_delete(struct rde_community *, struct community *,
diff --git a/usr.sbin/bgpd/rde_community.c b/usr.sbin/bgpd/rde_community.c
index 815ac40cd20..bdf0ce462d6 100644
--- a/usr.sbin/bgpd/rde_community.c
+++ b/usr.sbin/bgpd/rde_community.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: rde_community.c,v 1.5 2022/05/25 16:03:34 claudio Exp $ */
+/*	$OpenBSD: rde_community.c,v 1.6 2022/05/31 09:45:33 claudio Exp $ */
 
 /*
  * Copyright (c) 2019 Claudio Jeker <claudio@openbsd.org>
@@ -282,6 +282,45 @@ struct rde_peer *peer)
 	}
 }
 
+/*
+ * Count the number of communities of type type.
+ */
+int
+community_count(struct rde_community *comm, uint8_t type)
+{
+	size_t l;
+	int count = 0;
+	
+	/* use the fact that the array is ordered by type */
+	switch (type) {
+	case COMMUNITY_TYPE_BASIC:
+		for (l = 0; l < comm->nentries; l++) {
+			if ((uint8_t)comm->communities[l].flags == type)
+				count++;
+			else
+				break;
+		}
+		break;
+	case COMMUNITY_TYPE_EXT:
+		for (l = 0; l < comm->nentries; l++) {
+			if ((uint8_t)comm->communities[l].flags == type)
+				count++;
+			else if ((uint8_t)comm->communities[l].flags > type)
+				break;
+		}
+		break;
+	case COMMUNITY_TYPE_LARGE:
+		for (l = comm->nentries; l > 0; l--) {
+			if ((uint8_t)comm->communities[l - 1].flags == type)
+				count++;
+			else
+				break;
+		}
+		break;
+	}
+	return count;
+}
+
 /*
  * Insert a community, expanding local-as and neighbor-as if needed.
  */
diff --git a/usr.sbin/bgpd/rde_filter.c b/usr.sbin/bgpd/rde_filter.c
index 2b40cab34f8..c2c21ac062d 100644
--- a/usr.sbin/bgpd/rde_filter.c
+++ b/usr.sbin/bgpd/rde_filter.c
@@ -1,4 +1,4 @@
-/*	$OpenBSD: rde_filter.c,v 1.127 2022/02/06 09:51:19 claudio Exp $ */
+/*	$OpenBSD: rde_filter.c,v 1.128 2022/05/31 09:45:33 claudio Exp $ */
 
 /*
  * Copyright (c) 2004 Claudio Jeker <claudio@openbsd.org>
@@ -246,6 +246,22 @@ rde_filter_match(struct filter_rule *f, struct rde_peer *peer,
 			return (0);
 	}
 
+	if (f->match.maxcomm != 0) {
+		if (f->match.maxcomm >
+		    community_count(&state->communities, COMMUNITY_TYPE_BASIC))
+			return (0);
+	}
+	if (f->match.maxextcomm != 0) {
+		if (f->match.maxextcomm >
+		    community_count(&state->communities, COMMUNITY_TYPE_EXT))
+			return (0);
+	}
+	if (f->match.maxlargecomm != 0) {
+		if (f->match.maxlargecomm >
+		    community_count(&state->communities, COMMUNITY_TYPE_LARGE))
+			return (0);
+	}
+
 	if (f->match.nexthop.flags != 0) {
 		struct bgpd_addr *nexthop, *cmpaddr;
 		if (state->nexthop == NULL)
-- 
cgit v1.2.3