/*
 * Copyright (C) 2003 Yasuhiro Ohara
 *
 * 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 "log.h"
#include "memory.h"
#include "prefix.h"
#include "command.h"
#include "vty.h"
#include "routemap.h"
#include "table.h"
#include "plist.h"
#include "thread.h"
#include "linklist.h"

#include "ospf6_proto.h"
#include "ospf6_lsa.h"
#include "ospf6_lsdb.h"
#include "ospf6_route.h"
#include "ospf6_zebra.h"
#include "ospf6_message.h"

#include "ospf6_top.h"
#include "ospf6_area.h"
#include "ospf6_interface.h"
#include "ospf6_neighbor.h"
#include "ospf6_asbr.h"
#include "ospf6_intra.h"
#include "ospf6_flood.h"
#include "ospf6d.h"

unsigned char conf_debug_ospf6_asbr = 0;

#define ZROUTE_NAME(x) zebra_route_string(x)

/* AS External LSA origination */
static void
ospf6_as_external_lsa_originate (struct ospf6_route *route)
{
  char buffer[OSPF6_MAX_LSASIZE];
  struct ospf6_lsa_header *lsa_header;
  struct ospf6_lsa *old, *lsa;
  struct ospf6_external_info *info = route->route_option;

  struct ospf6_as_external_lsa *as_external_lsa;
  char buf[64];
  caddr_t p;

  /* find previous LSA */
  old = ospf6_lsdb_lookup (htons (OSPF6_LSTYPE_AS_EXTERNAL),
                           route->path.origin.id, ospf6->router_id,
                           ospf6->lsdb);

  if (IS_OSPF6_DEBUG_ASBR || IS_OSPF6_DEBUG_ORIGINATE (AS_EXTERNAL))
    {
      prefix2str (&route->prefix, buf, sizeof (buf));
      zlog_debug ("Originate AS-External-LSA for %s", buf);
    }

  /* prepare buffer */
  memset (buffer, 0, sizeof (buffer));
  lsa_header = (struct ospf6_lsa_header *) buffer;
  as_external_lsa = (struct ospf6_as_external_lsa *)
    ((caddr_t) lsa_header + sizeof (struct ospf6_lsa_header));
  p = (caddr_t)
    ((caddr_t) as_external_lsa + sizeof (struct ospf6_as_external_lsa));

  /* Fill AS-External-LSA */
  /* Metric type */
  if (route->path.metric_type == 2)
    SET_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E);
  else
    UNSET_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_E);

  /* forwarding address */
  if (! IN6_IS_ADDR_UNSPECIFIED (&info->forwarding))
    SET_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F);
  else
    UNSET_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F);

  /* external route tag */
  UNSET_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T);

  /* Set metric */
  OSPF6_ASBR_METRIC_SET (as_external_lsa, route->path.cost);

  /* prefixlen */
  as_external_lsa->prefix.prefix_length = route->prefix.prefixlen;

  /* PrefixOptions */
  as_external_lsa->prefix.prefix_options = route->path.prefix_options;

  /* don't use refer LS-type */
  as_external_lsa->prefix.prefix_refer_lstype = htons (0);

  /* set Prefix */
  memcpy (p, &route->prefix.u.prefix6,
          OSPF6_PREFIX_SPACE (route->prefix.prefixlen));
  ospf6_prefix_apply_mask (&as_external_lsa->prefix);
  p += OSPF6_PREFIX_SPACE (route->prefix.prefixlen);

  /* Forwarding address */
  if (CHECK_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_F))
    {
      memcpy (p, &info->forwarding, sizeof (struct in6_addr));
      p += sizeof (struct in6_addr);
    }

  /* External Route Tag */
  if (CHECK_FLAG (as_external_lsa->bits_metric, OSPF6_ASBR_BIT_T))
    {
      /* xxx */
    }

  /* Fill LSA Header */
  lsa_header->age = 0;
  lsa_header->type = htons (OSPF6_LSTYPE_AS_EXTERNAL);
  lsa_header->id = route->path.origin.id;
  lsa_header->adv_router = ospf6->router_id;
  lsa_header->seqnum =
    ospf6_new_ls_seqnum (lsa_header->type, lsa_header->id,
                         lsa_header->adv_router, ospf6->lsdb);
  lsa_header->length = htons ((caddr_t) p - (caddr_t) lsa_header);

  /* LSA checksum */
  ospf6_lsa_checksum (lsa_header);

  /* create LSA */
  lsa = ospf6_lsa_create (lsa_header);

  /* Originate */
  ospf6_lsa_originate_process (lsa, ospf6);
}


