Hello BIRD team,

This patch (for master branch) contains two commits for L3VPN protocol:

- 0001: It adds support for "import/export target none" (or "[]" to specify an 
empty set, which is the same). For example it can be used when we do not want 
to import/export any route from/to the VRF, of if we prefer to set the RT it in 
filters (adding different RT for different IP prefixes for example).

- 0002: It adds support for "import target all", ie all VPN routes are imported 
in the VRF IP table regardless of the RTs. Useful when we need to import any 
route or we want to have a more complex policy implemented in filters.

Thanks!
--
Sébastien
From e6c8ce119fdc6c084cf76fd4717eada4bc4d2a35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Tue, 12 Aug 2025 14:37:39 +0200
Subject: [PATCH 1/2] L3VPN: Allow empty sets for route targets (or 'none')

---
 doc/bird.sgml        | 12 ++++++------
 proto/l3vpn/config.Y |  2 ++
 proto/l3vpn/l3vpn.c  | 18 ++++++------------
 3 files changed, 14 insertions(+), 18 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 98a480f7..37ad1736 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -4528,7 +4528,7 @@ VRFs are associated with one or more RTs. Routes are also associated with one or
 more RTs, which are encoded as route target extended communities
 in <ref id="rta-bgp-ext-community" name="bgp_ext_community">. A route is then
 imported into each VRF that shares an associated Route Target. The L3VPN
-protocol implements this mechanism through mandatory <cf/import target/ and
+protocol implements this mechanism through <cf/import target/ and
 <cf/export target/ protocol options.
 
 <sect1>Configuration
@@ -4557,18 +4557,18 @@ could have up to 5 channels: <cf/ipv4/, <cf/ipv6/, <cf/vpn4/, <cf/vpn6/, and
 	<tag><label id="l3vpn-rd">rd <m/rd/</tag>
 	A shorthand for the option <cf/route distinguisher/.
 
-	<tag><label id="l3vpn-import-target">import target <m/ec/|<m/ec-set/</tag>
+	<tag><label id="l3vpn-import-target">import target <m/ec/|<m/ec-set/|none</tag>
 	Route target extended communities specifying which routes should be
 	imported. Either one community or a set. A route is imported if there is
 	non-empty intersection between extended communities of the route and the
-	import target of the L3VPN protocol. Mandatory.
+	import target of the L3VPN protocol.
 
-	<tag><label id="l3vpn-export-target">export target <m/ec/|<m/ec-set/</tag>
+	<tag><label id="l3vpn-export-target">export target <m/ec/|<m/ec-set/|none</tag>
 	Route target extended communities that are attached to the route in the
 	export direction. Either one community or a set. Other route target
-	extended communities are removed. Mandatory.
+	extended communities are removed.
 
-	<tag><label id="l3vpn-route-target">route target <m/ec/|<m/ec-set/</tag>
+	<tag><label id="l3vpn-route-target">route target <m/ec/|<m/ec-set/|none</tag>
 	A shorthand for both <cf/import target/ and <cf/export target/.
 </descrip>
 
diff --git a/proto/l3vpn/config.Y b/proto/l3vpn/config.Y
index e16e0c48..e49153ff 100644
--- a/proto/l3vpn/config.Y
+++ b/proto/l3vpn/config.Y
@@ -93,6 +93,8 @@ l3vpn_proto:
 l3vpn_targets:
    ec_item { f_tree_only_rt($1); $$ = $1; }
  | '[' ec_items ']' { f_tree_only_rt($2); $$ = build_tree($2); }
+ | '[' ']' { $$ = NULL; }
+ | NONE { $$ = NULL; }
  ;
 
 
diff --git a/proto/l3vpn/l3vpn.c b/proto/l3vpn/l3vpn.c
index bd5fbdaf..f4b1a587 100644
--- a/proto/l3vpn/l3vpn.c
+++ b/proto/l3vpn/l3vpn.c
@@ -84,6 +84,8 @@ mpls_valid_nexthop(const rta *a)
 static int
 l3vpn_import_targets(struct l3vpn_proto *p, const struct adata *list)
 {
+  if (!p->import_target)
+    return 0;
   return (p->import_target_one) ?
     ec_set_contains(list, p->import_target->from.val.ec) :
     eclist_match_set(list, p->import_target);
@@ -124,7 +126,7 @@ static inline void
 l3vpn_prepare_import_targets(struct l3vpn_proto *p)
 {
   const struct f_tree *t = p->import_target;
-  p->import_target_one = !t->left && !t->right && (t->from.val.ec == t->to.val.ec);
+  p->import_target_one = t ? !t->left && !t->right && (t->from.val.ec == t->to.val.ec) : 0;
 }
 
 static void
@@ -141,10 +143,11 @@ l3vpn_prepare_export_targets(struct l3vpn_proto *p)
   if (p->export_target_data)
     mb_free(p->export_target_data);
 
-  uint len = 2 * tree_node_count(p->export_target);
+  uint len = p->export_target ? 2 * tree_node_count(p->export_target) : 0;
   p->export_target_data = mb_alloc(p->p.pool, len * sizeof(u32));
   p->export_target_length = 0;
-  tree_walk(p->export_target, l3vpn_add_ec, p);
+  if (p->export_target)
+    tree_walk(p->export_target, l3vpn_add_ec, p);
   ASSERT(p->export_target_length == len);
 }
 
@@ -333,15 +336,6 @@ l3vpn_postconfig(struct proto_config *CF)
 
   if (rd_zero(cf->rd))
     cf_error("Route distinguisher not specified");
