From e6c8ce119fdc6c084cf76fd4717eada4bc4d2a35 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?S=C3=A9bastien=20Parisot?= <sparisot@iliad-free.fr>
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

