bgpd: RFC 5082 Generalized TTL Security Mechanism support

* bgpd: Add support for RFC 5082 GTSM, which allows the TTL field to be used
  to verify that incoming packets have been sent from neighbours no more
  than X IP hops away. In other words, this allows packets that were sent from
  further away (i.e. not by the neighbour with known distance, and so possibly
  a miscreant) to be filtered out.
* lib/sockunion.{c,h}: (sockopt_minttl) new function, to set a minimum TTL
  using the IP_MINTTL socket opt.
* bgpd.h: (BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK) define for command
  error for minttl.
  (struct peer) add a config variable, to store the configured minttl.
  (peer_ttl_security_hops_{set,unset}) configuration handlers
* bgpd.c: (peer_group_get) init gtsm_hops
  (peer_ebgp_multihop_{un,}set) check for conflicts with GTSM. Multihop and
  GTSM can't both be active for a peer at the same time.
  (peer_ttl_security_hops_set) set minttl, taking care to avoid conflicts with
  ebgp_multihop.
  (bgp_config_write_peer) write out minttl as "neighbor .. ttl-security hops X".
* bgp_vty.c: (bgp_vty_return) message for
  BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK
  (peer_ebgp_multihop_{un,}set_vty)
* bgp_network.c: (bgp_accept) set minttl on accepted sockets if appropriate.
  (bgp_connect) ditto for outbound.
diff --git a/bgpd/bgp_network.c b/bgpd/bgp_network.c
index 4c79aa6..502f567 100644
--- a/bgpd/bgp_network.c
+++ b/bgpd/bgp_network.c
@@ -173,8 +173,11 @@
     }
 
   /* In case of peer is EBGP, we should set TTL for this connection.  */
-  if (peer_sort (peer1) == BGP_PEER_EBGP)
+  if (peer_sort (peer1) == BGP_PEER_EBGP) {
     sockopt_ttl (peer1->su.sa.sa_family, bgp_sock, peer1->ttl);
+    if (peer1->gtsm_hops)
+      sockopt_minttl (peer1->su.sa.sa_family, bgp_sock, MAXTTL + 1 - peer1->gtsm_hops);
+  }
 
   /* Make dummy peer until read Open packet. */
   if (BGP_DEBUG (events, EVENTS))
@@ -314,8 +317,11 @@
     return -1;
 
   /* If we can get socket for the peer, adjest TTL and make connection. */
-  if (peer_sort (peer) == BGP_PEER_EBGP)
+  if (peer_sort (peer) == BGP_PEER_EBGP) {
     sockopt_ttl (peer->su.sa.sa_family, peer->fd, peer->ttl);
+    if (peer->gtsm_hops)
+      sockopt_minttl (peer->su.sa.sa_family, peer->fd, MAXTTL + 1 - peer->gtsm_hops);
+  }
 
   sockopt_reuseaddr (peer->fd);
   sockopt_reuseport (peer->fd);
@@ -462,7 +468,10 @@
 	  zlog_err ("socket: %s", safe_strerror (errno));
 	  continue;
 	}
-
+	
+      /* if we intend to implement ttl-security, this socket needs ttl=255 */
+      sockopt_ttl (ainfo->ai_family, sock, MAXTTL);
+      
       ret = bgp_listener (sock, ainfo->ai_addr, ainfo->ai_addrlen);
       if (ret == 0)
 	++count;
@@ -495,6 +504,9 @@
       return sock;
     }
 
+  /* if we intend to implement ttl-security, this socket needs ttl=255 */
+  sockopt_ttl (AF_INET, sock, MAXTTL);
+
   memset (&sin, 0, sizeof (struct sockaddr_in));
   sin.sin_family = AF_INET;
   sin.sin_port = htons (port);
diff --git a/bgpd/bgp_vty.c b/bgpd/bgp_vty.c
index 1f93eea..e1c47f4 100644
--- a/bgpd/bgp_vty.c
+++ b/bgpd/bgp_vty.c
@@ -213,6 +213,9 @@
     case BGP_ERR_TCPSIG_FAILED:
       str = "Error while applying TCP-Sig to session(s)";
       break;
