]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Nest: Add net_addr_nbr route type to track discovered neighbors
authorMatteo Perin <matteo.perin@canonical.com>
Tue, 24 Feb 2026 22:15:06 +0000 (23:15 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Mon, 2 Mar 2026 03:37:02 +0000 (04:37 +0100)
The definition and helper functions for a new route-like object to track
peer discovery data has been added. It only contains the (v4 or v6)
neighbor address and the ingress iface index, for now.

The main intent of this is, currently, to enable BGP unnumbered auto
peer discovery via RAdv incoming advertisments, but in the future the
same data structure could be used to allow discovery coming from
different protocols.

Minor changes by committer.

conf/confbase.Y
doc/bird.sgml
filter/test.conf
lib/net.c
lib/net.h
nest/config.Y
nest/proto.c
nest/rt-fib.c
proto/static/config.Y

index 27c422eabd53d5fd0170deedbc4668134d279928..59a63523e1fcbb0a6f1ceefd8c00c1dcc9811001 100644 (file)
@@ -146,7 +146,7 @@ CF_DECLS
 %type <time> expr_us time
 %type <a> ipa
 %type <net> net_ip4_ net_ip4 net_ip6_ net_ip6 net_ip_ net_ip net_or_ipa
-%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ net_aspa_
+%type <net_ptr> net_ net_any net_vpn4_ net_vpn6_ net_vpn_ net_roa4_ net_roa6_ net_roa_ net_ip6_sadr_ net_mpls_ net_aspa_ net_nbr_
 %type <mls> label_stack_start label_stack
 
 %type <t> text opttext
@@ -168,7 +168,7 @@ CF_DECLS
 /* See r_args */
 %expect 2
 
-CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM, MAX, AS)
+CF_KEYWORDS(DEFINE, ON, OFF, YES, NO, S, MS, US, PORT, VPN, MPLS, FROM, MAX, AS, NEIGHBOR)
 
 CF_GRAMMAR
 
@@ -372,6 +372,12 @@ net_aspa_: ASPA expr
   net_fill_aspa($$, $2);
 }
 
+net_nbr_: NEIGHBOR ipa '%' expr
+{
+  $$ = cfg_alloc(sizeof(net_addr_nbr));
+  net_fill_nbr($$, $2, $4);
+}
+
 net_ip_: net_ip4_ | net_ip6_ ;
 net_vpn_: net_vpn4_ | net_vpn6_ ;
 net_roa_: net_roa4_ | net_roa6_ ;
@@ -384,6 +390,7 @@ net_:
  | net_ip6_sadr_
  | net_mpls_
  | net_aspa_
+ | net_nbr_
  ;
 
 
index 8b68c87bdeb5bd1f4c7340548c05a39fa019c973..7035a6eed546151f5e1e242ab11af8993a0d518b 100644 (file)
@@ -354,6 +354,21 @@ corresponding MPLS routes. Configuration keyword is <cf/mpls/.
        <item>Route next hops
 </itemize>
 
+<sect1>Neighbor entries
+<label id="neighbor-routes">
+
+<p>Neighbor entries represent discovered network neighbors. These are used to
+exchange neighbor information between protocols. For example, in automatic BGP
+peering scenarios, protocols responsible for router discovery (such as RAdv)
+announce these entries, which are then exported to a dynamic BGP instance to
+automatically establish peering sessions. The same type is used for both IPv4
+and IPv6 neighbors. Configuration keyword is <cf/neighbor/.
+
+<itemize>
+       <item>(PK) IP address
+       <item>(PK) Interface index
+</itemize>
+
 <sect1>Route next hops
 <label id="route-next-hop">
 
@@ -1694,6 +1709,10 @@ in the foot).
        <cf/NET_MPLS/ holds a single MPLS label and its handling is currently
        not implemented.
 
+       <cf/NET_NEIGHBOR/ holds a single IP address and an interface index.
+       The IP address can be accessed by operator <cf/.ip/, while the access
+       to the interface index is not implemented.
+
        <tag><label id="type-rd"><label id="type-vpnrd">rd</tag>
        This is a route distinguisher according to <rfc id="4364">. There are
        three kinds of RDs: <cf><m/asn/:<m/32bit int/</cf>, <cf><m/asn4/:<m/16bit int/</cf>