void
ospf6_asbr_lsa_add (struct ospf6_lsa *lsa)
{
  struct ospf6_as_external_lsa *external;
  struct prefix asbr_id;
  struct ospf6_route *asbr_entry, *route;
  char buf[64];
  int i;

  external = (struct ospf6_as_external_lsa *)
    OSPF6_LSA_HEADER_END (lsa->header);

  if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
    zlog_debug ("Calculate AS-External route for %s", lsa->name);

  if (lsa->header->adv_router == ospf6->router_id)
    {
      if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
        zlog_debug ("Ignore self-originated AS-External-LSA");
      return;
    }

  if (OSPF6_ASBR_METRIC (external) == LS_INFINITY)
    {
      if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
        zlog_debug ("Ignore LSA with LSInfinity Metric");
      return;
    }

  ospf6_linkstate_prefix (lsa->header->adv_router, htonl (0), &asbr_id);
  asbr_entry = ospf6_route_lookup (&asbr_id, ospf6->brouter_table);
  if (asbr_entry == NULL ||
      ! CHECK_FLAG (asbr_entry->path.router_bits, OSPF6_ROUTER_BIT_E))
    {
      if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
        {
          prefix2str (&asbr_id, buf, sizeof (buf));
          zlog_debug ("ASBR entry not found: %s", buf);
        }
      return;
    }

  route = ospf6_route_create ();
  route->type = OSPF6_DEST_TYPE_NETWORK;
  route->prefix.family = AF_INET6;
  route->prefix.prefixlen = external->prefix.prefix_length;
  ospf6_prefix_in6_addr (&route->prefix.u.prefix6, &external->prefix);

  route->path.area_id = asbr_entry->path.area_id;
  route->path.origin.type = lsa->header->type;
  route->path.origin.id = lsa->header->id;
  route->path.origin.adv_router = lsa->header->adv_router;

  route->path.prefix_options = external->prefix.prefix_options;
  if (CHECK_FLAG (external->bits_metric, OSPF6_ASBR_BIT_E))
    {
      route->path.type = OSPF6_PATH_TYPE_EXTERNAL2;
      route->path.metric_type = 2;
      route->path.cost = asbr_entry->path.cost;
      route->path.cost_e2 = OSPF6_ASBR_METRIC (external);
    }
  else
    {
      route->path.type = OSPF6_PATH_TYPE_EXTERNAL1;
      route->path.metric_type = 1;
      route->path.cost = asbr_entry->path.cost + OSPF6_ASBR_METRIC (external);
      route->path.cost_e2 = 0;
    }

  for (i = 0; i < OSPF6_MULTI_PATH_LIMIT; i++)
    ospf6_nexthop_copy (&route->nexthop[i], &asbr_entry->nexthop[i]);

  if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
    {
      prefix2str (&route->prefix, buf, sizeof (buf));
      zlog_debug ("AS-External route add: %s", buf);
    }

  ospf6_route_add (route, ospf6->route_table);
}

void
ospf6_asbr_lsa_remove (struct ospf6_lsa *lsa)
{
  struct ospf6_as_external_lsa *external;
  struct prefix prefix;
  struct ospf6_route *route;
  char buf[64];

  external = (struct ospf6_as_external_lsa *)
    OSPF6_LSA_HEADER_END (lsa->header);

  if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
    zlog_debug ("Withdraw AS-External route for %s", lsa->name);

  if (lsa->header->adv_router == ospf6->router_id)
    {
      if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
        zlog_debug ("Ignore self-originated AS-External-LSA");
      return;
    }

  memset (&prefix, 0, sizeof (struct prefix));
  prefix.family = AF_INET6;
  prefix.prefixlen = external->prefix.prefix_length;
  ospf6_prefix_in6_addr (&prefix.u.prefix6, &external->prefix);

  route = ospf6_route_lookup (&prefix, ospf6->route_table);
  if (route == NULL)
    {
      if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
        {
          prefix2str (&prefix, buf, sizeof (buf));
          zlog_debug ("AS-External route %s not found", buf);
        }
      return;
    }

  for (ospf6_route_lock (route);
       route && ospf6_route_is_prefix (&prefix, route);
       route = ospf6_route_next (route))
    {
      if (route->type != OSPF6_DEST_TYPE_NETWORK)
        continue;
      if (route->path.origin.type != lsa->header->type)
        continue;
      if (route->path.origin.id != lsa->header->id)
        continue;
      if (route->path.origin.adv_router != lsa->header->adv_router)
        continue;

      if (IS_OSPF6_DEBUG_EXAMIN (AS_EXTERNAL))
        {
          prefix2str (&route->prefix, buf, sizeof (buf));
          zlog_debug ("AS-External route remove: %s", buf);
        }
      ospf6_route_remove (route, ospf6->route_table);
    }
}

void
ospf6_asbr_lsentry_add (struct ospf6_route *asbr_entry)
{
  struct ospf6_lsa *lsa;
  u_int16_t type;
  u_int32_t router;

  if (! CHECK_FLAG (asbr_entry->flag, OSPF6_ROUTE_BEST))
    {
      char buf[16];
      inet_ntop (AF_INET, &ADV_ROUTER_IN_PREFIX (&asbr_entry->prefix),
                 buf, sizeof (buf));
       zlog_info ("ignore non-best path: lsentry %s add", buf);
      return;
    }

  type = htons (OSPF6_LSTYPE_AS_EXTERNAL);
  router = ospf6_linkstate_prefix_adv_router (&asbr_entry->prefix);
  for (lsa = ospf6_lsdb_type_router_head (type, router, ospf6->lsdb); lsa;
       lsa = ospf6_lsdb_type_router_next (type, router, lsa))
    {
      if (! OSPF6_LSA_IS_MAXAGE (lsa))
        ospf6_asbr_lsa_add (lsa);
    }
}

void
ospf6_asbr_lsentry_remove (struct ospf6_route *asbr_entry)
{
  struct ospf6_lsa *lsa;
  u_int16_t type;
  u_int32_t router;

  type = htons (OSPF6_LSTYPE_AS_EXTERNAL);
  router = ospf6_linkstate_prefix_adv_router (&asbr_entry->prefix);
  for (lsa = ospf6_lsdb_type_router_head (type, router, ospf6->lsdb);
       lsa; lsa = ospf6_lsdb_type_router_next (type, router, lsa))
    ospf6_asbr_lsa_remove (lsa);
}