+    case BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK:
+      str = "ebgp-multihop and ttl-security cannot be configured together";
+      break;
     }
   if (str)
     {
@@ -2626,6 +2629,7 @@
 {
   struct peer *peer;
   unsigned int ttl;
+  int ret;
 
   peer = peer_and_group_lookup_vty (vty, ip_str);
   if (! peer)
@@ -2636,23 +2640,24 @@
   else
     VTY_GET_INTEGER_RANGE ("TTL", ttl, ttl_str, 1, 255);
 
-  peer_ebgp_multihop_set (peer, ttl);
-
-  return CMD_SUCCESS;
+  ret = peer_ebgp_multihop_set (peer, ttl);
+  
+  return bgp_vty_return (vty, ret);
 }
 
 static int
 peer_ebgp_multihop_unset_vty (struct vty *vty, const char *ip_str) 
 {
   struct peer *peer;
+  int ret;
 
   peer = peer_and_group_lookup_vty (vty, ip_str);
   if (! peer)
     return CMD_WARNING;
 
-  peer_ebgp_multihop_unset (peer);
-
-  return CMD_SUCCESS;
+  ret = peer_ebgp_multihop_unset (peer);
+  
+  return bgp_vty_return (vty, ret);
 }
 
 /* neighbor ebgp-multihop. */
@@ -3954,6 +3959,47 @@
   return bgp_vty_return (vty, ret);
 }
 
+DEFUN (neighbor_ttl_security,
+       neighbor_ttl_security_cmd,
+       NEIGHBOR_CMD2 "ttl-security hops <1-254>",
+       NEIGHBOR_STR
+       NEIGHBOR_ADDR_STR2
+       "Specify the maximum number of hops to the BGP peer\n")
+{
+  struct peer *peer;
+  int ret, gtsm_hops;
+
+  peer = peer_and_group_lookup_vty (vty, argv[0]);
+  if (! peer)
+    return CMD_WARNING;
+    
+  VTY_GET_INTEGER_RANGE ("", gtsm_hops, argv[1], 1, 254);
+
+  ret = peer_ttl_security_hops_set (peer, gtsm_hops);
+
+  return bgp_vty_return (vty, ret);
+}
+
+DEFUN (no_neighbor_ttl_security,
+       no_neighbor_ttl_security_cmd,
+       NO_NEIGHBOR_CMD2 "ttl-security hops <1-254>",
+       NO_STR
+       NEIGHBOR_STR
+       NEIGHBOR_ADDR_STR2
+       "Specify the maximum number of hops to the BGP peer\n")
+{
+  struct peer *peer;
+  int ret;
+
+  peer = peer_and_group_lookup_vty (vty, argv[0]);
+  if (! peer)
+    return CMD_WARNING;
+
+  ret = peer_ttl_security_hops_unset (peer);
+
+  return bgp_vty_return (vty, ret);
+}
+
 /* Address family configuration.  */
 DEFUN (address_family_ipv4,
        address_family_ipv4_cmd,
@@ -10060,6 +10106,10 @@
   install_element (BGP_IPV6_NODE, &no_bgp_redistribute_ipv6_metric_rmap_cmd);
 #endif /* HAVE_IPV6 */
 
+  /* ttl_security commands */
+  install_element (BGP_NODE, &neighbor_ttl_security_cmd);
+  install_element (BGP_NODE, &no_neighbor_ttl_security_cmd);
+
   /* "show bgp memory" commands. */
   install_element (VIEW_NODE, &show_bgp_memory_cmd);
   install_element (RESTRICTED_NODE, &show_bgp_memory_cmd);
diff --git a/bgpd/bgpd.c b/bgpd/bgpd.c
index 882fe37..cc0ea8d 100644
--- a/bgpd/bgpd.c
+++ b/bgpd/bgpd.c
@@ -1379,6 +1379,7 @@
   group->conf->group = group;
   group->conf->as = 0; 
   group->conf->ttl = 1;
+  group->conf->gtsm_hops = 0;
   group->conf->v_routeadv = BGP_DEFAULT_EBGP_ROUTEADV;
   UNSET_FLAG (group->conf->config, PEER_CONFIG_TIMER);
   UNSET_FLAG (group->conf->config, PEER_CONFIG_CONNECT);
@@ -1416,6 +1417,9 @@
   /* TTL */
   peer->ttl = conf->ttl;
 
+  /* GTSM hops */
+  peer->gtsm_hops = conf->gtsm_hops;
+
   /* Weight */
   peer->weight = conf->weight;
 
@@ -2663,10 +2667,36 @@
 {
   struct peer_group *group;
   struct listnode *node, *nnode;
+  struct peer *peer1;
 
   if (peer_sort (peer) == BGP_PEER_IBGP)
     return 0;
 
+  /* see comment in peer_ttl_security_hops_set() */
+  if (ttl != MAXTTL)
+    {
+      if (CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
+        {
+          group = peer->group;
+          if (group->conf->gtsm_hops != 0)
+            return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+
+          for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer1))
+            {
+              if (peer_sort (peer1) == BGP_PEER_IBGP)
+                continue;
+
+              if (peer1->gtsm_hops != 0)
+                return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+            }
+        }
+      else
+        {
+          if (peer->gtsm_hops != 0)
+            return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+        }
+    }
+
   peer->ttl = ttl;
 
   if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
@@ -2700,6 +2730,9 @@
   if (peer_sort (peer) == BGP_PEER_IBGP)
     return 0;
 
+  if (peer->gtsm_hops != 0 && peer->ttl != MAXTTL)
+      return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+
   if (peer_group_active (peer))
     peer->ttl = peer->group->conf->ttl;
   else
@@ -4331,6 +4364,121 @@
   return 0;
 }
 