index 096798c517700c03ec98a8cf39dfc3e13b3ee3bc..ef3ecb27db6c2e29c7b3ad571a79fa331c424cd0 100644 (file)
@@ -2031,6 +2031,33 @@ bt_test_suite(t_net_sadr, "Testing IPv6 SADR nets");
 
 
 
+/*
+ *     Testing neighbor nets
+ *     ----------------------
+ */
+
+function t_net_nbr()
+{
+       prefix p;
+
+       p = neighbor 10.1.2.3%10;
+       bt_assert(format(p) = "10.1.2.3%10");
+       bt_assert(p.type = NET_NEIGHBOR);
+       bt_assert(p.ip = 10.1.2.3);
+       bt_assert(p.len = 0);
+
+       p = neighbor 2001:db8:1:13::1%20;
+       bt_assert(format(p) = "2001:db8:1:13::1%20");
+       bt_assert(p.type = NET_NEIGHBOR);
+       bt_assert(p.ip = 2001:db8:1:13::1);
+       bt_assert(p.len = 0);
+}
+
+bt_test_suite(t_net_nbr, "Testing neighbor nets");
+
+
+
+
 /*
  *     Testing defined() function
  *     --------------------------
index 64cf9e047414c81073fedc7d65b7a970daf4dcd9..9bdf93817a78e898040edb819bf7cc2b50b7f22e 100644 (file)
--- a/lib/net.c
+++ b/lib/net.c
@@ -17,6 +17,7 @@ const char * const net_label[] = {
   [NET_IP6_SADR]= "ipv6-sadr",
   [NET_MPLS]   = "mpls",
   [NET_ASPA]   = "aspa",
+  [NET_NEIGHBOR]= "neighbor",
 };
 
 const u16 net_addr_length[] = {
@@ -31,6 +32,7 @@ const u16 net_addr_length[] = {
   [NET_IP6_SADR]= sizeof(net_addr_ip6_sadr),
   [NET_MPLS]   = sizeof(net_addr_mpls),
   [NET_ASPA]   = sizeof(net_addr_aspa),
+  [NET_NEIGHBOR]= sizeof(net_addr_nbr),
 };
 
 const u8 net_max_prefix_length[] = {
@@ -45,6 +47,7 @@ const u8 net_max_prefix_length[] = {
   [NET_IP6_SADR]= IP6_MAX_PREFIX_LENGTH,
   [NET_MPLS]   = 0,
   [NET_ASPA]   = 0,
+  [NET_NEIGHBOR]= 0,
 };
 
 const u16 net_max_text_length[] = {
@@ -59,6 +62,7 @@ const u16 net_max_text_length[] = {
   [NET_IP6_SADR]= 92,  /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128 from ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/128" */
   [NET_MPLS]   = 7,    /* "1048575" */
   [NET_ASPA]   = 10,   /* "4294967295" */
+  [NET_NEIGHBOR]= 50,  /* "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff%4294967295" */
 };
 
 /* There should be no implicit padding in net_addr structures */
@@ -74,6 +78,7 @@ STATIC_ASSERT(sizeof(net_addr_flow6)  == 20);
 STATIC_ASSERT(sizeof(net_addr_ip6_sadr)        == 40);
 STATIC_ASSERT(sizeof(net_addr_mpls)    ==  8);
 STATIC_ASSERT(sizeof(net_addr_aspa)    ==  8);
+STATIC_ASSERT(sizeof(net_addr_nbr)     == 24);
 
 /* Ensure that all net_addr structures have the same alignment */
 STATIC_ASSERT(alignof(net_addr_ip4)    == alignof(net_addr));
@@ -89,6 +94,7 @@ STATIC_ASSERT(alignof(net_addr_flow6) == alignof(net_addr));
 STATIC_ASSERT(alignof(net_addr_ip6_sadr) == alignof(net_addr));
 STATIC_ASSERT(alignof(net_addr_mpls)   == alignof(net_addr));
 STATIC_ASSERT(alignof(net_addr_aspa)   == alignof(net_addr));