/* redistribute function */

static void
ospf6_asbr_routemap_set (int type, const char *mapname)
{
  if (ospf6->rmap[type].name)
    free (ospf6->rmap[type].name);
  ospf6->rmap[type].name = strdup (mapname);
  ospf6->rmap[type].map = route_map_lookup_by_name (mapname);
}

static void
ospf6_asbr_routemap_unset (int type)
{
  if (ospf6->rmap[type].name)
    free (ospf6->rmap[type].name);
  ospf6->rmap[type].name = NULL;
  ospf6->rmap[type].map = NULL;
}

static void
ospf6_asbr_routemap_update (const char *mapname)
{
  int type;

  if (ospf6 == NULL)
    return;

  for (type = 0; type < ZEBRA_ROUTE_MAX; type++)
    {
      if (ospf6->rmap[type].name)
        ospf6->rmap[type].map =
          route_map_lookup_by_name (ospf6->rmap[type].name);
      else
        ospf6->rmap[type].map = NULL;
    }
}

int
ospf6_asbr_is_asbr (struct ospf6 *o)
{
  return o->external_table->count;
}

static void
ospf6_asbr_redistribute_set (int type)
{
  ospf6_zebra_redistribute (type);
}

static void
ospf6_asbr_redistribute_unset (int type)
{
  struct ospf6_route *route;
  struct ospf6_external_info *info;

  ospf6_zebra_no_redistribute (type);

  for (route = ospf6_route_head (ospf6->external_table); route;
       route = ospf6_route_next (route))
    {
      info = route->route_option;
      if (info->type != type)
        continue;

      ospf6_asbr_redistribute_remove (info->type, route->nexthop[0].ifindex,
                                      &route->prefix);
    }
}

void
ospf6_asbr_redistribute_add (int type, int ifindex, struct prefix *prefix,
                             u_int nexthop_num, struct in6_addr *nexthop)
{
  int ret;
  struct ospf6_route troute;
  struct ospf6_external_info tinfo;
  struct ospf6_route *route, *match;
  struct ospf6_external_info *info;
  struct prefix prefix_id;
  struct route_node *node;
  char pbuf[64], ibuf[16];
  struct listnode *lnode, *lnnode;
  struct ospf6_area *oa;

  if (! ospf6_zebra_is_redistribute (type))
    return;

  if (IS_OSPF6_DEBUG_ASBR)
    {
      prefix2str (prefix, pbuf, sizeof (pbuf));
      zlog_debug ("Redistribute %s (%s)", pbuf, ZROUTE_NAME (type));
    }

  /* if route-map was specified but not found, do not advertise */
  if (ospf6->rmap[type].name)
    {
      if (ospf6->rmap[type].map == NULL)
        ospf6_asbr_routemap_update (NULL);
      if (ospf6->rmap[type].map == NULL)
        {
          zlog_warn ("route-map \"%s\" not found, suppress redistributing",
                     ospf6->rmap[type].name);
          return;
        }
    }

  /* apply route-map */
  if (ospf6->rmap[type].map)
    {
      memset (&troute, 0, sizeof (troute));
      memset (&tinfo, 0, sizeof (tinfo));
      troute.route_option = &tinfo;
      tinfo.ifindex = ifindex;

      ret = route_map_apply (ospf6->rmap[type].map, prefix,
                             RMAP_OSPF6, &troute);
      if (ret == RMAP_DENYMATCH)
        {
          if (IS_OSPF6_DEBUG_ASBR)
            zlog_debug ("Denied by route-map \"%s\"", ospf6->rmap[type].name);
          return;
        }
    }

  match = ospf6_route_lookup (prefix, ospf6->external_table);
  if (match)
    {
      info = match->route_option;

      /* copy result of route-map */
      if (ospf6->rmap[type].map)
        {
          if (troute.path.metric_type)
            match->path.metric_type = troute.path.metric_type;
          if (troute.path.cost)
            match->path.cost = troute.path.cost;
          if (! IN6_IS_ADDR_UNSPECIFIED (&tinfo.forwarding))
            memcpy (&info->forwarding, &tinfo.forwarding,
                    sizeof (struct in6_addr));
        }

      info->type = type;
      match->nexthop[0].ifindex = ifindex;
      if (nexthop_num && nexthop)
        memcpy (&match->nexthop[0].address, nexthop, sizeof (struct in6_addr));

      /* create/update binding in external_id_table */
      prefix_id.family = AF_INET;
      prefix_id.prefixlen = 32;
      prefix_id.u.prefix4.s_addr = htonl (info->id);
      node = route_node_get (ospf6->external_id_table, &prefix_id);
      node->info = match;

      if (IS_OSPF6_DEBUG_ASBR)
        {
          inet_ntop (AF_INET, &prefix_id.u.prefix4, ibuf, sizeof (ibuf));
          zlog_debug ("Advertise as AS-External Id:%s", ibuf);
        }

      match->path.origin.id = htonl (info->id);
      ospf6_as_external_lsa_originate (match);
      return;
    }

  /* create new entry */
  route = ospf6_route_create ();
  route->type = OSPF6_DEST_TYPE_NETWORK;
  memcpy (&route->prefix, prefix, sizeof (struct prefix));

  info = (struct ospf6_external_info *)
    XCALLOC (MTYPE_OSPF6_EXTERNAL_INFO, sizeof (struct ospf6_external_info));
  route->route_option = info;
  info->id = ospf6->external_id++;

  /* copy result of route-map */
  if (ospf6->rmap[type].map)
    {
      if (troute.path.metric_type)
        route->path.metric_type = troute.path.metric_type;
      if (troute.path.cost)
        route->path.cost = troute.path.cost;
      if (! IN6_IS_ADDR_UNSPECIFIED (&tinfo.forwarding))
        memcpy (&info->forwarding, &tinfo.forwarding,
                sizeof (struct in6_addr));
    }

  info->type = type;
  route->nexthop[0].ifindex = ifindex;
  if (nexthop_num && nexthop)
    memcpy (&route->nexthop[0].address, nexthop, sizeof (struct in6_addr));

  /* create/update binding in external_id_table */
  prefix_id.family = AF_INET;
  prefix_id.prefixlen = 32;
  prefix_id.u.prefix4.s_addr = htonl (info->id);
  node = route_node_get (ospf6->external_id_table, &prefix_id);
  node->info = route;

  route = ospf6_route_add (route, ospf6->external_table);
  route->route_option = info;

  if (IS_OSPF6_DEBUG_ASBR)
    {
      inet_ntop (AF_INET, &prefix_id.u.prefix4, ibuf, sizeof (ibuf));
      zlog_debug ("Advertise as AS-External Id:%s", ibuf);
    }

  route->path.origin.id = htonl (info->id);
  ospf6_as_external_lsa_originate (route);

  /* Router-Bit (ASBR Flag) may have to be updated */
  for (ALL_LIST_ELEMENTS (ospf6->area_list, lnode, lnnode, oa))
    OSPF6_ROUTER_LSA_SCHEDULE (oa);
}

