bgpd L3VPN rework

classic Classic list List threaded Threaded
2 messages Options
Reply | Threaded
Open this post in threaded view
|

bgpd L3VPN rework

Claudio Jeker
At a2k19 David (dlg@) and I sat down and looked at BGP L3 MPLS VPN
support. Now David wants to use this in production and realized that
in his case it would be great to have more than one mpe(4) interface per
rdomain. This way it is possible to write good firewall rules using
interface names or groups.

The definition of VPNs in bgpd was never super elegant. The 'depend on
mpeX' config was a bit redundant and so after some discussions we decided
to rework this part.

L3 MPLS VPN are now configured like this:

vpn "staff" on mpe1 {
        rd $ASN:1
        import-target rt $ASN:100
        export-target rt $ASN:101
        network 0/0
}

vpn "users" on mpe2 {
        rd $ASN:2
        import-target rt $ASN:200
        export-target rt $ASN:201
        network 0/0
}

Now in this example mpe1 and mpe2 can be in the same rdomain. The rdomain
is now selected based on the rdomain of the mpe(4) interface. There are
probably still some gotchas around this which can be tackled in a 2nd
round.

This diff does a lot of shuffling of code and especially affect network
statements (since those are now per VPN and no longer per rdomain).
The rewrite of the network code fixed also some other behaviour bugs which
do not only affect VPN setups. In short conficts between 'network A.B.C.D/N'
and 'network static' are now properly handled (with 'network A.B.C.D/N'
having preference).

Since this is a major change please test (or suffer later).
--
:wq Claudio

Index: usr.sbin/bgpctl/bgpctl.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.c,v
retrieving revision 1.228
diff -u -p -r1.228 bgpctl.c
--- usr.sbin/bgpctl/bgpctl.c 20 Jan 2019 23:30:15 -0000 1.228
+++ usr.sbin/bgpctl/bgpctl.c 7 Feb 2019 10:11:19 -0000
@@ -346,7 +346,7 @@ main(int argc, char *argv[])
  bzero(&net, sizeof(net));
  net.prefix = res->addr;
  net.prefixlen = res->prefixlen;
- net.rtableid = tableid;
+ net.rd = res->rd;
  /* attribute sets are not supported */
  if (res->action == NETWORK_ADD) {
  imsg_compose(ibuf, IMSG_NETWORK_ADD, 0, 0, -1,
@@ -927,7 +927,7 @@ show_fib_head(void)
  printf("flags: "
     "* = valid, B = BGP, C = Connected, S = Static, D = Dynamic\n");
  printf("       "
-    "N = BGP Nexthop reachable via this route R = redistributed\n");
+    "N = BGP Nexthop reachable via this route\n");
  printf("       r = reject route, b = blackhole route\n\n");
  printf("flags prio destination          gateway\n");
 }
@@ -969,11 +969,6 @@ show_fib_flags(u_int16_t flags)
  else
  printf(" ");
 
- if (flags & F_REDISTRIBUTED)
- printf("R");
- else
- printf(" ");
-
  if (flags & F_REJECT && flags & F_BLACKHOLE)
  printf("f");
  else if (flags & F_REJECT)
@@ -1983,7 +1978,7 @@ network_bulk(struct parse_result *res)
  errx(1, "bad prefix: %s", b);
  net.prefix = h;
  net.prefixlen = len;