+STATIC_ASSERT(alignof(net_addr_nbr)    == alignof(net_addr));
 
 
 int
@@ -147,6 +153,8 @@ net_format(const net_addr *N, char *buf, int buflen)
     return bsnprintf(buf, buflen, "%u", n->mpls.label);
   case NET_ASPA:
     return bsnprintf(buf, buflen, "%u", n->aspa.asn);
+  case NET_NEIGHBOR:
+    return bsnprintf(buf, buflen, "%I%%%u", n->nbr.addr, n->nbr.ifindex);
   }
 
   bug("unknown network type");
@@ -172,6 +180,7 @@ net_pxmask(const net_addr *a)
 
   case NET_MPLS:
   case NET_ASPA:
+  case NET_NEIGHBOR:
   default:
     return IPA_NONE;
   }
@@ -207,6 +216,8 @@ net_compare(const net_addr *a, const net_addr *b)
     return net_compare_mpls((const net_addr_mpls *) a, (const net_addr_mpls *) b);
   case NET_ASPA:
     return net_compare_aspa((const net_addr_aspa *) a, (const net_addr_aspa *) b);
+  case NET_NEIGHBOR:
+    return net_compare_nbr((const net_addr_nbr *) a, (const net_addr_nbr *) b);
   }
   return 0;
 }
@@ -229,6 +240,7 @@ net_hash(const net_addr *n)
   case NET_IP6_SADR: return NET_HASH(n, ip6_sadr);
   case NET_MPLS: return NET_HASH(n, mpls);
   case NET_ASPA: return NET_HASH(n, aspa);
+  case NET_NEIGHBOR: return NET_HASH(n, nbr);
   default: bug("invalid type");
   }
 }
@@ -252,6 +264,7 @@ net_validate(const net_addr *n)
   case NET_IP6_SADR: return NET_VALIDATE(n, ip6_sadr);
   case NET_MPLS: return NET_VALIDATE(n, mpls);
   case NET_ASPA: return NET_VALIDATE(n, aspa);
+  case NET_NEIGHBOR: return NET_VALIDATE(n, nbr);
   default: return 0;
   }
 }
@@ -280,6 +293,7 @@ net_normalize(net_addr *N)
 
   case NET_MPLS:
   case NET_ASPA:
+  case NET_NEIGHBOR:
     return;
   }
 }
@@ -308,6 +322,7 @@ net_classify(const net_addr *N)
 
   case NET_MPLS:
   case NET_ASPA:
+  case NET_NEIGHBOR:
     return IADDR_HOST | SCOPE_UNIVERSE;
   }
 
@@ -342,6 +357,7 @@ ipa_in_netX(const ip_addr a, const net_addr *n)
 
   case NET_MPLS:
   case NET_ASPA:
+  case NET_NEIGHBOR:
   default:
     return 0;
   }
index a1fd0291e445b9f50115e0df68b0022f5205e6d6..58ef366470a2e2d24313305007efb18b2a84ae7b 100644 (file)
--- a/lib/net.h
+++ b/lib/net.h
@@ -24,7 +24,8 @@
 #define NET_IP6_SADR   9
 #define NET_MPLS       10
 #define NET_ASPA       11
-#define NET_MAX                12
+#define NET_NEIGHBOR   12
+#define NET_MAX                13
 
 #define NB_IP4         (1 << NET_IP4)
 #define NB_IP6         (1 << NET_IP6)
@@ -37,6 +38,7 @@
 #define NB_IP6_SADR    (1 << NET_IP6_SADR)
 #define NB_MPLS                (1 << NET_MPLS)
 #define NB_ASPA                (1 << NET_ASPA)
+#define NB_NEIGHBOR    (1 << NET_NEIGHBOR)
 
 #define NB_IP          (NB_IP4 | NB_IP6)
 #define NB_VPN         (NB_VPN4 | NB_VPN6)
@@ -133,6 +135,14 @@ typedef struct net_addr_aspa {
   u32 asn;
 } net_addr_aspa;
 