void
ospf6_asbr_redistribute_remove (int type, int ifindex, struct prefix *prefix)
{
  struct ospf6_route *match;
  struct ospf6_external_info *info = NULL;
  struct route_node *node;
  struct ospf6_lsa *lsa;
  struct prefix prefix_id;
  char pbuf[64], ibuf[16];
  struct listnode *lnode, *lnnode;
  struct ospf6_area *oa;

  match = ospf6_route_lookup (prefix, ospf6->external_table);
  if (match == NULL)
    {
      if (IS_OSPF6_DEBUG_ASBR)
        {
          prefix2str (prefix, pbuf, sizeof (pbuf));
          zlog_debug ("No such route %s to withdraw", pbuf);
        }
      return;
    }

  info = match->route_option;
  assert (info);

  if (info->type != type)
    {
      if (IS_OSPF6_DEBUG_ASBR)
        {
          prefix2str (prefix, pbuf, sizeof (pbuf));
          zlog_debug ("Original protocol mismatch: %s", pbuf);
        }
      return;
    }

  if (IS_OSPF6_DEBUG_ASBR)
    {
      prefix2str (prefix, pbuf, sizeof (pbuf));
      inet_ntop (AF_INET, &prefix_id.u.prefix4, ibuf, sizeof (ibuf));
      zlog_debug ("Withdraw %s (AS-External Id:%s)", pbuf, ibuf);
    }

  lsa = ospf6_lsdb_lookup (htons (OSPF6_LSTYPE_AS_EXTERNAL),
                           htonl (info->id), ospf6->router_id, ospf6->lsdb);
  if (lsa)
    ospf6_lsa_purge (lsa);

  /* remove binding in external_id_table */
  prefix_id.family = AF_INET;
  prefix_id.prefixlen = 32;
  prefix_id.u.prefix4.s_addr = htonl (info->id);
  node = route_node_lookup (ospf6->external_id_table, &prefix_id);
  assert (node);
  node->info = NULL;
  route_unlock_node (node);

  ospf6_route_remove (match, ospf6->external_table);
  XFREE (MTYPE_OSPF6_EXTERNAL_INFO, info);

  /* Router-Bit (ASBR Flag) may have to be updated */
  for (ALL_LIST_ELEMENTS (ospf6->area_list, lnode, lnnode, oa))
    OSPF6_ROUTER_LSA_SCHEDULE (oa);
}

DEFUN (ospf6_redistribute,
       ospf6_redistribute_cmd,
       "redistribute " QUAGGA_REDIST_STR_OSPF6D,
       "Redistribute\n"
       QUAGGA_REDIST_HELP_STR_OSPF6D
      )
{
  int type;

  type = proto_redistnum(AFI_IP6, argv[0]);
  if (type < 0 || type == ZEBRA_ROUTE_OSPF6)
    return CMD_WARNING;

  ospf6_asbr_redistribute_unset (type);
  ospf6_asbr_routemap_unset (type);
  ospf6_asbr_redistribute_set (type);
  return CMD_SUCCESS;
}

DEFUN (ospf6_redistribute_routemap,
       ospf6_redistribute_routemap_cmd,
       "redistribute " QUAGGA_REDIST_STR_OSPF6D " route-map WORD",
       "Redistribute\n"
       QUAGGA_REDIST_HELP_STR_OSPF6D
       "Route map reference\n"
       "Route map name\n"
      )
{
  int type;

  type = proto_redistnum(AFI_IP6, argv[0]);
  if (type < 0 || type == ZEBRA_ROUTE_OSPF6)
    return CMD_WARNING;

  ospf6_asbr_redistribute_unset (type);
  ospf6_asbr_routemap_set (type, argv[1]);
  ospf6_asbr_redistribute_set (type);
  return CMD_SUCCESS;
}