- net.rtableid = tableid;
+ net.rd = res->rd;
 
  if (res->action == NETWORK_BULK_ADD) {
  imsg_compose(ibuf, IMSG_NETWORK_ADD,
@@ -2128,7 +2123,7 @@ network_mrt_dump(struct mrt_rib *mr, str
  net.prefix = ctl.prefix;
  net.prefixlen = ctl.prefixlen;
  net.type = NETWORK_MRTCLONE;
- /* XXX rtableid */
+ /* XXX rd can't be set and will be 0 */
 
  imsg_compose(ibuf, IMSG_NETWORK_ADD, 0, 0, -1,
     &net, sizeof(net));
Index: usr.sbin/bgpctl/parser.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/parser.c,v
retrieving revision 1.89
diff -u -p -r1.89 parser.c
--- usr.sbin/bgpctl/parser.c 20 Jan 2019 23:30:15 -0000 1.89
+++ usr.sbin/bgpctl/parser.c 7 Feb 2019 10:11:19 -0000
@@ -58,6 +58,7 @@ enum token_type {
  PREPNBR,
  PREPSELF,
  WEIGHT,
+ RD,
  FAMILY,
  GETOPT,
  RTABLE,
@@ -374,6 +375,11 @@ static const struct token t_network_show
  { ENDTOKEN, "", NONE, NULL}
 };
 
+static const struct token t_rd[] = {
+ { RD, "", NONE, t_set},
+ { ENDTOKEN, "", NONE, NULL}
+};
+
 static const struct token t_set[] = {
  { NOTOKEN, "", NONE, NULL},
  { KEYWORD, "community", NONE, t_community},
@@ -386,6 +392,7 @@ static const struct token t_set[] = {
  { KEYWORD, "pftable", NONE, t_pftable},
  { KEYWORD, "prepend-neighbor", NONE, t_prepnbr},
  { KEYWORD, "prepend-self", NONE, t_prepself},
+ { KEYWORD, "rd", NONE, t_rd},
  { KEYWORD, "weight", NONE, t_weight},
  { KEYWORD, "add", NETWORK_BULK_ADD, NULL},
  { KEYWORD, "delete", NETWORK_BULK_REMOVE, NULL},
@@ -493,18 +500,14 @@ static struct parse_result res;
 const struct token *match_token(int *argc, char **argv[],
     const struct token []);
 void show_valid_args(const struct token []);
-int parse_addr(const char *, struct bgpd_addr *);
-int parse_asnum(const char *, size_t, u_int32_t *);
-int parse_number(const char *, struct parse_result *,
-     enum token_type);
-int parse_community(const char *, struct parse_result *);
-int parsesubtype(const char *, u_int8_t *, u_int8_t *);
-int parseextvalue(const char *, u_int32_t *);
-u_int parseextcommunity(const char *, struct parse_result *);
-int parse_largecommunity(const char *,
-     struct parse_result *);
-int parse_nexthop(const char *, struct parse_result *);
-int bgpctl_getopt(int *, char **[], int);
+
+int parse_addr(const char *, struct bgpd_addr *);
+int parse_asnum(const char *, size_t, u_int32_t *);
+int parse_number(const char *, struct parse_result *, enum token_type);
+void parsecommunity(struct filter_community *c, int type, char *s);
+int parseextcommunity(struct filter_community *c, const char *t, char *s);
+int parse_nexthop(const char *, struct parse_result *);
+int bgpctl_getopt(int *, char **[], int);
 
 struct parse_result *
 parse(int argc, char *argv[])
@@ -675,8 +678,25 @@ match_token(int *argc, char **argv[], co
  }
  break;
  case COMMUNITY:
- if (word != NULL && wordlen > 0 &&
-    parse_community(word, &res)) {
+ case LARGE_COMMUNITY:
+ if (word != NULL && wordlen > 0) {
+ int type = COMMUNITY_TYPE_BASIC;
+ char *p = strdup(word);
+
+ if (p == NULL)
+ err(1, NULL);
+ if (table[i].type == LARGE_COMMUNITY)
+ type = COMMUNITY_TYPE_LARGE;
+ parsecommunity(&res.community,
+    COMMUNITY_TYPE_BASIC, p);
+ free(p);
+
+ if ((fs = calloc(1, sizeof(*fs))) == NULL)
+ err(1, NULL);
+ fs->type = ACTION_SET_COMMUNITY;
+ fs->action.community = res.community;
+ TAILQ_INSERT_TAIL(&res.set, fs, entry);
+
  match++;
  t = &table[i];
  }
@@ -684,24 +704,62 @@ match_token(int *argc, char **argv[], co
  case EXTCOM_SUBTYPE:
  if (word != NULL && strncmp(word, table[i].keyword,
     wordlen) == 0) {
- if (parsesubtype(word, &res.community.c.e.type,
-    &res.community.c.e.subtype) == 0)
- errx(1, "Bad ext-community unknown "
-    "type");
+ res.ext_comm_subtype = table[i].keyword;
  match++;
  t = &table[i];
  }
  break;
  case EXTCOMMUNITY:
- if (word != NULL && wordlen > 0 &&
-    parseextcommunity(word, &res)) {
+ if (word != NULL && wordlen > 0) {
+ char *p = strdup(word);
+
+ if (p == NULL)
+ err(1, NULL);
+ parseextcommunity(&res.community,
+    res.ext_comm_subtype, p);
+ free(p);
+
+ if ((fs = calloc(1, sizeof(*fs))) == NULL)
+ err(1, NULL);
+ fs->type = ACTION_SET_COMMUNITY;
+ fs->action.community = res.community;
+ TAILQ_INSERT_TAIL(&res.set, fs, entry);
+
  match++;
  t = &table[i];
  }
  break;
- case LARGE_COMMUNITY:
- if (word != NULL && wordlen > 0 &&
-    parse_largecommunity(word, &res)) {
+ case RD:
+ if (word != NULL && wordlen > 0) {
+ char *p = strdup(word);
+ struct filter_community ext;
+ u_int64_t rd;
+
+ if (p == NULL)
+ err(1, NULL);
+ parseextcommunity(&ext, "rt", p);
+ free(p);
+
+ switch (ext.c.e.type) {
+ case EXT_COMMUNITY_TRANS_TWO_AS:
+ rd = (0ULL << 48);
+ rd |= (u_int64_t)ext.c.e.data1 << 32;
+ rd |= ext.c.e.data2 & 0xffffffff;
+ break;
+ case EXT_COMMUNITY_TRANS_IPV4:
+ rd = (1ULL << 48);
+ rd |= (u_int64_t)ext.c.e.data1 << 16;
+ rd |= ext.c.e.data2 & 0xffff;
+ break;
+ case EXT_COMMUNITY_TRANS_FOUR_AS:
+ rd = (2ULL << 48);
+ rd |= (u_int64_t)ext.c.e.data1 << 16;
+ rd |= ext.c.e.data2 & 0xffff;
+ break;
+ default:
+ errx(1, "bad encoding of rd");
+ }
+ res.rd = htobe64(rd);
  match++;
  t = &table[i];
  }
@@ -823,11 +881,14 @@ show_valid_args(const struct token table
  case COMMUNITY:
  fprintf(stderr, "  <community>\n");
  break;
+ case LARGE_COMMUNITY:
+ fprintf(stderr, "  <large-community>\n");
+ break;
  case EXTCOMMUNITY:
  fprintf(stderr, "  <extended-community>\n");
  break;
- case LARGE_COMMUNITY:
- fprintf(stderr, "  <large-community>\n");
+ case RD:
+ fprintf(stderr, "  <route-distinguisher>\n");
  break;
  case LOCALPREF:
  case MED:
@@ -1035,95 +1096,118 @@ parse_number(const char *word, struct pa
  return (1);
 }
 
-static u_int32_t
-getcommunity(const char *s, int large, u_int8_t *flag)
+static void
+getcommunity(char *s, int large, u_int32_t *val, u_int8_t *flag)
 {
  int64_t max = USHRT_MAX;
  const char *errstr;
- u_int32_t uval;
 
+ *flag = 0;
+ *val = 0;
  if (strcmp(s, "*") == 0) {
  *flag = COMMUNITY_ANY;
- return (0);
+ return;
+ } else if (strcmp(s, "neighbor-as") == 0) {
+ *flag = COMMUNITY_NEIGHBOR_AS;
+ return;
+ } else if (strcmp(s, "local-as") == 0) {
+ *flag =  COMMUNITY_LOCAL_AS;
+ return;
  }
-
  if (large)
  max = UINT_MAX;
-
- uval = strtonum(s, 0, max, &errstr);
+ *val = strtonum(s, 0, max, &errstr);
  if (errstr)
- errx(1, "Community is %s: %s", errstr, s);
-
- *flag = 0;
- return (uval);
+ errx(1, "Community %s is %s (max: %llu)", s, errstr, max);
 }
 
-int
-parse_community(const char *word, struct parse_result *r)
+static void
+setcommunity(struct filter_community *c, u_int32_t as, u_int32_t data,
+    u_int8_t asflag, u_int8_t dataflag)
 {
- struct filter_set *fs;
- char *p;
- u_int32_t as, type;
- u_int8_t asflag, tflag;
+ memset(c, 0, sizeof(*c));
+ c->type = COMMUNITY_TYPE_BASIC;
+ c->dflag1 = asflag;
+ c->dflag2 = dataflag;
+ c->c.b.data1 = as;
+ c->c.b.data2 = data;
+}
 
- /* Well-known communities */
- if (strcasecmp(word, "GRACEFUL_SHUTDOWN") == 0) {
- as = COMMUNITY_WELLKNOWN;
- type = COMMUNITY_GRACEFUL_SHUTDOWN;
- goto done;
- } else if (strcasecmp(word, "NO_EXPORT") == 0) {
- as = COMMUNITY_WELLKNOWN;
- type = COMMUNITY_NO_EXPORT;
- goto done;
- } else if (strcasecmp(word, "NO_ADVERTISE") == 0) {
- as = COMMUNITY_WELLKNOWN;
- type = COMMUNITY_NO_ADVERTISE;
- goto done;
- } else if (strcasecmp(word, "NO_EXPORT_SUBCONFED") == 0) {
- as = COMMUNITY_WELLKNOWN;
- type = COMMUNITY_NO_EXPSUBCONFED;
- goto done;
- } else if (strcasecmp(word, "NO_PEER") == 0) {
- as = COMMUNITY_WELLKNOWN;
- type = COMMUNITY_NO_PEER;
- goto done;
- } else if (strcasecmp(word, "BLACKHOLE") == 0) {
- as = COMMUNITY_WELLKNOWN;
- type = COMMUNITY_BLACKHOLE;
- goto done;
- }
+static void
+parselargecommunity(struct filter_community *c, char *s)
+{
+ char *p, *q;
 
- if ((p = strchr(word, ':')) == NULL) {
- fprintf(stderr, "Bad community syntax\n");
- return (0);
- }
+ if ((p = strchr(s, ':')) == NULL)
+ errx(1, "Bad community syntax");
  *p++ = 0;
 
- as = getcommunity(word, 0, &asflag);
- type = getcommunity(p, 0, &tflag);
+ if ((q = strchr(p, ':')) == NULL)
+ errx(1, "Bad community syntax");
+ *q++ = 0;
+
+ getcommunity(s, 1, &c->c.l.data1, &c->dflag1);
+ getcommunity(p, 1, &c->c.l.data2, &c->dflag2);
+ getcommunity(q, 1, &c->c.l.data3, &c->dflag3);
 
-done:
- if ((fs = calloc(1, sizeof(struct filter_set))) == NULL)
- err(1, NULL);
- fs->type = ACTION_SET_COMMUNITY;
- fs->action.community.type = COMMUNITY_TYPE_BASIC;
- fs->action.community.c.b.data1 = as;
- fs->action.community.c.b.data2 = type;
- fs->action.community.dflag1 = asflag;
- fs->action.community.dflag2 = tflag;
+ c->type = COMMUNITY_TYPE_LARGE;
+}
 
- r->community = fs->action.community;
+void
+parsecommunity(struct filter_community *c, int type, char *s)
+{
+ char *p;
+ u_int32_t as, data;
+ u_int8_t asflag, dataflag;
+
+ if (type == COMMUNITY_TYPE_LARGE) {
+ parselargecommunity(c, s);
+ return;
+ }
 
- TAILQ_INSERT_TAIL(&r->set, fs, entry);
- return (1);
+ /* Well-known communities */
+ if (strcasecmp(s, "GRACEFUL_SHUTDOWN") == 0) {
+ setcommunity(c, COMMUNITY_WELLKNOWN,
+    COMMUNITY_GRACEFUL_SHUTDOWN, 0, 0);
+ return;
+ } else if (strcasecmp(s, "NO_EXPORT") == 0) {
+ setcommunity(c, COMMUNITY_WELLKNOWN,
+    COMMUNITY_NO_EXPORT, 0, 0);
+ return;
+ } else if (strcasecmp(s, "NO_ADVERTISE") == 0) {
+ setcommunity(c, COMMUNITY_WELLKNOWN,
+    COMMUNITY_NO_ADVERTISE, 0, 0);
+ return;
+ } else if (strcasecmp(s, "NO_EXPORT_SUBCONFED") == 0) {
+ setcommunity(c, COMMUNITY_WELLKNOWN,
+    COMMUNITY_NO_EXPSUBCONFED, 0, 0);
+ return;
+ } else if (strcasecmp(s, "NO_PEER") == 0) {
+ setcommunity(c, COMMUNITY_WELLKNOWN,
+    COMMUNITY_NO_PEER, 0, 0);
+ return;
+ } else if (strcasecmp(s, "BLACKHOLE") == 0) {
+ setcommunity(c, COMMUNITY_WELLKNOWN,
+    COMMUNITY_BLACKHOLE, 0, 0);
+ return;
+ }
+
+ if ((p = strchr(s, ':')) == NULL)
+ errx(1, "Bad community syntax");
+ *p++ = 0;
+
+ getcommunity(s, 0, &as, &asflag);
+ getcommunity(p, 0, &data, &dataflag);
+ setcommunity(c, as, data, asflag, dataflag);
 }
 
-int
-parsesubtype(const char *name, u_int8_t *type, u_int8_t *subtype)
+static int
+parsesubtype(const char *name, int *type, int *subtype)
 {
  const struct ext_comm_pairs *cp;
  int found = 0;
 
+printf("%s: looking for %s\n", __func__, name);
  for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
  if (strcmp(name, cp->subname) == 0) {
  if (found == 0) {
@@ -1138,79 +1222,89 @@ parsesubtype(const char *name, u_int8_t
  return (found);
 }
 
-int
-parseextvalue(const char *s, u_int32_t *v)
+static int
+parseextvalue(int type, char *s, u_int32_t *v)
 {
- const char *errstr;
+ const char *errstr;
  char *p;
  struct in_addr ip;
- u_int32_t uvalh = 0, uval;
+ u_int32_t uvalh, uval;
 
- if ((p = strchr(s, '.')) == NULL) {
+ if (type != -1) {
+ /* nothing */
+ } else if ((p = strchr(s, '.')) == NULL) {
  /* AS_PLAIN number (4 or 2 byte) */
- uval = strtonum(s, 0, UINT_MAX, &errstr);
- if (errstr) {
- fprintf(stderr, "Bad ext-community: %s is %s\n", s,
-    errstr);
- return (-1);
- }
- *v = uval;
- if (uval <= USHRT_MAX)
- return (EXT_COMMUNITY_TRANS_TWO_AS);
+ strtonum(s, 0, USHRT_MAX, &errstr);
+ if (errstr == NULL)
+ type = EXT_COMMUNITY_TRANS_TWO_AS;
  else
- return (EXT_COMMUNITY_TRANS_FOUR_AS);
+ type = EXT_COMMUNITY_TRANS_FOUR_AS;
  } else if (strchr(p + 1, '.') == NULL) {
  /* AS_DOT number (4-byte) */
+ type = EXT_COMMUNITY_TRANS_FOUR_AS;
+ } else {
+ /* more than one dot -> IP address */
+ type = EXT_COMMUNITY_TRANS_IPV4;
+ }
+
+ switch (type) {
+ case EXT_COMMUNITY_TRANS_TWO_AS:
+ uval = strtonum(s, 0, USHRT_MAX, &errstr);
+ if (errstr)
+ errx(1, "Bad ext-community %s is %s", s, errstr);
+ *v = uval;
+ break;
+ case EXT_COMMUNITY_TRANS_FOUR_AS:
+ if ((p = strchr(s, '.')) == NULL) {
+ uval = strtonum(s, 0, UINT_MAX, &errstr);
+ if (errstr)
+ errx(1, "Bad ext-community %s is %s", s,
+    errstr);
+ *v = uval;
+ break;
+ }
  *p++ = '\0';
  uvalh = strtonum(s, 0, USHRT_MAX, &errstr);
- if (errstr) {
- fprintf(stderr, "Bad ext-community: %s is %s\n", s,
-    errstr);
- return (-1);
- }
+ if (errstr)
+ errx(1, "Bad ext-community %s is %s", s, errstr);
  uval = strtonum(p, 0, USHRT_MAX, &errstr);
- if (errstr) {
- fprintf(stderr, "Bad ext-community: %s is %s\n", p,
-    errstr);
- return (-1);
- }
+ if (errstr)
+ errx(1, "Bad ext-community %s is %s", p, errstr);
  *v = uval | (uvalh << 16);
- return (EXT_COMMUNITY_TRANS_FOUR_AS);
- } else {
- /* more than one dot -> IP address */
- if (inet_aton(s, &ip) == 0) {
- fprintf(stderr, "Bad ext-community: %s not parseable\n",
-    s);
- return (-1);
- }
+ break;
+ case EXT_COMMUNITY_TRANS_IPV4:
+ if (inet_aton(s, &ip) == 0)
+ errx(1, "Bad ext-community %s not parseable", s);
  *v = ntohl(ip.s_addr);
- return (EXT_COMMUNITY_TRANS_IPV4);
+ break;
+ default:
+ errx(1, "%s: unexpected type %d", __func__, type);
  }
- return (-1);
+ return (type);
 }
 
-u_int
-parseextcommunity(const char *word, struct parse_result *r)
+int
+parseextcommunity(struct filter_community *c, const char *t, char *s)
 {
- struct filter_set *fs;
- const struct ext_comm_pairs *cp;
- const char *errstr;
- u_int64_t ullval;
- u_int32_t uval;
- char *p, *ep;
- int type;
+ const struct ext_comm_pairs *cp;
+ const char *errstr;
+ u_int64_t ullval;
+ u_int32_t uval;
+ char *p, *ep;
+ int type, subtype;
 
- type = r->community.c.e.type;
+ if (parsesubtype(t, &type, &subtype) == 0)
+ errx(1, "Bad ext-community unknown type");
 
  switch (type) {
- case 0xff:
- if ((p = strchr(word, ':')) == NULL) {
- fprintf(stderr, "Bad ext-community: %s\n", word);
- return (0);
- }
+ case EXT_COMMUNITY_TRANS_TWO_AS:
+ case EXT_COMMUNITY_TRANS_FOUR_AS:
+ case EXT_COMMUNITY_TRANS_IPV4:
+ case -1:
+ if ((p = strchr(s, ':')) == NULL)
+ errx(1, "Bad ext-community %s", s);
  *p++ = '\0';
- if ((type = parseextvalue(word, &uval)) == -1)
- return (0);
+ type = parseextvalue(type, s, &uval);
  switch (type) {
  case EXT_COMMUNITY_TRANS_TWO_AS:
  ullval = strtonum(p, 0, UINT_MAX, &errstr);
@@ -1220,118 +1314,46 @@ parseextcommunity(const char *word, stru
  ullval = strtonum(p, 0, USHRT_MAX, &errstr);
  break;
  default:
- fprintf(stderr, "parseextcommunity: unexpected "
-    "result\n");
- return (0);
- }
- if (errstr) {
- fprintf(stderr, "Bad ext-community: %s is %s\n", p,
-    errstr);
- return (0);
- }
- switch (type) {
- case EXT_COMMUNITY_TRANS_TWO_AS:
- r->community.c.e.data1 = uval;
- r->community.c.e.data2 = ullval;
- break;
- case EXT_COMMUNITY_TRANS_IPV4:
- case EXT_COMMUNITY_TRANS_FOUR_AS:
- r->community.c.e.data1 = uval;
- r->community.c.e.data2 = ullval;
- break;
+ errx(1, "parseextcommunity: unexpected result");
  }
+ if (errstr)
+ errx(1, "Bad ext-community %s is %s", p, errstr);
+ c->c.e.data1 = uval;
+ c->c.e.data2 = ullval;
  break;
  case EXT_COMMUNITY_TRANS_OPAQUE:
  case EXT_COMMUNITY_TRANS_EVPN:
  errno = 0;
- ullval = strtoull(word, &ep, 0);
- if (word[0] == '\0' || *ep != '\0') {
- fprintf(stderr, "Bad ext-community: bad value\n");
- return (0);
- }
- if (errno == ERANGE && ullval > EXT_COMMUNITY_OPAQUE_MAX) {
- fprintf(stderr, "Bad ext-community: too big\n");
- return (0);
- }
- r->community.c.e.data2 = ullval;
+ ullval = strtoull(s, &ep, 0);
+ if (s[0] == '\0' || *ep != '\0')
+ errx(1, "Bad ext-community bad value");
+ if (errno == ERANGE && ullval > EXT_COMMUNITY_OPAQUE_MAX)
+ errx(1, "Bad ext-community value too big");
+ c->c.e.data2 = ullval;
  break;
  case EXT_COMMUNITY_NON_TRANS_OPAQUE:
- if (strcmp(word, "valid") == 0)
- r->community.c.e.data2 = EXT_COMMUNITY_OVS_VALID;
- else if (strcmp(word, "invalid") == 0)
- r->community.c.e.data2 = EXT_COMMUNITY_OVS_INVALID;
- else if (strcmp(word, "not-found") == 0)
- r->community.c.e.data2 = EXT_COMMUNITY_OVS_NOTFOUND;
- else {
- fprintf(stderr, "Bad ext-community value: %s\n", word);
- return (0);
- }
+ if (strcmp(s, "valid") == 0)
+ c->c.e.data2 = EXT_COMMUNITY_OVS_VALID;
+ else if (strcmp(s, "invalid") == 0)
+ c->c.e.data2 = EXT_COMMUNITY_OVS_INVALID;
+ else if (strcmp(s, "not-found") == 0)
+ c->c.e.data2 = EXT_COMMUNITY_OVS_NOTFOUND;
+ else
+ errx(1, "Bad ext-community %s", s);
  break;
  }
- r->community.c.e.type = type;
+ c->c.e.type = type;
+ c->c.e.subtype = subtype;
 
  /* verify type/subtype combo */
  for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
- if (cp->type == r->community.c.e.type &&
-    cp->subtype == r->community.c.e.subtype) {
- r->community.type = COMMUNITY_TYPE_EXT;;
- if ((fs = calloc(1, sizeof(struct filter_set))) == NULL)
- err(1, NULL);
-
- fs->type = ACTION_SET_COMMUNITY;
- memcpy(&fs->action.community, &r->community,
-    sizeof(struct filter_community));
-
- TAILQ_INSERT_TAIL(&r->set, fs, entry);
- return (1);
+ if (cp->type == type && cp->subtype == subtype) {
+ c->type = COMMUNITY_TYPE_EXT;
+ return (0);
  }
  }
 
- fprintf(stderr, "Bad ext-community: bad format for type\n");
- return (0);
-}
-
-int
-parse_largecommunity(const char *word, struct parse_result *r)
-{
- struct filter_set *fs;
- char *p, *po = strdup(word);
- char *array[3] = { NULL, NULL, NULL };
- char *val;
- u_int32_t as, ld1, ld2;
- u_int8_t asflag, ld1flag, ld2flag;
- int i = 0;
-
- p = po;
- while ((p != NULL) && (i < 3)) {
- val = strsep(&p, ":");
- array[i++] = val;
- }
-
- if ((p != NULL) || !(array[0] && array[1] && array[2]))
- errx(1, "Invalid Large-Community syntax");
-
- as = getcommunity(array[0], 1, &asflag);
- ld1 = getcommunity(array[1], 1, &ld1flag);
- ld2 = getcommunity(array[2], 1, &ld2flag);
-
- free(po);
-
- if ((fs = calloc(1, sizeof(struct filter_set))) == NULL)
- err(1, NULL);
- fs->type = ACTION_SET_COMMUNITY;
- fs->action.community.type = COMMUNITY_TYPE_LARGE;
- fs->action.community.c.l.data1 = as;
- fs->action.community.c.l.data2 = ld1;
- fs->action.community.c.l.data3 = ld2;
- fs->action.community.dflag1 = asflag;
- fs->action.community.dflag2 = ld1flag;
- fs->action.community.dflag3 = ld2flag;
-
- r->community = fs->action.community;
-
- TAILQ_INSERT_TAIL(&r->set, fs, entry);
- return (1);
+ errx(1, "Bad ext-community bad format for type");
 }
 
 int
Index: usr.sbin/bgpctl/parser.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpctl/parser.h,v
retrieving revision 1.34
diff -u -p -r1.34 parser.h
--- usr.sbin/bgpctl/parser.h 20 Jan 2019 23:30:15 -0000 1.34
+++ usr.sbin/bgpctl/parser.h 7 Feb 2019 10:11:19 -0000
@@ -67,6 +67,8 @@ struct parse_result {
  char rib[PEER_DESCR_LEN];
  char shutcomm[SHUT_COMM_LEN];
  char *irr_outdir;
+ const char *ext_comm_subtype;
+ u_int64_t rd;
  int flags;
  int is_group;
  u_int8_t validation_state;
Index: usr.sbin/bgpd/bgpd.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.c,v
retrieving revision 1.207
diff -u -p -r1.207 bgpd.c
--- usr.sbin/bgpd/bgpd.c 20 Jan 2019 06:13:40 -0000 1.207
+++ usr.sbin/bgpd/bgpd.c 7 Feb 2019 10:11:19 -0000
@@ -173,7 +173,7 @@ main(int argc, char *argv[])
 
  if (cmd_opts & BGPD_OPT_VERBOSE)
  print_config(conf, &ribnames, &conf->networks, peer_l,
-    conf->filters, conf->mrt, &conf->rdomains);
+    conf->filters, conf->mrt, &conf->l3vpns);
  else
  fprintf(stderr, "configuration OK\n");
  exit(0);
@@ -438,7 +438,7 @@ reconfigure(char *conffile, struct bgpd_
  struct filter_rule *r;
  struct listen_addr *la;
  struct rde_rib *rr;
- struct rdomain *rd;
+ struct l3vpn *vpn;
  struct as_set *aset;
  struct prefixset *ps;
  struct prefixset_item *psi, *npsi;
@@ -512,8 +512,7 @@ reconfigure(char *conffile, struct bgpd_
  }
 
  /* networks go via kroute to the RDE */
- if (kr_net_reload(conf->default_tableid, &conf->networks))
- return (-1);
+ kr_net_reload(conf->default_tableid, 0, &conf->networks);
 
  /* prefixsets for filters in the RDE */
  while ((ps = SIMPLEQ_FIRST(&conf->prefixsets)) != NULL) {
@@ -627,43 +626,42 @@ reconfigure(char *conffile, struct bgpd_
  free(r);
  }
 
- while ((rd = SIMPLEQ_FIRST(&conf->rdomains)) != NULL) {
- SIMPLEQ_REMOVE_HEAD(&conf->rdomains, entry);
- if (ktable_update(rd->rtableid, rd->descr, rd->flags,
+ while ((vpn = SIMPLEQ_FIRST(&conf->l3vpns)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(&conf->l3vpns, entry);
+ if (ktable_update(vpn->rtableid, vpn->descr, vpn->flags,
     conf->fib_priority) == -1) {
  log_warnx("failed to load rdomain %d",
-    rd->rtableid);
+    vpn->rtableid);
  return (-1);
  }
  /* networks go via kroute to the RDE */
- if (kr_net_reload(rd->rtableid, &rd->net_l))
- return (-1);
+ kr_net_reload(vpn->rtableid, vpn->rd, &vpn->net_l);
 
- if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN, 0, 0, -1,
-    rd, sizeof(*rd)) == -1)
+ if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN, 0, 0, -1,
+    vpn, sizeof(*vpn)) == -1)
  return (-1);
 
  /* export targets */
- if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_EXPORT, 0, 0,
+ if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_EXPORT, 0, 0,
     -1, NULL, 0) == -1)
  return (-1);
- if (send_filterset(ibuf_rde, &rd->export) == -1)
+ if (send_filterset(ibuf_rde, &vpn->export) == -1)
  return (-1);
- filterset_free(&rd->export);
+ filterset_free(&vpn->export);
 
  /* import targets */
- if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_IMPORT, 0, 0,
+ if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_IMPORT, 0, 0,
     -1, NULL, 0) == -1)
  return (-1);
- if (send_filterset(ibuf_rde, &rd->import) == -1)
+ if (send_filterset(ibuf_rde, &vpn->import) == -1)
  return (-1);
- filterset_free(&rd->import);
+ filterset_free(&vpn->import);
 
- if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_DONE, 0, 0,
+ if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_DONE, 0, 0,
     -1, NULL, 0) == -1)
  return (-1);
 
- free(rd);
+ free(vpn);
  }
 
  /* send a drain message to know when all messages where processed */
Index: usr.sbin/bgpd/bgpd.conf.5
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.conf.5,v
retrieving revision 1.183
diff -u -p -r1.183 bgpd.conf.5
--- usr.sbin/bgpd/bgpd.conf.5 4 Feb 2019 20:32:23 -0000 1.183
+++ usr.sbin/bgpd/bgpd.conf.5 7 Feb 2019 10:11:19 -0000
@@ -558,37 +558,56 @@ See also the
 section.
 .Sh MPLS VPN CONFIGURATION
 .Xr bgpd 8
-supports the setup and distribution of Virtual Private Networks.
-It is possible to import and export prefixes between routing domains.
-Each routing domain is specified by an
-.Ic rdomain
-section, which allows properties to be set specifically for that rdomain:
+supports the setup and distribution of MPLS Virtual Private Networks.
+A router can be configured to participate in a VPN by specifying a
+.Ic vpn
+section with a description for the VPN and an
+.Xr mpe 4
+interface.
+.Pp
+The vpn configuraion section allows properties to be set specifically
+for that VPN:
 .Bd -literal -offset indent
-rdomain 1 {
- descr "a rdomain"
- rd 65002:1
+vpn "description" on mpe1 {
+        rd 65002:1
  import-target rt 65002:42
  export-target rt 65002:42
  network 192.168.1/24
- depend on mpe0
 }
 .Ed
 .Pp
-There are several routing domain properties:
-.Pp
-.Bl -tag -width Ds -compact
-.It Ic depend on Ar interface
-Routes added to the rdomain will use this interface as the outgoing interface.
-Normally this will be an MPLS Provider Edge,
-.Xr mpe 4 ,
-interface that is part of the rdomain.
-Local networks will be announced with the MPLS label specified on the interface.
-.Pp
-.It Ic descr Ar description
-Add a description.
 The description is used when logging but has no further meaning to
 .Xr bgpd 8 .
 .Pp
+The
+.Xr mpe 4
+interface will be used as the outgoing interface for routes to
+the VPN, and local networks will be annouced with the MPLS label
+specified on the interface.
+The interface can provide VPN connectivity for another rdomain by
+being configured in that rdomain.
+The required rdomain must be configured on the interface before
+.Xr bgpd 8
+uses it.
+Multiple VPNs may be connected to a single rdomain, including the rdomain that
+.Xr bgpd 8
+is running in.
+.Pp
+An example
+.Xr hostname.if 8
+configuration for an
+.Xr mpe 4
+interface providing connectivity to rdomain 1:
+.Bd -literal -offset indent
+rdomain 1
+mplslabel 2000
+inet 192.198.0.1 255.255.255.255
+up
+.Ed
+.Pp
+There are several VPN properties:
+.Pp
+.Bl -tag -width Ds -compact
 .It Ic export-target Ar subtype Ar as-number : Ns Ar local
 .It Ic export-target Ar subtype Ar IP : Ns Ar local
 Specify an extended community which will be attached to announced networks.
Index: usr.sbin/bgpd/bgpd.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v
retrieving revision 1.364
diff -u -p -r1.364 bgpd.h
--- usr.sbin/bgpd/bgpd.h 4 Feb 2019 18:53:10 -0000 1.364
+++ usr.sbin/bgpd/bgpd.h 7 Feb 2019 10:11:19 -0000
@@ -81,7 +81,6 @@
 #define F_BLACKHOLE 0x0100
 #define F_LONGER 0x0200
 #define F_MPLS 0x0400
-#define F_REDISTRIBUTED 0x0800
 #define F_CTL_DETAIL 0x1000 /* only used by bgpctl */
 #define F_CTL_ADJ_IN 0x2000
 #define F_CTL_ADJ_OUT 0x4000
@@ -231,8 +230,8 @@ struct listen_addr {
 TAILQ_HEAD(listen_addrs, listen_addr);
 TAILQ_HEAD(filter_set_head, filter_set);
 
-struct rdomain;
-SIMPLEQ_HEAD(rdomain_head, rdomain);
+struct l3vpn;
+SIMPLEQ_HEAD(l3vpn_head, l3vpn);
 
 struct network;
 TAILQ_HEAD(network_head, network);
@@ -267,7 +266,7 @@ struct filter_rule;
 TAILQ_HEAD(filter_head, filter_rule);
 
 struct bgpd_config {
- struct rdomain_head rdomains;
+ struct l3vpn_head l3vpns;
  struct network_head networks;
  struct filter_head *filters;
  struct listen_addrs *listen_addrs;
@@ -411,7 +410,7 @@ struct network_config {
  struct filter_set_head attrset;
  struct rde_aspath *asp;
  char psname[SET_NAME_LEN];
- u_int rtableid;
+ u_int64_t rd;
  u_int16_t rtlabel;
  enum network_type type;
  u_int8_t prefixlen;
@@ -467,10 +466,10 @@ enum imsg_type {
  IMSG_RECONF_FILTER,
  IMSG_RECONF_LISTENER,
  IMSG_RECONF_CTRL,
- IMSG_RECONF_RDOMAIN,
- IMSG_RECONF_RDOMAIN_EXPORT,
- IMSG_RECONF_RDOMAIN_IMPORT,
- IMSG_RECONF_RDOMAIN_DONE,
+ IMSG_RECONF_VPN,
+ IMSG_RECONF_VPN_EXPORT,
+ IMSG_RECONF_VPN_IMPORT,
+ IMSG_RECONF_VPN_DONE,
  IMSG_RECONF_PREFIX_SET,
  IMSG_RECONF_PREFIX_SET_ITEM,
  IMSG_RECONF_AS_SET,
@@ -567,15 +566,18 @@ enum suberr_cease {
 struct kroute_node;
 struct kroute6_node;
 struct knexthop_node;
+struct kredist_node;
 RB_HEAD(kroute_tree, kroute_node);
 RB_HEAD(kroute6_tree, kroute6_node);
 RB_HEAD(knexthop_tree, knexthop_node);
+RB_HEAD(kredist_tree, kredist_node);
 
 struct ktable {
  char descr[PEER_DESCR_LEN];
  struct kroute_tree krt;
  struct kroute6_tree krt6;
  struct knexthop_tree knt;
+ struct kredist_tree kredist;
  struct network_head krn;
  u_int rtableid;
  u_int nhtableid; /* rdomain id for nexthop lookup */
@@ -1024,8 +1026,8 @@ struct as_set {
  int dirty;
 };
 
-struct rdomain {
- SIMPLEQ_ENTRY(rdomain) entry;
+struct l3vpn {
+ SIMPLEQ_ENTRY(l3vpn) entry;
  char descr[PEER_DESCR_LEN];
  char ifmpe[IFNAMSIZ];
  struct filter_set_head import;
@@ -1176,7 +1178,7 @@ void kr_nexthop_delete(u_int32_t, stru
     struct bgpd_config *);
 void kr_show_route(struct imsg *);
 void kr_ifinfo(char *);
-int kr_net_reload(u_int, struct network_head *);
+void kr_net_reload(u_int, u_int64_t, struct network_head *);
 int kr_reload(void);
 struct in6_addr *prefixlen2mask6(u_int8_t prefixlen);
 
Index: usr.sbin/bgpd/config.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/config.c,v
retrieving revision 1.79
diff -u -p -r1.79 config.c
--- usr.sbin/bgpd/config.c 27 Dec 2018 20:23:24 -0000 1.79
+++ usr.sbin/bgpd/config.c 7 Feb 2019 10:11:19 -0000
@@ -39,7 +39,7 @@
 u_int32_t get_bgpid(void);
 int host_ip(const char *, struct bgpd_addr *, u_int8_t *);
 void free_networks(struct network_head *);
-void free_rdomains(struct rdomain_head *);
+void free_l3vpns(struct l3vpn_head *);
 
 struct bgpd_config *
 new_config(void)
@@ -75,7 +75,7 @@ new_config(void)
 
  /* init the various list for later */
  TAILQ_INIT(&conf->networks);
- SIMPLEQ_INIT(&conf->rdomains);
+ SIMPLEQ_INIT(&conf->l3vpns);
  SIMPLEQ_INIT(&conf->prefixsets);
  SIMPLEQ_INIT(&conf->originsets);
  RB_INIT(&conf->roa);
@@ -101,16 +101,16 @@ free_networks(struct network_head *netwo
 }
 
 void
-free_rdomains(struct rdomain_head *rdomains)
+free_l3vpns(struct l3vpn_head *l3vpns)
 {
- struct rdomain *rd;
+ struct l3vpn *vpn;
 
- while ((rd = SIMPLEQ_FIRST(rdomains)) != NULL) {
- SIMPLEQ_REMOVE_HEAD(rdomains, entry);
- filterset_free(&rd->export);
- filterset_free(&rd->import);
- free_networks(&rd->net_l);
- free(rd);
+ while ((vpn = SIMPLEQ_FIRST(l3vpns)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(l3vpns, entry);
+ filterset_free(&vpn->export);
+ filterset_free(&vpn->import);
+ free_networks(&vpn->net_l);
+ free(vpn);
  }
 }
 
@@ -145,7 +145,7 @@ free_config(struct bgpd_config *conf)
  struct listen_addr *la;
  struct mrt *m;
 
- free_rdomains(&conf->rdomains);
+ free_l3vpns(&conf->l3vpns);
  free_networks(&conf->networks);
  filterlist_free(conf->filters);
  free_prefixsets(&conf->prefixsets);
@@ -250,9 +250,9 @@ merge_config(struct bgpd_config *xconf,
  TAILQ_INSERT_TAIL(&xconf->networks, n, entry);
  }
 
- /* switch the rdomain configs, first remove the old ones */
- free_rdomains(&xconf->rdomains);
- SIMPLEQ_CONCAT(&xconf->rdomains, &conf->rdomains);
+ /* switch the l3vpn configs, first remove the old ones */
+ free_l3vpns(&xconf->l3vpns);
+ SIMPLEQ_CONCAT(&xconf->l3vpns, &conf->l3vpns);
 
  /*
  * merge new listeners:
@@ -469,27 +469,42 @@ prepare_listeners(struct bgpd_config *co
 }
 
 int
-get_mpe_label(struct rdomain *r)
+get_mpe_config(const char *name, u_int *rdomain, u_int *label)
 {
  struct  ifreq ifr;
  struct shim_hdr shim;
  int s;
 
+ *label = 0;
+ *rdomain = 0;
+
  s = socket(AF_INET, SOCK_DGRAM, 0);
  if (s == -1)
  return (-1);
 
  bzero(&shim, sizeof(shim));
  bzero(&ifr, sizeof(ifr));
- strlcpy(ifr.ifr_name, r->ifmpe, sizeof(ifr.ifr_name));
+ strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
  ifr.ifr_data = (caddr_t)&shim;
 
  if (ioctl(s, SIOCGETLABEL, (caddr_t)&ifr) == -1) {
  close(s);
  return (-1);
  }
+
+ ifr.ifr_data = NULL;
+ if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) {
+ close(s);
+ return (-1);
+ }
+
  close(s);
- r->label = shim.shim_label;
+
+ *rdomain = ifr.ifr_rdomainid;
+ *label = shim.shim_label;
+
+ log_debug("%s: rdomain %u label %u", __func__, *rdomain, *label);
+
  return (0);
 }
 
Index: usr.sbin/bgpd/kroute.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/kroute.c,v
retrieving revision 1.229
diff -u -p -r1.229 kroute.c
--- usr.sbin/bgpd/kroute.c 18 Jan 2019 23:30:45 -0000 1.229
+++ usr.sbin/bgpd/kroute.c 7 Feb 2019 10:11:19 -0000
@@ -65,6 +65,14 @@ struct knexthop_node {
  void *kroute;
 };
 
+struct kredist_node {
+ RB_ENTRY(kredist_node) entry;
+ struct bgpd_addr prefix;
+ u_int64_t rd;
+ u_int8_t prefixlen;
+ u_int8_t dynamic;
+};
+
 struct kif_kr {
  LIST_ENTRY(kif_kr) entry;
  struct kroute_node *kr;
@@ -99,16 +107,16 @@ int kr6_delete(struct ktable *, struct k
 int krVPN4_delete(struct ktable *, struct kroute_full *, u_int8_t);
 int krVPN6_delete(struct ktable *, struct kroute_full *, u_int8_t);
 void kr_net_delete(struct network *);
-struct network *kr_net_match(struct ktable *, struct kroute *);
-struct network *kr_net_match6(struct ktable *, struct kroute6 *);
+int kr_net_match(struct ktable *, struct network_config *, u_int16_t);
 struct network *kr_net_find(struct ktable *, struct network *);
-int kr_redistribute(int, struct ktable *, struct kroute *);
-int kr_redistribute6(int, struct ktable *, struct kroute6 *);
+void kr_redistribute(int, struct ktable *, struct kroute *);
+void kr_redistribute6(int, struct ktable *, struct kroute6 *);
 struct kroute_full *kr_tofull(struct kroute *);
 struct kroute_full *kr6_tofull(struct kroute6 *);
 int kroute_compare(struct kroute_node *, struct kroute_node *);
 int kroute6_compare(struct kroute6_node *, struct kroute6_node *);
 int knexthop_compare(struct knexthop_node *, struct knexthop_node *);
+int kredist_compare(struct kredist_node *, struct kredist_node *);
 int kif_compare(struct kif_node *, struct kif_node *);
 void kr_fib_update_prio(u_int, u_int8_t);
 
@@ -185,6 +193,9 @@ RB_GENERATE(kroute6_tree, kroute6_node,
 RB_PROTOTYPE(knexthop_tree, knexthop_node, entry, knexthop_compare)
 RB_GENERATE(knexthop_tree, knexthop_node, entry, knexthop_compare)
 
+RB_PROTOTYPE(kredist_tree, kredist_node, entry, kredist_compare)
+RB_GENERATE(kredist_tree, kredist_node, entry, kredist_compare)
+
 RB_HEAD(kif_tree, kif_node) kit;
 RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare)
 RB_GENERATE(kif_tree, kif_node, entry, kif_compare)
@@ -399,35 +410,6 @@ ktable_update(u_int rtableid, char *name
  return (0);
 }
 
-void
-ktable_preload(void)
-{
- struct ktable *kt;
- u_int i;
-
- for (i = 0; i < krt_size; i++) {
- if ((kt = ktable_get(i)) == NULL)
- continue;
- kt->state = RECONF_DELETE;
- }
-}
-
-void
-ktable_postload(u_int8_t fib_prio)
-{
- struct ktable *kt;
- u_int i;
-
- for (i = krt_size; i > 0; i--) {
- if ((kt = ktable_get(i - 1)) == NULL)
- continue;
- if (kt->state == RECONF_DELETE)
- ktable_free(i - 1, fib_prio);
- else if (kt->state == RECONF_REINIT)
- kt->fib_sync = kt->fib_conf;
- }
-}
-
 int
 ktable_exists(u_int rtableid, u_int *rdomid)
 {
@@ -1199,93 +1181,105 @@ kr_net_delete(struct network *n)
  free(n);
 }
 
-struct network *
-kr_net_match(struct ktable *kt, struct kroute *kr)
+static int
+kr_net_redist_add(struct ktable *kt, struct network_config *net,
+    struct filter_set_head *attr, int dynamic)
+{
+ struct kredist_node *r, *xr;
+
+ if ((r = calloc(1, sizeof(*r))) == NULL)
+ fatal("%s", __func__);
+ r->prefix = net->prefix;
+ r->prefixlen = net->prefixlen;
+ r->rd = net->rd;
+ r->dynamic = dynamic;
+
+ xr = RB_INSERT(kredist_tree, &kt->kredist, r);
+ if (xr != NULL && dynamic != xr->dynamic) {
+ if (dynamic) {
+ /*
+ * ignore update, a non-dynamic announcement
+ * is already present.
+ */
+ free(r);
+ return 0;
+ }
+ /* non-dynamic announcments are preferred */
+ xr->dynamic = dynamic;
+ }
+
+ if (send_network(IMSG_NETWORK_ADD, net, attr) == -1)
+ log_warnx("%s: faild to send network update", __func__);
+ return 1;
+}
+
+static void
+kr_net_redist_del(struct ktable *kt, struct network_config *net, int dynamic)
 {
- struct network *xn;
+ struct kredist_node *r, node;
 
- TAILQ_FOREACH(xn, &kt->krn, entry) {
- if (xn->net.prefix.aid != AID_INET)
- continue;
- switch (xn->net.type) {
- case NETWORK_DEFAULT:
- if (xn->net.prefixlen == kr->prefixlen &&
-    xn->net.prefix.v4.s_addr == kr->prefix.s_addr)
- /* static match already redistributed */
- return (NULL);
- break;
- case NETWORK_STATIC:
- if (kr->flags & F_STATIC)
- return (xn);
- break;
- case NETWORK_CONNECTED:
- if (kr->flags & F_CONNECTED)
- return (xn);
- break;
- case NETWORK_RTLABEL:
- if (kr->labelid == xn->net.rtlabel)
- return (xn);
- break;
- case NETWORK_MRTCLONE:
- /* can not happen */
- break;
- case NETWORK_PRIORITY:
- if (kr->priority == xn->net.priority)
- return (xn);
- break;
- case NETWORK_PREFIXSET:
- /* must not happen */
- log_warnx("%s: found a NETWORK_PREFIXSET, "
-    "please send a bug report", __func__);
- break;
- }
+ bzero(&node, sizeof(node));
+ node.prefix = net->prefix;
+ node.prefixlen = net->prefixlen;
+ node.rd = net->rd;
+
+ r = RB_FIND(kredist_tree, &kt->kredist, &node);
+ if (r == NULL || dynamic != r->dynamic)
+ return;
+
+ if (RB_REMOVE(kredist_tree, &kt->kredist, r) == NULL) {
+ log_warnx("%s: failed to remove network %s/%u", __func__,
+    log_addr(&node.prefix), node.prefixlen);
+ return;
  }
- return (NULL);
+ free(r);
+
+ if (send_network(IMSG_NETWORK_REMOVE, net, NULL) == -1)
+ log_warnx("%s: faild to send network update", __func__);
 }
 
-struct network *
-kr_net_match6(struct ktable *kt, struct kroute6 *kr6)
+int
+kr_net_match(struct ktable *kt, struct network_config *net, u_int16_t flags)
 {
  struct network *xn;
+ int matched = 0;
 
  TAILQ_FOREACH(xn, &kt->krn, entry) {
- if (xn->net.prefix.aid != AID_INET6)
+ if (xn->net.prefix.aid != net->prefix.aid)
  continue;
  switch (xn->net.type) {
  case NETWORK_DEFAULT:
- if (xn->net.prefixlen == kr6->prefixlen &&
-    memcmp(&xn->net.prefix.v6, &kr6->prefix,
-    sizeof(struct in6_addr)) == 0)
- /* static match already redistributed */
- return (NULL);
- break;
+ /* static match already redistributed */
+ continue;
  case NETWORK_STATIC:
- if (kr6->flags & F_STATIC)
- return (xn);
- break;
+ if (flags & F_STATIC)
+ break;
+ continue;
  case NETWORK_CONNECTED:
- if (kr6->flags & F_CONNECTED)
- return (xn);
- break;
+ if (flags & F_CONNECTED)
+ break;
+ continue;
  case NETWORK_RTLABEL:
- if (kr6->labelid == xn->net.rtlabel)
- return (xn);
- break;
- case NETWORK_MRTCLONE:
- /* can not happen */
- break;
+ if (net->rtlabel == xn->net.rtlabel)
+ break;
+ continue;
  case NETWORK_PRIORITY:
- if (kr6->priority == xn->net.priority)
- return (xn);
- break;
+ if (net->priority == xn->net.priority)
+ break;
+ continue;
+ case NETWORK_MRTCLONE:
  case NETWORK_PREFIXSET:
  /* must not happen */
  log_warnx("%s: found a NETWORK_PREFIXSET, "
     "please send a bug report", __func__);
- break;
+ continue;
  }
+
+ net->rd = xn->net.rd;
+ if (kr_net_redist_add(kt, net, &xn->net.attrset, 1))
+ matched = 1;
  }
- return (NULL);
+ return matched;
 }
 
 struct network *
@@ -1296,7 +1290,7 @@ kr_net_find(struct ktable *kt, struct ne
  TAILQ_FOREACH(xn, &kt->krn, entry) {
  if (n->net.type != xn->net.type ||
     n->net.prefixlen != xn->net.prefixlen ||
-    n->net.rtableid != xn->net.rtableid)
+    n->net.rd != xn->net.rd)
  continue;
  if (memcmp(&n->net.prefix, &xn->net.prefix,
     sizeof(n->net.prefix)) == 0)
@@ -1305,26 +1299,21 @@ kr_net_find(struct ktable *kt, struct ne
  return (NULL);
 }
 
-int
-kr_net_reload(u_int rtableid, struct network_head *nh)
+void
+kr_net_reload(u_int rtableid, u_int64_t rd, struct network_head *nh)
 {
  struct network *n, *xn;
  struct ktable *kt;
 
- if ((kt = ktable_get(rtableid)) == NULL) {
- log_warnx("%s: non-existent rtableid %d", __func__, rtableid);
- return (-1);
- }
-
- TAILQ_FOREACH(n, &kt->krn, entry)
- n->net.old = 1;
+ if ((kt = ktable_get(rtableid)) == NULL)
+ fatalx("%s: non-existent rtableid %d", __func__, rtableid);
 
  while ((n = TAILQ_FIRST(nh)) != NULL) {
  log_debug("%s: processing %s/%u", __func__,
     log_addr(&n->net.prefix), n->net.prefixlen);
  TAILQ_REMOVE(nh, n, entry);
  n->net.old = 0;
- n->net.rtableid = rtableid;
+ n->net.rd = rd;
  xn = kr_net_find(kt, n);
  if (xn) {
  xn->net.old = 0;
@@ -1334,44 +1323,33 @@ kr_net_reload(u_int rtableid, struct net
  } else
  TAILQ_INSERT_TAIL(&kt->krn, n, entry);
  }
-
- for (n = TAILQ_FIRST(&kt->krn); n != NULL; n = xn) {
- xn = TAILQ_NEXT(n, entry);
- if (n->net.old) {
- if (n->net.type == NETWORK_DEFAULT)
- if (send_network(IMSG_NETWORK_REMOVE, &n->net,
-    NULL))
- return (-1);
- TAILQ_REMOVE(&kt->krn, n, entry);
- kr_net_delete(n);
- }
- }
-
- return (0);
 }
 
-int
+void
 kr_redistribute(int type, struct ktable *kt, struct kroute *kr)
 {
- struct network *match;
  struct network_config net;
  u_int32_t a;
 
+ bzero(&net, sizeof(net));
+ net.prefix.aid = AID_INET;
+ net.prefix.v4.s_addr = kr->prefix.s_addr;
+ net.prefixlen = kr->prefixlen;
+ net.rtlabel = kr->labelid;
+ net.priority = kr->priority;
+
  /* shortcut for removals */
  if (type == IMSG_NETWORK_REMOVE) {
- if (!(kr->flags & F_REDISTRIBUTED))
- return (0); /* no match, don't redistribute */
- kr->flags &= ~F_REDISTRIBUTED;
- match = NULL;
- goto sendit;
+ kr_net_redist_del(kt, &net, 1);
+ return;
  }
 
  if (!(kr->flags & F_KERNEL))
- return (0);
+ return;
 
  /* Dynamic routes are not redistributable. */
  if (kr->flags & F_DYNAMIC)
- return (0);
+ return;
 
  /*
  * We consider the loopback net, multicast and experimental addresses
@@ -1380,61 +1358,48 @@ kr_redistribute(int type, struct ktable
  a = ntohl(kr->prefix.s_addr);
  if (IN_MULTICAST(a) || IN_BADCLASS(a) ||
     (a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
- return (0);
+ return;
 
  /* Consider networks with nexthop loopback as not redistributable. */
  if (kr->nexthop.s_addr == htonl(INADDR_LOOPBACK))
- return (0);
+ return;
 
  /*
  * never allow 0.0.0.0/0 the default route can only be redistributed
  * with announce default.
  */
  if (kr->prefix.s_addr == INADDR_ANY && kr->prefixlen == 0)
- return (0);
-
- match = kr_net_match(kt, kr);
- if (match == NULL) {
- if (!(kr->flags & F_REDISTRIBUTED))
- return (0); /* no match, don't redistribute */
- /* route no longer matches but is redistributed, so remove */
- kr->flags &= ~F_REDISTRIBUTED;
- type = IMSG_NETWORK_REMOVE;
- } else
- kr->flags |= F_REDISTRIBUTED;
-
-sendit:
- bzero(&net, sizeof(net));
- net.prefix.aid = AID_INET;
- net.prefix.v4.s_addr = kr->prefix.s_addr;
- net.prefixlen = kr->prefixlen;
- net.rtlabel = kr->labelid;
- net.rtableid = kt->rtableid;
+ return;
 
- return (send_network(type, &net, match ? &match->net.attrset : NULL));
+ if (kr_net_match(kt, &net, kr->flags) == 0)
+ /* no longer matches, if still present remove it */
+ kr_net_redist_del(kt, &net, 1);
 }
 
-int
+void
 kr_redistribute6(int type, struct ktable *kt, struct kroute6 *kr6)
 {
- struct network *match;
  struct network_config net;
 
+ bzero(&net, sizeof(net));
+ net.prefix.aid = AID_INET6;
+ memcpy(&net.prefix.v6, &kr6->prefix, sizeof(struct in6_addr));
+ net.prefixlen = kr6->prefixlen;
+ net.rtlabel = kr6->labelid;
+ net.priority = kr6->priority;
+
  /* shortcut for removals */
  if (type == IMSG_NETWORK_REMOVE) {
- if (!(kr6->flags & F_REDISTRIBUTED))
- return (0); /* no match, don't redistribute */
- kr6->flags &= ~F_REDISTRIBUTED;
- match = NULL;
- goto sendit;
+ kr_net_redist_del(kt, &net, 1);
+ return;
  }
 
  if (!(kr6->flags & F_KERNEL))
- return (0);
+ return;
 
  /* Dynamic routes are not redistributable. */
  if (kr6->flags & F_DYNAMIC)
- return (0);
+ return;
 
  /*
  * We consider unspecified, loopback, multicast, link- and site-local,
@@ -1447,13 +1412,13 @@ kr_redistribute6(int type, struct ktable
     IN6_IS_ADDR_SITELOCAL(&kr6->prefix) ||
     IN6_IS_ADDR_V4MAPPED(&kr6->prefix) ||
     IN6_IS_ADDR_V4COMPAT(&kr6->prefix))
- return (0);
+ return;
 
  /*
  * Consider networks with nexthop loopback as not redistributable.
  */
  if (IN6_IS_ADDR_LOOPBACK(&kr6->nexthop))
- return (0);
+ return;
 
  /*
  * never allow ::/0 the default route can only be redistributed
@@ -1461,26 +1426,57 @@ kr_redistribute6(int type, struct ktable
  */
  if (kr6->prefixlen == 0 &&
     memcmp(&kr6->prefix, &in6addr_any, sizeof(struct in6_addr)) == 0)
- return (0);
+ return;
 
- match = kr_net_match6(kt, kr6);
- if (match == NULL) {
- if (!(kr6->flags & F_REDISTRIBUTED))
- return (0); /* no match, don't redistribute */
- /* route no longer matches but is redistributed, so remove */
- kr6->flags &= ~F_REDISTRIBUTED;
- type = IMSG_NETWORK_REMOVE;
- } else
- kr6->flags |= F_REDISTRIBUTED;
-sendit:
- bzero(&net, sizeof(net));
- net.prefix.aid = AID_INET6;
- memcpy(&net.prefix.v6, &kr6->prefix, sizeof(struct in6_addr));
- net.prefixlen = kr6->prefixlen;
- net.rtlabel = kr6->labelid;
- net.rtableid = kt->rtableid;
+ if (kr_net_match(kt, &net, kr6->flags) == 0)
+ /* no longer matches, if still present remove it */
+ kr_net_redist_del(kt, &net, 1);
+}
+
+void
+ktable_preload(void)
+{
+ struct ktable *kt;
+ struct network *n;
+ u_int i;
+
+ for (i = 0; i < krt_size; i++) {
+ if ((kt = ktable_get(i)) == NULL)
+ continue;
+ kt->state = RECONF_DELETE;
+
+ /* mark all networks as old */
+ TAILQ_FOREACH(n, &kt->krn, entry)
+ n->net.old = 1;
+ }
+}
+
+void
+ktable_postload(u_int8_t fib_prio)
+{
+ struct ktable *kt;
+ struct network *n, *xn;
+ u_int i;
+
+ for (i = krt_size; i > 0; i--) {
+ if ((kt = ktable_get(i - 1)) == NULL)
+ continue;
+ if (kt->state == RECONF_DELETE) {
+ ktable_free(i - 1, fib_prio);
+ continue;
+ } else if (kt->state == RECONF_REINIT)
+ kt->fib_sync = kt->fib_conf;
 
- return (send_network(type, &net, match ? &match->net.attrset : NULL));
+ /* cleanup old networks */
+ TAILQ_FOREACH_SAFE(n, &kt->krn, entry, xn) {
+ if (n->net.old) {
+ TAILQ_REMOVE(&kt->krn, n, entry);
+ if (n->net.type == NETWORK_DEFAULT)
+ kr_net_redist_del(kt, &n->net, 0);
+ kr_net_delete(n);
+ }
+ }
+ }
 }
 
 int
@@ -1503,9 +1499,8 @@ kr_reload(void)
 
  TAILQ_FOREACH(n, &kt->krn, entry)
  if (n->net.type == NETWORK_DEFAULT) {
- if (send_network(IMSG_NETWORK_ADD, &n->net,
-    &n->net.attrset))
- return (-1);
+ kr_net_redist_add(kt, &n->net,
+    &n->net.attrset, 0);
  } else
  hasdyn = 1;
 
@@ -1640,13 +1635,53 @@ knexthop_compare(struct knexthop_node *a
  }
  break;
  default:
- fatalx("knexthop_compare: unknown AF");
+ fatalx("%s: unknown AF", __func__);
  }
 
  return (0);
 }
 
 int
+kredist_compare(struct kredist_node *a, struct kredist_node *b)
+{
+ int i;
+
+ if (a->prefix.aid != b->prefix.aid)
+ return (b->prefix.aid - a->prefix.aid);
+
+ if (a->prefixlen < b->prefixlen)
+ return (-1);
+ if (a->prefixlen > b->prefixlen)
+ return (1);
+
+ switch (a->prefix.aid) {
+ case AID_INET:
+ if (ntohl(a->prefix.v4.s_addr) < ntohl(b->prefix.v4.s_addr))
+ return (-1);
+ if (ntohl(a->prefix.v4.s_addr) > ntohl(b->prefix.v4.s_addr))
+ return (1);
+ break;
+ case AID_INET6:
+ for (i = 0; i < 16; i++) {
+ if (a->prefix.v6.s6_addr[i] < b->prefix.v6.s6_addr[i])
+ return (-1);
+ if (a->prefix.v6.s6_addr[i] > b->prefix.v6.s6_addr[i])
+ return (1);
+ }
+ break;
+ default:
+ fatalx("%s: unknown AF", __func__);
+ }
+
+ if (a->rd < b->rd)
+ return (-1);
+ if (a->rd > b->rd)
+ return (1);
+
+ return (0);
+}
+
+int
 kif_compare(struct kif_node *a, struct kif_node *b)
 {
  return (b->k.ifindex - a->k.ifindex);
@@ -3506,21 +3541,14 @@ dispatch_rtmsg_addr(struct rt_msghdr *rt
  changed = 1;
  kr->r.flags = flags;
 
- if (rtlabel_changed) {
- if (oflags & F_REDISTRIBUTED) {
- kr->r.flags |= F_REDISTRIBUTED;
- kr_redistribute(
-    IMSG_NETWORK_REMOVE, kt,
-    &kr->r);
- }
+ if (rtlabel_changed)
  kr_redistribute(IMSG_NETWORK_ADD,
     kt, &kr->r);
- }
 
  if ((oflags & F_CONNECTED) &&
     !(flags & F_CONNECTED)) {
  kif_kr_remove(kr);
- kr_redistribute(IMSG_NETWORK_REMOVE,
+ kr_redistribute(IMSG_NETWORK_ADD,
     kt, &kr->r);
  }
  if ((flags & F_CONNECTED) &&
@@ -3619,21 +3647,14 @@ add4:
  changed = 1;
  kr6->r.flags = flags;
 
- if (rtlabel_changed) {
- if (oflags & F_REDISTRIBUTED) {
- kr6->r.flags |= F_REDISTRIBUTED;
- kr_redistribute6(
-    IMSG_NETWORK_REMOVE, kt,
-    &kr6->r);
- }
+ if (rtlabel_changed)
  kr_redistribute6(IMSG_NETWORK_ADD,
     kt, &kr6->r);
- }
 
  if ((oflags & F_CONNECTED) &&
     !(flags & F_CONNECTED)) {
  kif_kr6_remove(kr6);
- kr_redistribute6(IMSG_NETWORK_REMOVE,
+ kr_redistribute6(IMSG_NETWORK_ADD,
     kt, &kr6->r);
  }
  if ((flags & F_CONNECTED) &&
Index: usr.sbin/bgpd/parse.y
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/parse.y,v
retrieving revision 1.369
diff -u -p -r1.369 parse.y
--- usr.sbin/bgpd/parse.y 4 Feb 2019 18:53:10 -0000 1.369
+++ usr.sbin/bgpd/parse.y 7 Feb 2019 10:11:19 -0000
@@ -91,7 +91,7 @@ static struct network_head *netconf;
 static struct peer *peer_l, *peer_l_old;
 static struct peer *curpeer;
 static struct peer *curgroup;
-static struct rdomain *currdom;
+static struct l3vpn *curvpn;
 static struct prefixset *curpset, *curoset;
 static struct prefixset_tree *curpsitree;
 static struct filter_head *filter_l;
@@ -154,7 +154,6 @@ void optimize_filters(struct filter_he
 struct filter_rule *get_rule(enum action_types);
 
 int parsecommunity(struct filter_community *, int, char *);
-int parsesubtype(char *, int *, int *);
 int parseextcommunity(struct filter_community *, char *,
     char *);
 static int new_as_set(char *);
@@ -195,7 +194,7 @@ typedef struct {
 %}
 
 %token AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE FIBPRIORITY RTABLE
-%token RDOMAIN RD EXPORT EXPORTTRGT IMPORTTRGT
+%token NONE UNICAST VPN RD EXPORT EXPORTTRGT IMPORTTRGT
 %token RDE RIB EVALUATE IGNORE COMPARE
 %token GROUP NEIGHBOR NETWORK
 %token EBGP IBGP
@@ -223,7 +222,7 @@ typedef struct {
 %token <v.string> STRING
 %token <v.number> NUMBER
 %type <v.number> asnumber as4number as4number_any optnumber
-%type <v.number> espah family restart origincode nettype
+%type <v.number> espah family safi restart origincode nettype
 %type <v.number> yesno inout restricted validity
 %type <v.string> string
 %type <v.addr> address
@@ -253,7 +252,7 @@ grammar : /* empty */
  | grammar roa_set '\n'
  | grammar origin_set '\n'
  | grammar conf_main '\n'
- | grammar rdomain '\n'
+ | grammar l3vpn '\n'
  | grammar neighbor '\n'
  | grammar group '\n'
  | grammar filterrule '\n'
@@ -1044,39 +1043,57 @@ optnumber : /* empty */ { $$ = 0; }
  | NUMBER
  ;
 
-rdomain : RDOMAIN NUMBER {
- if ($2 > RT_TABLEID_MAX) {
- yyerror("rtable %llu too big: max %u", $2,
-    RT_TABLEID_MAX);
- YYERROR;
+l3vpn : VPN STRING ON STRING {
+ u_int rdomain, label;
+
+ if (get_mpe_config($4, &rdomain, &label) == -1) {
+ yyerror("troubles getting config of %s", $4);
+ if ((cmd_opts & BGPD_OPT_NOACTION) == 0) {
+ free($4);
+ free($2);
+ YYERROR;
+ }
  }
- if ((cmd_opts & BGPD_OPT_NOACTION) == 0 &&
-    ktable_exists($2, NULL) != 1) {
- yyerror("rdomain %lld does not exist", $2);
+
+ if (!(curvpn = calloc(1, sizeof(struct l3vpn))))
+ fatal(NULL);
+ strlcpy(curvpn->ifmpe, $4, IFNAMSIZ);
+
+ if (strlcpy(curvpn->descr, $2,
+    sizeof(curvpn->descr)) >=
+    sizeof(curvpn->descr)) {
+ yyerror("descr \"%s\" too long: max %zu",
+    $2, sizeof(curvpn->descr) - 1);
+ free($2);
+ free($4);
+ free(curvpn);
+ curvpn = NULL;
  YYERROR;
  }
- if (!(currdom = calloc(1, sizeof(struct rdomain))))
- fatal(NULL);
- currdom->rtableid = $2;
- TAILQ_INIT(&currdom->import);
- TAILQ_INIT(&currdom->export);
- TAILQ_INIT(&currdom->net_l);
- netconf = &currdom->net_l;
- } '{' rdomainopts_l '}' {
+ free($2);
+ free($4);
+
+ TAILQ_INIT(&curvpn->import);
+ TAILQ_INIT(&curvpn->export);
+ TAILQ_INIT(&curvpn->net_l);
+ curvpn->label = label;
+ curvpn->rtableid = rdomain;
+ netconf = &curvpn->net_l;
+ } '{' l3vpnopts_l '}' {
  /* insert into list */
- SIMPLEQ_INSERT_TAIL(&conf->rdomains, currdom, entry);
- currdom = NULL;
+ SIMPLEQ_INSERT_TAIL(&conf->l3vpns, curvpn, entry);
+ curvpn = NULL;
  netconf = &conf->networks;
  }
  ;
 
-rdomainopts_l : /* empty */
- | rdomainopts_l '\n'
- | rdomainopts_l rdomainopts '\n'
- | rdomainopts_l error '\n'
+l3vpnopts_l : /* empty */
+ | l3vpnopts_l '\n'
+ | l3vpnopts_l l3vpnopts '\n'
+ | l3vpnopts_l error '\n'
  ;
 
-rdomainopts : RD STRING {
+l3vpnopts : RD STRING {
  struct filter_community ext;
  u_int64_t rd;
 
@@ -1108,7 +1125,7 @@ rdomainopts : RD STRING {
  yyerror("bad encoding of rd");
  YYERROR;
  }
- currdom->rd = htobe64(rd);
+ curvpn->rd = htobe64(rd);
  }
  | EXPORTTRGT STRING STRING {
  struct filter_set *set;
@@ -1126,7 +1143,7 @@ rdomainopts : RD STRING {
  }
  free($3);
  free($2);
- TAILQ_INSERT_TAIL(&currdom->export, set, entry);
+ TAILQ_INSERT_TAIL(&curvpn->export, set, entry);
  }
  | IMPORTTRGT STRING STRING {
  struct filter_set *set;
@@ -1144,44 +1161,15 @@ rdomainopts : RD STRING {
  }
  free($3);
  free($2);
- TAILQ_INSERT_TAIL(&currdom->import, set, entry);
- }
- | DESCR string {
- if (strlcpy(currdom->descr, $2,
-    sizeof(currdom->descr)) >=
-    sizeof(currdom->descr)) {
- yyerror("descr \"%s\" too long: max %zu",
-    $2, sizeof(currdom->descr) - 1);
- free($2);
- YYERROR;
- }
- free($2);
+ TAILQ_INSERT_TAIL(&curvpn->import, set, entry);
  }
  | FIBUPDATE yesno {
  if ($2 == 0)
- currdom->flags |= F_RIB_NOFIBSYNC;
+ curvpn->flags |= F_RIB_NOFIBSYNC;
  else
- currdom->flags &= ~F_RIB_NOFIBSYNC;
+ curvpn->flags &= ~F_RIB_NOFIBSYNC;
  }
  | network
- | DEPEND ON STRING {
- /* XXX this is a hack */
- if ((cmd_opts & BGPD_OPT_NOACTION) == 0 &&
-    if_nametoindex($3) == 0) {
- yyerror("interface %s does not exist", $3);
- free($3);
- YYERROR;
- }
- strlcpy(currdom->ifmpe, $3, IFNAMSIZ);
- free($3);
- /* XXX this is in the wrong place */
- if ((cmd_opts & BGPD_OPT_NOACTION) == 0 &&
-    get_mpe_label(currdom)) {
- yyerror("failed to get mpls label from %s",
-    currdom->ifmpe);
- YYERROR;
- }
- }
  ;
 
 neighbor : { curpeer = new_peer(); }
@@ -1350,30 +1338,23 @@ peeropts : REMOTEAS as4number {
  }
  curpeer->conf.min_holdtime = $3;
  }
- | ANNOUNCE family STRING {
+ | ANNOUNCE family safi {
  u_int8_t aid, safi;
- int8_t val = 1;
+ u_int16_t afi;
 
- if (!strcmp($3, "none")) {
- safi = SAFI_UNICAST;
- val = 0;
- } else if (!strcmp($3, "unicast")) {
- safi = SAFI_UNICAST;
- } else if (!strcmp($3, "vpn")) {
- safi = SAFI_MPLSVPN;
+ if ($3 == SAFI_NONE) {
+ for (aid = 0; aid < AID_MAX; aid++) {
+ if (aid2afi(aid, &afi, &safi) == -1)
+ continue;
+ curpeer->conf.capabilities.mp[aid] = 0;
+ }
  } else {
- yyerror("unknown/unsupported SAFI \"%s\"",
-    $3);
- free($3);
- YYERROR;
- }
- free($3);
-
- if (afi2aid($2, safi, &aid) == -1) {
- yyerror("unknown AFI/SAFI pair");
- YYERROR;
+ if (afi2aid($2, $3, &aid) == -1) {
+ yyerror("unknown AFI/SAFI pair");
+ YYERROR;
+ }
+ curpeer->conf.capabilities.mp[aid] = 1;
  }
- curpeer->conf.capabilities.mp[aid] = val;
  }
  | ANNOUNCE CAPABILITIES yesno {
  curpeer->conf.announce_capa = $3;
@@ -1717,6 +1698,11 @@ family : IPV4 { $$ = AFI_IPv4; }
  | IPV6 { $$ = AFI_IPv6; }
  ;
 
+safi : NONE { $$ = SAFI_NONE; }
+ | UNICAST { $$ = SAFI_UNICAST; }
+ | VPN { $$ = SAFI_MPLSVPN; }
+ ;
+
 nettype : STATIC { $$ = 1; },
  | CONNECTED { $$ = 0; }
  ;
@@ -2880,6 +2866,7 @@ lookup(char *s)
  { "network", NETWORK},
  { "nexthop", NEXTHOP},
  { "no-modify", NOMODIFY},
+ { "none", NONE},
  { "on", ON},
  { "or-longer", LONGER},
  { "origin", ORIGIN},
@@ -2900,7 +2887,6 @@ lookup(char *s)
  { "quick", QUICK},
  { "rd", RD},
  { "rde", RDE},
- { "rdomain", RDOMAIN},
  { "refresh", REFRESH },
  { "reject", REJECT},
  { "remote-as", REMOTEAS},
@@ -2924,7 +2910,9 @@ lookup(char *s)
  { "transit-as", TRANSITAS},
  { "transparent-as", TRANSPARENT},
  { "ttl-security", TTLSECURITY},
+ { "unicast", UNICAST},
  { "via", VIA},
+ { "vpn", VPN},
  { "weight", WEIGHT}
  };
  const struct keywords *p;
@@ -3586,7 +3574,7 @@ parsecommunity(struct filter_community *
  return (0);
 }
 
-int
+static int
 parsesubtype(char *name, int *type, int *subtype)
 {
  const struct ext_comm_pairs *cp;
Index: usr.sbin/bgpd/printconf.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/printconf.c,v
retrieving revision 1.127
diff -u -p -r1.127 printconf.c
--- usr.sbin/bgpd/printconf.c 4 Feb 2019 18:53:10 -0000 1.127
+++ usr.sbin/bgpd/printconf.c 7 Feb 2019 10:11:19 -0000
@@ -35,8 +35,8 @@ void print_community(struct filter_com
 void print_origin(u_int8_t);
 void print_set(struct filter_set_head *);
 void print_mainconf(struct bgpd_config *);
-void print_rdomain_targets(struct filter_set_head *, const char *);
-void print_rdomain(struct rdomain *);
+void print_l3vpn_targets(struct filter_set_head *, const char *);
+void print_l3vpn(struct l3vpn *);
 const char *print_af(u_int8_t);
 void print_network(struct network_config *, const char *);
 void print_as_sets(struct as_set_head *);
@@ -378,7 +378,7 @@ print_mainconf(struct bgpd_config *conf)
 }
 
 void
-print_rdomain_targets(struct filter_set_head *set, const char *tgt)
+print_l3vpn_targets(struct filter_set_head *set, const char *tgt)
 {
  struct filter_set *s;
  TAILQ_FOREACH(s, set, entry) {
@@ -389,27 +389,24 @@ print_rdomain_targets(struct filter_set_
 }
 
 void
-print_rdomain(struct rdomain *r)
+print_l3vpn(struct l3vpn *vpn)
 {
  struct network *n;
 
- printf("rdomain %u {\n", r->rtableid);
- if (*r->descr)
- printf("\tdescr \"%s\"\n", r->descr);
- if (r->flags & F_RIB_NOFIBSYNC)
+ printf("vpn \"%s\" on %s {\n", vpn->descr, vpn->ifmpe);
+ printf("\n\t%s\n", log_rd(vpn->rd));
+
+ print_l3vpn_targets(&vpn->export, "export-target");
+ print_l3vpn_targets(&vpn->import, "import-target");
+
+ if (vpn->flags & F_RIB_NOFIBSYNC)
  printf("\tfib-update no\n");
  else
  printf("\tfib-update yes\n");
- printf("\tdepend on %s\n", r->ifmpe);
 
- TAILQ_FOREACH(n, &r->net_l, entry)
+ TAILQ_FOREACH(n, &vpn->net_l, entry)
  print_network(&n->net, "\t");
 
- printf("\n\t%s\n", log_rd(r->rd));
-
- print_rdomain_targets(&r->export, "export-target");
- print_rdomain_targets(&r->import, "import-target");
-
  printf("}\n");
 }
 
@@ -958,12 +955,12 @@ void
 print_config(struct bgpd_config *conf, struct rib_names *rib_l,
     struct network_head *net_l, struct peer *peer_l,
     struct filter_head *rules_l, struct mrt_head *mrt_l,
-    struct rdomain_head *rdom_l)
+    struct l3vpn_head *vpns_l)
 {
  struct filter_rule *r;
  struct network *n;
  struct rde_rib *rr;
- struct rdomain *rd;
+ struct l3vpn *vpn;
 
  print_mainconf(conf);
  print_roa(&conf->roa);
@@ -972,10 +969,10 @@ print_config(struct bgpd_config *conf, s
  print_originsets(&conf->originsets);
  TAILQ_FOREACH(n, net_l, entry)
  print_network(&n->net, "");
- if (!SIMPLEQ_EMPTY(rdom_l))
+ if (!SIMPLEQ_EMPTY(vpns_l))
  printf("\n");
- SIMPLEQ_FOREACH(rd, rdom_l, entry)
- print_rdomain(rd);
+ SIMPLEQ_FOREACH(vpn, vpns_l, entry)
+ print_l3vpn(vpn);
  printf("\n");
  SIMPLEQ_FOREACH(rr, rib_l, entry) {
  if (rr->flags & F_RIB_NOEVALUATE)
Index: usr.sbin/bgpd/rde.c
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
retrieving revision 1.461
diff -u -p -r1.461 rde.c
--- usr.sbin/bgpd/rde.c 21 Jan 2019 02:07:56 -0000 1.461
+++ usr.sbin/bgpd/rde.c 7 Feb 2019 10:11:19 -0000
@@ -75,7 +75,7 @@ void rde_dump_ctx_throttle(pid_t, int)
 void rde_dump_ctx_terminate(pid_t);
 void rde_dump_mrt_new(struct mrt *, pid_t, int);
 
-int rde_rdomain_import(struct rde_aspath *, struct rdomain *);
+int rde_l3vpn_import(struct rde_aspath *, struct l3vpn *);
 void rde_reload_done(void);
 static void rde_softreconfig_in_done(void *, u_int8_t);
 static void rde_softreconfig_out_done(void *, u_int8_t);
@@ -124,7 +124,7 @@ struct rde_prefixset_head originsets_old
 struct rde_prefixset roa_old;
 struct as_set_head *as_sets_tmp, *as_sets_old;
 struct filter_head *out_rules, *out_rules_tmp;
-struct rdomain_head *rdomains_l, *newdomains;
+struct l3vpn_head *l3vpns_l, *newdomains;
 struct imsgbuf *ibuf_se;
 struct imsgbuf *ibuf_se_ctl;
 struct imsgbuf *ibuf_main;
@@ -227,10 +227,10 @@ rde_main(int debug, int verbose)
  fatal(NULL);
  TAILQ_INIT(out_rules);
 
- rdomains_l = calloc(1, sizeof(struct rdomain_head));
- if (rdomains_l == NULL)
+ l3vpns_l = calloc(1, sizeof(struct l3vpn_head));
+ if (l3vpns_l == NULL)
  fatal(NULL);
- SIMPLEQ_INIT(rdomains_l);
+ SIMPLEQ_INIT(l3vpns_l);
 
  if ((conf = calloc(1, sizeof(struct bgpd_config))) == NULL)
  fatal(NULL);
@@ -675,7 +675,7 @@ rde_dispatch_imsg_parent(struct imsgbuf
  static struct rde_prefixset *last_prefixset;
  static struct as_set *last_as_set;
  static struct set_table *last_set;
- static struct rdomain *rd;
+ static struct l3vpn *vpn;
  struct imsg imsg;
  struct mrt xmrt;
  struct rde_rib rn;
@@ -764,7 +764,7 @@ rde_dispatch_imsg_parent(struct imsgbuf
  if (out_rules_tmp == NULL)
  fatal(NULL);
  TAILQ_INIT(out_rules_tmp);
- newdomains = calloc(1, sizeof(struct rdomain_head));
+ newdomains = calloc(1, sizeof(struct l3vpn_head));
  if (newdomains == NULL)
  fatal(NULL);
  SIMPLEQ_INIT(newdomains);
@@ -948,34 +948,34 @@ rde_dispatch_imsg_parent(struct imsgbuf
  set_prep(last_as_set->set);
  last_as_set = NULL;
  break;
- case IMSG_RECONF_RDOMAIN:
+ case IMSG_RECONF_VPN:
  if (imsg.hdr.len - IMSG_HEADER_SIZE !=
-    sizeof(struct rdomain))
- fatalx("IMSG_RECONF_RDOMAIN bad len");
- if ((rd = malloc(sizeof(struct rdomain))) == NULL)
+    sizeof(struct l3vpn))
+ fatalx("IMSG_RECONF_VPN bad len");
+ if ((vpn = malloc(sizeof(struct l3vpn))) == NULL)
  fatal(NULL);
- memcpy(rd, imsg.data, sizeof(struct rdomain));
- TAILQ_INIT(&rd->import);
- TAILQ_INIT(&rd->export);
- SIMPLEQ_INSERT_TAIL(newdomains, rd, entry);
+ memcpy(vpn, imsg.data, sizeof(struct l3vpn));
+ TAILQ_INIT(&vpn->import);
+ TAILQ_INIT(&vpn->export);
+ SIMPLEQ_INSERT_TAIL(newdomains, vpn, entry);
  break;
- case IMSG_RECONF_RDOMAIN_EXPORT:
- if (rd == NULL) {
+ case IMSG_RECONF_VPN_EXPORT:
+ if (vpn == NULL) {
  log_warnx("rde_dispatch_imsg_parent: "
-    "IMSG_RECONF_RDOMAIN_EXPORT unexpected");
+    "IMSG_RECONF_VPN_EXPORT unexpected");
  break;
  }
- parent_set = &rd->export;
+ parent_set = &vpn->export;
  break;
- case IMSG_RECONF_RDOMAIN_IMPORT:
- if (rd == NULL) {
+ case IMSG_RECONF_VPN_IMPORT:
+ if (vpn == NULL) {
  log_warnx("rde_dispatch_imsg_parent: "
-    "IMSG_RECONF_RDOMAIN_IMPORT unexpected");
+    "IMSG_RECONF_VPN_IMPORT unexpected");
  break;
  }
- parent_set = &rd->import;
+ parent_set = &vpn->import;
  break;
- case IMSG_RECONF_RDOMAIN_DONE:
+ case IMSG_RECONF_VPN_DONE:
  parent_set = NULL;
  break;
  case IMSG_RECONF_DRAIN:
@@ -2530,7 +2530,7 @@ rde_dump_mrt_new(struct mrt *mrt, pid_t
  * kroute specific functions
  */
 int
-rde_rdomain_import(struct rde_aspath *asp, struct rdomain *rd)
+rde_l3vpn_import(struct rde_aspath *asp, struct l3vpn *rd)
 {
  struct filter_set *s;
 
@@ -2548,7 +2548,7 @@ rde_send_kroute(struct rib *rib, struct
  struct bgpd_addr addr;
  struct prefix *p;
  struct rde_aspath *asp;
- struct rdomain *rd;
+ struct l3vpn *vpn;
  enum imsg_type type;
 
  /*
@@ -2588,8 +2588,8 @@ rde_send_kroute(struct rib *rib, struct
  /* not Loc-RIB, no update for VPNs */
  break;
 
- SIMPLEQ_FOREACH(rd, rdomains_l, entry) {
- if (!rde_rdomain_import(asp, rd))
+ SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
+ if (!rde_l3vpn_import(asp, vpn))
  continue;
  /* must send exit_nexthop so that correct MPLS tunnel
  * is chosen
@@ -2599,8 +2599,8 @@ rde_send_kroute(struct rib *rib, struct
     &prefix_nexthop(p)->exit_nexthop,
     sizeof(kr.nexthop));
  /* XXX not ideal but this will change */
- kr.ifindex = if_nametoindex(rd->ifmpe);
- if (imsg_compose(ibuf_main, type, rd->rtableid, 0, -1,
+ kr.ifindex = if_nametoindex(vpn->ifmpe);
+ if (imsg_compose(ibuf_main, type, vpn->rtableid, 0, -1,
     &kr, sizeof(kr)) == -1)
  fatal("%s %d imsg_compose error", __func__,
     __LINE__);
@@ -2881,7 +2881,7 @@ rde_send_nexthop(struct bgpd_addr *next,
 void
 rde_reload_done(void)
 {
- struct rdomain *rd;
+ struct l3vpn *vpn;
  struct rde_peer *peer;
  struct filter_head *fh;
  u_int16_t rid;
@@ -2923,15 +2923,15 @@ rde_reload_done(void)
  peerself->conf.remote_masklen = 32;
  peerself->short_as = conf->short_as;
 
- /* apply new set of rdomain, sync will be done later */
- while ((rd = SIMPLEQ_FIRST(rdomains_l)) != NULL) {
- SIMPLEQ_REMOVE_HEAD(rdomains_l, entry);
- filterset_free(&rd->import);
- filterset_free(&rd->export);
- free(rd);
+ /* apply new set of l3vpn, sync will be done later */
+ while ((vpn = SIMPLEQ_FIRST(l3vpns_l)) != NULL) {
+ SIMPLEQ_REMOVE_HEAD(l3vpns_l, entry);
+ filterset_free(&vpn->import);
+ filterset_free(&vpn->export);
+ free(vpn);
  }
- free(rdomains_l);
- rdomains_l = newdomains;
+ free(l3vpns_l);
+ l3vpns_l = newdomains;
  /* XXX WHERE IS THE SYNC ??? */
 
  /* check if roa changed */
@@ -3687,7 +3687,7 @@ void
 network_add(struct network_config *nc, int flagstatic)
 {
  struct filterstate state;
- struct rdomain *rd;
+ struct l3vpn *vpn;
  struct rde_aspath *asp;
  struct filter_set_head *vpnset = NULL;
  in_addr_t prefix4;
@@ -3695,44 +3695,44 @@ network_add(struct network_config *nc, i
  u_int8_t vstate;
  u_int16_t i;
 
- if (nc->rtableid != conf->default_tableid) {
- SIMPLEQ_FOREACH(rd, rdomains_l, entry) {
- if (rd->rtableid != nc->rtableid)
+ if (nc->rd != 0) {
+ SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
+ if (vpn->rd != nc->rd)
  continue;
  switch (nc->prefix.aid) {
  case AID_INET:
  prefix4 = nc->prefix.v4.s_addr;
  bzero(&nc->prefix, sizeof(nc->prefix));
  nc->prefix.aid = AID_VPN_IPv4;
- nc->prefix.vpn4.rd = rd->rd;
+ nc->prefix.vpn4.rd = vpn->rd;
  nc->prefix.vpn4.addr.s_addr = prefix4;
  nc->prefix.vpn4.labellen = 3;
  nc->prefix.vpn4.labelstack[0] =
-    (rd->label >> 12) & 0xff;
+    (vpn->label >> 12) & 0xff;
  nc->prefix.vpn4.labelstack[1] =
-    (rd->label >> 4) & 0xff;
+    (vpn->label >> 4) & 0xff;
  nc->prefix.vpn4.labelstack[2] =
-    (rd->label << 4) & 0xf0;
+    (vpn->label << 4) & 0xf0;
  nc->prefix.vpn4.labelstack[2] |= BGP_MPLS_BOS;
- vpnset = &rd->export;
+ vpnset = &vpn->export;
  break;
  case AID_INET6:
  memcpy(&prefix6, &nc->prefix.v6.s6_addr,
     sizeof(struct in6_addr));
  memset(&nc->prefix, 0, sizeof(nc->prefix));
  nc->prefix.aid = AID_VPN_IPv6;
- nc->prefix.vpn6.rd = rd->rd;
+ nc->prefix.vpn6.rd = vpn->rd;
  memcpy(&nc->prefix.vpn6.addr.s6_addr, &prefix6,
     sizeof(struct in6_addr));
  nc->prefix.vpn6.labellen = 3;
  nc->prefix.vpn6.labelstack[0] =
-    (rd->label >> 12) & 0xff;
+    (vpn->label >> 12) & 0xff;
  nc->prefix.vpn6.labelstack[1] =
-    (rd->label >> 4) & 0xff;
+    (vpn->label >> 4) & 0xff;
  nc->prefix.vpn6.labelstack[2] =
-    (rd->label << 4) & 0xf0;
+    (vpn->label << 4) & 0xf0;
  nc->prefix.vpn6.labelstack[2] |= BGP_MPLS_BOS;
- vpnset = &rd->export;
+ vpnset = &vpn->export;
  break;
  default:
  log_warnx("unable to VPNize prefix");
@@ -3741,10 +3741,11 @@ network_add(struct network_config *nc, i
  }
  break;
  }
- if (rd == NULL) {
+ if (vpn == NULL) {
  log_warnx("network_add: "
-    "prefix %s/%u in non-existing rdomain %u",
-    log_addr(&nc->prefix), nc->prefixlen, nc->rtableid);
+    "prefix %s/%u in non-existing l3vpn %s",
+    log_addr(&nc->prefix), nc->prefixlen,
+    log_rd(nc->rd));
  return;
  }
  }
@@ -3789,29 +3790,29 @@ network_add(struct network_config *nc, i
 void
 network_delete(struct network_config *nc)
 {
- struct rdomain *rd;
+ struct l3vpn *vpn;
  in_addr_t prefix4;
  struct in6_addr prefix6;
  u_int32_t i;
 
- if (nc->rtableid) {
- SIMPLEQ_FOREACH(rd, rdomains_l, entry) {
- if (rd->rtableid != nc->rtableid)
+ if (nc->rd) {
+ SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
+ if (vpn->rd != nc->rd)
  continue;
  switch (nc->prefix.aid) {
  case AID_INET:
  prefix4 = nc->prefix.v4.s_addr;
  bzero(&nc->prefix, sizeof(nc->prefix));
  nc->prefix.aid = AID_VPN_IPv4;
- nc->prefix.vpn4.rd = rd->rd;
+ nc->prefix.vpn4.rd = vpn->rd;
  nc->prefix.vpn4.addr.s_addr = prefix4;
  nc->prefix.vpn4.labellen = 3;
  nc->prefix.vpn4.labelstack[0] =
-    (rd->label >> 12) & 0xff;
+    (vpn->label >> 12) & 0xff;
  nc->prefix.vpn4.labelstack[1] =
-    (rd->label >> 4) & 0xff;
+    (vpn->label >> 4) & 0xff;
  nc->prefix.vpn4.labelstack[2] =
-    (rd->label << 4) & 0xf0;
+    (vpn->label << 4) & 0xf0;
  nc->prefix.vpn4.labelstack[2] |= BGP_MPLS_BOS;
  break;
  case AID_INET6:
@@ -3819,16 +3820,16 @@ network_delete(struct network_config *nc
     sizeof(struct in6_addr));
  memset(&nc->prefix, 0, sizeof(nc->prefix));
  nc->prefix.aid = AID_VPN_IPv6;
- nc->prefix.vpn6.rd = rd->rd;
+ nc->prefix.vpn6.rd = vpn->rd;
  memcpy(&nc->prefix.vpn6.addr.s6_addr, &prefix6,
     sizeof(struct in6_addr));
  nc->prefix.vpn6.labellen = 3;
  nc->prefix.vpn6.labelstack[0] =
-    (rd->label >> 12) & 0xff;
+    (vpn->label >> 12) & 0xff;
  nc->prefix.vpn6.labelstack[1] =
-    (rd->label >> 4) & 0xff;
+    (vpn->label >> 4) & 0xff;
  nc->prefix.vpn6.labelstack[2] =
-    (rd->label << 4) & 0xf0;
+    (vpn->label << 4) & 0xf0;
  nc->prefix.vpn6.labelstack[2] |= BGP_MPLS_BOS;
  break;
  default:
Index: usr.sbin/bgpd/session.h
===================================================================
RCS file: /cvs/src/usr.sbin/bgpd/session.h,v
retrieving revision 1.128
diff -u -p -r1.128 session.h
--- usr.sbin/bgpd/session.h 20 Jan 2019 23:27:48 -0000 1.128
+++ usr.sbin/bgpd/session.h 7 Feb 2019 10:11:19 -0000
@@ -248,7 +248,7 @@ int carp_demote_set(char *, int);
 int merge_config(struct bgpd_config *, struct bgpd_config *,
     struct peer *);
 int prepare_listeners(struct bgpd_config *);
-int get_mpe_label(struct rdomain *);
+int get_mpe_config(const char *, u_int *, u_int *);
 
 /* control.c */
 int control_check(char *);
@@ -285,7 +285,7 @@ int pfkey_init(struct bgpd_sysdep *);
 /* printconf.c */
 void print_config(struct bgpd_config *, struct rib_names *,
     struct network_head *, struct peer *, struct filter_head *,
-    struct mrt_head *, struct rdomain_head *);
+    struct mrt_head *, struct l3vpn_head *);
 
 /* rde.c */
 void rde_main(int, int);

Reply | Threaded
Open this post in threaded view
|

Re: bgpd L3VPN rework

Denis Fondras-3
On Thu, Feb 07, 2019 at 11:41:39AM +0100, Claudio Jeker wrote:

> At a2k19 David (dlg@) and I sat down and looked at BGP L3 MPLS VPN
> support. Now David wants to use this in production and realized that
> in his case it would be great to have more than one mpe(4) interface per
> rdomain. This way it is possible to write good firewall rules using
> interface names or groups.
>
> The definition of VPNs in bgpd was never super elegant. The 'depend on
> mpeX' config was a bit redundant and so after some discussions we decided
> to rework this part.
>
> L3 MPLS VPN are now configured like this:
>
> vpn "staff" on mpe1 {
>         rd $ASN:1
>         import-target rt $ASN:100
>         export-target rt $ASN:101
>         network 0/0
> }
>
> vpn "users" on mpe2 {
>         rd $ASN:2
>         import-target rt $ASN:200
>         export-target rt $ASN:201
>         network 0/0
> }
>
> Now in this example mpe1 and mpe2 can be in the same rdomain. The rdomain
> is now selected based on the rdomain of the mpe(4) interface. There are
> probably still some gotchas around this which can be tackled in a 2nd
> round.
>
> This diff does a lot of shuffling of code and especially affect network
> statements (since those are now per VPN and no longer per rdomain).
> The rewrite of the network code fixed also some other behaviour bugs which
> do not only affect VPN setups. In short conficts between 'network A.B.C.D/N'
> and 'network static' are now properly handled (with 'network A.B.C.D/N'
> having preference).
>
> Since this is a major change please test (or suffer later).

Reads good, works fine.

OK denis@

While testing, I noticed that import-target/export-target are not updated on
reload (problem not introduced by this diff though)

> --
> :wq Claudio
>
> Index: usr.sbin/bgpctl/bgpctl.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/bgpctl.c,v
> retrieving revision 1.228
> diff -u -p -r1.228 bgpctl.c
> --- usr.sbin/bgpctl/bgpctl.c 20 Jan 2019 23:30:15 -0000 1.228
> +++ usr.sbin/bgpctl/bgpctl.c 7 Feb 2019 10:11:19 -0000
> @@ -346,7 +346,7 @@ main(int argc, char *argv[])
>   bzero(&net, sizeof(net));
>   net.prefix = res->addr;
>   net.prefixlen = res->prefixlen;
> - net.rtableid = tableid;
> + net.rd = res->rd;
>   /* attribute sets are not supported */
>   if (res->action == NETWORK_ADD) {
>   imsg_compose(ibuf, IMSG_NETWORK_ADD, 0, 0, -1,
> @@ -927,7 +927,7 @@ show_fib_head(void)
>   printf("flags: "
>      "* = valid, B = BGP, C = Connected, S = Static, D = Dynamic\n");
>   printf("       "
> -    "N = BGP Nexthop reachable via this route R = redistributed\n");
> +    "N = BGP Nexthop reachable via this route\n");
>   printf("       r = reject route, b = blackhole route\n\n");
>   printf("flags prio destination          gateway\n");
>  }
> @@ -969,11 +969,6 @@ show_fib_flags(u_int16_t flags)
>   else
>   printf(" ");
>  
> - if (flags & F_REDISTRIBUTED)
> - printf("R");
> - else
> - printf(" ");
> -
>   if (flags & F_REJECT && flags & F_BLACKHOLE)
>   printf("f");
>   else if (flags & F_REJECT)
> @@ -1983,7 +1978,7 @@ network_bulk(struct parse_result *res)
>   errx(1, "bad prefix: %s", b);
>   net.prefix = h;
>   net.prefixlen = len;
> - net.rtableid = tableid;
> + net.rd = res->rd;
>  
>   if (res->action == NETWORK_BULK_ADD) {
>   imsg_compose(ibuf, IMSG_NETWORK_ADD,
> @@ -2128,7 +2123,7 @@ network_mrt_dump(struct mrt_rib *mr, str
>   net.prefix = ctl.prefix;
>   net.prefixlen = ctl.prefixlen;
>   net.type = NETWORK_MRTCLONE;
> - /* XXX rtableid */
> + /* XXX rd can't be set and will be 0 */
>  
>   imsg_compose(ibuf, IMSG_NETWORK_ADD, 0, 0, -1,
>      &net, sizeof(net));
> Index: usr.sbin/bgpctl/parser.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/parser.c,v
> retrieving revision 1.89
> diff -u -p -r1.89 parser.c
> --- usr.sbin/bgpctl/parser.c 20 Jan 2019 23:30:15 -0000 1.89
> +++ usr.sbin/bgpctl/parser.c 7 Feb 2019 10:11:19 -0000
> @@ -58,6 +58,7 @@ enum token_type {
>   PREPNBR,
>   PREPSELF,
>   WEIGHT,
> + RD,
>   FAMILY,
>   GETOPT,
>   RTABLE,
> @@ -374,6 +375,11 @@ static const struct token t_network_show
>   { ENDTOKEN, "", NONE, NULL}
>  };
>  
> +static const struct token t_rd[] = {
> + { RD, "", NONE, t_set},
> + { ENDTOKEN, "", NONE, NULL}
> +};
> +
>  static const struct token t_set[] = {
>   { NOTOKEN, "", NONE, NULL},
>   { KEYWORD, "community", NONE, t_community},
> @@ -386,6 +392,7 @@ static const struct token t_set[] = {
>   { KEYWORD, "pftable", NONE, t_pftable},
>   { KEYWORD, "prepend-neighbor", NONE, t_prepnbr},
>   { KEYWORD, "prepend-self", NONE, t_prepself},
> + { KEYWORD, "rd", NONE, t_rd},
>   { KEYWORD, "weight", NONE, t_weight},
>   { KEYWORD, "add", NETWORK_BULK_ADD, NULL},
>   { KEYWORD, "delete", NETWORK_BULK_REMOVE, NULL},
> @@ -493,18 +500,14 @@ static struct parse_result res;
>  const struct token *match_token(int *argc, char **argv[],
>      const struct token []);
>  void show_valid_args(const struct token []);
> -int parse_addr(const char *, struct bgpd_addr *);
> -int parse_asnum(const char *, size_t, u_int32_t *);
> -int parse_number(const char *, struct parse_result *,
> -     enum token_type);
> -int parse_community(const char *, struct parse_result *);
> -int parsesubtype(const char *, u_int8_t *, u_int8_t *);
> -int parseextvalue(const char *, u_int32_t *);
> -u_int parseextcommunity(const char *, struct parse_result *);
> -int parse_largecommunity(const char *,
> -     struct parse_result *);
> -int parse_nexthop(const char *, struct parse_result *);
> -int bgpctl_getopt(int *, char **[], int);
> +
> +int parse_addr(const char *, struct bgpd_addr *);
> +int parse_asnum(const char *, size_t, u_int32_t *);
> +int parse_number(const char *, struct parse_result *, enum token_type);
> +void parsecommunity(struct filter_community *c, int type, char *s);
> +int parseextcommunity(struct filter_community *c, const char *t, char *s);
> +int parse_nexthop(const char *, struct parse_result *);
> +int bgpctl_getopt(int *, char **[], int);
>  
>  struct parse_result *
>  parse(int argc, char *argv[])
> @@ -675,8 +678,25 @@ match_token(int *argc, char **argv[], co
>   }
>   break;
>   case COMMUNITY:
> - if (word != NULL && wordlen > 0 &&
> -    parse_community(word, &res)) {
> + case LARGE_COMMUNITY:
> + if (word != NULL && wordlen > 0) {
> + int type = COMMUNITY_TYPE_BASIC;
> + char *p = strdup(word);
> +
> + if (p == NULL)
> + err(1, NULL);
> + if (table[i].type == LARGE_COMMUNITY)
> + type = COMMUNITY_TYPE_LARGE;
> + parsecommunity(&res.community,
> +    COMMUNITY_TYPE_BASIC, p);
> + free(p);
> +
> + if ((fs = calloc(1, sizeof(*fs))) == NULL)
> + err(1, NULL);
> + fs->type = ACTION_SET_COMMUNITY;
> + fs->action.community = res.community;
> + TAILQ_INSERT_TAIL(&res.set, fs, entry);
> +
>   match++;
>   t = &table[i];
>   }
> @@ -684,24 +704,62 @@ match_token(int *argc, char **argv[], co
>   case EXTCOM_SUBTYPE:
>   if (word != NULL && strncmp(word, table[i].keyword,
>      wordlen) == 0) {
> - if (parsesubtype(word, &res.community.c.e.type,
> -    &res.community.c.e.subtype) == 0)
> - errx(1, "Bad ext-community unknown "
> -    "type");
> + res.ext_comm_subtype = table[i].keyword;
>   match++;
>   t = &table[i];
>   }
>   break;
>   case EXTCOMMUNITY:
> - if (word != NULL && wordlen > 0 &&
> -    parseextcommunity(word, &res)) {
> + if (word != NULL && wordlen > 0) {
> + char *p = strdup(word);
> +
> + if (p == NULL)
> + err(1, NULL);
> + parseextcommunity(&res.community,
> +    res.ext_comm_subtype, p);
> + free(p);
> +
> + if ((fs = calloc(1, sizeof(*fs))) == NULL)
> + err(1, NULL);
> + fs->type = ACTION_SET_COMMUNITY;
> + fs->action.community = res.community;
> + TAILQ_INSERT_TAIL(&res.set, fs, entry);
> +
>   match++;
>   t = &table[i];
>   }
>   break;
> - case LARGE_COMMUNITY:
> - if (word != NULL && wordlen > 0 &&
> -    parse_largecommunity(word, &res)) {
> + case RD:
> + if (word != NULL && wordlen > 0) {
> + char *p = strdup(word);
> + struct filter_community ext;
> + u_int64_t rd;
> +
> + if (p == NULL)
> + err(1, NULL);
> + parseextcommunity(&ext, "rt", p);
> + free(p);
> +
> + switch (ext.c.e.type) {
> + case EXT_COMMUNITY_TRANS_TWO_AS:
> + rd = (0ULL << 48);
> + rd |= (u_int64_t)ext.c.e.data1 << 32;
> + rd |= ext.c.e.data2 & 0xffffffff;
> + break;
> + case EXT_COMMUNITY_TRANS_IPV4:
> + rd = (1ULL << 48);
> + rd |= (u_int64_t)ext.c.e.data1 << 16;
> + rd |= ext.c.e.data2 & 0xffff;
> + break;
> + case EXT_COMMUNITY_TRANS_FOUR_AS:
> + rd = (2ULL << 48);
> + rd |= (u_int64_t)ext.c.e.data1 << 16;
> + rd |= ext.c.e.data2 & 0xffff;
> + break;
> + default:
> + errx(1, "bad encoding of rd");
> + }
> + res.rd = htobe64(rd);
>   match++;
>   t = &table[i];
>   }
> @@ -823,11 +881,14 @@ show_valid_args(const struct token table
>   case COMMUNITY:
>   fprintf(stderr, "  <community>\n");
>   break;
> + case LARGE_COMMUNITY:
> + fprintf(stderr, "  <large-community>\n");
> + break;
>   case EXTCOMMUNITY:
>   fprintf(stderr, "  <extended-community>\n");
>   break;
> - case LARGE_COMMUNITY:
> - fprintf(stderr, "  <large-community>\n");
> + case RD:
> + fprintf(stderr, "  <route-distinguisher>\n");
>   break;
>   case LOCALPREF:
>   case MED:
> @@ -1035,95 +1096,118 @@ parse_number(const char *word, struct pa
>   return (1);
>  }
>  
> -static u_int32_t
> -getcommunity(const char *s, int large, u_int8_t *flag)
> +static void
> +getcommunity(char *s, int large, u_int32_t *val, u_int8_t *flag)
>  {
>   int64_t max = USHRT_MAX;
>   const char *errstr;
> - u_int32_t uval;
>  
> + *flag = 0;
> + *val = 0;
>   if (strcmp(s, "*") == 0) {
>   *flag = COMMUNITY_ANY;
> - return (0);
> + return;
> + } else if (strcmp(s, "neighbor-as") == 0) {
> + *flag = COMMUNITY_NEIGHBOR_AS;
> + return;
> + } else if (strcmp(s, "local-as") == 0) {
> + *flag =  COMMUNITY_LOCAL_AS;
> + return;
>   }
> -
>   if (large)
>   max = UINT_MAX;
> -
> - uval = strtonum(s, 0, max, &errstr);
> + *val = strtonum(s, 0, max, &errstr);
>   if (errstr)
> - errx(1, "Community is %s: %s", errstr, s);
> -
> - *flag = 0;
> - return (uval);
> + errx(1, "Community %s is %s (max: %llu)", s, errstr, max);
>  }
>  
> -int
> -parse_community(const char *word, struct parse_result *r)
> +static void
> +setcommunity(struct filter_community *c, u_int32_t as, u_int32_t data,
> +    u_int8_t asflag, u_int8_t dataflag)
>  {
> - struct filter_set *fs;
> - char *p;
> - u_int32_t as, type;
> - u_int8_t asflag, tflag;
> + memset(c, 0, sizeof(*c));
> + c->type = COMMUNITY_TYPE_BASIC;
> + c->dflag1 = asflag;
> + c->dflag2 = dataflag;
> + c->c.b.data1 = as;
> + c->c.b.data2 = data;
> +}
>  
> - /* Well-known communities */
> - if (strcasecmp(word, "GRACEFUL_SHUTDOWN") == 0) {
> - as = COMMUNITY_WELLKNOWN;
> - type = COMMUNITY_GRACEFUL_SHUTDOWN;
> - goto done;
> - } else if (strcasecmp(word, "NO_EXPORT") == 0) {
> - as = COMMUNITY_WELLKNOWN;
> - type = COMMUNITY_NO_EXPORT;
> - goto done;
> - } else if (strcasecmp(word, "NO_ADVERTISE") == 0) {
> - as = COMMUNITY_WELLKNOWN;
> - type = COMMUNITY_NO_ADVERTISE;
> - goto done;
> - } else if (strcasecmp(word, "NO_EXPORT_SUBCONFED") == 0) {
> - as = COMMUNITY_WELLKNOWN;
> - type = COMMUNITY_NO_EXPSUBCONFED;
> - goto done;
> - } else if (strcasecmp(word, "NO_PEER") == 0) {
> - as = COMMUNITY_WELLKNOWN;
> - type = COMMUNITY_NO_PEER;
> - goto done;
> - } else if (strcasecmp(word, "BLACKHOLE") == 0) {
> - as = COMMUNITY_WELLKNOWN;
> - type = COMMUNITY_BLACKHOLE;
> - goto done;
> - }
> +static void
> +parselargecommunity(struct filter_community *c, char *s)
> +{
> + char *p, *q;
>  
> - if ((p = strchr(word, ':')) == NULL) {
> - fprintf(stderr, "Bad community syntax\n");
> - return (0);
> - }
> + if ((p = strchr(s, ':')) == NULL)
> + errx(1, "Bad community syntax");
>   *p++ = 0;
>  
> - as = getcommunity(word, 0, &asflag);
> - type = getcommunity(p, 0, &tflag);
> + if ((q = strchr(p, ':')) == NULL)
> + errx(1, "Bad community syntax");
> + *q++ = 0;
> +
> + getcommunity(s, 1, &c->c.l.data1, &c->dflag1);
> + getcommunity(p, 1, &c->c.l.data2, &c->dflag2);
> + getcommunity(q, 1, &c->c.l.data3, &c->dflag3);
>  
> -done:
> - if ((fs = calloc(1, sizeof(struct filter_set))) == NULL)
> - err(1, NULL);
> - fs->type = ACTION_SET_COMMUNITY;
> - fs->action.community.type = COMMUNITY_TYPE_BASIC;
> - fs->action.community.c.b.data1 = as;
> - fs->action.community.c.b.data2 = type;
> - fs->action.community.dflag1 = asflag;
> - fs->action.community.dflag2 = tflag;
> + c->type = COMMUNITY_TYPE_LARGE;
> +}
>  
> - r->community = fs->action.community;
> +void
> +parsecommunity(struct filter_community *c, int type, char *s)
> +{
> + char *p;
> + u_int32_t as, data;
> + u_int8_t asflag, dataflag;
> +
> + if (type == COMMUNITY_TYPE_LARGE) {
> + parselargecommunity(c, s);
> + return;
> + }
>  
> - TAILQ_INSERT_TAIL(&r->set, fs, entry);
> - return (1);
> + /* Well-known communities */
> + if (strcasecmp(s, "GRACEFUL_SHUTDOWN") == 0) {
> + setcommunity(c, COMMUNITY_WELLKNOWN,
> +    COMMUNITY_GRACEFUL_SHUTDOWN, 0, 0);
> + return;
> + } else if (strcasecmp(s, "NO_EXPORT") == 0) {
> + setcommunity(c, COMMUNITY_WELLKNOWN,
> +    COMMUNITY_NO_EXPORT, 0, 0);
> + return;
> + } else if (strcasecmp(s, "NO_ADVERTISE") == 0) {
> + setcommunity(c, COMMUNITY_WELLKNOWN,
> +    COMMUNITY_NO_ADVERTISE, 0, 0);
> + return;
> + } else if (strcasecmp(s, "NO_EXPORT_SUBCONFED") == 0) {
> + setcommunity(c, COMMUNITY_WELLKNOWN,
> +    COMMUNITY_NO_EXPSUBCONFED, 0, 0);
> + return;
> + } else if (strcasecmp(s, "NO_PEER") == 0) {
> + setcommunity(c, COMMUNITY_WELLKNOWN,
> +    COMMUNITY_NO_PEER, 0, 0);
> + return;
> + } else if (strcasecmp(s, "BLACKHOLE") == 0) {
> + setcommunity(c, COMMUNITY_WELLKNOWN,
> +    COMMUNITY_BLACKHOLE, 0, 0);
> + return;
> + }
> +
> + if ((p = strchr(s, ':')) == NULL)
> + errx(1, "Bad community syntax");
> + *p++ = 0;
> +
> + getcommunity(s, 0, &as, &asflag);
> + getcommunity(p, 0, &data, &dataflag);
> + setcommunity(c, as, data, asflag, dataflag);
>  }
>  
> -int
> -parsesubtype(const char *name, u_int8_t *type, u_int8_t *subtype)
> +static int
> +parsesubtype(const char *name, int *type, int *subtype)
>  {
>   const struct ext_comm_pairs *cp;
>   int found = 0;
>  
> +printf("%s: looking for %s\n", __func__, name);
>   for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
>   if (strcmp(name, cp->subname) == 0) {
>   if (found == 0) {
> @@ -1138,79 +1222,89 @@ parsesubtype(const char *name, u_int8_t
>   return (found);
>  }
>  
> -int
> -parseextvalue(const char *s, u_int32_t *v)
> +static int
> +parseextvalue(int type, char *s, u_int32_t *v)
>  {
> - const char *errstr;
> + const char *errstr;
>   char *p;
>   struct in_addr ip;
> - u_int32_t uvalh = 0, uval;
> + u_int32_t uvalh, uval;
>  
> - if ((p = strchr(s, '.')) == NULL) {
> + if (type != -1) {
> + /* nothing */
> + } else if ((p = strchr(s, '.')) == NULL) {
>   /* AS_PLAIN number (4 or 2 byte) */
> - uval = strtonum(s, 0, UINT_MAX, &errstr);
> - if (errstr) {
> - fprintf(stderr, "Bad ext-community: %s is %s\n", s,
> -    errstr);
> - return (-1);
> - }
> - *v = uval;
> - if (uval <= USHRT_MAX)
> - return (EXT_COMMUNITY_TRANS_TWO_AS);
> + strtonum(s, 0, USHRT_MAX, &errstr);
> + if (errstr == NULL)
> + type = EXT_COMMUNITY_TRANS_TWO_AS;
>   else
> - return (EXT_COMMUNITY_TRANS_FOUR_AS);
> + type = EXT_COMMUNITY_TRANS_FOUR_AS;
>   } else if (strchr(p + 1, '.') == NULL) {
>   /* AS_DOT number (4-byte) */
> + type = EXT_COMMUNITY_TRANS_FOUR_AS;
> + } else {
> + /* more than one dot -> IP address */
> + type = EXT_COMMUNITY_TRANS_IPV4;
> + }
> +
> + switch (type) {
> + case EXT_COMMUNITY_TRANS_TWO_AS:
> + uval = strtonum(s, 0, USHRT_MAX, &errstr);
> + if (errstr)
> + errx(1, "Bad ext-community %s is %s", s, errstr);
> + *v = uval;
> + break;
> + case EXT_COMMUNITY_TRANS_FOUR_AS:
> + if ((p = strchr(s, '.')) == NULL) {
> + uval = strtonum(s, 0, UINT_MAX, &errstr);
> + if (errstr)
> + errx(1, "Bad ext-community %s is %s", s,
> +    errstr);
> + *v = uval;
> + break;
> + }
>   *p++ = '\0';
>   uvalh = strtonum(s, 0, USHRT_MAX, &errstr);
> - if (errstr) {
> - fprintf(stderr, "Bad ext-community: %s is %s\n", s,
> -    errstr);
> - return (-1);
> - }
> + if (errstr)
> + errx(1, "Bad ext-community %s is %s", s, errstr);
>   uval = strtonum(p, 0, USHRT_MAX, &errstr);
> - if (errstr) {
> - fprintf(stderr, "Bad ext-community: %s is %s\n", p,
> -    errstr);
> - return (-1);
> - }
> + if (errstr)
> + errx(1, "Bad ext-community %s is %s", p, errstr);
>   *v = uval | (uvalh << 16);
> - return (EXT_COMMUNITY_TRANS_FOUR_AS);
> - } else {
> - /* more than one dot -> IP address */
> - if (inet_aton(s, &ip) == 0) {
> - fprintf(stderr, "Bad ext-community: %s not parseable\n",
> -    s);
> - return (-1);
> - }
> + break;
> + case EXT_COMMUNITY_TRANS_IPV4:
> + if (inet_aton(s, &ip) == 0)
> + errx(1, "Bad ext-community %s not parseable", s);
>   *v = ntohl(ip.s_addr);
> - return (EXT_COMMUNITY_TRANS_IPV4);
> + break;
> + default:
> + errx(1, "%s: unexpected type %d", __func__, type);
>   }
> - return (-1);
> + return (type);
>  }
>  
> -u_int
> -parseextcommunity(const char *word, struct parse_result *r)
> +int
> +parseextcommunity(struct filter_community *c, const char *t, char *s)
>  {
> - struct filter_set *fs;
> - const struct ext_comm_pairs *cp;
> - const char *errstr;
> - u_int64_t ullval;
> - u_int32_t uval;
> - char *p, *ep;
> - int type;
> + const struct ext_comm_pairs *cp;
> + const char *errstr;
> + u_int64_t ullval;
> + u_int32_t uval;
> + char *p, *ep;
> + int type, subtype;
>  
> - type = r->community.c.e.type;
> + if (parsesubtype(t, &type, &subtype) == 0)
> + errx(1, "Bad ext-community unknown type");
>  
>   switch (type) {
> - case 0xff:
> - if ((p = strchr(word, ':')) == NULL) {
> - fprintf(stderr, "Bad ext-community: %s\n", word);
> - return (0);
> - }
> + case EXT_COMMUNITY_TRANS_TWO_AS:
> + case EXT_COMMUNITY_TRANS_FOUR_AS:
> + case EXT_COMMUNITY_TRANS_IPV4:
> + case -1:
> + if ((p = strchr(s, ':')) == NULL)
> + errx(1, "Bad ext-community %s", s);
>   *p++ = '\0';
> - if ((type = parseextvalue(word, &uval)) == -1)
> - return (0);
> + type = parseextvalue(type, s, &uval);
>   switch (type) {
>   case EXT_COMMUNITY_TRANS_TWO_AS:
>   ullval = strtonum(p, 0, UINT_MAX, &errstr);
> @@ -1220,118 +1314,46 @@ parseextcommunity(const char *word, stru
>   ullval = strtonum(p, 0, USHRT_MAX, &errstr);
>   break;
>   default:
> - fprintf(stderr, "parseextcommunity: unexpected "
> -    "result\n");
> - return (0);
> - }
> - if (errstr) {
> - fprintf(stderr, "Bad ext-community: %s is %s\n", p,
> -    errstr);
> - return (0);
> - }
> - switch (type) {
> - case EXT_COMMUNITY_TRANS_TWO_AS:
> - r->community.c.e.data1 = uval;
> - r->community.c.e.data2 = ullval;
> - break;
> - case EXT_COMMUNITY_TRANS_IPV4:
> - case EXT_COMMUNITY_TRANS_FOUR_AS:
> - r->community.c.e.data1 = uval;
> - r->community.c.e.data2 = ullval;
> - break;
> + errx(1, "parseextcommunity: unexpected result");
>   }
> + if (errstr)
> + errx(1, "Bad ext-community %s is %s", p, errstr);
> + c->c.e.data1 = uval;
> + c->c.e.data2 = ullval;
>   break;
>   case EXT_COMMUNITY_TRANS_OPAQUE:
>   case EXT_COMMUNITY_TRANS_EVPN:
>   errno = 0;
> - ullval = strtoull(word, &ep, 0);
> - if (word[0] == '\0' || *ep != '\0') {
> - fprintf(stderr, "Bad ext-community: bad value\n");
> - return (0);
> - }
> - if (errno == ERANGE && ullval > EXT_COMMUNITY_OPAQUE_MAX) {
> - fprintf(stderr, "Bad ext-community: too big\n");
> - return (0);
> - }
> - r->community.c.e.data2 = ullval;
> + ullval = strtoull(s, &ep, 0);
> + if (s[0] == '\0' || *ep != '\0')
> + errx(1, "Bad ext-community bad value");
> + if (errno == ERANGE && ullval > EXT_COMMUNITY_OPAQUE_MAX)
> + errx(1, "Bad ext-community value too big");
> + c->c.e.data2 = ullval;
>   break;
>   case EXT_COMMUNITY_NON_TRANS_OPAQUE:
> - if (strcmp(word, "valid") == 0)
> - r->community.c.e.data2 = EXT_COMMUNITY_OVS_VALID;
> - else if (strcmp(word, "invalid") == 0)
> - r->community.c.e.data2 = EXT_COMMUNITY_OVS_INVALID;
> - else if (strcmp(word, "not-found") == 0)
> - r->community.c.e.data2 = EXT_COMMUNITY_OVS_NOTFOUND;
> - else {
> - fprintf(stderr, "Bad ext-community value: %s\n", word);
> - return (0);
> - }
> + if (strcmp(s, "valid") == 0)
> + c->c.e.data2 = EXT_COMMUNITY_OVS_VALID;
> + else if (strcmp(s, "invalid") == 0)
> + c->c.e.data2 = EXT_COMMUNITY_OVS_INVALID;
> + else if (strcmp(s, "not-found") == 0)
> + c->c.e.data2 = EXT_COMMUNITY_OVS_NOTFOUND;
> + else
> + errx(1, "Bad ext-community %s", s);
>   break;
>   }
> - r->community.c.e.type = type;
> + c->c.e.type = type;
> + c->c.e.subtype = subtype;
>  
>   /* verify type/subtype combo */
>   for (cp = iana_ext_comms; cp->subname != NULL; cp++) {
> - if (cp->type == r->community.c.e.type &&
> -    cp->subtype == r->community.c.e.subtype) {
> - r->community.type = COMMUNITY_TYPE_EXT;;
> - if ((fs = calloc(1, sizeof(struct filter_set))) == NULL)
> - err(1, NULL);
> -
> - fs->type = ACTION_SET_COMMUNITY;
> - memcpy(&fs->action.community, &r->community,
> -    sizeof(struct filter_community));
> -
> - TAILQ_INSERT_TAIL(&r->set, fs, entry);
> - return (1);
> + if (cp->type == type && cp->subtype == subtype) {
> + c->type = COMMUNITY_TYPE_EXT;
> + return (0);
>   }
>   }
>  
> - fprintf(stderr, "Bad ext-community: bad format for type\n");
> - return (0);
> -}
> -
> -int
> -parse_largecommunity(const char *word, struct parse_result *r)
> -{
> - struct filter_set *fs;
> - char *p, *po = strdup(word);
> - char *array[3] = { NULL, NULL, NULL };
> - char *val;
> - u_int32_t as, ld1, ld2;
> - u_int8_t asflag, ld1flag, ld2flag;
> - int i = 0;
> -
> - p = po;
> - while ((p != NULL) && (i < 3)) {
> - val = strsep(&p, ":");
> - array[i++] = val;
> - }
> -
> - if ((p != NULL) || !(array[0] && array[1] && array[2]))
> - errx(1, "Invalid Large-Community syntax");
> -
> - as = getcommunity(array[0], 1, &asflag);
> - ld1 = getcommunity(array[1], 1, &ld1flag);
> - ld2 = getcommunity(array[2], 1, &ld2flag);
> -
> - free(po);
> -
> - if ((fs = calloc(1, sizeof(struct filter_set))) == NULL)
> - err(1, NULL);
> - fs->type = ACTION_SET_COMMUNITY;
> - fs->action.community.type = COMMUNITY_TYPE_LARGE;
> - fs->action.community.c.l.data1 = as;
> - fs->action.community.c.l.data2 = ld1;
> - fs->action.community.c.l.data3 = ld2;
> - fs->action.community.dflag1 = asflag;
> - fs->action.community.dflag2 = ld1flag;
> - fs->action.community.dflag3 = ld2flag;
> -
> - r->community = fs->action.community;
> -
> - TAILQ_INSERT_TAIL(&r->set, fs, entry);
> - return (1);
> + errx(1, "Bad ext-community bad format for type");
>  }
>  
>  int
> Index: usr.sbin/bgpctl/parser.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpctl/parser.h,v
> retrieving revision 1.34
> diff -u -p -r1.34 parser.h
> --- usr.sbin/bgpctl/parser.h 20 Jan 2019 23:30:15 -0000 1.34
> +++ usr.sbin/bgpctl/parser.h 7 Feb 2019 10:11:19 -0000
> @@ -67,6 +67,8 @@ struct parse_result {
>   char rib[PEER_DESCR_LEN];
>   char shutcomm[SHUT_COMM_LEN];
>   char *irr_outdir;
> + const char *ext_comm_subtype;
> + u_int64_t rd;
>   int flags;
>   int is_group;
>   u_int8_t validation_state;
> Index: usr.sbin/bgpd/bgpd.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/bgpd.c,v
> retrieving revision 1.207
> diff -u -p -r1.207 bgpd.c
> --- usr.sbin/bgpd/bgpd.c 20 Jan 2019 06:13:40 -0000 1.207
> +++ usr.sbin/bgpd/bgpd.c 7 Feb 2019 10:11:19 -0000
> @@ -173,7 +173,7 @@ main(int argc, char *argv[])
>  
>   if (cmd_opts & BGPD_OPT_VERBOSE)
>   print_config(conf, &ribnames, &conf->networks, peer_l,
> -    conf->filters, conf->mrt, &conf->rdomains);
> +    conf->filters, conf->mrt, &conf->l3vpns);
>   else
>   fprintf(stderr, "configuration OK\n");
>   exit(0);
> @@ -438,7 +438,7 @@ reconfigure(char *conffile, struct bgpd_
>   struct filter_rule *r;
>   struct listen_addr *la;
>   struct rde_rib *rr;
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>   struct as_set *aset;
>   struct prefixset *ps;
>   struct prefixset_item *psi, *npsi;
> @@ -512,8 +512,7 @@ reconfigure(char *conffile, struct bgpd_
>   }
>  
>   /* networks go via kroute to the RDE */
> - if (kr_net_reload(conf->default_tableid, &conf->networks))
> - return (-1);
> + kr_net_reload(conf->default_tableid, 0, &conf->networks);
>  
>   /* prefixsets for filters in the RDE */
>   while ((ps = SIMPLEQ_FIRST(&conf->prefixsets)) != NULL) {
> @@ -627,43 +626,42 @@ reconfigure(char *conffile, struct bgpd_
>   free(r);
>   }
>  
> - while ((rd = SIMPLEQ_FIRST(&conf->rdomains)) != NULL) {
> - SIMPLEQ_REMOVE_HEAD(&conf->rdomains, entry);
> - if (ktable_update(rd->rtableid, rd->descr, rd->flags,
> + while ((vpn = SIMPLEQ_FIRST(&conf->l3vpns)) != NULL) {
> + SIMPLEQ_REMOVE_HEAD(&conf->l3vpns, entry);
> + if (ktable_update(vpn->rtableid, vpn->descr, vpn->flags,
>      conf->fib_priority) == -1) {
>   log_warnx("failed to load rdomain %d",
> -    rd->rtableid);
> +    vpn->rtableid);
>   return (-1);
>   }
>   /* networks go via kroute to the RDE */
> - if (kr_net_reload(rd->rtableid, &rd->net_l))
> - return (-1);
> + kr_net_reload(vpn->rtableid, vpn->rd, &vpn->net_l);
>  
> - if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN, 0, 0, -1,
> -    rd, sizeof(*rd)) == -1)
> + if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN, 0, 0, -1,
> +    vpn, sizeof(*vpn)) == -1)
>   return (-1);
>  
>   /* export targets */
> - if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_EXPORT, 0, 0,
> + if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_EXPORT, 0, 0,
>      -1, NULL, 0) == -1)
>   return (-1);
> - if (send_filterset(ibuf_rde, &rd->export) == -1)
> + if (send_filterset(ibuf_rde, &vpn->export) == -1)
>   return (-1);
> - filterset_free(&rd->export);
> + filterset_free(&vpn->export);
>  
>   /* import targets */
> - if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_IMPORT, 0, 0,
> + if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_IMPORT, 0, 0,
>      -1, NULL, 0) == -1)
>   return (-1);
> - if (send_filterset(ibuf_rde, &rd->import) == -1)
> + if (send_filterset(ibuf_rde, &vpn->import) == -1)
>   return (-1);
> - filterset_free(&rd->import);
> + filterset_free(&vpn->import);
>  
> - if (imsg_compose(ibuf_rde, IMSG_RECONF_RDOMAIN_DONE, 0, 0,
> + if (imsg_compose(ibuf_rde, IMSG_RECONF_VPN_DONE, 0, 0,
>      -1, NULL, 0) == -1)
>   return (-1);
>  
> - free(rd);
> + free(vpn);
>   }
>  
>   /* send a drain message to know when all messages where processed */
> Index: usr.sbin/bgpd/bgpd.conf.5
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/bgpd.conf.5,v
> retrieving revision 1.183
> diff -u -p -r1.183 bgpd.conf.5
> --- usr.sbin/bgpd/bgpd.conf.5 4 Feb 2019 20:32:23 -0000 1.183
> +++ usr.sbin/bgpd/bgpd.conf.5 7 Feb 2019 10:11:19 -0000
> @@ -558,37 +558,56 @@ See also the
>  section.
>  .Sh MPLS VPN CONFIGURATION
>  .Xr bgpd 8
> -supports the setup and distribution of Virtual Private Networks.
> -It is possible to import and export prefixes between routing domains.
> -Each routing domain is specified by an
> -.Ic rdomain
> -section, which allows properties to be set specifically for that rdomain:
> +supports the setup and distribution of MPLS Virtual Private Networks.
> +A router can be configured to participate in a VPN by specifying a
> +.Ic vpn
> +section with a description for the VPN and an
> +.Xr mpe 4
> +interface.
> +.Pp
> +The vpn configuraion section allows properties to be set specifically
> +for that VPN:
>  .Bd -literal -offset indent
> -rdomain 1 {
> - descr "a rdomain"
> - rd 65002:1
> +vpn "description" on mpe1 {
> +        rd 65002:1
>   import-target rt 65002:42
>   export-target rt 65002:42
>   network 192.168.1/24
> - depend on mpe0
>  }
>  .Ed
>  .Pp
> -There are several routing domain properties:
> -.Pp
> -.Bl -tag -width Ds -compact
> -.It Ic depend on Ar interface
> -Routes added to the rdomain will use this interface as the outgoing interface.
> -Normally this will be an MPLS Provider Edge,
> -.Xr mpe 4 ,
> -interface that is part of the rdomain.
> -Local networks will be announced with the MPLS label specified on the interface.
> -.Pp
> -.It Ic descr Ar description
> -Add a description.
>  The description is used when logging but has no further meaning to
>  .Xr bgpd 8 .
>  .Pp
> +The
> +.Xr mpe 4
> +interface will be used as the outgoing interface for routes to
> +the VPN, and local networks will be annouced with the MPLS label
> +specified on the interface.
> +The interface can provide VPN connectivity for another rdomain by
> +being configured in that rdomain.
> +The required rdomain must be configured on the interface before
> +.Xr bgpd 8
> +uses it.
> +Multiple VPNs may be connected to a single rdomain, including the rdomain that
> +.Xr bgpd 8
> +is running in.
> +.Pp
> +An example
> +.Xr hostname.if 8
> +configuration for an
> +.Xr mpe 4
> +interface providing connectivity to rdomain 1:
> +.Bd -literal -offset indent
> +rdomain 1
> +mplslabel 2000
> +inet 192.198.0.1 255.255.255.255
> +up
> +.Ed
> +.Pp
> +There are several VPN properties:
> +.Pp
> +.Bl -tag -width Ds -compact
>  .It Ic export-target Ar subtype Ar as-number : Ns Ar local
>  .It Ic export-target Ar subtype Ar IP : Ns Ar local
>  Specify an extended community which will be attached to announced networks.
> Index: usr.sbin/bgpd/bgpd.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/bgpd.h,v
> retrieving revision 1.364
> diff -u -p -r1.364 bgpd.h
> --- usr.sbin/bgpd/bgpd.h 4 Feb 2019 18:53:10 -0000 1.364
> +++ usr.sbin/bgpd/bgpd.h 7 Feb 2019 10:11:19 -0000
> @@ -81,7 +81,6 @@
>  #define F_BLACKHOLE 0x0100
>  #define F_LONGER 0x0200
>  #define F_MPLS 0x0400
> -#define F_REDISTRIBUTED 0x0800
>  #define F_CTL_DETAIL 0x1000 /* only used by bgpctl */
>  #define F_CTL_ADJ_IN 0x2000
>  #define F_CTL_ADJ_OUT 0x4000
> @@ -231,8 +230,8 @@ struct listen_addr {
>  TAILQ_HEAD(listen_addrs, listen_addr);
>  TAILQ_HEAD(filter_set_head, filter_set);
>  
> -struct rdomain;
> -SIMPLEQ_HEAD(rdomain_head, rdomain);
> +struct l3vpn;
> +SIMPLEQ_HEAD(l3vpn_head, l3vpn);
>  
>  struct network;
>  TAILQ_HEAD(network_head, network);
> @@ -267,7 +266,7 @@ struct filter_rule;
>  TAILQ_HEAD(filter_head, filter_rule);
>  
>  struct bgpd_config {
> - struct rdomain_head rdomains;
> + struct l3vpn_head l3vpns;
>   struct network_head networks;
>   struct filter_head *filters;
>   struct listen_addrs *listen_addrs;
> @@ -411,7 +410,7 @@ struct network_config {
>   struct filter_set_head attrset;
>   struct rde_aspath *asp;
>   char psname[SET_NAME_LEN];
> - u_int rtableid;
> + u_int64_t rd;
>   u_int16_t rtlabel;
>   enum network_type type;
>   u_int8_t prefixlen;
> @@ -467,10 +466,10 @@ enum imsg_type {
>   IMSG_RECONF_FILTER,
>   IMSG_RECONF_LISTENER,
>   IMSG_RECONF_CTRL,
> - IMSG_RECONF_RDOMAIN,
> - IMSG_RECONF_RDOMAIN_EXPORT,
> - IMSG_RECONF_RDOMAIN_IMPORT,
> - IMSG_RECONF_RDOMAIN_DONE,
> + IMSG_RECONF_VPN,
> + IMSG_RECONF_VPN_EXPORT,
> + IMSG_RECONF_VPN_IMPORT,
> + IMSG_RECONF_VPN_DONE,
>   IMSG_RECONF_PREFIX_SET,
>   IMSG_RECONF_PREFIX_SET_ITEM,
>   IMSG_RECONF_AS_SET,
> @@ -567,15 +566,18 @@ enum suberr_cease {
>  struct kroute_node;
>  struct kroute6_node;
>  struct knexthop_node;
> +struct kredist_node;
>  RB_HEAD(kroute_tree, kroute_node);
>  RB_HEAD(kroute6_tree, kroute6_node);
>  RB_HEAD(knexthop_tree, knexthop_node);
> +RB_HEAD(kredist_tree, kredist_node);
>  
>  struct ktable {
>   char descr[PEER_DESCR_LEN];
>   struct kroute_tree krt;
>   struct kroute6_tree krt6;
>   struct knexthop_tree knt;
> + struct kredist_tree kredist;
>   struct network_head krn;
>   u_int rtableid;
>   u_int nhtableid; /* rdomain id for nexthop lookup */
> @@ -1024,8 +1026,8 @@ struct as_set {
>   int dirty;
>  };
>  
> -struct rdomain {
> - SIMPLEQ_ENTRY(rdomain) entry;
> +struct l3vpn {
> + SIMPLEQ_ENTRY(l3vpn) entry;
>   char descr[PEER_DESCR_LEN];
>   char ifmpe[IFNAMSIZ];
>   struct filter_set_head import;
> @@ -1176,7 +1178,7 @@ void kr_nexthop_delete(u_int32_t, stru
>      struct bgpd_config *);
>  void kr_show_route(struct imsg *);
>  void kr_ifinfo(char *);
> -int kr_net_reload(u_int, struct network_head *);
> +void kr_net_reload(u_int, u_int64_t, struct network_head *);
>  int kr_reload(void);
>  struct in6_addr *prefixlen2mask6(u_int8_t prefixlen);
>  
> Index: usr.sbin/bgpd/config.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/config.c,v
> retrieving revision 1.79
> diff -u -p -r1.79 config.c
> --- usr.sbin/bgpd/config.c 27 Dec 2018 20:23:24 -0000 1.79
> +++ usr.sbin/bgpd/config.c 7 Feb 2019 10:11:19 -0000
> @@ -39,7 +39,7 @@
>  u_int32_t get_bgpid(void);
>  int host_ip(const char *, struct bgpd_addr *, u_int8_t *);
>  void free_networks(struct network_head *);
> -void free_rdomains(struct rdomain_head *);
> +void free_l3vpns(struct l3vpn_head *);
>  
>  struct bgpd_config *
>  new_config(void)
> @@ -75,7 +75,7 @@ new_config(void)
>  
>   /* init the various list for later */
>   TAILQ_INIT(&conf->networks);
> - SIMPLEQ_INIT(&conf->rdomains);
> + SIMPLEQ_INIT(&conf->l3vpns);
>   SIMPLEQ_INIT(&conf->prefixsets);
>   SIMPLEQ_INIT(&conf->originsets);
>   RB_INIT(&conf->roa);
> @@ -101,16 +101,16 @@ free_networks(struct network_head *netwo
>  }
>  
>  void
> -free_rdomains(struct rdomain_head *rdomains)
> +free_l3vpns(struct l3vpn_head *l3vpns)
>  {
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>  
> - while ((rd = SIMPLEQ_FIRST(rdomains)) != NULL) {
> - SIMPLEQ_REMOVE_HEAD(rdomains, entry);
> - filterset_free(&rd->export);
> - filterset_free(&rd->import);
> - free_networks(&rd->net_l);
> - free(rd);
> + while ((vpn = SIMPLEQ_FIRST(l3vpns)) != NULL) {
> + SIMPLEQ_REMOVE_HEAD(l3vpns, entry);
> + filterset_free(&vpn->export);
> + filterset_free(&vpn->import);
> + free_networks(&vpn->net_l);
> + free(vpn);
>   }
>  }
>  
> @@ -145,7 +145,7 @@ free_config(struct bgpd_config *conf)
>   struct listen_addr *la;
>   struct mrt *m;
>  
> - free_rdomains(&conf->rdomains);
> + free_l3vpns(&conf->l3vpns);
>   free_networks(&conf->networks);
>   filterlist_free(conf->filters);
>   free_prefixsets(&conf->prefixsets);
> @@ -250,9 +250,9 @@ merge_config(struct bgpd_config *xconf,
>   TAILQ_INSERT_TAIL(&xconf->networks, n, entry);
>   }
>  
> - /* switch the rdomain configs, first remove the old ones */
> - free_rdomains(&xconf->rdomains);
> - SIMPLEQ_CONCAT(&xconf->rdomains, &conf->rdomains);
> + /* switch the l3vpn configs, first remove the old ones */
> + free_l3vpns(&xconf->l3vpns);
> + SIMPLEQ_CONCAT(&xconf->l3vpns, &conf->l3vpns);
>  
>   /*
>   * merge new listeners:
> @@ -469,27 +469,42 @@ prepare_listeners(struct bgpd_config *co
>  }
>  
>  int
> -get_mpe_label(struct rdomain *r)
> +get_mpe_config(const char *name, u_int *rdomain, u_int *label)
>  {
>   struct  ifreq ifr;
>   struct shim_hdr shim;
>   int s;
>  
> + *label = 0;
> + *rdomain = 0;
> +
>   s = socket(AF_INET, SOCK_DGRAM, 0);
>   if (s == -1)
>   return (-1);
>  
>   bzero(&shim, sizeof(shim));
>   bzero(&ifr, sizeof(ifr));
> - strlcpy(ifr.ifr_name, r->ifmpe, sizeof(ifr.ifr_name));
> + strlcpy(ifr.ifr_name, name, sizeof(ifr.ifr_name));
>   ifr.ifr_data = (caddr_t)&shim;
>  
>   if (ioctl(s, SIOCGETLABEL, (caddr_t)&ifr) == -1) {
>   close(s);
>   return (-1);
>   }
> +
> + ifr.ifr_data = NULL;
> + if (ioctl(s, SIOCGIFRDOMAIN, (caddr_t)&ifr) == -1) {
> + close(s);
> + return (-1);
> + }
> +
>   close(s);
> - r->label = shim.shim_label;
> +
> + *rdomain = ifr.ifr_rdomainid;
> + *label = shim.shim_label;
> +
> + log_debug("%s: rdomain %u label %u", __func__, *rdomain, *label);
> +
>   return (0);
>  }
>  
> Index: usr.sbin/bgpd/kroute.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/kroute.c,v
> retrieving revision 1.229
> diff -u -p -r1.229 kroute.c
> --- usr.sbin/bgpd/kroute.c 18 Jan 2019 23:30:45 -0000 1.229
> +++ usr.sbin/bgpd/kroute.c 7 Feb 2019 10:11:19 -0000
> @@ -65,6 +65,14 @@ struct knexthop_node {
>   void *kroute;
>  };
>  
> +struct kredist_node {
> + RB_ENTRY(kredist_node) entry;
> + struct bgpd_addr prefix;
> + u_int64_t rd;
> + u_int8_t prefixlen;
> + u_int8_t dynamic;
> +};
> +
>  struct kif_kr {
>   LIST_ENTRY(kif_kr) entry;
>   struct kroute_node *kr;
> @@ -99,16 +107,16 @@ int kr6_delete(struct ktable *, struct k
>  int krVPN4_delete(struct ktable *, struct kroute_full *, u_int8_t);
>  int krVPN6_delete(struct ktable *, struct kroute_full *, u_int8_t);
>  void kr_net_delete(struct network *);
> -struct network *kr_net_match(struct ktable *, struct kroute *);
> -struct network *kr_net_match6(struct ktable *, struct kroute6 *);
> +int kr_net_match(struct ktable *, struct network_config *, u_int16_t);
>  struct network *kr_net_find(struct ktable *, struct network *);
> -int kr_redistribute(int, struct ktable *, struct kroute *);
> -int kr_redistribute6(int, struct ktable *, struct kroute6 *);
> +void kr_redistribute(int, struct ktable *, struct kroute *);
> +void kr_redistribute6(int, struct ktable *, struct kroute6 *);
>  struct kroute_full *kr_tofull(struct kroute *);
>  struct kroute_full *kr6_tofull(struct kroute6 *);
>  int kroute_compare(struct kroute_node *, struct kroute_node *);
>  int kroute6_compare(struct kroute6_node *, struct kroute6_node *);
>  int knexthop_compare(struct knexthop_node *, struct knexthop_node *);
> +int kredist_compare(struct kredist_node *, struct kredist_node *);
>  int kif_compare(struct kif_node *, struct kif_node *);
>  void kr_fib_update_prio(u_int, u_int8_t);
>  
> @@ -185,6 +193,9 @@ RB_GENERATE(kroute6_tree, kroute6_node,
>  RB_PROTOTYPE(knexthop_tree, knexthop_node, entry, knexthop_compare)
>  RB_GENERATE(knexthop_tree, knexthop_node, entry, knexthop_compare)
>  
> +RB_PROTOTYPE(kredist_tree, kredist_node, entry, kredist_compare)
> +RB_GENERATE(kredist_tree, kredist_node, entry, kredist_compare)
> +
>  RB_HEAD(kif_tree, kif_node) kit;
>  RB_PROTOTYPE(kif_tree, kif_node, entry, kif_compare)
>  RB_GENERATE(kif_tree, kif_node, entry, kif_compare)
> @@ -399,35 +410,6 @@ ktable_update(u_int rtableid, char *name
>   return (0);
>  }
>  
> -void
> -ktable_preload(void)
> -{
> - struct ktable *kt;
> - u_int i;
> -
> - for (i = 0; i < krt_size; i++) {
> - if ((kt = ktable_get(i)) == NULL)
> - continue;
> - kt->state = RECONF_DELETE;
> - }
> -}
> -
> -void
> -ktable_postload(u_int8_t fib_prio)
> -{
> - struct ktable *kt;
> - u_int i;
> -
> - for (i = krt_size; i > 0; i--) {
> - if ((kt = ktable_get(i - 1)) == NULL)
> - continue;
> - if (kt->state == RECONF_DELETE)
> - ktable_free(i - 1, fib_prio);
> - else if (kt->state == RECONF_REINIT)
> - kt->fib_sync = kt->fib_conf;
> - }
> -}
> -
>  int
>  ktable_exists(u_int rtableid, u_int *rdomid)
>  {
> @@ -1199,93 +1181,105 @@ kr_net_delete(struct network *n)
>   free(n);
>  }
>  
> -struct network *
> -kr_net_match(struct ktable *kt, struct kroute *kr)
> +static int
> +kr_net_redist_add(struct ktable *kt, struct network_config *net,
> +    struct filter_set_head *attr, int dynamic)
> +{
> + struct kredist_node *r, *xr;
> +
> + if ((r = calloc(1, sizeof(*r))) == NULL)
> + fatal("%s", __func__);
> + r->prefix = net->prefix;
> + r->prefixlen = net->prefixlen;
> + r->rd = net->rd;
> + r->dynamic = dynamic;
> +
> + xr = RB_INSERT(kredist_tree, &kt->kredist, r);
> + if (xr != NULL && dynamic != xr->dynamic) {
> + if (dynamic) {
> + /*
> + * ignore update, a non-dynamic announcement
> + * is already present.
> + */
> + free(r);
> + return 0;
> + }
> + /* non-dynamic announcments are preferred */
> + xr->dynamic = dynamic;
> + }
> +
> + if (send_network(IMSG_NETWORK_ADD, net, attr) == -1)
> + log_warnx("%s: faild to send network update", __func__);
> + return 1;
> +}
> +
> +static void
> +kr_net_redist_del(struct ktable *kt, struct network_config *net, int dynamic)
>  {
> - struct network *xn;
> + struct kredist_node *r, node;
>  
> - TAILQ_FOREACH(xn, &kt->krn, entry) {
> - if (xn->net.prefix.aid != AID_INET)
> - continue;
> - switch (xn->net.type) {
> - case NETWORK_DEFAULT:
> - if (xn->net.prefixlen == kr->prefixlen &&
> -    xn->net.prefix.v4.s_addr == kr->prefix.s_addr)
> - /* static match already redistributed */
> - return (NULL);
> - break;
> - case NETWORK_STATIC:
> - if (kr->flags & F_STATIC)
> - return (xn);
> - break;
> - case NETWORK_CONNECTED:
> - if (kr->flags & F_CONNECTED)
> - return (xn);
> - break;
> - case NETWORK_RTLABEL:
> - if (kr->labelid == xn->net.rtlabel)
> - return (xn);
> - break;
> - case NETWORK_MRTCLONE:
> - /* can not happen */
> - break;
> - case NETWORK_PRIORITY:
> - if (kr->priority == xn->net.priority)
> - return (xn);
> - break;
> - case NETWORK_PREFIXSET:
> - /* must not happen */
> - log_warnx("%s: found a NETWORK_PREFIXSET, "
> -    "please send a bug report", __func__);
> - break;
> - }
> + bzero(&node, sizeof(node));
> + node.prefix = net->prefix;
> + node.prefixlen = net->prefixlen;
> + node.rd = net->rd;
> +
> + r = RB_FIND(kredist_tree, &kt->kredist, &node);
> + if (r == NULL || dynamic != r->dynamic)
> + return;
> +
> + if (RB_REMOVE(kredist_tree, &kt->kredist, r) == NULL) {
> + log_warnx("%s: failed to remove network %s/%u", __func__,
> +    log_addr(&node.prefix), node.prefixlen);
> + return;
>   }
> - return (NULL);
> + free(r);
> +
> + if (send_network(IMSG_NETWORK_REMOVE, net, NULL) == -1)
> + log_warnx("%s: faild to send network update", __func__);
>  }
>  
> -struct network *
> -kr_net_match6(struct ktable *kt, struct kroute6 *kr6)
> +int
> +kr_net_match(struct ktable *kt, struct network_config *net, u_int16_t flags)
>  {
>   struct network *xn;
> + int matched = 0;
>  
>   TAILQ_FOREACH(xn, &kt->krn, entry) {
> - if (xn->net.prefix.aid != AID_INET6)
> + if (xn->net.prefix.aid != net->prefix.aid)
>   continue;
>   switch (xn->net.type) {
>   case NETWORK_DEFAULT:
> - if (xn->net.prefixlen == kr6->prefixlen &&
> -    memcmp(&xn->net.prefix.v6, &kr6->prefix,
> -    sizeof(struct in6_addr)) == 0)
> - /* static match already redistributed */
> - return (NULL);
> - break;
> + /* static match already redistributed */
> + continue;
>   case NETWORK_STATIC:
> - if (kr6->flags & F_STATIC)
> - return (xn);
> - break;
> + if (flags & F_STATIC)
> + break;
> + continue;
>   case NETWORK_CONNECTED:
> - if (kr6->flags & F_CONNECTED)
> - return (xn);
> - break;
> + if (flags & F_CONNECTED)
> + break;
> + continue;
>   case NETWORK_RTLABEL:
> - if (kr6->labelid == xn->net.rtlabel)
> - return (xn);
> - break;
> - case NETWORK_MRTCLONE:
> - /* can not happen */
> - break;
> + if (net->rtlabel == xn->net.rtlabel)
> + break;
> + continue;
>   case NETWORK_PRIORITY:
> - if (kr6->priority == xn->net.priority)
> - return (xn);
> - break;
> + if (net->priority == xn->net.priority)
> + break;
> + continue;
> + case NETWORK_MRTCLONE:
>   case NETWORK_PREFIXSET:
>   /* must not happen */
>   log_warnx("%s: found a NETWORK_PREFIXSET, "
>      "please send a bug report", __func__);
> - break;
> + continue;
>   }
> +
> + net->rd = xn->net.rd;
> + if (kr_net_redist_add(kt, net, &xn->net.attrset, 1))
> + matched = 1;
>   }
> - return (NULL);
> + return matched;
>  }
>  
>  struct network *
> @@ -1296,7 +1290,7 @@ kr_net_find(struct ktable *kt, struct ne
>   TAILQ_FOREACH(xn, &kt->krn, entry) {
>   if (n->net.type != xn->net.type ||
>      n->net.prefixlen != xn->net.prefixlen ||
> -    n->net.rtableid != xn->net.rtableid)
> +    n->net.rd != xn->net.rd)
>   continue;
>   if (memcmp(&n->net.prefix, &xn->net.prefix,
>      sizeof(n->net.prefix)) == 0)
> @@ -1305,26 +1299,21 @@ kr_net_find(struct ktable *kt, struct ne
>   return (NULL);
>  }
>  
> -int
> -kr_net_reload(u_int rtableid, struct network_head *nh)
> +void
> +kr_net_reload(u_int rtableid, u_int64_t rd, struct network_head *nh)
>  {
>   struct network *n, *xn;
>   struct ktable *kt;
>  
> - if ((kt = ktable_get(rtableid)) == NULL) {
> - log_warnx("%s: non-existent rtableid %d", __func__, rtableid);
> - return (-1);
> - }
> -
> - TAILQ_FOREACH(n, &kt->krn, entry)
> - n->net.old = 1;
> + if ((kt = ktable_get(rtableid)) == NULL)
> + fatalx("%s: non-existent rtableid %d", __func__, rtableid);
>  
>   while ((n = TAILQ_FIRST(nh)) != NULL) {
>   log_debug("%s: processing %s/%u", __func__,
>      log_addr(&n->net.prefix), n->net.prefixlen);
>   TAILQ_REMOVE(nh, n, entry);
>   n->net.old = 0;
> - n->net.rtableid = rtableid;
> + n->net.rd = rd;
>   xn = kr_net_find(kt, n);
>   if (xn) {
>   xn->net.old = 0;
> @@ -1334,44 +1323,33 @@ kr_net_reload(u_int rtableid, struct net
>   } else
>   TAILQ_INSERT_TAIL(&kt->krn, n, entry);
>   }
> -
> - for (n = TAILQ_FIRST(&kt->krn); n != NULL; n = xn) {
> - xn = TAILQ_NEXT(n, entry);
> - if (n->net.old) {
> - if (n->net.type == NETWORK_DEFAULT)
> - if (send_network(IMSG_NETWORK_REMOVE, &n->net,
> -    NULL))
> - return (-1);
> - TAILQ_REMOVE(&kt->krn, n, entry);
> - kr_net_delete(n);
> - }
> - }
> -
> - return (0);
>  }
>  
> -int
> +void
>  kr_redistribute(int type, struct ktable *kt, struct kroute *kr)
>  {
> - struct network *match;
>   struct network_config net;
>   u_int32_t a;
>  
> + bzero(&net, sizeof(net));
> + net.prefix.aid = AID_INET;
> + net.prefix.v4.s_addr = kr->prefix.s_addr;
> + net.prefixlen = kr->prefixlen;
> + net.rtlabel = kr->labelid;
> + net.priority = kr->priority;
> +
>   /* shortcut for removals */
>   if (type == IMSG_NETWORK_REMOVE) {
> - if (!(kr->flags & F_REDISTRIBUTED))
> - return (0); /* no match, don't redistribute */
> - kr->flags &= ~F_REDISTRIBUTED;
> - match = NULL;
> - goto sendit;
> + kr_net_redist_del(kt, &net, 1);
> + return;
>   }
>  
>   if (!(kr->flags & F_KERNEL))
> - return (0);
> + return;
>  
>   /* Dynamic routes are not redistributable. */
>   if (kr->flags & F_DYNAMIC)
> - return (0);
> + return;
>  
>   /*
>   * We consider the loopback net, multicast and experimental addresses
> @@ -1380,61 +1358,48 @@ kr_redistribute(int type, struct ktable
>   a = ntohl(kr->prefix.s_addr);
>   if (IN_MULTICAST(a) || IN_BADCLASS(a) ||
>      (a >> IN_CLASSA_NSHIFT) == IN_LOOPBACKNET)
> - return (0);
> + return;
>  
>   /* Consider networks with nexthop loopback as not redistributable. */
>   if (kr->nexthop.s_addr == htonl(INADDR_LOOPBACK))
> - return (0);
> + return;
>  
>   /*
>   * never allow 0.0.0.0/0 the default route can only be redistributed
>   * with announce default.
>   */
>   if (kr->prefix.s_addr == INADDR_ANY && kr->prefixlen == 0)
> - return (0);
> -
> - match = kr_net_match(kt, kr);
> - if (match == NULL) {
> - if (!(kr->flags & F_REDISTRIBUTED))
> - return (0); /* no match, don't redistribute */
> - /* route no longer matches but is redistributed, so remove */
> - kr->flags &= ~F_REDISTRIBUTED;
> - type = IMSG_NETWORK_REMOVE;
> - } else
> - kr->flags |= F_REDISTRIBUTED;
> -
> -sendit:
> - bzero(&net, sizeof(net));
> - net.prefix.aid = AID_INET;
> - net.prefix.v4.s_addr = kr->prefix.s_addr;
> - net.prefixlen = kr->prefixlen;
> - net.rtlabel = kr->labelid;
> - net.rtableid = kt->rtableid;
> + return;
>  
> - return (send_network(type, &net, match ? &match->net.attrset : NULL));
> + if (kr_net_match(kt, &net, kr->flags) == 0)
> + /* no longer matches, if still present remove it */
> + kr_net_redist_del(kt, &net, 1);
>  }
>  
> -int
> +void
>  kr_redistribute6(int type, struct ktable *kt, struct kroute6 *kr6)
>  {
> - struct network *match;
>   struct network_config net;
>  
> + bzero(&net, sizeof(net));
> + net.prefix.aid = AID_INET6;
> + memcpy(&net.prefix.v6, &kr6->prefix, sizeof(struct in6_addr));
> + net.prefixlen = kr6->prefixlen;
> + net.rtlabel = kr6->labelid;
> + net.priority = kr6->priority;
> +
>   /* shortcut for removals */
>   if (type == IMSG_NETWORK_REMOVE) {
> - if (!(kr6->flags & F_REDISTRIBUTED))
> - return (0); /* no match, don't redistribute */
> - kr6->flags &= ~F_REDISTRIBUTED;
> - match = NULL;
> - goto sendit;
> + kr_net_redist_del(kt, &net, 1);
> + return;
>   }
>  
>   if (!(kr6->flags & F_KERNEL))
> - return (0);
> + return;
>  
>   /* Dynamic routes are not redistributable. */
>   if (kr6->flags & F_DYNAMIC)
> - return (0);
> + return;
>  
>   /*
>   * We consider unspecified, loopback, multicast, link- and site-local,
> @@ -1447,13 +1412,13 @@ kr_redistribute6(int type, struct ktable
>      IN6_IS_ADDR_SITELOCAL(&kr6->prefix) ||
>      IN6_IS_ADDR_V4MAPPED(&kr6->prefix) ||
>      IN6_IS_ADDR_V4COMPAT(&kr6->prefix))
> - return (0);
> + return;
>  
>   /*
>   * Consider networks with nexthop loopback as not redistributable.
>   */
>   if (IN6_IS_ADDR_LOOPBACK(&kr6->nexthop))
> - return (0);
> + return;
>  
>   /*
>   * never allow ::/0 the default route can only be redistributed
> @@ -1461,26 +1426,57 @@ kr_redistribute6(int type, struct ktable
>   */
>   if (kr6->prefixlen == 0 &&
>      memcmp(&kr6->prefix, &in6addr_any, sizeof(struct in6_addr)) == 0)
> - return (0);
> + return;
>  
> - match = kr_net_match6(kt, kr6);
> - if (match == NULL) {
> - if (!(kr6->flags & F_REDISTRIBUTED))
> - return (0); /* no match, don't redistribute */
> - /* route no longer matches but is redistributed, so remove */
> - kr6->flags &= ~F_REDISTRIBUTED;
> - type = IMSG_NETWORK_REMOVE;
> - } else
> - kr6->flags |= F_REDISTRIBUTED;
> -sendit:
> - bzero(&net, sizeof(net));
> - net.prefix.aid = AID_INET6;
> - memcpy(&net.prefix.v6, &kr6->prefix, sizeof(struct in6_addr));
> - net.prefixlen = kr6->prefixlen;
> - net.rtlabel = kr6->labelid;
> - net.rtableid = kt->rtableid;
> + if (kr_net_match(kt, &net, kr6->flags) == 0)
> + /* no longer matches, if still present remove it */
> + kr_net_redist_del(kt, &net, 1);
> +}
> +
> +void
> +ktable_preload(void)
> +{
> + struct ktable *kt;
> + struct network *n;
> + u_int i;
> +
> + for (i = 0; i < krt_size; i++) {
> + if ((kt = ktable_get(i)) == NULL)
> + continue;
> + kt->state = RECONF_DELETE;
> +
> + /* mark all networks as old */
> + TAILQ_FOREACH(n, &kt->krn, entry)
> + n->net.old = 1;
> + }
> +}
> +
> +void
> +ktable_postload(u_int8_t fib_prio)
> +{
> + struct ktable *kt;
> + struct network *n, *xn;
> + u_int i;
> +
> + for (i = krt_size; i > 0; i--) {
> + if ((kt = ktable_get(i - 1)) == NULL)
> + continue;
> + if (kt->state == RECONF_DELETE) {
> + ktable_free(i - 1, fib_prio);
> + continue;
> + } else if (kt->state == RECONF_REINIT)
> + kt->fib_sync = kt->fib_conf;
>  
> - return (send_network(type, &net, match ? &match->net.attrset : NULL));
> + /* cleanup old networks */
> + TAILQ_FOREACH_SAFE(n, &kt->krn, entry, xn) {
> + if (n->net.old) {
> + TAILQ_REMOVE(&kt->krn, n, entry);
> + if (n->net.type == NETWORK_DEFAULT)
> + kr_net_redist_del(kt, &n->net, 0);
> + kr_net_delete(n);
> + }
> + }
> + }
>  }
>  
>  int
> @@ -1503,9 +1499,8 @@ kr_reload(void)
>  
>   TAILQ_FOREACH(n, &kt->krn, entry)
>   if (n->net.type == NETWORK_DEFAULT) {
> - if (send_network(IMSG_NETWORK_ADD, &n->net,
> -    &n->net.attrset))
> - return (-1);
> + kr_net_redist_add(kt, &n->net,
> +    &n->net.attrset, 0);
>   } else
>   hasdyn = 1;
>  
> @@ -1640,13 +1635,53 @@ knexthop_compare(struct knexthop_node *a
>   }
>   break;
>   default:
> - fatalx("knexthop_compare: unknown AF");
> + fatalx("%s: unknown AF", __func__);
>   }
>  
>   return (0);
>  }
>  
>  int
> +kredist_compare(struct kredist_node *a, struct kredist_node *b)
> +{
> + int i;
> +
> + if (a->prefix.aid != b->prefix.aid)
> + return (b->prefix.aid - a->prefix.aid);
> +
> + if (a->prefixlen < b->prefixlen)
> + return (-1);
> + if (a->prefixlen > b->prefixlen)
> + return (1);
> +
> + switch (a->prefix.aid) {
> + case AID_INET:
> + if (ntohl(a->prefix.v4.s_addr) < ntohl(b->prefix.v4.s_addr))
> + return (-1);
> + if (ntohl(a->prefix.v4.s_addr) > ntohl(b->prefix.v4.s_addr))
> + return (1);
> + break;
> + case AID_INET6:
> + for (i = 0; i < 16; i++) {
> + if (a->prefix.v6.s6_addr[i] < b->prefix.v6.s6_addr[i])
> + return (-1);
> + if (a->prefix.v6.s6_addr[i] > b->prefix.v6.s6_addr[i])
> + return (1);
> + }
> + break;
> + default:
> + fatalx("%s: unknown AF", __func__);
> + }
> +
> + if (a->rd < b->rd)
> + return (-1);
> + if (a->rd > b->rd)
> + return (1);
> +
> + return (0);
> +}
> +
> +int
>  kif_compare(struct kif_node *a, struct kif_node *b)
>  {
>   return (b->k.ifindex - a->k.ifindex);
> @@ -3506,21 +3541,14 @@ dispatch_rtmsg_addr(struct rt_msghdr *rt
>   changed = 1;
>   kr->r.flags = flags;
>  
> - if (rtlabel_changed) {
> - if (oflags & F_REDISTRIBUTED) {
> - kr->r.flags |= F_REDISTRIBUTED;
> - kr_redistribute(
> -    IMSG_NETWORK_REMOVE, kt,
> -    &kr->r);
> - }
> + if (rtlabel_changed)
>   kr_redistribute(IMSG_NETWORK_ADD,
>      kt, &kr->r);
> - }
>  
>   if ((oflags & F_CONNECTED) &&
>      !(flags & F_CONNECTED)) {
>   kif_kr_remove(kr);
> - kr_redistribute(IMSG_NETWORK_REMOVE,
> + kr_redistribute(IMSG_NETWORK_ADD,
>      kt, &kr->r);
>   }
>   if ((flags & F_CONNECTED) &&
> @@ -3619,21 +3647,14 @@ add4:
>   changed = 1;
>   kr6->r.flags = flags;
>  
> - if (rtlabel_changed) {
> - if (oflags & F_REDISTRIBUTED) {
> - kr6->r.flags |= F_REDISTRIBUTED;
> - kr_redistribute6(
> -    IMSG_NETWORK_REMOVE, kt,
> -    &kr6->r);
> - }
> + if (rtlabel_changed)
>   kr_redistribute6(IMSG_NETWORK_ADD,
>      kt, &kr6->r);
> - }
>  
>   if ((oflags & F_CONNECTED) &&
>      !(flags & F_CONNECTED)) {
>   kif_kr6_remove(kr6);
> - kr_redistribute6(IMSG_NETWORK_REMOVE,
> + kr_redistribute6(IMSG_NETWORK_ADD,
>      kt, &kr6->r);
>   }
>   if ((flags & F_CONNECTED) &&
> Index: usr.sbin/bgpd/parse.y
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/parse.y,v
> retrieving revision 1.369
> diff -u -p -r1.369 parse.y
> --- usr.sbin/bgpd/parse.y 4 Feb 2019 18:53:10 -0000 1.369
> +++ usr.sbin/bgpd/parse.y 7 Feb 2019 10:11:19 -0000
> @@ -91,7 +91,7 @@ static struct network_head *netconf;
>  static struct peer *peer_l, *peer_l_old;
>  static struct peer *curpeer;
>  static struct peer *curgroup;
> -static struct rdomain *currdom;
> +static struct l3vpn *curvpn;
>  static struct prefixset *curpset, *curoset;
>  static struct prefixset_tree *curpsitree;
>  static struct filter_head *filter_l;
> @@ -154,7 +154,6 @@ void optimize_filters(struct filter_he
>  struct filter_rule *get_rule(enum action_types);
>  
>  int parsecommunity(struct filter_community *, int, char *);
> -int parsesubtype(char *, int *, int *);
>  int parseextcommunity(struct filter_community *, char *,
>      char *);
>  static int new_as_set(char *);
> @@ -195,7 +194,7 @@ typedef struct {
>  %}
>  
>  %token AS ROUTERID HOLDTIME YMIN LISTEN ON FIBUPDATE FIBPRIORITY RTABLE
> -%token RDOMAIN RD EXPORT EXPORTTRGT IMPORTTRGT
> +%token NONE UNICAST VPN RD EXPORT EXPORTTRGT IMPORTTRGT
>  %token RDE RIB EVALUATE IGNORE COMPARE
>  %token GROUP NEIGHBOR NETWORK
>  %token EBGP IBGP
> @@ -223,7 +222,7 @@ typedef struct {
>  %token <v.string> STRING
>  %token <v.number> NUMBER
>  %type <v.number> asnumber as4number as4number_any optnumber
> -%type <v.number> espah family restart origincode nettype
> +%type <v.number> espah family safi restart origincode nettype
>  %type <v.number> yesno inout restricted validity
>  %type <v.string> string
>  %type <v.addr> address
> @@ -253,7 +252,7 @@ grammar : /* empty */
>   | grammar roa_set '\n'
>   | grammar origin_set '\n'
>   | grammar conf_main '\n'
> - | grammar rdomain '\n'
> + | grammar l3vpn '\n'
>   | grammar neighbor '\n'
>   | grammar group '\n'
>   | grammar filterrule '\n'
> @@ -1044,39 +1043,57 @@ optnumber : /* empty */ { $$ = 0; }
>   | NUMBER
>   ;
>  
> -rdomain : RDOMAIN NUMBER {
> - if ($2 > RT_TABLEID_MAX) {
> - yyerror("rtable %llu too big: max %u", $2,
> -    RT_TABLEID_MAX);
> - YYERROR;
> +l3vpn : VPN STRING ON STRING {
> + u_int rdomain, label;
> +
> + if (get_mpe_config($4, &rdomain, &label) == -1) {
> + yyerror("troubles getting config of %s", $4);
> + if ((cmd_opts & BGPD_OPT_NOACTION) == 0) {
> + free($4);
> + free($2);
> + YYERROR;
> + }
>   }
> - if ((cmd_opts & BGPD_OPT_NOACTION) == 0 &&
> -    ktable_exists($2, NULL) != 1) {
> - yyerror("rdomain %lld does not exist", $2);
> +
> + if (!(curvpn = calloc(1, sizeof(struct l3vpn))))
> + fatal(NULL);
> + strlcpy(curvpn->ifmpe, $4, IFNAMSIZ);
> +
> + if (strlcpy(curvpn->descr, $2,
> +    sizeof(curvpn->descr)) >=
> +    sizeof(curvpn->descr)) {
> + yyerror("descr \"%s\" too long: max %zu",
> +    $2, sizeof(curvpn->descr) - 1);
> + free($2);
> + free($4);
> + free(curvpn);
> + curvpn = NULL;
>   YYERROR;
>   }
> - if (!(currdom = calloc(1, sizeof(struct rdomain))))
> - fatal(NULL);
> - currdom->rtableid = $2;
> - TAILQ_INIT(&currdom->import);
> - TAILQ_INIT(&currdom->export);
> - TAILQ_INIT(&currdom->net_l);
> - netconf = &currdom->net_l;
> - } '{' rdomainopts_l '}' {
> + free($2);
> + free($4);
> +
> + TAILQ_INIT(&curvpn->import);
> + TAILQ_INIT(&curvpn->export);
> + TAILQ_INIT(&curvpn->net_l);
> + curvpn->label = label;
> + curvpn->rtableid = rdomain;
> + netconf = &curvpn->net_l;
> + } '{' l3vpnopts_l '}' {
>   /* insert into list */
> - SIMPLEQ_INSERT_TAIL(&conf->rdomains, currdom, entry);
> - currdom = NULL;
> + SIMPLEQ_INSERT_TAIL(&conf->l3vpns, curvpn, entry);
> + curvpn = NULL;
>   netconf = &conf->networks;
>   }
>   ;
>  
> -rdomainopts_l : /* empty */
> - | rdomainopts_l '\n'
> - | rdomainopts_l rdomainopts '\n'
> - | rdomainopts_l error '\n'
> +l3vpnopts_l : /* empty */
> + | l3vpnopts_l '\n'
> + | l3vpnopts_l l3vpnopts '\n'
> + | l3vpnopts_l error '\n'
>   ;
>  
> -rdomainopts : RD STRING {
> +l3vpnopts : RD STRING {
>   struct filter_community ext;
>   u_int64_t rd;
>  
> @@ -1108,7 +1125,7 @@ rdomainopts : RD STRING {
>   yyerror("bad encoding of rd");
>   YYERROR;
>   }
> - currdom->rd = htobe64(rd);
> + curvpn->rd = htobe64(rd);
>   }
>   | EXPORTTRGT STRING STRING {
>   struct filter_set *set;
> @@ -1126,7 +1143,7 @@ rdomainopts : RD STRING {
>   }
>   free($3);
>   free($2);
> - TAILQ_INSERT_TAIL(&currdom->export, set, entry);
> + TAILQ_INSERT_TAIL(&curvpn->export, set, entry);
>   }
>   | IMPORTTRGT STRING STRING {
>   struct filter_set *set;
> @@ -1144,44 +1161,15 @@ rdomainopts : RD STRING {
>   }
>   free($3);
>   free($2);
> - TAILQ_INSERT_TAIL(&currdom->import, set, entry);
> - }
> - | DESCR string {
> - if (strlcpy(currdom->descr, $2,
> -    sizeof(currdom->descr)) >=
> -    sizeof(currdom->descr)) {
> - yyerror("descr \"%s\" too long: max %zu",
> -    $2, sizeof(currdom->descr) - 1);
> - free($2);
> - YYERROR;
> - }
> - free($2);
> + TAILQ_INSERT_TAIL(&curvpn->import, set, entry);
>   }
>   | FIBUPDATE yesno {
>   if ($2 == 0)
> - currdom->flags |= F_RIB_NOFIBSYNC;
> + curvpn->flags |= F_RIB_NOFIBSYNC;
>   else
> - currdom->flags &= ~F_RIB_NOFIBSYNC;
> + curvpn->flags &= ~F_RIB_NOFIBSYNC;
>   }
>   | network
> - | DEPEND ON STRING {
> - /* XXX this is a hack */
> - if ((cmd_opts & BGPD_OPT_NOACTION) == 0 &&
> -    if_nametoindex($3) == 0) {
> - yyerror("interface %s does not exist", $3);
> - free($3);
> - YYERROR;
> - }
> - strlcpy(currdom->ifmpe, $3, IFNAMSIZ);
> - free($3);
> - /* XXX this is in the wrong place */
> - if ((cmd_opts & BGPD_OPT_NOACTION) == 0 &&
> -    get_mpe_label(currdom)) {
> - yyerror("failed to get mpls label from %s",
> -    currdom->ifmpe);
> - YYERROR;
> - }
> - }
>   ;
>  
>  neighbor : { curpeer = new_peer(); }
> @@ -1350,30 +1338,23 @@ peeropts : REMOTEAS as4number {
>   }
>   curpeer->conf.min_holdtime = $3;
>   }
> - | ANNOUNCE family STRING {
> + | ANNOUNCE family safi {
>   u_int8_t aid, safi;
> - int8_t val = 1;
> + u_int16_t afi;
>  
> - if (!strcmp($3, "none")) {
> - safi = SAFI_UNICAST;
> - val = 0;
> - } else if (!strcmp($3, "unicast")) {
> - safi = SAFI_UNICAST;
> - } else if (!strcmp($3, "vpn")) {
> - safi = SAFI_MPLSVPN;
> + if ($3 == SAFI_NONE) {
> + for (aid = 0; aid < AID_MAX; aid++) {
> + if (aid2afi(aid, &afi, &safi) == -1)
> + continue;
> + curpeer->conf.capabilities.mp[aid] = 0;
> + }
>   } else {
> - yyerror("unknown/unsupported SAFI \"%s\"",
> -    $3);
> - free($3);
> - YYERROR;
> - }
> - free($3);
> -
> - if (afi2aid($2, safi, &aid) == -1) {
> - yyerror("unknown AFI/SAFI pair");
> - YYERROR;
> + if (afi2aid($2, $3, &aid) == -1) {
> + yyerror("unknown AFI/SAFI pair");
> + YYERROR;
> + }
> + curpeer->conf.capabilities.mp[aid] = 1;
>   }
> - curpeer->conf.capabilities.mp[aid] = val;
>   }
>   | ANNOUNCE CAPABILITIES yesno {
>   curpeer->conf.announce_capa = $3;
> @@ -1717,6 +1698,11 @@ family : IPV4 { $$ = AFI_IPv4; }
>   | IPV6 { $$ = AFI_IPv6; }
>   ;
>  
> +safi : NONE { $$ = SAFI_NONE; }
> + | UNICAST { $$ = SAFI_UNICAST; }
> + | VPN { $$ = SAFI_MPLSVPN; }
> + ;
> +
>  nettype : STATIC { $$ = 1; },
>   | CONNECTED { $$ = 0; }
>   ;
> @@ -2880,6 +2866,7 @@ lookup(char *s)
>   { "network", NETWORK},
>   { "nexthop", NEXTHOP},
>   { "no-modify", NOMODIFY},
> + { "none", NONE},
>   { "on", ON},
>   { "or-longer", LONGER},
>   { "origin", ORIGIN},
> @@ -2900,7 +2887,6 @@ lookup(char *s)
>   { "quick", QUICK},
>   { "rd", RD},
>   { "rde", RDE},
> - { "rdomain", RDOMAIN},
>   { "refresh", REFRESH },
>   { "reject", REJECT},
>   { "remote-as", REMOTEAS},
> @@ -2924,7 +2910,9 @@ lookup(char *s)
>   { "transit-as", TRANSITAS},
>   { "transparent-as", TRANSPARENT},
>   { "ttl-security", TTLSECURITY},
> + { "unicast", UNICAST},
>   { "via", VIA},
> + { "vpn", VPN},
>   { "weight", WEIGHT}
>   };
>   const struct keywords *p;
> @@ -3586,7 +3574,7 @@ parsecommunity(struct filter_community *
>   return (0);
>  }
>  
> -int
> +static int
>  parsesubtype(char *name, int *type, int *subtype)
>  {
>   const struct ext_comm_pairs *cp;
> Index: usr.sbin/bgpd/printconf.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/printconf.c,v
> retrieving revision 1.127
> diff -u -p -r1.127 printconf.c
> --- usr.sbin/bgpd/printconf.c 4 Feb 2019 18:53:10 -0000 1.127
> +++ usr.sbin/bgpd/printconf.c 7 Feb 2019 10:11:19 -0000
> @@ -35,8 +35,8 @@ void print_community(struct filter_com
>  void print_origin(u_int8_t);
>  void print_set(struct filter_set_head *);
>  void print_mainconf(struct bgpd_config *);
> -void print_rdomain_targets(struct filter_set_head *, const char *);
> -void print_rdomain(struct rdomain *);
> +void print_l3vpn_targets(struct filter_set_head *, const char *);
> +void print_l3vpn(struct l3vpn *);
>  const char *print_af(u_int8_t);
>  void print_network(struct network_config *, const char *);
>  void print_as_sets(struct as_set_head *);
> @@ -378,7 +378,7 @@ print_mainconf(struct bgpd_config *conf)
>  }
>  
>  void
> -print_rdomain_targets(struct filter_set_head *set, const char *tgt)
> +print_l3vpn_targets(struct filter_set_head *set, const char *tgt)
>  {
>   struct filter_set *s;
>   TAILQ_FOREACH(s, set, entry) {
> @@ -389,27 +389,24 @@ print_rdomain_targets(struct filter_set_
>  }
>  
>  void
> -print_rdomain(struct rdomain *r)
> +print_l3vpn(struct l3vpn *vpn)
>  {
>   struct network *n;
>  
> - printf("rdomain %u {\n", r->rtableid);
> - if (*r->descr)
> - printf("\tdescr \"%s\"\n", r->descr);
> - if (r->flags & F_RIB_NOFIBSYNC)
> + printf("vpn \"%s\" on %s {\n", vpn->descr, vpn->ifmpe);
> + printf("\n\t%s\n", log_rd(vpn->rd));
> +
> + print_l3vpn_targets(&vpn->export, "export-target");
> + print_l3vpn_targets(&vpn->import, "import-target");
> +
> + if (vpn->flags & F_RIB_NOFIBSYNC)
>   printf("\tfib-update no\n");
>   else
>   printf("\tfib-update yes\n");
> - printf("\tdepend on %s\n", r->ifmpe);
>  
> - TAILQ_FOREACH(n, &r->net_l, entry)
> + TAILQ_FOREACH(n, &vpn->net_l, entry)
>   print_network(&n->net, "\t");
>  
> - printf("\n\t%s\n", log_rd(r->rd));
> -
> - print_rdomain_targets(&r->export, "export-target");
> - print_rdomain_targets(&r->import, "import-target");
> -
>   printf("}\n");
>  }
>  
> @@ -958,12 +955,12 @@ void
>  print_config(struct bgpd_config *conf, struct rib_names *rib_l,
>      struct network_head *net_l, struct peer *peer_l,
>      struct filter_head *rules_l, struct mrt_head *mrt_l,
> -    struct rdomain_head *rdom_l)
> +    struct l3vpn_head *vpns_l)
>  {
>   struct filter_rule *r;
>   struct network *n;
>   struct rde_rib *rr;
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>  
>   print_mainconf(conf);
>   print_roa(&conf->roa);
> @@ -972,10 +969,10 @@ print_config(struct bgpd_config *conf, s
>   print_originsets(&conf->originsets);
>   TAILQ_FOREACH(n, net_l, entry)
>   print_network(&n->net, "");
> - if (!SIMPLEQ_EMPTY(rdom_l))
> + if (!SIMPLEQ_EMPTY(vpns_l))
>   printf("\n");
> - SIMPLEQ_FOREACH(rd, rdom_l, entry)
> - print_rdomain(rd);
> + SIMPLEQ_FOREACH(vpn, vpns_l, entry)
> + print_l3vpn(vpn);
>   printf("\n");
>   SIMPLEQ_FOREACH(rr, rib_l, entry) {
>   if (rr->flags & F_RIB_NOEVALUATE)
> Index: usr.sbin/bgpd/rde.c
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/rde.c,v
> retrieving revision 1.461
> diff -u -p -r1.461 rde.c
> --- usr.sbin/bgpd/rde.c 21 Jan 2019 02:07:56 -0000 1.461
> +++ usr.sbin/bgpd/rde.c 7 Feb 2019 10:11:19 -0000
> @@ -75,7 +75,7 @@ void rde_dump_ctx_throttle(pid_t, int)
>  void rde_dump_ctx_terminate(pid_t);
>  void rde_dump_mrt_new(struct mrt *, pid_t, int);
>  
> -int rde_rdomain_import(struct rde_aspath *, struct rdomain *);
> +int rde_l3vpn_import(struct rde_aspath *, struct l3vpn *);
>  void rde_reload_done(void);
>  static void rde_softreconfig_in_done(void *, u_int8_t);
>  static void rde_softreconfig_out_done(void *, u_int8_t);
> @@ -124,7 +124,7 @@ struct rde_prefixset_head originsets_old
>  struct rde_prefixset roa_old;
>  struct as_set_head *as_sets_tmp, *as_sets_old;
>  struct filter_head *out_rules, *out_rules_tmp;
> -struct rdomain_head *rdomains_l, *newdomains;
> +struct l3vpn_head *l3vpns_l, *newdomains;
>  struct imsgbuf *ibuf_se;
>  struct imsgbuf *ibuf_se_ctl;
>  struct imsgbuf *ibuf_main;
> @@ -227,10 +227,10 @@ rde_main(int debug, int verbose)
>   fatal(NULL);
>   TAILQ_INIT(out_rules);
>  
> - rdomains_l = calloc(1, sizeof(struct rdomain_head));
> - if (rdomains_l == NULL)
> + l3vpns_l = calloc(1, sizeof(struct l3vpn_head));
> + if (l3vpns_l == NULL)
>   fatal(NULL);
> - SIMPLEQ_INIT(rdomains_l);
> + SIMPLEQ_INIT(l3vpns_l);
>  
>   if ((conf = calloc(1, sizeof(struct bgpd_config))) == NULL)
>   fatal(NULL);
> @@ -675,7 +675,7 @@ rde_dispatch_imsg_parent(struct imsgbuf
>   static struct rde_prefixset *last_prefixset;
>   static struct as_set *last_as_set;
>   static struct set_table *last_set;
> - static struct rdomain *rd;
> + static struct l3vpn *vpn;
>   struct imsg imsg;
>   struct mrt xmrt;
>   struct rde_rib rn;
> @@ -764,7 +764,7 @@ rde_dispatch_imsg_parent(struct imsgbuf
>   if (out_rules_tmp == NULL)
>   fatal(NULL);
>   TAILQ_INIT(out_rules_tmp);
> - newdomains = calloc(1, sizeof(struct rdomain_head));
> + newdomains = calloc(1, sizeof(struct l3vpn_head));
>   if (newdomains == NULL)
>   fatal(NULL);
>   SIMPLEQ_INIT(newdomains);
> @@ -948,34 +948,34 @@ rde_dispatch_imsg_parent(struct imsgbuf
>   set_prep(last_as_set->set);
>   last_as_set = NULL;
>   break;
> - case IMSG_RECONF_RDOMAIN:
> + case IMSG_RECONF_VPN:
>   if (imsg.hdr.len - IMSG_HEADER_SIZE !=
> -    sizeof(struct rdomain))
> - fatalx("IMSG_RECONF_RDOMAIN bad len");
> - if ((rd = malloc(sizeof(struct rdomain))) == NULL)
> +    sizeof(struct l3vpn))
> + fatalx("IMSG_RECONF_VPN bad len");
> + if ((vpn = malloc(sizeof(struct l3vpn))) == NULL)
>   fatal(NULL);
> - memcpy(rd, imsg.data, sizeof(struct rdomain));
> - TAILQ_INIT(&rd->import);
> - TAILQ_INIT(&rd->export);
> - SIMPLEQ_INSERT_TAIL(newdomains, rd, entry);
> + memcpy(vpn, imsg.data, sizeof(struct l3vpn));
> + TAILQ_INIT(&vpn->import);
> + TAILQ_INIT(&vpn->export);
> + SIMPLEQ_INSERT_TAIL(newdomains, vpn, entry);
>   break;
> - case IMSG_RECONF_RDOMAIN_EXPORT:
> - if (rd == NULL) {
> + case IMSG_RECONF_VPN_EXPORT:
> + if (vpn == NULL) {
>   log_warnx("rde_dispatch_imsg_parent: "
> -    "IMSG_RECONF_RDOMAIN_EXPORT unexpected");
> +    "IMSG_RECONF_VPN_EXPORT unexpected");
>   break;
>   }
> - parent_set = &rd->export;
> + parent_set = &vpn->export;
>   break;
> - case IMSG_RECONF_RDOMAIN_IMPORT:
> - if (rd == NULL) {
> + case IMSG_RECONF_VPN_IMPORT:
> + if (vpn == NULL) {
>   log_warnx("rde_dispatch_imsg_parent: "
> -    "IMSG_RECONF_RDOMAIN_IMPORT unexpected");
> +    "IMSG_RECONF_VPN_IMPORT unexpected");
>   break;
>   }
> - parent_set = &rd->import;
> + parent_set = &vpn->import;
>   break;
> - case IMSG_RECONF_RDOMAIN_DONE:
> + case IMSG_RECONF_VPN_DONE:
>   parent_set = NULL;
>   break;
>   case IMSG_RECONF_DRAIN:
> @@ -2530,7 +2530,7 @@ rde_dump_mrt_new(struct mrt *mrt, pid_t
>   * kroute specific functions
>   */
>  int
> -rde_rdomain_import(struct rde_aspath *asp, struct rdomain *rd)
> +rde_l3vpn_import(struct rde_aspath *asp, struct l3vpn *rd)
>  {
>   struct filter_set *s;
>  
> @@ -2548,7 +2548,7 @@ rde_send_kroute(struct rib *rib, struct
>   struct bgpd_addr addr;
>   struct prefix *p;
>   struct rde_aspath *asp;
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>   enum imsg_type type;
>  
>   /*
> @@ -2588,8 +2588,8 @@ rde_send_kroute(struct rib *rib, struct
>   /* not Loc-RIB, no update for VPNs */
>   break;
>  
> - SIMPLEQ_FOREACH(rd, rdomains_l, entry) {
> - if (!rde_rdomain_import(asp, rd))
> + SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
> + if (!rde_l3vpn_import(asp, vpn))
>   continue;
>   /* must send exit_nexthop so that correct MPLS tunnel
>   * is chosen
> @@ -2599,8 +2599,8 @@ rde_send_kroute(struct rib *rib, struct
>      &prefix_nexthop(p)->exit_nexthop,
>      sizeof(kr.nexthop));
>   /* XXX not ideal but this will change */
> - kr.ifindex = if_nametoindex(rd->ifmpe);
> - if (imsg_compose(ibuf_main, type, rd->rtableid, 0, -1,
> + kr.ifindex = if_nametoindex(vpn->ifmpe);
> + if (imsg_compose(ibuf_main, type, vpn->rtableid, 0, -1,
>      &kr, sizeof(kr)) == -1)
>   fatal("%s %d imsg_compose error", __func__,
>      __LINE__);
> @@ -2881,7 +2881,7 @@ rde_send_nexthop(struct bgpd_addr *next,
>  void
>  rde_reload_done(void)
>  {
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>   struct rde_peer *peer;
>   struct filter_head *fh;
>   u_int16_t rid;
> @@ -2923,15 +2923,15 @@ rde_reload_done(void)
>   peerself->conf.remote_masklen = 32;
>   peerself->short_as = conf->short_as;
>  
> - /* apply new set of rdomain, sync will be done later */
> - while ((rd = SIMPLEQ_FIRST(rdomains_l)) != NULL) {
> - SIMPLEQ_REMOVE_HEAD(rdomains_l, entry);
> - filterset_free(&rd->import);
> - filterset_free(&rd->export);
> - free(rd);
> + /* apply new set of l3vpn, sync will be done later */
> + while ((vpn = SIMPLEQ_FIRST(l3vpns_l)) != NULL) {
> + SIMPLEQ_REMOVE_HEAD(l3vpns_l, entry);
> + filterset_free(&vpn->import);
> + filterset_free(&vpn->export);
> + free(vpn);
>   }
> - free(rdomains_l);
> - rdomains_l = newdomains;
> + free(l3vpns_l);
> + l3vpns_l = newdomains;
>   /* XXX WHERE IS THE SYNC ??? */
>  
>   /* check if roa changed */
> @@ -3687,7 +3687,7 @@ void
>  network_add(struct network_config *nc, int flagstatic)
>  {
>   struct filterstate state;
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>   struct rde_aspath *asp;
>   struct filter_set_head *vpnset = NULL;
>   in_addr_t prefix4;
> @@ -3695,44 +3695,44 @@ network_add(struct network_config *nc, i
>   u_int8_t vstate;
>   u_int16_t i;
>  
> - if (nc->rtableid != conf->default_tableid) {
> - SIMPLEQ_FOREACH(rd, rdomains_l, entry) {
> - if (rd->rtableid != nc->rtableid)
> + if (nc->rd != 0) {
> + SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
> + if (vpn->rd != nc->rd)
>   continue;
>   switch (nc->prefix.aid) {
>   case AID_INET:
>   prefix4 = nc->prefix.v4.s_addr;
>   bzero(&nc->prefix, sizeof(nc->prefix));
>   nc->prefix.aid = AID_VPN_IPv4;
> - nc->prefix.vpn4.rd = rd->rd;
> + nc->prefix.vpn4.rd = vpn->rd;
>   nc->prefix.vpn4.addr.s_addr = prefix4;
>   nc->prefix.vpn4.labellen = 3;
>   nc->prefix.vpn4.labelstack[0] =
> -    (rd->label >> 12) & 0xff;
> +    (vpn->label >> 12) & 0xff;
>   nc->prefix.vpn4.labelstack[1] =
> -    (rd->label >> 4) & 0xff;
> +    (vpn->label >> 4) & 0xff;
>   nc->prefix.vpn4.labelstack[2] =
> -    (rd->label << 4) & 0xf0;
> +    (vpn->label << 4) & 0xf0;
>   nc->prefix.vpn4.labelstack[2] |= BGP_MPLS_BOS;
> - vpnset = &rd->export;
> + vpnset = &vpn->export;
>   break;
>   case AID_INET6:
>   memcpy(&prefix6, &nc->prefix.v6.s6_addr,
>      sizeof(struct in6_addr));
>   memset(&nc->prefix, 0, sizeof(nc->prefix));
>   nc->prefix.aid = AID_VPN_IPv6;
> - nc->prefix.vpn6.rd = rd->rd;
> + nc->prefix.vpn6.rd = vpn->rd;
>   memcpy(&nc->prefix.vpn6.addr.s6_addr, &prefix6,
>      sizeof(struct in6_addr));
>   nc->prefix.vpn6.labellen = 3;
>   nc->prefix.vpn6.labelstack[0] =
> -    (rd->label >> 12) & 0xff;
> +    (vpn->label >> 12) & 0xff;
>   nc->prefix.vpn6.labelstack[1] =
> -    (rd->label >> 4) & 0xff;
> +    (vpn->label >> 4) & 0xff;
>   nc->prefix.vpn6.labelstack[2] =
> -    (rd->label << 4) & 0xf0;
> +    (vpn->label << 4) & 0xf0;
>   nc->prefix.vpn6.labelstack[2] |= BGP_MPLS_BOS;
> - vpnset = &rd->export;
> + vpnset = &vpn->export;
>   break;
>   default:
>   log_warnx("unable to VPNize prefix");
> @@ -3741,10 +3741,11 @@ network_add(struct network_config *nc, i
>   }
>   break;
>   }
> - if (rd == NULL) {
> + if (vpn == NULL) {
>   log_warnx("network_add: "
> -    "prefix %s/%u in non-existing rdomain %u",
> -    log_addr(&nc->prefix), nc->prefixlen, nc->rtableid);
> +    "prefix %s/%u in non-existing l3vpn %s",
> +    log_addr(&nc->prefix), nc->prefixlen,
> +    log_rd(nc->rd));
>   return;
>   }
>   }
> @@ -3789,29 +3790,29 @@ network_add(struct network_config *nc, i
>  void
>  network_delete(struct network_config *nc)
>  {
> - struct rdomain *rd;
> + struct l3vpn *vpn;
>   in_addr_t prefix4;
>   struct in6_addr prefix6;
>   u_int32_t i;
>  
> - if (nc->rtableid) {
> - SIMPLEQ_FOREACH(rd, rdomains_l, entry) {
> - if (rd->rtableid != nc->rtableid)
> + if (nc->rd) {
> + SIMPLEQ_FOREACH(vpn, l3vpns_l, entry) {
> + if (vpn->rd != nc->rd)
>   continue;
>   switch (nc->prefix.aid) {
>   case AID_INET:
>   prefix4 = nc->prefix.v4.s_addr;
>   bzero(&nc->prefix, sizeof(nc->prefix));
>   nc->prefix.aid = AID_VPN_IPv4;
> - nc->prefix.vpn4.rd = rd->rd;
> + nc->prefix.vpn4.rd = vpn->rd;
>   nc->prefix.vpn4.addr.s_addr = prefix4;
>   nc->prefix.vpn4.labellen = 3;
>   nc->prefix.vpn4.labelstack[0] =
> -    (rd->label >> 12) & 0xff;
> +    (vpn->label >> 12) & 0xff;
>   nc->prefix.vpn4.labelstack[1] =
> -    (rd->label >> 4) & 0xff;
> +    (vpn->label >> 4) & 0xff;
>   nc->prefix.vpn4.labelstack[2] =
> -    (rd->label << 4) & 0xf0;
> +    (vpn->label << 4) & 0xf0;
>   nc->prefix.vpn4.labelstack[2] |= BGP_MPLS_BOS;
>   break;
>   case AID_INET6:
> @@ -3819,16 +3820,16 @@ network_delete(struct network_config *nc
>      sizeof(struct in6_addr));
>   memset(&nc->prefix, 0, sizeof(nc->prefix));
>   nc->prefix.aid = AID_VPN_IPv6;
> - nc->prefix.vpn6.rd = rd->rd;
> + nc->prefix.vpn6.rd = vpn->rd;
>   memcpy(&nc->prefix.vpn6.addr.s6_addr, &prefix6,
>      sizeof(struct in6_addr));
>   nc->prefix.vpn6.labellen = 3;
>   nc->prefix.vpn6.labelstack[0] =
> -    (rd->label >> 12) & 0xff;
> +    (vpn->label >> 12) & 0xff;
>   nc->prefix.vpn6.labelstack[1] =
> -    (rd->label >> 4) & 0xff;
> +    (vpn->label >> 4) & 0xff;
>   nc->prefix.vpn6.labelstack[2] =
> -    (rd->label << 4) & 0xf0;
> +    (vpn->label << 4) & 0xf0;
>   nc->prefix.vpn6.labelstack[2] |= BGP_MPLS_BOS;
>   break;
>   default:
> Index: usr.sbin/bgpd/session.h
> ===================================================================
> RCS file: /cvs/src/usr.sbin/bgpd/session.h,v
> retrieving revision 1.128
> diff -u -p -r1.128 session.h
> --- usr.sbin/bgpd/session.h 20 Jan 2019 23:27:48 -0000 1.128
> +++ usr.sbin/bgpd/session.h 7 Feb 2019 10:11:19 -0000
> @@ -248,7 +248,7 @@ int carp_demote_set(char *, int);
>  int merge_config(struct bgpd_config *, struct bgpd_config *,
>      struct peer *);
>  int prepare_listeners(struct bgpd_config *);
> -int get_mpe_label(struct rdomain *);
> +int get_mpe_config(const char *, u_int *, u_int *);
>  
>  /* control.c */
>  int control_check(char *);
> @@ -285,7 +285,7 @@ int pfkey_init(struct bgpd_sysdep *);
>  /* printconf.c */
>  void print_config(struct bgpd_config *, struct rib_names *,
>      struct network_head *, struct peer *, struct filter_head *,
> -    struct mrt_head *, struct rdomain_head *);
> +    struct mrt_head *, struct l3vpn_head *);
>  
>  /* rde.c */
>  void rde_main(int, int);
>