+typedef struct net_addr_nbr {
+  u8 type;
+  u8 pxlen;
+  u16 length;
+  ip_addr addr;                        /* Peer IP address (IPv6 or IPv4) */
+  u32 ifindex;                 /* Interface index */
+} net_addr_nbr;
+
 typedef struct net_addr_ip6_sadr {
   u8 type;
   u8 dst_pxlen;
@@ -155,6 +165,7 @@ typedef union net_addr_union {
   net_addr_ip6_sadr ip6_sadr;
   net_addr_mpls mpls;
   net_addr_aspa aspa;
+  net_addr_nbr nbr;
 } net_addr_union;
 
 
@@ -199,6 +210,9 @@ extern const u16 net_max_text_length[];
 #define NET_ADDR_MPLS(label) \
   ((net_addr_mpls) { NET_MPLS, 20, sizeof(net_addr_mpls), label })
 
+#define NET_ADDR_NBR(addr, ifindex) \
+  ((net_addr_nbr) { NET_NEIGHBOR, 0, sizeof(net_addr_nbr), addr, ifindex })
+
 
 static inline void net_fill_ip4(net_addr *a, ip4_addr prefix, uint pxlen)
 { *(net_addr_ip4 *)a = NET_ADDR_IP4(prefix, pxlen); }
@@ -227,6 +241,9 @@ static inline void net_fill_mpls(net_addr *a, u32 label)
 static inline void net_fill_aspa(net_addr *a, u32 asn)
 { *(net_addr_aspa *)a = NET_ADDR_ASPA(asn); }
 
+static inline void net_fill_nbr(net_addr *a, ip_addr addr, u32 ifindex)
+{ *(net_addr_nbr *)a = NET_ADDR_NBR(addr, ifindex); }
+
 static inline void net_fill_ipa(net_addr *a, ip_addr prefix, uint pxlen)
 {
   if (ipa_is_ip4(prefix))
@@ -314,6 +331,10 @@ static inline ip_addr net_prefix(const net_addr *a)
   case NET_IP6_SADR:
     return ipa_from_ip6(net6_prefix(a));
 
+  case NET_NEIGHBOR:
+    /* Arguably it is not exactly prefix */
+    return ((net_addr_nbr *) a)->addr;
+
   case NET_MPLS:
   case NET_ASPA:
   default:
@@ -389,6 +410,9 @@ static inline int net_equal_mpls(const net_addr_mpls *a, const net_addr_mpls *b)
 static inline int net_equal_aspa(const net_addr_aspa *a, const net_addr_aspa *b)
 { return !memcmp(a, b, sizeof(net_addr_aspa)); }
 
+static inline int net_equal_nbr(const net_addr_nbr *a, const net_addr_nbr *b)
+{ return !memcmp(a, b, sizeof(net_addr_nbr)); }
+
 
 static inline int net_equal_prefix_roa4(const net_addr_roa4 *a, const net_addr_roa4 *b)
 { return ip4_equal(a->prefix, b->prefix) && (a->pxlen == b->pxlen); }
@@ -433,6 +457,9 @@ static inline int net_zero_mpls(const net_addr_mpls *a)
 static inline int net_zero_aspa(const net_addr_aspa *a)
 { return !a->asn; }
 
+static inline int net_zero_nbr(const net_addr_nbr *a)
+{ return ipa_zero(a->addr) && !a->ifindex; }
+
 
 static inline int net_compare_ip4(const net_addr_ip4 *a, const net_addr_ip4 *b)
 { return ip4_compare(a->prefix, b->prefix) ?: uint_cmp(a->pxlen, b->pxlen); }
@@ -471,6 +498,9 @@ static inline int net_compare_mpls(const net_addr_mpls *a, const net_addr_mpls *
 static inline int net_compare_aspa(const net_addr_aspa *a, const net_addr_aspa *b)
 { return uint_cmp(a->asn, b->asn); }
 
+static inline int net_compare_nbr(const net_addr_nbr *a, const net_addr_nbr *b)
+{ return ipa_compare(a->addr, b->addr) ?: uint_cmp(a->ifindex, b->ifindex); }
+
 int net_compare(const net_addr *a, const net_addr *b);
 
 
@@ -510,6 +540,9 @@ static inline void net_copy_mpls(net_addr_mpls *dst, const net_addr_mpls *src)
 static inline void net_copy_aspa(net_addr_aspa *dst, const net_addr_aspa *src)
 { memcpy(dst, src, sizeof(net_addr_aspa)); }
 
+static inline void net_copy_nbr(net_addr_nbr *dst, const net_addr_nbr *src)
+{ memcpy(dst, src, sizeof(net_addr_nbr)); }
+
 
 static inline u32 px4_hash(ip4_addr prefix, u32 pxlen)
 { return ip4_hash(prefix) ^ (pxlen << 26); }
@@ -556,6 +589,9 @@ static inline u32 net_hash_mpls(const net_addr_mpls *n)
 static inline u32 net_hash_aspa(const net_addr_aspa *n)
 { return u32_hash(n->asn); }
 
+static inline u32 net_hash_nbr(const net_addr_nbr *n)
+{ return ipa_hash(n->addr) ^ u32_hash(n->ifindex); }
+
 u32 net_hash(const net_addr *a);
 
 
@@ -608,6 +644,9 @@ static inline int net_validate_mpls(const net_addr_mpls *n)
 static inline int net_validate_aspa(const net_addr_aspa *n)
 { return n->asn > 0; }
 
+static inline int net_validate_nbr(const net_addr_nbr *n)
+{ return ipa_nonzero(n->addr); }
+
 static inline int net_validate_ip6_sadr(const net_addr_ip6_sadr *n)
 { return net_validate_px6(n->dst_prefix, n->dst_pxlen) && net_validate_px6(n->src_prefix, n->src_pxlen); }
 
index 4a3b45b507c60dbdcfe868a358a768187f17ded1..32e1c67b379c513a6a5576b1435a4101d463a3eb 100644 (file)
@@ -115,7 +115,7 @@ CF_DECLS
 
 CF_KEYWORDS(ROUTER, ID, HOSTNAME, PROTOCOL, TEMPLATE, PREFERENCE, DISABLED, DEBUG, ALL, OFF, DIRECT)
 CF_KEYWORDS(INTERFACE, IMPORT, EXPORT, FILTER, NONE, VRF, DEFAULT, TABLE, TABLES, STATES, ROUTES, FILTERS)
-CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA)
+CF_KEYWORDS(IPV4, IPV6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, SADR, MPLS, ASPA, NEIGHBOR, NEIGHBORS)
 CF_KEYWORDS(RECEIVE, LIMIT, ACTION, WARN, BLOCK, RESTART, DISABLE, KEEP, FILTERED, RPKI)
 CF_KEYWORDS(PASSWORD, KEY, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, CHANNELS, INTERFACES)
 CF_KEYWORDS(ALGORITHM, KEYED, HMAC, MD5, SHA1, SHA256, SHA384, SHA512, BLAKE2S128, BLAKE2S256, BLAKE2B256, BLAKE2B512)
@@ -132,7 +132,7 @@ CF_KEYWORDS(ASPA_PROVIDERS)
 /* For r_args_channel */
 CF_KEYWORDS(IPV4, IPV4_MC, IPV4_MPLS, IPV6, IPV6_MC, IPV6_MPLS, IPV6_SADR, VPN4, VPN4_MC, VPN4_MPLS, VPN6, VPN6_MC, VPN6_MPLS, ROA4, ROA6, FLOW4, FLOW6, MPLS, PRI, SEC, ASPA)
 
-CF_ENUM(T_ENUM_NET_TYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, IP6_SADR, MPLS, ASPA)
+CF_ENUM(T_ENUM_NET_TYPE, NET_, IP4, IP6, VPN4, VPN6, ROA4, ROA6, FLOW4, FLOW6, IP6_SADR, MPLS, ASPA, NEIGHBOR)
 CF_ENUM(T_ENUM_RTS, RTS_, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
        RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE, BABEL, RPKI, L3VPN,
        AGGREGATED)
@@ -150,7 +150,7 @@ CF_ENUM(T_ENUM_MPLS_POLICY, MPLS_POLICY_, NONE, STATIC, PREFIX, AGGREGATE, VRF)
 %type <sd> sym_args
 %type <i> proto_start echo_mask echo_size debug_mask debug_list debug_flag mrtdump_mask mrtdump_list mrtdump_flag export_mode limit_action net_type net_type_base tos password_algorithm
 %type <ps> proto_patt proto_patt2
-%type <cc> channel_start proto_channel
+%type <cc> channel_start proto_channel nbrs_channel_start nbrs_channel
 %type <cl> limit_spec
 %type <tf> timeformat_spec
 %type <tfp> timeformat_which
@@ -197,6 +197,7 @@ net_type_base:
 net_type:
    net_type_base
  | MPLS { $$ = NET_MPLS; }
+ | NEIGHBOR { $$ = NET_NEIGHBOR; }
  ;
 
 
@@ -342,6 +343,12 @@ channel_end:
 proto_channel: channel_start channel_opt_list channel_end;
 
 
+nbrs_channel_start: NEIGHBORS
+{ $$ = this_channel = channel_config_get(NULL, "neighbors", NET_NEIGHBOR, this_proto); };
+
+nbrs_channel: nbrs_channel_start channel_opt_list channel_end;
+
+
 rtable: CF_SYM_KNOWN { cf_assert_symbol($1, SYM_TABLE); $$ = $1->table; } ;
 
 imexport:
@@ -840,6 +847,7 @@ channel_sym:
  | FLOW6       { $$ = "flow6"; }
  | MPLS                { $$ = "mpls"; }
  | ASPA                { $$ = "aspa"; }
+ | NEIGHBORS   { $$ = "neighbors"; }
  | PRI         { $$ = "pri"; }
  | SEC         { $$ = "sec"; }
  ;
index 1b4a98571e3ed640f55bee9adfa64cce926589c1..9e3d7d9b49f2ff2451680dc453e92a2e5fe040b2 100644 (file)
@@ -770,7 +770,7 @@ channel_config_new(const struct channel_class *cc, const char *name, uint net_ty
     if (!net_val_match(net_type, proto->protocol->channel_mask))
       cf_error("Unsupported channel type");
 
-    if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS))
+    if (proto->net_type && (net_type != proto->net_type) && (net_type != NET_MPLS) && (net_type != NET_NEIGHBOR))
       cf_error("Different channel type");
 
     tab = new_config->def_tables[net_type];
index 688d0b96e83776c544a7bf67e87b74815197aee9..d7b6757ef3daacda34f0e6a453650b432a25b6e3 100644 (file)
@@ -280,6 +280,7 @@ fib_find(struct fib *f, const net_addr *a)
   case NET_IP6_SADR: return FIB_FIND(f, a, ip6_sadr);
   case NET_MPLS: return FIB_FIND(f, a, mpls);
   case NET_ASPA: return FIB_FIND(f, a, aspa);
+  case NET_NEIGHBOR: return FIB_FIND(f, a, nbr);
   default: bug("invalid type");
   }
 }
@@ -302,6 +303,7 @@ fib_insert(struct fib *f, const net_addr *a, struct fib_node *e)
   case NET_IP6_SADR: FIB_INSERT(f, a, e, ip6_sadr); return;
   case NET_MPLS: FIB_INSERT(f, a, e, mpls); return;
   case NET_ASPA: FIB_INSERT(f, a, e, aspa); return;
+  case NET_NEIGHBOR: FIB_INSERT(f, a, e, nbr); return;
   default: bug("invalid type");
   }
 }
index f840947b2a237fb9edb23b2c4d12e59620a58b2a..b49dfb55ca6dc4c10c27e68acf996944bdfeb61c 100644 (file)
@@ -65,6 +65,7 @@ static_proto:
    static_proto_start proto_name '{'
  | static_proto proto_item ';'
  | static_proto proto_channel ';' { this_proto->net_type = $2->net_type; }
+ | static_proto nbrs_channel ';' { this_proto->net_type = $2->net_type; }
  | static_proto mpls_channel ';'
  | static_proto CHECK LINK bool ';' { STATIC_CFG->check_link = $4; }
  | static_proto IGP TABLE rtable ';' {