/* BGP nexthop scan
   Copyright (C) 2000 Kunihiro Ishiguro

This file is part of GNU Zebra.

GNU Zebra is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

GNU Zebra is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Zebra; see the file COPYING.  If not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.  */

#include <zebra.h>

#include "command.h"
#include "thread.h"
#include "prefix.h"
#include "zclient.h"
#include "stream.h"
#include "network.h"
#include "log.h"
#include "memory.h"
#include "hash.h"
#include "jhash.h"
#include "filter.h"
#include "nexthop.h"

#include "bgpd/bgpd.h"
#include "bgpd/bgp_table.h"
#include "bgpd/bgp_route.h"
#include "bgpd/bgp_attr.h"
#include "bgpd/bgp_nexthop.h"
#include "bgpd/bgp_nht.h"
#include "bgpd/bgp_debug.h"
#include "bgpd/bgp_damp.h"
#include "zebra/rib.h"
#include "zebra/zserv.h"	/* For ZEBRA_SERV_PATH. */


/* Route table for next-hop lookup cache. */
struct bgp_table *bgp_nexthop_cache_table[AFI_MAX];
static struct bgp_table *cache1_table[AFI_MAX];

/* Route table for connected route. */
static struct bgp_table *bgp_connected_table[AFI_MAX];

char *
bnc_str (struct bgp_nexthop_cache *bnc, char *buf, int size)
{
  prefix2str(&(bnc->node->p), buf, size);
  return buf;
}

void
bnc_nexthop_free (struct bgp_nexthop_cache *bnc)
{
  struct nexthop *nexthop;
  struct nexthop *next = NULL;

  for (nexthop = bnc->nexthop; nexthop; nexthop = next)
    {
      next = nexthop->next;
      XFREE (MTYPE_NEXTHOP, nexthop);
    }
}

struct bgp_nexthop_cache *
bnc_new (void)
{
  struct bgp_nexthop_cache *bnc;

  bnc = XCALLOC (MTYPE_BGP_NEXTHOP_CACHE, sizeof (struct bgp_nexthop_cache));
  LIST_INIT(&(bnc->paths));
  return bnc;
}

void
bnc_free (struct bgp_nexthop_cache *bnc)
{
  bnc_nexthop_free (bnc);
  XFREE (MTYPE_BGP_NEXTHOP_CACHE, bnc);
}

/* If nexthop exists on connected network return 1. */
int
bgp_nexthop_onlink (afi_t afi, struct attr *attr)
{
  struct bgp_node *rn;
  
  /* Lookup the address is onlink or not. */
  if (afi == AFI_IP)
    {
      rn = bgp_node_match_ipv4 (bgp_connected_table[AFI_IP], &attr->nexthop);
      if (rn)
	{
	  bgp_unlock_node (rn);
	  return 1;
	}
    }
  else if (afi == AFI_IP6)
    {
      if (attr->extra->mp_nexthop_len == 32)
	return 1;
      else if (attr->extra->mp_nexthop_len == 16)
	{
	  if (IN6_IS_ADDR_LINKLOCAL (&attr->extra->mp_nexthop_global))
	    return 1;

	  rn = bgp_node_match_ipv6 (bgp_connected_table[AFI_IP6],
				      &attr->extra->mp_nexthop_global);
	  if (rn)
	    {
	      bgp_unlock_node (rn);
	      return 1;
	    }
	}
    }
  return 0;
}

/* BGP own address structure */
struct bgp_addr
{
  struct in_addr addr;
  int refcnt;
};

static struct hash *bgp_address_hash;

static void *
bgp_address_hash_alloc (void *p)
{
  struct in_addr *val = p;
  struct bgp_addr *addr;

  addr = XMALLOC (MTYPE_BGP_ADDR, sizeof (struct bgp_addr));
  addr->refcnt = 0;
  addr->addr.s_addr = val->s_addr;

  return addr;
}

static unsigned int
bgp_address_hash_key_make (void *p)
{
  const struct bgp_addr *addr = p;

  return jhash_1word(addr->addr.s_addr, 0);
}

static int
bgp_address_hash_cmp (const void *p1, const void *p2)
{
  const struct bgp_addr *addr1 = p1;
  const struct bgp_addr *addr2 = p2;

  return addr1->addr.s_addr == addr2->addr.s_addr;
}

