Add set ipv6 next-hop peer-address command.

IPv4 has the ability to specify the peer address with the keyword peer-address.
IPv6 mandates the use of a specific global or local address only in setting the
next-hop in routemaps. This makes it cumbersome to configure some large networks
with BGP and IPv6. This patch fixes that deficiency.

Signed-off-by: Dinesh G Dutt <ddutt@cumulusnetworks.com>
Reviewed-by: Paul Jakma <paul@opensourcerouting.org>
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index 06b0859..a1b7f33 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -2021,6 +2021,100 @@
   route_set_ipv6_nexthop_local_compile,
   route_set_ipv6_nexthop_local_free
 };
+
+/* `set ipv6 nexthop peer-address' */
+
+/* Set nexthop to object.  ojbect must be pointer to struct attr. */
+static route_map_result_t
+route_set_ipv6_nexthop_peer (void *rule, struct prefix *prefix,
+			     route_map_object_t type, void *object)
+{
+  int *use_peer_address;
+  struct in6_addr peer_address;
+  struct bgp_info *bgp_info;
+  struct peer *peer;
+  char peer_addr_buf[INET6_ADDRSTRLEN];
+
+  if (type == RMAP_BGP)
+    {
+      /* Fetch routemap's rule information. */
+      use_peer_address = rule;
+      bgp_info = object;
+      peer = bgp_info->peer;
+
+      if ((CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_IN) ||
+           CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_IMPORT))
+	  && peer->su_remote
+	  && sockunion_family (peer->su_remote) == AF_INET6)
+	{
+	  inet_pton (AF_INET6, sockunion2str (peer->su_remote,
+					      peer_addr_buf,
+					      INET6_ADDRSTRLEN),
+		     &peer_address);
+	}
+      else if (CHECK_FLAG (peer->rmap_type, PEER_RMAP_TYPE_OUT)
+	       && peer->su_local
+	       && sockunion_family (peer->su_local) == AF_INET6)
+	{
+	  inet_pton (AF_INET, sockunion2str (peer->su_local,
+					     peer_addr_buf,
+					     INET6_ADDRSTRLEN),
+		     &peer_address);
+	}
+
+      if (IN6_IS_ADDR_LINKLOCAL(&peer_address))
+	{
+	  /* Set next hop value. */
+	  (bgp_attr_extra_get (bgp_info->attr))->mp_nexthop_local = peer_address;
+
+	  /* Set nexthop length. */
+	  if (bgp_info->attr->extra->mp_nexthop_len != 32)
+	    bgp_info->attr->extra->mp_nexthop_len = 32;
+	}
+      else
+	{
+	  /* Set next hop value. */
+	  (bgp_attr_extra_get (bgp_info->attr))->mp_nexthop_global = peer_address;
+
+	  /* Set nexthop length. */
+	  if (bgp_info->attr->extra->mp_nexthop_len == 0)
+	    bgp_info->attr->extra->mp_nexthop_len = 16;
+	}
+    }
+
+  return RMAP_OKAY;
+}
+
+/* Route map `ip next-hop' compile function.  Given string is converted
+   to struct in_addr structure. */
+static void *
+route_set_ipv6_nexthop_peer_compile (const char *arg)
+{
+  int ret;
+  int *rins = NULL;
+
+  rins = XCALLOC (MTYPE_ROUTE_MAP_COMPILED, sizeof (int));
+  *rins = 1;
+
+  return rins;
+}
+
+/* Free route map's compiled `ip next-hop' value. */
+static void
+route_set_ipv6_nexthop_peer_free (void *rule)
+{
+  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+/* Route map commands for ip nexthop set. */
+struct route_map_rule_cmd route_set_ipv6_nexthop_peer_cmd =
+{
+  "ipv6 next-hop peer-address",
+  route_set_ipv6_nexthop_peer,
+  route_set_ipv6_nexthop_peer_compile,
+  route_set_ipv6_nexthop_peer_free
+};
+
 #endif /* HAVE_IPV6 */
 
 /* `set vpnv4 nexthop A.B.C.D' */
@@ -3571,6 +3665,29 @@
   return bgp_route_match_delete (vty, vty->index, "ipv6 address prefix-list", argv[0]);
 }
 
+DEFUN (set_ipv6_nexthop_peer,
+       set_ipv6_nexthop_peer_cmd,
+       "set ipv6 next-hop peer-address",
+       SET_STR
+       IPV6_STR
+       "Next hop address\n"
+       "Use peer address (for BGP only)\n")
+{
+  return bgp_route_set_add (vty, vty->index, "ipv6 next-hop peer-address", NULL);
+}
+
+DEFUN (no_set_ipv6_nexthop_peer,
+       no_set_ipv6_nexthop_peer_cmd,
+       "no set ipv6 next-hop peer-address",
+       NO_STR
+       SET_STR
+       IPV6_STR
+       "IPv6 next-hop address\n"
+       )
+{
+  return bgp_route_set_delete (vty, vty->index, "ipv6 next-hop", argv[0]);
+}
+
 DEFUN (set_ipv6_nexthop_global,
        set_ipv6_nexthop_global_cmd,
        "set ipv6 next-hop global X:X::X:X",
@@ -3909,7 +4026,8 @@
   route_map_install_match (&route_match_ipv6_address_prefix_list_cmd);
   route_map_install_set (&route_set_ipv6_nexthop_global_cmd);
   route_map_install_set (&route_set_ipv6_nexthop_local_cmd);
-  
+  route_map_install_set (&route_set_ipv6_nexthop_peer_cmd);
+
   install_element (RMAP_NODE, &match_ipv6_address_cmd);
   install_element (RMAP_NODE, &no_match_ipv6_address_cmd);
   install_element (RMAP_NODE, &match_ipv6_next_hop_cmd);
@@ -3922,6 +4040,8 @@
   install_element (RMAP_NODE, &set_ipv6_nexthop_local_cmd);
   install_element (RMAP_NODE, &no_set_ipv6_nexthop_local_cmd);
   install_element (RMAP_NODE, &no_set_ipv6_nexthop_local_val_cmd);
+  install_element (RMAP_NODE, &set_ipv6_nexthop_peer_cmd);
+  install_element (RMAP_NODE, &no_set_ipv6_nexthop_peer_cmd);
 #endif /* HAVE_IPV6 */
 
   /* AS-Pathlimit: functionality removed, commands kept for