DEFUN (no_ospf6_redistribute,
       no_ospf6_redistribute_cmd,
       "no redistribute " QUAGGA_REDIST_STR_OSPF6D,
       NO_STR
       "Redistribute\n"
       QUAGGA_REDIST_HELP_STR_OSPF6D
      )
{
  int type;

  type = proto_redistnum(AFI_IP6, argv[0]);
  if (type < 0 || type == ZEBRA_ROUTE_OSPF6)
    return CMD_WARNING;

  ospf6_asbr_redistribute_unset (type);
  ospf6_asbr_routemap_unset (type);

  return CMD_SUCCESS;
}

int
ospf6_redistribute_config_write (struct vty *vty)
{
  int type;

  for (type = 0; type < ZEBRA_ROUTE_MAX; type++)
    {
      if (type == ZEBRA_ROUTE_OSPF6)
        continue;
      if (! ospf6_zebra_is_redistribute (type))
        continue;

      if (ospf6->rmap[type].name)
        vty_out (vty, " redistribute %s route-map %s%s",
                 ZROUTE_NAME (type), ospf6->rmap[type].name, VNL);
      else
        vty_out (vty, " redistribute %s%s",
                 ZROUTE_NAME (type), VNL);
    }

  return 0;
}

static void
ospf6_redistribute_show_config (struct vty *vty)
{
  int type;
  int nroute[ZEBRA_ROUTE_MAX];
  int total;
  struct ospf6_route *route;
  struct ospf6_external_info *info;

  total = 0;
  for (type = 0; type < ZEBRA_ROUTE_MAX; type++)
    nroute[type] = 0;
  for (route = ospf6_route_head (ospf6->external_table); route;
       route = ospf6_route_next (route))
    {
      info = route->route_option;
      nroute[info->type]++;
      total++;
    }

  vty_out (vty, "Redistributing External Routes from:%s", VNL);
  for (type = 0; type < ZEBRA_ROUTE_MAX; type++)
    {
      if (type == ZEBRA_ROUTE_OSPF6)
        continue;
      if (! ospf6_zebra_is_redistribute (type))
        continue;

      if (ospf6->rmap[type].name)
        vty_out (vty, "    %d: %s with route-map \"%s\"%s%s", nroute[type],
                 ZROUTE_NAME (type), ospf6->rmap[type].name,
                 (ospf6->rmap[type].map ? "" : " (not found !)"),
                 VNL);
      else
        vty_out (vty, "    %d: %s%s", nroute[type],
                 ZROUTE_NAME (type), VNL);
    }
  vty_out (vty, "Total %d routes%s", total, VNL);
}



/* Routemap Functions */
static route_map_result_t
ospf6_routemap_rule_match_address_prefixlist (void *rule,
                                              struct prefix *prefix,
                                              route_map_object_t type,
                                              void *object)
{
  struct prefix_list *plist;

  if (type != RMAP_OSPF6)
    return RMAP_NOMATCH;

  plist = prefix_list_lookup (AFI_IP6, (char *) rule);
  if (plist == NULL)
    return RMAP_NOMATCH;

  return (prefix_list_apply (plist, prefix) == PREFIX_DENY ?
          RMAP_NOMATCH : RMAP_MATCH);
}