void
bgp_address_init (void)
{
  bgp_address_hash = hash_create (bgp_address_hash_key_make,
                                  bgp_address_hash_cmp);
}

void
bgp_address_destroy (void)
{
  if (bgp_address_hash == NULL)
    return;

  hash_clean(bgp_address_hash, NULL);
  hash_free(bgp_address_hash);
  bgp_address_hash = NULL;
}

static void
bgp_address_add (struct prefix *p)
{
  struct bgp_addr tmp;
  struct bgp_addr *addr;

  tmp.addr = p->u.prefix4;

  addr = hash_get (bgp_address_hash, &tmp, bgp_address_hash_alloc);
  if (!addr)
    return;

  addr->refcnt++;
}

static void
bgp_address_del (struct prefix *p)
{
  struct bgp_addr tmp;
  struct bgp_addr *addr;

  tmp.addr = p->u.prefix4;

  addr = hash_lookup (bgp_address_hash, &tmp);
  /* may have been deleted earlier by bgp_interface_down() */
  if (addr == NULL)
    return;

  addr->refcnt--;

  if (addr->refcnt == 0)
    {
      hash_release (bgp_address_hash, addr);
      XFREE (MTYPE_BGP_ADDR, addr);
    }
}


struct bgp_connected_ref
{
  unsigned int refcnt;
};

void
bgp_connected_add (struct connected *ifc)
{
  struct prefix p;
  struct prefix *addr;
  struct interface *ifp;
  struct bgp_node *rn;
  struct bgp_connected_ref *bc;

  ifp = ifc->ifp;

  if (! ifp)
    return;

  if (if_is_loopback (ifp))
    return;

  addr = ifc->address;

  p = *(CONNECTED_PREFIX(ifc));
  if (addr->family == AF_INET)
    {
      apply_mask_ipv4 ((struct prefix_ipv4 *) &p);

      if (prefix_ipv4_any ((struct prefix_ipv4 *) &p))
	return;

      bgp_address_add (addr);

      rn = bgp_node_get (bgp_connected_table[AFI_IP], (struct prefix *) &p);
      if (rn->info)
	{
	  bc = rn->info;
	  bc->refcnt++;
	}
      else
	{
	  bc = XCALLOC (MTYPE_BGP_CONN, sizeof (struct bgp_connected_ref));
	  bc->refcnt = 1;
	  rn->info = bc;
	}
    }
  else if (addr->family == AF_INET6)
    {
      apply_mask_ipv6 ((struct prefix_ipv6 *) &p);

      if (IN6_IS_ADDR_UNSPECIFIED (&p.u.prefix6))
	return;

      if (IN6_IS_ADDR_LINKLOCAL (&p.u.prefix6))
	return;

      rn = bgp_node_get (bgp_connected_table[AFI_IP6], (struct prefix *) &p);
      if (rn->info)
	{
	  bc = rn->info;
	  bc->refcnt++;
	}
      else
	{
	  bc = XCALLOC (MTYPE_BGP_CONN, sizeof (struct bgp_connected_ref));
	  bc->refcnt = 1;
	  rn->info = bc;
	}
    }
}

void
bgp_connected_delete (struct connected *ifc)
{
  struct prefix p;
  struct prefix *addr;
  struct interface *ifp;
  struct bgp_node *rn;
  struct bgp_connected_ref *bc;

  ifp = ifc->ifp;

  if (if_is_loopback (ifp))
    return;

  addr = ifc->address;

  p = *(CONNECTED_PREFIX(ifc));
  if (addr->family == AF_INET)
    {
      apply_mask_ipv4 ((struct prefix_ipv4 *) &p);

      if (prefix_ipv4_any ((struct prefix_ipv4 *) &p))
	return;

      bgp_address_del (addr);

      rn = bgp_node_lookup (bgp_connected_table[AFI_IP], &p);
      if (! rn)
	return;

      bc = rn->info;
      bc->refcnt--;
      if (bc->refcnt == 0)
	{
	  XFREE (MTYPE_BGP_CONN, bc);
	  rn->info = NULL;
	}
      bgp_unlock_node (rn);
      bgp_unlock_node (rn);
    }
  else if (addr->family == AF_INET6)
    {
      apply_mask_ipv6 ((struct prefix_ipv6 *) &p);

      if (IN6_IS_ADDR_UNSPECIFIED (&p.u.prefix6))
	return;

      if (IN6_IS_ADDR_LINKLOCAL (&p.u.prefix6))
	return;

      rn = bgp_node_lookup (bgp_connected_table[AFI_IP6], (struct prefix *) &p);
      if (! rn)
	return;

      bc = rn->info;
      bc->refcnt--;
      if (bc->refcnt == 0)
	{
	  XFREE (MTYPE_BGP_CONN, bc);
	  rn->info = NULL;
	}
      bgp_unlock_node (rn);
      bgp_unlock_node (rn);
    }
}

