Browse Source

zebra: initial DNSSL implementation

So far Quagga implemented RFC5006, which mainly consisted of RDNSS RA
option only. RFC5006 was eventually obsoleted by RFC6106, which extended
the spec with another RA option (DNSSL). This commit adds enough DNSSL
code and documentation to consider Quagga RFC6106-compliant.

* configure.ac: add check for struct nd_opt_dnssl
* doc/ipv6.texi: add description of "[no] ipv6 nd dnssl ..." commands
* zebra/interface.h: one more list in struct rtadvconf
* zebra/interface.c
  * if_zebra_new_hook(): add list initialization
  * nd_dump_vty(): update
* zebra/rtadv.h: add related macros and structures
* zebra/rtadv.c
  * rtadv_send_packet(): add DNSSL TLV generation
  * ipv6_nd_ra_interval_msec(): add DNSSL Lifetime constraint check
  * ipv6_nd_ra_interval(): idem
  * is_rfc1035_digit(): new static inline helper function
  * is_rfc1035_letter(): idem
  * is_rfc1035_let_dig(): idem
  * is_rfc1035_let_dig_hyp(): idem
  * is_rfc1035_ldh_str(): idem
  * is_rfc1035_label(): idem
  * parse_rfc1035_subdomain(): new function, RFC1035 validator/encoder
  * rtadv_dnssl_lookup(): new function similar to rtadv_rdnss_lookup()
  * ipv6_nd_dnssl_domain: new DEFUN with ALIAS
  * no_ipv6_nd_dnssl_domain(): idem
  * rtadv_config_write(): add "ipv6 nd dnssl ..." generation block
  * rtadv_init(): update for added VTY commands
Denis Ovsienko 7 years ago
parent
commit
b7618535d6
6 changed files with 345 additions and 1 deletions
  1. 1 1
      configure.ac
  2. 24 0
      doc/ipv6.texi
  3. 3 0
      zebra/interface.c
  4. 2 0
      zebra/interface.h
  5. 284 0
      zebra/rtadv.c
  6. 31 0
      zebra/rtadv.h

+ 1 - 1
configure.ac

@@ -1398,7 +1398,7 @@ AC_CHECK_TYPES([struct sockaddr, struct sockaddr_in,
 	struct ifaliasreq, struct if6_aliasreq, struct in6_aliasreq,
 	struct nd_opt_adv_interval, struct rt_addrinfo,
 	struct nd_opt_homeagent_info, struct nd_opt_adv_interval,
-	struct nd_opt_rdnss],
+	struct nd_opt_rdnss, struct nd_opt_dnssl],
 	[], [], QUAGGA_INCLUDES)
 
 AC_CHECK_MEMBERS([struct sockaddr.sa_len,

+ 24 - 0
doc/ipv6.texi

@@ -188,12 +188,36 @@ the packet, make "running-config" list them as required. In a CLI session this
 takes necessary amount of "no ipv6 nd rdnss ..." and "ipv6 nd rdnss..." commands.
 @end deffn
 
+@deffn {Interface Command} {ipv6 nd dnssl @var{domain} [@var{lifetime}]} {}
+@deffnx {Interface Command} {no ipv6 nd dnssl @var{domain} [@var{lifetime}]} {}
+
+Include DNSSL options to advertise DNS search list domains. Note, that ordering
+of DNSSL records is subject for the same rules, as for the RDNSS records
+described above.
+
+@itemize @bullet
+@item
+@var{domain} - a DNS search domain (e.g. domain.example.com) without the
+trailing "root" period. The domain name must meet the requirements of RFC1035.
+
+@item
+@var{lifetime} - lifetime in seconds with the same semantics as in
+"ipv6 nd rdnss ..." command.
+
+Range: @code{(obsolete|<1-4294967294>|infinite)}
+
+Default: 2 times current @var{ra-interval}
+@end itemize
+@end deffn
+
 @example
 @group
 interface eth0
  no ipv6 nd suppress-ra
  ipv6 nd prefix 2001:0DB8:5009::/64
  ipv6 nd rdnss 2001:0DB8:5009::1
+ ipv6 nd dnssl old.example.com obsolete
+ ipv6 nd dnssl new.example.com
 @end group
 @end example
 

+ 3 - 0
zebra/interface.c

@@ -142,6 +142,7 @@ if_zebra_new_hook (struct interface *ifp)
 
     rtadv->AdvPrefixList = list_new ();
     rtadv->AdvRDNSSList = list_new ();
+    rtadv->AdvDNSSLList = list_new ();
   }    
 #endif /* HAVE_RTADV */
 
@@ -805,6 +806,8 @@ nd_dump_vty (struct vty *vty, struct interface *ifp)
       if (listcount (rtadv->AdvRDNSSList))
 		  vty_out (vty, "  ND router advertisements with "
                 "RDNSS information.%s", VTY_NEWLINE);
+      if (listcount (rtadv->AdvDNSSLList))
+        vty_out (vty, "  ND router advertisements with DNSSL information%s", VTY_NEWLINE);
       if (rtadv->AdvIntervalOption)
       	vty_out (vty, "  ND router advertisements with Adv. Interval option.%s",
 		 VTY_NEWLINE);

+ 2 - 0
zebra/interface.h

@@ -189,6 +189,8 @@ struct rtadvconf
   struct list *AdvRDNSSList;
 #define	RTADV_DNS_INFINITY_LIFETIME (0xffffffff)
 #define	RTADV_DNS_OBSOLETE_LIFETIME (0x00000000)
+  /* a list of configured DNS Search List domains (RFC6106) */
+  struct list *AdvDNSSLList;
 };
 
 #endif /* HAVE_RTADV */