+/* Set # of hops between us and BGP peer. */
+int
+peer_ttl_security_hops_set (struct peer *peer, int gtsm_hops)
+{
+  struct peer_group *group;
+  struct listnode *node, *nnode;
+  struct peer *peer1;
+  int ret;
+
+  zlog_debug ("peer_ttl_security_hops_set: set gtsm_hops to %d for %s", gtsm_hops, peer->host);
+
+  if (peer_sort (peer) == BGP_PEER_IBGP)
+      return 0;
+
+  /* We cannot configure ttl-security hops when ebgp-multihop is already
+     set.  For non peer-groups, the check is simple.  For peer-groups, it's
+     slightly messy, because we need to check both the peer-group structure
+     and all peer-group members for any trace of ebgp-multihop configuration
+     before actually applying the ttl-security rules.  Cisco really made a
+     mess of this configuration parameter, and OpenBGPD got it right.
+  */
+
+  if (CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
+    {
+      group = peer->group;
+      if (group->conf->ttl != 1)
+        return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+
+      for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer1))
+        {
+          if (peer_sort (peer1) == BGP_PEER_IBGP)
+            continue;
+
+          if (peer1->ttl != 1)
+            return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+        }
+    }
+  else
+    {
+      if (peer->ttl != 1)
+        return BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK;
+    }
+
+  peer->gtsm_hops = gtsm_hops;
+
+  /* specify MAXTTL on outgoing packets */
+  ret = peer_ebgp_multihop_set (peer, MAXTTL);
+  if (ret != 0)
+    return ret;
+
+  if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
+    {
+      if (peer->fd >= 0 && peer_sort (peer) != BGP_PEER_IBGP)
+	sockopt_minttl (peer->su.sa.sa_family, peer->fd, MAXTTL + 1 - gtsm_hops);
+    }
+  else
+    {
+      group = peer->group;
+      for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer))
+	{
+	  if (peer_sort (peer) == BGP_PEER_IBGP)
+	    continue;
+
+	  peer->gtsm_hops = group->conf->gtsm_hops;
+
+	  if (peer->fd >= 0 && peer->gtsm_hops != 0)
+            sockopt_minttl (peer->su.sa.sa_family, peer->fd, MAXTTL + 1 - peer->gtsm_hops);
+	}
+    }
+
+  return 0;
+}
+
+int
+peer_ttl_security_hops_unset (struct peer *peer)
+{
+  struct peer_group *group;
+  struct listnode *node, *nnode;
+  struct peer *opeer;
+
+  zlog_debug ("peer_ttl_security_hops_unset: set gtsm_hops to zero for %s", peer->host);
+
+  if (peer_sort (peer) == BGP_PEER_IBGP)
+      return 0;
+
+  /* if a peer-group member, then reset to peer-group default rather than 0 */
+  if (peer_group_active (peer))
+    peer->gtsm_hops = peer->group->conf->gtsm_hops;
+  else
+    peer->gtsm_hops = 0;
+
+  opeer = peer;
+  if (! CHECK_FLAG (peer->sflags, PEER_STATUS_GROUP))
+    {
+      if (peer->fd >= 0 && peer_sort (peer) != BGP_PEER_IBGP)
+	sockopt_minttl (peer->su.sa.sa_family, peer->fd, 0);
+    }
+  else
+    {
+      group = peer->group;
+      for (ALL_LIST_ELEMENTS (group->peer, node, nnode, peer))
+	{
+	  if (peer_sort (peer) == BGP_PEER_IBGP)
+	    continue;
+
+	  peer->gtsm_hops = 0;
+	  
+	  if (peer->fd >= 0)
+	    sockopt_minttl (peer->su.sa.sa_family, peer->fd, 0);
+	}
+    }
+
+  return peer_ebgp_multihop_unset (opeer);
+}
+
 int
 peer_clear (struct peer *peer)
 {
@@ -4635,12 +4783,19 @@
 	  vty_out (vty, " neighbor %s passive%s", addr, VTY_NEWLINE);
 
       /* EBGP multihop.  */
-      if (peer_sort (peer) != BGP_PEER_IBGP && peer->ttl != 1)
+      if (peer_sort (peer) != BGP_PEER_IBGP && peer->ttl != 1 &&
+                   !(peer->gtsm_hops != 0 && peer->ttl == MAXTTL))
         if (! peer_group_active (peer) ||
 	    g_peer->ttl != peer->ttl)
 	  vty_out (vty, " neighbor %s ebgp-multihop %d%s", addr, peer->ttl,
 		   VTY_NEWLINE);
 
+     /* ttl-security hops */
+      if (peer_sort (peer) != BGP_PEER_IBGP && peer->gtsm_hops != 0)
+        if (! peer_group_active (peer) || g_peer->gtsm_hops != peer->gtsm_hops)
+          vty_out (vty, " neighbor %s ttl-security hops %d%s", addr, 
+                   peer->gtsm_hops, VTY_NEWLINE);
+
       /* disable-connected-check.  */
       if (CHECK_FLAG (peer->flags, PEER_FLAG_DISABLE_CONNECTED_CHECK))
 	if (! peer_group_active (peer) ||
diff --git a/bgpd/bgpd.h b/bgpd/bgpd.h
index a5afaed..39cdf8e 100644
--- a/bgpd/bgpd.h
+++ b/bgpd/bgpd.h
@@ -303,6 +303,7 @@
   /* Peer information */
   int fd;			/* File descriptor */
   int ttl;			/* TTL of TCP connection to the peer. */
+  int gtsm_hops;		/* minimum hopcount to peer */
   char *desc;			/* Description of the peer. */
   unsigned short port;          /* Destination port for peer */
   char *host;			/* Printable address of the peer. */
@@ -800,7 +801,8 @@
 #define BGP_ERR_LOCAL_AS_ALLOWED_ONLY_FOR_EBGP  -27
 #define BGP_ERR_CANNOT_HAVE_LOCAL_AS_SAME_AS    -28
 #define BGP_ERR_TCPSIG_FAILED			-29
-#define BGP_ERR_MAX                             -30
+#define BGP_ERR_NO_EBGP_MULTIHOP_WITH_TTLHACK	-30
+#define BGP_ERR_MAX				-31
 
 extern struct bgp_master *bm;
 
@@ -953,4 +955,7 @@
 extern int peer_clear (struct peer *);
 extern int peer_clear_soft (struct peer *, afi_t, safi_t, enum bgp_clear_type);
 
+extern int peer_ttl_security_hops_set (struct peer *, int);
+extern int peer_ttl_security_hops_unset (struct peer *);
+
 #endif /* _QUAGGA_BGPD_H */
diff --git a/lib/sockunion.c b/lib/sockunion.c
index f6c060f..a32809c 100644
--- a/lib/sockunion.c
+++ b/lib/sockunion.c
@@ -537,6 +537,28 @@
 #endif
 }
 
+int
+sockopt_minttl (int family, int sock, int minttl)
+{
+  int ret;
+  
+  zlog_debug ("sockopt_minttl: set minttl to %d", minttl);
+
+#ifdef IP_MINTTL
+  ret = setsockopt (sock, IPPROTO_IP, IP_MINTTL, &minttl, sizeof(minttl));
+#else
+  ret = -1;
+  errno = EOPNOTSUPP;
+#endif /* IP_MINTTL */
+  if (ret < 0)
+    {
+      zlog (NULL, LOG_WARNING, "can't set sockopt IP_MINTTL to %d on socket %d: %s", minttl, sock, safe_strerror (errno));
+      return -1;
+    }
+
+  return 0;
+}
+
 /* If same family and same prefix return 1. */
 int
 sockunion_same (union sockunion *su1, union sockunion *su2)
diff --git a/lib/sockunion.h b/lib/sockunion.h
index 91bfbc7..0ee2d63 100644
--- a/lib/sockunion.h
+++ b/lib/sockunion.h
@@ -102,6 +102,7 @@
 extern int sockunion_bind (int sock, union sockunion *, 
                            unsigned short, union sockunion *);
 extern int sockopt_ttl (int family, int sock, int ttl);
+extern int sockopt_minttl (int family, int sock, int minttl);
 extern int sockopt_cork (int sock, int onoff);
 extern int sockunion_socket (union sockunion *su);
 extern const char *inet_sutop (union sockunion *su, char *str);