int
bgp_nexthop_self (struct attr *attr)
{
  struct bgp_addr tmp, *addr;

  tmp.addr = attr->nexthop;

  addr = hash_lookup (bgp_address_hash, &tmp);
  if (addr)
    return 1;

  return 0;
}

int
bgp_multiaccess_check_v4 (struct in_addr nexthop, struct peer *peer)
{
  struct bgp_node *rn1;
  struct bgp_node *rn2;
  struct prefix p;
  int ret;

  p.family = AF_INET;
  p.prefixlen = IPV4_MAX_BITLEN;
  p.u.prefix4 = nexthop;

  rn1 = bgp_node_match (bgp_connected_table[AFI_IP], &p);
  if (!rn1)
    return 0;

  p.family = AF_INET;
  p.prefixlen = IPV4_MAX_BITLEN;
  p.u.prefix4 = peer->su.sin.sin_addr;

  rn2 = bgp_node_match (bgp_connected_table[AFI_IP], &p);
  if (!rn2)
    {
      bgp_unlock_node(rn1);
      return 0;
    }

  ret = (rn1 == rn2) ? 1 : 0;

  bgp_unlock_node(rn1);
  bgp_unlock_node(rn2);

  return (ret);
}

static int
show_ip_bgp_nexthop_table (struct vty *vty, int detail)
{
  struct bgp_node *rn;
  struct bgp_nexthop_cache *bnc;
  char buf[INET6_ADDRSTRLEN];
  struct nexthop *nexthop;
  time_t tbuf;
  afi_t afi;

  vty_out (vty, "Current BGP nexthop cache:%s", VTY_NEWLINE);
  for (afi = AFI_IP ; afi < AFI_MAX ; afi++)
    {
      if (!bgp_nexthop_cache_table[afi])
        continue;
      
      for (rn = bgp_table_top (bgp_nexthop_cache_table[afi]); rn; rn = bgp_route_next (rn))
	{
	  if ((bnc = rn->info) != NULL)
	    {
	      if (CHECK_FLAG(bnc->flags, BGP_NEXTHOP_VALID))
		{
		  vty_out (vty, " %s valid [IGP metric %d], #paths %d%s",
			   inet_ntop (rn->p.family, &rn->p.u.prefix, buf, sizeof (buf)),
			   bnc->metric, bnc->path_count, VTY_NEWLINE);
		  if (detail)
		    for (nexthop = bnc->nexthop ; nexthop; nexthop = nexthop->next)
		      switch (nexthop->type)
			{
			case NEXTHOP_TYPE_IPV6:
			  vty_out (vty, "  gate %s%s",
				   inet_ntop (AF_INET6, &nexthop->gate.ipv6,
					      buf, INET6_ADDRSTRLEN), VTY_NEWLINE);
			  break;
			case NEXTHOP_TYPE_IPV6_IFINDEX:
			  vty_out(vty, "  gate %s, if %s%s",
				  inet_ntop(AF_INET6, &nexthop->gate.ipv6, buf,
					    INET6_ADDRSTRLEN),
				  ifindex2ifname(nexthop->ifindex),
				  VTY_NEWLINE);
			  break;
			case NEXTHOP_TYPE_IPV4:
			  vty_out (vty, "  gate %s%s",
				   inet_ntop (AF_INET, &nexthop->gate.ipv4, buf,
					      INET6_ADDRSTRLEN), VTY_NEWLINE);
			  break;
			case NEXTHOP_TYPE_IFINDEX:
			  vty_out (vty, "  if %s%s",
				   ifindex2ifname(nexthop->ifindex), VTY_NEWLINE);
			  break;
			case NEXTHOP_TYPE_IPV4_IFINDEX:
			  vty_out (vty, "  gate %s, if %s%s",
				   inet_ntop(AF_INET, &nexthop->gate.ipv4, buf,
					     INET6_ADDRSTRLEN),
				   ifindex2ifname(nexthop->ifindex), VTY_NEWLINE);
			  break;
			default:
			  vty_out (vty, "  invalid nexthop type %u%s",
				   nexthop->type, VTY_NEWLINE);
			}
		}
	      else
		vty_out (vty, " %s invalid%s",
			 inet_ntop (AF_INET, &rn->p.u.prefix, buf, sizeof (buf)), VTY_NEWLINE);
#ifdef HAVE_CLOCK_MONOTONIC
	      tbuf = time(NULL) - (bgp_clock() - bnc->last_update);
	      vty_out (vty, "  Last update: %s", ctime(&tbuf));
#else
	      vty_out (vty, "  Last update: %s", ctime(&bnc->uptime));
#endif /* HAVE_CLOCK_MONOTONIC */
	      vty_out(vty, "%s", VTY_NEWLINE);
	    }
	}
    }
  return CMD_SUCCESS;
}