+ 284 - 0
zebra/rtadv.c

@@ -146,6 +146,7 @@ rtadv_send_packet (int sock, struct interface *ifp)
   struct listnode *node;
   u_int16_t pkt_RouterLifetime;
   struct rtadv_rdnss_entry *rdnss_entry;
+  struct rtadv_dnssl_entry *dnssl_entry;
 
   /*
    * Allocate control message bufffer.  This is dynamic because
@@ -267,6 +268,33 @@ rtadv_send_packet (int sock, struct interface *ifp)
       len += sizeof (struct in6_addr);
     }
 
+  /* DNSSL TLV(s) */
+  for (ALL_LIST_ELEMENTS_RO (zif->rtadv.AdvDNSSLList, node, dnssl_entry))
+    {
+      /* This implementation generates one TLV per subdomain, changing this
+         requires handling padding outside of the cycle and making sure
+         the size of each TLV doesn't exceed the maximum value. */
+      struct nd_opt_dnssl *tlv = (struct nd_opt_dnssl *) (buf + len);
+      size_t padding_bytes = 8 - (dnssl_entry->length_rfc1035 % 8);
+
+      tlv->nd_opt_dnssl_type = ND_OPT_DNSSL;
+      tlv->nd_opt_dnssl_len = (8 + dnssl_entry->length_rfc1035 + padding_bytes) / 8;
+      tlv->nd_opt_dnssl_reserved = 0;
+      /* see RDNSS Lifetime comment */
+      tlv->nd_opt_dnssl_lifetime = htonl
+      (
+        ! dnssl_entry->track_maxrai ? dnssl_entry->lifetime :
+        MAX (1, (unsigned)zif->rtadv.MaxRtrAdvInterval / 500) /* 2*MaxRAI in seconds */
+      );
+      len += sizeof (struct nd_opt_dnssl);
+      memcpy (buf + len, dnssl_entry->subdomain_rfc1035, dnssl_entry->length_rfc1035);
+      len += dnssl_entry->length_rfc1035;
+      if (! padding_bytes)
+        continue;
+      memset (buf + len, 0, padding_bytes);
+      len += padding_bytes;
+    }
+
   if (zif->rtadv.AdvIntervalOption)
     {
       struct nd_opt_adv_interval *ndopt_adv = 
@@ -765,6 +793,7 @@ DEFUN (ipv6_nd_ra_interval_msec,
   struct zebra_vrf *zvrf = vrf_info_lookup (ifp->vrf_id);
   struct listnode *node;
   struct rtadv_rdnss_entry *rdnss_entry;
+  struct rtadv_dnssl_entry *dnssl_entry;
 
   VTY_GET_INTEGER_RANGE ("router advertisement interval", interval, argv[0], 70, 1800000);
   if ((zif->rtadv.AdvDefaultLifetime != -1 && interval > (unsigned)zif->rtadv.AdvDefaultLifetime * 1000))
@@ -785,6 +814,17 @@ DEFUN (ipv6_nd_ra_interval_msec,
                rdnss_entry->lifetime, buf, VTY_NEWLINE);
       return CMD_WARNING;
     }
+  for (ALL_LIST_ELEMENTS_RO (zif->rtadv.AdvDNSSLList, node, dnssl_entry))
+    if
+    (
+      ! dnssl_entry->track_maxrai &&
+      ! rtadv_dns_lifetime_fits (dnssl_entry->lifetime * 1000, interval)
+    )
+    {
+      vty_out (vty, "This ra-interval would conflict with lifetime %u of DNSSL %s !%s",
+               dnssl_entry->lifetime, dnssl_entry->subdomain_str, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
 
   if (zif->rtadv.MaxRtrAdvInterval % 1000)
     zvrf->rtadv.adv_msec_if_count--;
@@ -813,6 +853,7 @@ DEFUN (ipv6_nd_ra_interval,
   struct zebra_vrf *zvrf = vrf_info_lookup (ifp->vrf_id);
   struct listnode *node;
   struct rtadv_rdnss_entry *rdnss_entry;
+  struct rtadv_dnssl_entry *dnssl_entry;
 
   VTY_GET_INTEGER_RANGE ("router advertisement interval", interval, argv[0], 1, 1800);
   if ((zif->rtadv.AdvDefaultLifetime != -1 && interval > (unsigned)zif->rtadv.AdvDefaultLifetime))
@@ -833,6 +874,17 @@ DEFUN (ipv6_nd_ra_interval,
                rdnss_entry->lifetime, buf, VTY_NEWLINE);
       return CMD_WARNING;
     }
+  for (ALL_LIST_ELEMENTS_RO (zif->rtadv.AdvDNSSLList, node, dnssl_entry))
+    if
+    (
+      ! dnssl_entry->track_maxrai &&
+      ! rtadv_dns_lifetime_fits (dnssl_entry->lifetime * 1000, interval * 1000)
+    )
+    {
+      vty_out (vty, "This ra-interval would conflict with lifetime %u of DNSSL %s !%s",
+               dnssl_entry->lifetime, dnssl_entry->subdomain_str, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
 
   if (zif->rtadv.MaxRtrAdvInterval % 1000)
     zvrf->rtadv.adv_msec_if_count--;
@@ -1699,6 +1751,212 @@ ALIAS (no_ipv6_nd_rdnss_addr,
        "IPv6 address of the server\n"
        "Lifetime in seconds (track ra-interval, if not set)")
 
+/* start of RFC1035 BNF implementation */
+
+/* <digit> ::= any one of the ten digits 0 through 9 */
+static inline char
+is_rfc1035_digit (const char c)
+{
+  return '0' <= c && c <= '9';
+}
+
+/* <letter> ::= any one of the 52 alphabetic characters A through Z in
+   upper case and a through z in lower case */
+static inline char
+is_rfc1035_letter (const char c)
+{
+  return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z');
+}
+
+/* <let-dig> ::= <letter> | <digit> */
+static inline char
+is_rfc1035_let_dig (const char c)
+{
+  return is_rfc1035_letter (c) || is_rfc1035_digit (c);
+}
+
+/* <let-dig-hyp> ::= <let-dig> | "-" */
+static inline char
+is_rfc1035_let_dig_hyp (const char c)
+{
+  return is_rfc1035_let_dig (c) || c == '-';
+}
+
+/* <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str> */
+static inline char
+is_rfc1035_ldh_str (const char * str, const size_t len)
+{
+  size_t i;
+  for (i = 0; i < len; i++)
+    if (! is_rfc1035_let_dig_hyp (str[i]))
+      return 0;
+  return 1;
+}
+
+/* <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ] */
+static char
+is_rfc1035_label (const char * str, const size_t len)
+{
+  if (len < 1 || len > 63) /* 00111111 */
+    return 0;
+  if (! is_rfc1035_letter (str[0]))
+    return 0;
+  if (len > 1 && ! is_rfc1035_let_dig (str[len - 1]))
+    return 0;
+  if (len > 2 && ! is_rfc1035_ldh_str (str + 1, len - 2))
+    return 0;
+  return 1;
+}
+
+/* Transform the provided "clear-text" DNS domain name into a RFC1035
+   representation and return number of bytes the resulting encoding takes,
+   or -1 in case of error.
+   <subdomain> ::= <label> | <subdomain> "." <label> */
+static int
+parse_rfc1035_subdomain
+(
+  const char * inbuf, /* NULL-terminated sequence of period-separated words */
+  u_int8_t * outbuf /* NULL-terminated sequence of RFC1035-styled "labels" */
+)
+{
+  unsigned char do_next = 1, chars_done = 0;
+  size_t chars_total = strlen (inbuf);
+
+  if (chars_total < 1 || chars_total > SUBDOMAIN_MAX_STRLEN)
+    return -1;
+
+  do
+  {
+    size_t word_len;
+    char * period = strchr (inbuf + chars_done, '.');
+
+    /* Find the end of the current word and decide if it is the last one. */
+    if (period)
+      word_len = period - inbuf - chars_done;
+    else
+    {
+      do_next = 0;
+      word_len = chars_total - chars_done;
+    }
+    if (! is_rfc1035_label (inbuf + chars_done, word_len))
+      return -1;
+    /* count trailing '.'/0 input byte for leading "label length" output byte */
+    outbuf[chars_done] = word_len;
+    memcpy (outbuf + chars_done + 1, inbuf + chars_done, word_len);
+    chars_done += word_len + 1;
+  }
+  while (do_next);
+  outbuf[chars_done++] = 0; /* the extra byte */
+  return chars_done;
+}
+
+/* end of RFC1035 BNF implementation */
+
+static struct rtadv_dnssl_entry *
+rtadv_dnssl_lookup (struct list *list, const char * subdomain)
+{
+  struct listnode *node;
+  struct rtadv_dnssl_entry *entry;
+
+  for (ALL_LIST_ELEMENTS_RO (list, node, entry))
+    if (! strcasecmp (entry->subdomain_str, subdomain))
+      return entry;
+  return NULL;
+}
+
+DEFUN (ipv6_nd_dnssl_domain,
+       ipv6_nd_dnssl_domain_cmd,
+       "ipv6 nd dnssl DNS.DOMA.IN",
+       "Interface IPv6 config commands\n"
+       "Neighbor discovery\n"
+       "DNS search list\n"
+       "DNS domain\n")
+{
+  struct interface *ifp = (struct interface *) vty->index;
+  struct zebra_if *zif = ifp->info;
+  struct rtadv_dnssl_entry input, *stored;
+  int parsed_bytes;
+
+  /* validate input */
+  parsed_bytes = parse_rfc1035_subdomain (argv[0], input.subdomain_rfc1035);
+  if (parsed_bytes <= 0)
+  {
+    vty_out (vty, "Invalid DNS domain name '%s'%s", argv[0], VTY_NEWLINE);
+    return CMD_WARNING;
+  }
+  input.length_rfc1035 = parsed_bytes;
+  strcpy (input.subdomain_str, argv[0]);
+
+  if (argc < 2) /* no explicit lifetime*/
+    input.track_maxrai = 1;
+  else
+  {
+    input.track_maxrai = 0;
+    if (! strncmp (argv[1], "i", 1)) /* infinite */
+      input.lifetime = RTADV_DNS_INFINITY_LIFETIME;
+    else if (! strncmp (argv[1], "o", 1)) /* obsolete */
+      input.lifetime = RTADV_DNS_OBSOLETE_LIFETIME;
+    else
+    {
+      VTY_GET_INTEGER_RANGE ("lifetime", input.lifetime, argv[1], 1, 4294967294);
+      if (! rtadv_dns_lifetime_fits (input.lifetime * 1000, zif->rtadv.MaxRtrAdvInterval))
+      {
+        vty_out (vty, "This lifetime conflicts with ra-interval (%u ms)%s",
+                 zif->rtadv.MaxRtrAdvInterval, VTY_NEWLINE);
+        return CMD_WARNING;
+      }
+    }
+  }
+
+  /* OK to commit the update */
+  if (! (stored = rtadv_dnssl_lookup (zif->rtadv.AdvDNSSLList, input.subdomain_str)))
+  {
+    stored = XCALLOC (MTYPE_RTADV_PREFIX, sizeof (struct rtadv_dnssl_entry));
+    listnode_add (zif->rtadv.AdvDNSSLList, stored);
+  }
+  memcpy (stored, &input, sizeof (struct rtadv_dnssl_entry));
+  return CMD_SUCCESS;
+}
+
+ALIAS (ipv6_nd_dnssl_domain,
+       ipv6_nd_dnssl_domain_lft_cmd,
+       "ipv6 nd dnssl DNS.DOMA.IN (<1-4294967294>|infinite|obsolete)",
+       "Interface IPv6 config commands\n"
+       "Neighbor discovery\n"
+       "DNS search list\n"
+       "DNS domain\n"
+       "Lifetime in seconds (track ra-interval, if not set)")
+
+DEFUN (no_ipv6_nd_dnssl_domain,
+       no_ipv6_nd_dnssl_domain_cmd,
+       "no ipv6 nd dnssl DNS.DOMA.IN",
+       NO_STR
+       "Interface IPv6 config commands\n"
+       "Neighbor discovery\n"
+       "DNS search list\n"
+       "DNS domain\n")
+{
+  struct interface *ifp = (struct interface *) vty->index;
+  struct zebra_if *zif = ifp->info;
+  struct rtadv_dnssl_entry *entry;
+
+  if (! (entry = rtadv_dnssl_lookup (zif->rtadv.AdvDNSSLList, argv[0])))
+    return CMD_ERR_NO_MATCH;
+  listnode_delete (zif->rtadv.AdvDNSSLList, entry);
+  XFREE (MTYPE_RTADV_PREFIX, entry);
+  return CMD_SUCCESS;
+}
+
+ALIAS (no_ipv6_nd_dnssl_domain,
+       no_ipv6_nd_dnssl_domain_lft_cmd,
+       "no ipv6 nd dnssl DNS.DOMA.IN (<1-4294967294>|infinite|obsolete)",
+       NO_STR
+       "Interface IPv6 config commands\n"
+       "Neighbor discovery\n"
+       "DNS search list\n"
+       "DNS domain\n"
+       "Lifetime in seconds (track ra-interval, if not set)")
+
 
 /* Write configuration about router advertisement. */
 void
@@ -1709,6 +1967,7 @@ rtadv_config_write (struct vty *vty, struct interface *ifp)
   struct rtadv_prefix *rprefix;
   char buf[PREFIX_STRLEN];
   struct rtadv_rdnss_entry *rdnss_entry;
+  struct rtadv_dnssl_entry *dnssl_entry;
   int interval;
 
   zif = ifp->info;
@@ -1811,6 +2070,27 @@ rtadv_config_write (struct vty *vty, struct interface *ifp)
           break;
         }
     }
+  for (ALL_LIST_ELEMENTS_RO (zif->rtadv.AdvDNSSLList, node, dnssl_entry))
+    {
+      vty_out (vty, " ipv6 nd dnssl %s", dnssl_entry->subdomain_str);
+      if (dnssl_entry->track_maxrai)
+        {
+          vty_out (vty, "%s", VTY_NEWLINE);
+          continue;
+        }
+      switch (dnssl_entry->lifetime)
+        {
+        case RTADV_DNS_OBSOLETE_LIFETIME:
+          vty_out (vty, " obsolete%s", VTY_NEWLINE);
+          break;
+        case RTADV_DNS_INFINITY_LIFETIME:
+          vty_out (vty, " infinite%s", VTY_NEWLINE);
+          break;
+        default:
+          vty_out (vty, " %u%s", dnssl_entry->lifetime, VTY_NEWLINE);
+          break;
+        }
+    }
 }
 
 
@@ -1936,6 +2216,10 @@ rtadv_cmd_init (void)
   install_element (INTERFACE_NODE, &ipv6_nd_rdnss_addr_lft_cmd);
   install_element (INTERFACE_NODE, &no_ipv6_nd_rdnss_addr_cmd);
   install_element (INTERFACE_NODE, &no_ipv6_nd_rdnss_addr_lft_cmd);
+  install_element (INTERFACE_NODE, &ipv6_nd_dnssl_domain_cmd);
+  install_element (INTERFACE_NODE, &ipv6_nd_dnssl_domain_lft_cmd);
+  install_element (INTERFACE_NODE, &no_ipv6_nd_dnssl_domain_cmd);
+  install_element (INTERFACE_NODE, &no_ipv6_nd_dnssl_domain_lft_cmd);
 }
 
 static int

+ 31 - 0
zebra/rtadv.h

@@ -71,6 +71,9 @@ extern void rtadv_config_write (struct vty *, struct interface *);
 #ifndef ND_OPT_RDNSS
 #define ND_OPT_RDNSS 25 /* Recursive DNS Server Option (RFC 6106) */
 #endif
+#ifndef ND_OPT_DNSSL
+#define ND_OPT_DNSSL 31 /* DNS Search List Option (RFC6106) */
+#endif
 
 #ifndef HAVE_STRUCT_ND_OPT_ADV_INTERVAL
 struct nd_opt_adv_interval {   /* Advertisement interval option */
@@ -118,6 +121,34 @@ struct rtadv_rdnss_entry
   u_int32_t       lifetime;
 };
 
+#ifndef HAVE_STRUCT_ND_OPT_DNSSL
+struct nd_opt_dnssl
+{
+  u_int8_t  nd_opt_dnssl_type;
+  u_int8_t  nd_opt_dnssl_len;
+  u_int16_t nd_opt_dnssl_reserved;
+  u_int32_t nd_opt_dnssl_lifetime;
+  /* followed by list of DNS search domains */
+} __attribute__((__packed__));
+#endif
+
+/* RFC1035 sets the maximum length of a label-encoded domain name to 255 bytes,
+ * which stand for 253 non-0 chars of the NULL-terminated string representation,
+ * one trailing 0 byte and one extra "length" byte before the last label, which
+ * is not set off with a '.' byte of the respective input word (trailing "root"
+ * dot is considered a syntax error in this implementation). */
+#define SUBDOMAIN_MAX_STRLEN 253
+
+/* for internal tracking of configured DNSSL entries */
+struct rtadv_dnssl_entry
+{
+  char      subdomain_str[SUBDOMAIN_MAX_STRLEN + 1];
+  u_int8_t  subdomain_rfc1035[SUBDOMAIN_MAX_STRLEN + 2];
+  u_char    length_rfc1035;
+  u_char    track_maxrai;
+  u_int32_t lifetime;
+};
+
 extern const char *rtadv_pref_strs[];
 
 #endif /* HAVE_RTADV */