-
-  if (!cf->import_target && !cf->export_target)
-    cf_error("Route target not specified");
-
-  if (!cf->import_target)
-    cf_error("Import target not specified");
-
-  if (!cf->export_target)
-    cf_error("Export target not specified");
 }
 
 static struct proto *
-- 
2.39.5

From d29a1d3572749e08bffe90075257bdd4606b1a63 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <[email protected]>
Date: Tue, 12 Aug 2025 14:37:45 +0200
Subject: [PATCH 2/2] L3VPN: Added support for 'import target all' to allow
 routes with any route targets

---
 doc/bird.sgml        | 2 +-
 proto/l3vpn/config.Y | 5 +++--
 proto/l3vpn/l3vpn.c  | 7 +++++--
 proto/l3vpn/l3vpn.h  | 2 ++
 4 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/doc/bird.sgml b/doc/bird.sgml
index 37ad1736..f563a3fe 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -4557,7 +4557,7 @@ could have up to 5 channels: <cf/ipv4/, <cf/ipv6/, <cf/vpn4/, <cf/vpn6/, and
 	<tag><label id="l3vpn-rd">rd <m/rd/</tag>
 	A shorthand for the option <cf/route distinguisher/.
 
-	<tag><label id="l3vpn-import-target">import target <m/ec/|<m/ec-set/|none</tag>
+	<tag><label id="l3vpn-import-target">import target <m/ec/|<m/ec-set/|none|all</tag>
 	Route target extended communities specifying which routes should be
 	imported. Either one community or a set. A route is imported if there is
 	non-empty intersection between extended communities of the route and the
diff --git a/proto/l3vpn/config.Y b/proto/l3vpn/config.Y
index e49153ff..3ff75251 100644
--- a/proto/l3vpn/config.Y
+++ b/proto/l3vpn/config.Y
@@ -76,9 +76,10 @@ l3vpn_proto_item:
  | mpls_channel
  | RD VPN_RD { L3VPN_CFG->rd = $2; }
  | ROUTE DISTINGUISHER VPN_RD { L3VPN_CFG->rd = $3; }
- | IMPORT TARGET l3vpn_targets { L3VPN_CFG->import_target = $3; }
+ | IMPORT TARGET l3vpn_targets { L3VPN_CFG->import_target = $3; L3VPN_CFG->import_target_all = 0; }
+ | IMPORT TARGET ALL { L3VPN_CFG->import_target = NULL; L3VPN_CFG->import_target_all = 1; }
  | EXPORT TARGET l3vpn_targets { L3VPN_CFG->export_target = $3; }
- | ROUTE TARGET l3vpn_targets { L3VPN_CFG->import_target = L3VPN_CFG->export_target = $3; }
+ | ROUTE TARGET l3vpn_targets { L3VPN_CFG->import_target = L3VPN_CFG->export_target = $3; L3VPN_CFG->import_target_all = 0; }
  ;
 
 l3vpn_proto_opts:
diff --git a/proto/l3vpn/l3vpn.c b/proto/l3vpn/l3vpn.c
index f4b1a587..7c2bad98 100644
--- a/proto/l3vpn/l3vpn.c
+++ b/proto/l3vpn/l3vpn.c
@@ -53,7 +53,6 @@
  * - check for simple nodes in export route
  * - replace pair of channels with shared channel for one address family
  * - improve route comparisons in VRFs
- * - optional import/export target all
  * - optional support for route origins
  * - optional automatic assignment of RDs
  * - MPLS-in-IP encapsulation
@@ -84,6 +83,8 @@ mpls_valid_nexthop(const rta *a)
 static int
 l3vpn_import_targets(struct l3vpn_proto *p, const struct adata *list)
 {
+  if (p->import_target_all)
+    return 1;
   if (!p->import_target)
     return 0;
   return (p->import_target_one) ?
@@ -367,6 +368,7 @@ l3vpn_start(struct proto *P)
 
   p->rd = cf->rd;
   p->import_target = cf->import_target;
+  p->import_target_all = cf->import_target_all;
   p->export_target = cf->export_target;
   p->export_target_data = NULL;
 
@@ -407,11 +409,12 @@ l3vpn_reconfigure(struct proto *P, struct proto_config *CF)
   if (!rd_equal(p->rd, cf->rd))
     return 0;
 
-  int import_changed = !same_tree(p->import_target, cf->import_target);
+  int import_changed = !same_tree(p->import_target, cf->import_target) || !!p->import_target_all != !!cf->import_target_all;
   int export_changed = !same_tree(p->export_target, cf->export_target);
 
   /* Update pointers to config structures */
   p->import_target = cf->import_target;
+  p->import_target_all = cf->import_target_all;
   p->export_target = cf->export_target;
 
   proto_setup_mpls_map(P, RTS_L3VPN, 1);
diff --git a/proto/l3vpn/l3vpn.h b/proto/l3vpn/l3vpn.h
index 1cce28fa..5d0997fe 100644
--- a/proto/l3vpn/l3vpn.h
+++ b/proto/l3vpn/l3vpn.h
@@ -16,6 +16,7 @@ struct l3vpn_config {
   vpn_rd rd;
   struct f_tree *import_target;
   struct f_tree *export_target;
+  uint import_target_all;
 };
 
 struct l3vpn_proto {
@@ -31,6 +32,7 @@ struct l3vpn_proto {
   u32 *export_target_data;
   uint export_target_length;
   uint import_target_one;
+  uint import_target_all;
 };
 
 #endif
-- 
2.39.5

Reply via email to