DEFUN (show_ip_bgp_nexthop,
       show_ip_bgp_nexthop_cmd,
       "show ip bgp nexthop",
       SHOW_STR
       IP_STR
       BGP_STR
       "BGP nexthop table\n")
{
  return show_ip_bgp_nexthop_table (vty, 0);
}

DEFUN (show_ip_bgp_nexthop_detail,
       show_ip_bgp_nexthop_detail_cmd,
       "show ip bgp nexthop detail",
       SHOW_STR
       IP_STR
       BGP_STR
       "BGP nexthop table\n")
{
  return show_ip_bgp_nexthop_table (vty, 1);
}

void
bgp_scan_init (void)
{
  cache1_table[AFI_IP] = bgp_table_init (AFI_IP, SAFI_UNICAST);
  bgp_nexthop_cache_table[AFI_IP] = cache1_table[AFI_IP];

  bgp_connected_table[AFI_IP] = bgp_table_init (AFI_IP, SAFI_UNICAST);

  cache1_table[AFI_IP6] = bgp_table_init (AFI_IP6, SAFI_UNICAST);
  bgp_nexthop_cache_table[AFI_IP6] = cache1_table[AFI_IP6];
  bgp_connected_table[AFI_IP6] = bgp_table_init (AFI_IP6, SAFI_UNICAST);
  
  cache1_table[AFI_ETHER] = bgp_table_init (AFI_ETHER, SAFI_UNICAST);
  bgp_nexthop_cache_table[AFI_ETHER] = cache1_table[AFI_ETHER];
  bgp_connected_table[AFI_ETHER] = bgp_table_init (AFI_ETHER, SAFI_UNICAST);
}

void
bgp_scan_vty_init()
{
  install_element (VIEW_NODE, &show_ip_bgp_nexthop_cmd);
  install_element (VIEW_NODE, &show_ip_bgp_nexthop_detail_cmd);
}

void
bgp_scan_finish (void)
{
  if (cache1_table[AFI_IP])
    bgp_table_unlock (cache1_table[AFI_IP]);
  cache1_table[AFI_IP] = NULL;

  if (bgp_connected_table[AFI_IP])
    bgp_table_unlock (bgp_connected_table[AFI_IP]);
  bgp_connected_table[AFI_IP] = NULL;

  if (cache1_table[AFI_IP6])
    bgp_table_unlock (cache1_table[AFI_IP6]);
  cache1_table[AFI_IP6] = NULL;

  if (bgp_connected_table[AFI_IP6])
    bgp_table_unlock (bgp_connected_table[AFI_IP6]);
  bgp_connected_table[AFI_IP6] = NULL;

  if (cache1_table[AFI_ETHER])
    bgp_table_unlock (cache1_table[AFI_ETHER]);
  cache1_table[AFI_ETHER] = NULL;
  
  if (bgp_connected_table[AFI_ETHER])
    bgp_table_unlock (bgp_connected_table[AFI_ETHER]);
  bgp_connected_table[AFI_ETHER] = NULL;

}

void
bgp_scan_destroy (void)
{
  bgp_scan_finish();
}