static void *
ospf6_routemap_rule_match_address_prefixlist_compile (const char *arg)
{
  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void
ospf6_routemap_rule_match_address_prefixlist_free (void *rule)
{
  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
}

struct route_map_rule_cmd
ospf6_routemap_rule_match_address_prefixlist_cmd =
{
  "ipv6 address prefix-list",
  ospf6_routemap_rule_match_address_prefixlist,
  ospf6_routemap_rule_match_address_prefixlist_compile,
  ospf6_routemap_rule_match_address_prefixlist_free,
};

/* `match interface IFNAME' */
/* Match function should return 1 if match is success else return
   zero. */
static route_map_result_t
ospf6_routemap_rule_match_interface (void *rule, struct prefix *prefix,
		       route_map_object_t type, void *object)
{
  struct interface   *ifp;
  struct ospf6_external_info *ei;

  if (type == RMAP_OSPF6)
    {
      ei = ((struct ospf6_route *) object)->route_option;
      ifp = if_lookup_by_name ((char *)rule);

      if (ifp != NULL
      &&  ei->ifindex == ifp->ifindex)
          return RMAP_MATCH;
    }

  return RMAP_NOMATCH;
}

/* Route map `interface' match statement.  `arg' should be
   interface name. */
static void *
ospf6_routemap_rule_match_interface_compile (const char *arg)
{
  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
}

/* Free route map's compiled `interface' value. */
static void
ospf6_routemap_rule_match_interface_free (void *rule)
{
  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
}

/* Route map commands for interface matching. */
struct route_map_rule_cmd
ospf6_routemap_rule_match_interface_cmd =
{
  "interface",
  ospf6_routemap_rule_match_interface,
  ospf6_routemap_rule_match_interface_compile,
  ospf6_routemap_rule_match_interface_free
};

static route_map_result_t
ospf6_routemap_rule_set_metric_type (void *rule, struct prefix *prefix,
                                     route_map_object_t type, void *object)
{
  char *metric_type = rule;
  struct ospf6_route *route = object;

  if (type != RMAP_OSPF6)
    return RMAP_OKAY;

  if (strcmp (metric_type, "type-2") == 0)
    route->path.metric_type = 2;
  else
    route->path.metric_type = 1;

  return RMAP_OKAY;
}

static void *
ospf6_routemap_rule_set_metric_type_compile (const char *arg)
{
  if (strcmp (arg, "type-2") && strcmp (arg, "type-1"))
    return NULL;
  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void
ospf6_routemap_rule_set_metric_type_free (void *rule)
{
  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
}

struct route_map_rule_cmd
ospf6_routemap_rule_set_metric_type_cmd =
{
  "metric-type",
  ospf6_routemap_rule_set_metric_type,
  ospf6_routemap_rule_set_metric_type_compile,
  ospf6_routemap_rule_set_metric_type_free,
};

static route_map_result_t
ospf6_routemap_rule_set_metric (void *rule, struct prefix *prefix,
                                route_map_object_t type, void *object)
{
  char *metric = rule;
  struct ospf6_route *route = object;

  if (type != RMAP_OSPF6)
    return RMAP_OKAY;

  route->path.cost = atoi (metric);
  return RMAP_OKAY;
}

static void *
ospf6_routemap_rule_set_metric_compile (const char *arg)
{
  u_int32_t metric;
  char *endp;
  metric = strtoul (arg, &endp, 0);
  if (metric > LS_INFINITY || *endp != '\0')
    return NULL;
  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void
ospf6_routemap_rule_set_metric_free (void *rule)
{
  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
}

struct route_map_rule_cmd
ospf6_routemap_rule_set_metric_cmd =
{
  "metric",
  ospf6_routemap_rule_set_metric,
  ospf6_routemap_rule_set_metric_compile,
  ospf6_routemap_rule_set_metric_free,
};

static route_map_result_t
ospf6_routemap_rule_set_forwarding (void *rule, struct prefix *prefix,
                                    route_map_object_t type, void *object)
{
  char *forwarding = rule;
  struct ospf6_route *route = object;
  struct ospf6_external_info *info = route->route_option;

  if (type != RMAP_OSPF6)
    return RMAP_OKAY;

  if (inet_pton (AF_INET6, forwarding, &info->forwarding) != 1)
    {
      memset (&info->forwarding, 0, sizeof (struct in6_addr));
      return RMAP_ERROR;
    }

  return RMAP_OKAY;
}

static void *
ospf6_routemap_rule_set_forwarding_compile (const char *arg)
{
  struct in6_addr a;
  if (inet_pton (AF_INET6, arg, &a) != 1)
    return NULL;
  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
}

static void
ospf6_routemap_rule_set_forwarding_free (void *rule)
{
  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
}

struct route_map_rule_cmd
ospf6_routemap_rule_set_forwarding_cmd =
{
  "forwarding-address",
  ospf6_routemap_rule_set_forwarding,
  ospf6_routemap_rule_set_forwarding_compile,
  ospf6_routemap_rule_set_forwarding_free,
};

static int
route_map_command_status (struct vty *vty, int ret)
{
  if (! ret)
    return CMD_SUCCESS;

  switch (ret)
    {
    case RMAP_RULE_MISSING:
      vty_out (vty, "Can't find rule.%s", VNL);
      break;
    case RMAP_COMPILE_ERROR:
      vty_out (vty, "Argument is malformed.%s", VNL);
      break;
    default:
      vty_out (vty, "route-map add set failed.%s", VNL);
      break;
    }
  return CMD_WARNING;
}

/* add "match address" */
DEFUN (ospf6_routemap_match_address_prefixlist,
       ospf6_routemap_match_address_prefixlist_cmd,
       "match ipv6 address prefix-list WORD",
       "Match values\n"
       IPV6_STR
       "Match address of route\n"
       "Match entries of prefix-lists\n"
       "IPv6 prefix-list name\n")
{
  int ret = route_map_add_match ((struct route_map_index *) vty->index,
                                 "ipv6 address prefix-list", argv[0]);
  return route_map_command_status (vty, ret);
}

/* delete "match address" */
DEFUN (ospf6_routemap_no_match_address_prefixlist,
       ospf6_routemap_no_match_address_prefixlist_cmd,
       "no match ipv6 address prefix-list WORD",
       NO_STR
       "Match values\n"
       IPV6_STR
       "Match address of route\n"
       "Match entries of prefix-lists\n"
       "IPv6 prefix-list name\n")
{
  int ret = route_map_delete_match ((struct route_map_index *) vty->index,
                                    "ipv6 address prefix-list", argv[0]);
  return route_map_command_status (vty, ret);
}

/* "match interface" */
DEFUN (ospf6_routemap_match_interface,
       ospf6_routemap_match_interface_cmd,
       "match interface WORD",
       MATCH_STR
       "Match first hop interface of route\n"
       "Interface name\n")
{
  return route_map_add_match ((struct route_map_index *) vty->index,
                              "interface", argv[0]);
}

/* "no match interface WORD" */
DEFUN (ospf6_routemap_no_match_interface,
       ospf6_routemap_no_match_interface_cmd,
       "no match interface",
       MATCH_STR
       NO_STR
       "Match first hop interface of route\n")
{
  int ret = route_map_delete_match ((struct route_map_index *) vty->index,
                                    "interface", (argc == 0) ? NULL : argv[0]);
  return route_map_command_status (vty, ret);
}

ALIAS (ospf6_routemap_no_match_interface,
       ospf6_routemap_no_match_interface_val_cmd,
       "no match interface WORD",
       MATCH_STR
       NO_STR
       "Match first hop interface of route\n"
       "Interface name\n")

/* add "set metric-type" */
DEFUN (ospf6_routemap_set_metric_type,
       ospf6_routemap_set_metric_type_cmd,
       "set metric-type (type-1|type-2)",
       "Set value\n"
       "Type of metric\n"
       "OSPF6 external type 1 metric\n"
       "OSPF6 external type 2 metric\n")
{
  int ret = route_map_add_set ((struct route_map_index *) vty->index,
                               "metric-type", argv[0]);
  return route_map_command_status (vty, ret);
}

/* delete "set metric-type" */
DEFUN (ospf6_routemap_no_set_metric_type,
       ospf6_routemap_no_set_metric_type_cmd,
       "no set metric-type (type-1|type-2)",
       NO_STR
       "Set value\n"
       "Type of metric\n"
       "OSPF6 external type 1 metric\n"
       "OSPF6 external type 2 metric\n")
{
  int ret = route_map_delete_set ((struct route_map_index *) vty->index,
                                  "metric-type", argv[0]);
  return route_map_command_status (vty, ret);
}

/* add "set metric" */
DEFUN (set_metric,
       set_metric_cmd,
       "set metric <0-4294967295>",
       "Set value\n"
       "Metric value\n"
       "Metric value\n")
{
  int ret = route_map_add_set ((struct route_map_index *) vty->index,
                               "metric", argv[0]);
  return route_map_command_status (vty, ret);
}

/* delete "set metric" */
DEFUN (no_set_metric,
       no_set_metric_cmd,
       "no set metric <0-4294967295>",
       NO_STR
       "Set value\n"
       "Metric\n"
       "METRIC value\n")
{
  int ret = route_map_delete_set ((struct route_map_index *) vty->index,
                                  "metric", argv[0]);
  return route_map_command_status (vty, ret);
}

/* add "set forwarding-address" */
DEFUN (ospf6_routemap_set_forwarding,
       ospf6_routemap_set_forwarding_cmd,
       "set forwarding-address X:X::X:X",
       "Set value\n"
       "Forwarding Address\n"
       "IPv6 Address\n")
{
  int ret = route_map_add_set ((struct route_map_index *) vty->index,
                               "forwarding-address", argv[0]);
  return route_map_command_status (vty, ret);
}

/* delete "set forwarding-address" */
DEFUN (ospf6_routemap_no_set_forwarding,
       ospf6_routemap_no_set_forwarding_cmd,
       "no set forwarding-address X:X::X:X",
       NO_STR
       "Set value\n"
       "Forwarding Address\n"
       "IPv6 Address\n")
{
  int ret = route_map_delete_set ((struct route_map_index *) vty->index,
                                  "forwarding-address", argv[0]);
  return route_map_command_status (vty, ret);
}

static void
ospf6_routemap_init (void)
{
  route_map_init ();
  route_map_init_vty ();
  route_map_add_hook (ospf6_asbr_routemap_update);
  route_map_delete_hook (ospf6_asbr_routemap_update);

  route_map_install_match (&ospf6_routemap_rule_match_address_prefixlist_cmd);
  route_map_install_match (&ospf6_routemap_rule_match_interface_cmd);

  route_map_install_set (&ospf6_routemap_rule_set_metric_type_cmd);
  route_map_install_set (&ospf6_routemap_rule_set_metric_cmd);
  route_map_install_set (&ospf6_routemap_rule_set_forwarding_cmd);

  /* Match address prefix-list */
  install_element (RMAP_NODE, &ospf6_routemap_match_address_prefixlist_cmd);
  install_element (RMAP_NODE, &ospf6_routemap_no_match_address_prefixlist_cmd);

  /* Match interface */
  install_element (RMAP_NODE, &ospf6_routemap_match_interface_cmd);
  install_element (RMAP_NODE, &ospf6_routemap_no_match_interface_cmd);
  install_element (RMAP_NODE, &ospf6_routemap_no_match_interface_val_cmd);

  /* ASE Metric Type (e.g. Type-1/Type-2) */
  install_element (RMAP_NODE, &ospf6_routemap_set_metric_type_cmd);
  install_element (RMAP_NODE, &ospf6_routemap_no_set_metric_type_cmd);

  /* ASE Metric */
  install_element (RMAP_NODE, &set_metric_cmd);
  install_element (RMAP_NODE, &no_set_metric_cmd);

  /* ASE Metric */
  install_element (RMAP_NODE, &ospf6_routemap_set_forwarding_cmd);
  install_element (RMAP_NODE, &ospf6_routemap_no_set_forwarding_cmd);
}


/* Display functions */
static int
ospf6_as_external_lsa_show (struct vty *vty, struct ospf6_lsa *lsa)
{
  struct ospf6_as_external_lsa *external;
  char buf[64];
  struct in6_addr in6, *forwarding;

  assert (lsa->header);
  external = (struct ospf6_as_external_lsa *)
    OSPF6_LSA_HEADER_END (lsa->header);
  
  /* bits */
  snprintf (buf, sizeof (buf), "%c%c%c",
    (CHECK_FLAG (external->bits_metric, OSPF6_ASBR_BIT_E) ? 'E' : '-'),
    (CHECK_FLAG (external->bits_metric, OSPF6_ASBR_BIT_F) ? 'F' : '-'),
    (CHECK_FLAG (external->bits_metric, OSPF6_ASBR_BIT_T) ? 'T' : '-'));

  vty_out (vty, "     Bits: %s%s", buf, VNL);
  vty_out (vty, "     Metric: %5lu%s", (u_long) OSPF6_ASBR_METRIC (external),
           VNL);

  ospf6_prefix_options_printbuf (external->prefix.prefix_options,
                                 buf, sizeof (buf));
  vty_out (vty, "     Prefix Options: %s%s", buf,
           VNL);

  vty_out (vty, "     Referenced LSType: %d%s",
           ntohs (external->prefix.prefix_refer_lstype),
           VNL);

  ospf6_prefix_in6_addr (&in6, &external->prefix);
  inet_ntop (AF_INET6, &in6, buf, sizeof (buf));
  vty_out (vty, "     Prefix: %s/%d%s", buf,
           external->prefix.prefix_length, VNL);

  /* Forwarding-Address */
  if (CHECK_FLAG (external->bits_metric, OSPF6_ASBR_BIT_F))
    {
      forwarding = (struct in6_addr *)
        ((caddr_t) external + sizeof (struct ospf6_as_external_lsa) +
         OSPF6_PREFIX_SPACE (external->prefix.prefix_length));
      inet_ntop (AF_INET6, forwarding, buf, sizeof (buf));
      vty_out (vty, "     Forwarding-Address: %s%s", buf, VNL);
    }

  return 0;
}

static void
ospf6_asbr_external_route_show (struct vty *vty, struct ospf6_route *route)
{
  struct ospf6_external_info *info = route->route_option;
  char prefix[64], id[16], forwarding[64];
  u_int32_t tmp_id;

  prefix2str (&route->prefix, prefix, sizeof (prefix));
  tmp_id = ntohl (info->id);
  inet_ntop (AF_INET, &tmp_id, id, sizeof (id));
  if (! IN6_IS_ADDR_UNSPECIFIED (&info->forwarding))
    inet_ntop (AF_INET6, &info->forwarding, forwarding, sizeof (forwarding));
  else
    snprintf (forwarding, sizeof (forwarding), ":: (ifindex %d)",
              route->nexthop[0].ifindex);

  vty_out (vty, "%c %-32s %-15s type-%d %5lu %s%s",
           zebra_route_char(info->type),
           prefix, id, route->path.metric_type,
           (u_long) (route->path.metric_type == 2 ?
                     route->path.cost_e2 : route->path.cost),
           forwarding, VNL);
}

DEFUN (show_ipv6_ospf6_redistribute,
       show_ipv6_ospf6_redistribute_cmd,
       "show ipv6 ospf6 redistribute",
       SHOW_STR
       IP6_STR
       OSPF6_STR
       "redistributing External information\n"
       )
{
  struct ospf6_route *route;

  ospf6_redistribute_show_config (vty);

  for (route = ospf6_route_head (ospf6->external_table); route;
       route = ospf6_route_next (route))
    ospf6_asbr_external_route_show (vty, route);

  return CMD_SUCCESS;
}

struct ospf6_lsa_handler as_external_handler =
{
  OSPF6_LSTYPE_AS_EXTERNAL,
  "AS-External",
  ospf6_as_external_lsa_show
};

void
ospf6_asbr_init (void)
{
  ospf6_routemap_init ();

  ospf6_install_lsa_handler (&as_external_handler);

  install_element (VIEW_NODE, &show_ipv6_ospf6_redistribute_cmd);
  install_element (ENABLE_NODE, &show_ipv6_ospf6_redistribute_cmd);

  install_element (OSPF6_NODE, &ospf6_redistribute_cmd);
  install_element (OSPF6_NODE, &ospf6_redistribute_routemap_cmd);
  install_element (OSPF6_NODE, &no_ospf6_redistribute_cmd);
}

void
ospf6_asbr_terminate (void)
{
  route_map_finish ();
}

DEFUN (debug_ospf6_asbr,
       debug_ospf6_asbr_cmd,
       "debug ospf6 asbr",
       DEBUG_STR
       OSPF6_STR
       "Debug OSPFv3 ASBR function\n"
      )
{
  OSPF6_DEBUG_ASBR_ON ();
  return CMD_SUCCESS;
}

DEFUN (no_debug_ospf6_asbr,
       no_debug_ospf6_asbr_cmd,
       "no debug ospf6 asbr",
       NO_STR
       DEBUG_STR
       OSPF6_STR
       "Debug OSPFv3 ASBR function\n"
      )
{
  OSPF6_DEBUG_ASBR_OFF ();
  return CMD_SUCCESS;
}

int
config_write_ospf6_debug_asbr (struct vty *vty)
{
  if (IS_OSPF6_DEBUG_ASBR)
    vty_out (vty, "debug ospf6 asbr%s", VNL);
  return 0;
}

void
install_element_ospf6_debug_asbr ()
{
  install_element (ENABLE_NODE, &debug_ospf6_asbr_cmd);
  install_element (ENABLE_NODE, &no_debug_ospf6_asbr_cmd);
  install_element (CONFIG_NODE, &debug_ospf6_asbr_cmd);
  install_element (CONFIG_NODE, &no_debug_ospf6_asbr_cmd);
}


