isisd: add support to import routes from other protocols

Signed-off-by: Christian Franke <chris@opensourcerouting.org>
diff --git a/isisd/Makefile.am b/isisd/Makefile.am
index ff6ce3c..5f86663 100644
--- a/isisd/Makefile.am
+++ b/isisd/Makefile.am
@@ -16,14 +16,15 @@
 	isis_adjacency.c isis_lsp.c dict.c isis_circuit.c isis_pdu.c \
 	isis_tlv.c isisd.c isis_misc.c isis_zebra.c isis_dr.c \
 	isis_flags.c isis_dynhn.c iso_checksum.c isis_csm.c isis_events.c \
-	isis_spf.c isis_route.c isis_routemap.c
+	isis_spf.c isis_redist.c isis_route.c isis_routemap.c
 
 
 noinst_HEADERS = \
 	isisd.h isis_pdu.h isis_tlv.h isis_adjacency.h isis_constants.h \
 	isis_lsp.h dict.h isis_circuit.h isis_misc.h isis_network.h \
 	isis_zebra.h isis_dr.h isis_flags.h isis_dynhn.h isis_common.h \
-	iso_checksum.h isis_csm.h isis_events.h isis_spf.h isis_route.h \
+	iso_checksum.h isis_csm.h isis_events.h isis_spf.h isis_redist.h \
+	isis_route.h isis_routemap.h \
 	include-netbsd/clnp.h include-netbsd/esis.h include-netbsd/iso.h
 
 isisd_SOURCES = \
diff --git a/isisd/isis_lsp.c b/isisd/isis_lsp.c
index 2b478b1..7c64eac 100644
--- a/isisd/isis_lsp.c
+++ b/isisd/isis_lsp.c
@@ -5,9 +5,10 @@
  * Copyright (C) 2001,2002   Sampo Saaristo
  *                           Tampere University of Technology      
  *                           Institute of Communications Engineering
+ * Copyright (C) 2013-2015   Christian Franke <chris@opensourcerouting.org>
  *
  * This program is free software; you can redistribute it and/or modify it 
- * under the terms of the GNU General Public Licenseas published by the Free 
+ * under the terms of the GNU General Public License as published by the Free 
  * Software Foundation; either version 2 of the License, or (at your option) 
  * any later version.
  *
@@ -35,6 +36,7 @@
 #include "if.h"
 #include "checksum.h"
 #include "md5.h"
+#include "table.h"
 
 #include "isisd/dict.h"
 #include "isisd/isis_constants.h"
@@ -1146,6 +1148,119 @@
   return lsp;
 }
 
+static void
+lsp_build_ext_reach_ipv4(struct isis_lsp *lsp, struct isis_area *area,
+                         struct tlvs *tlv_data)
+{
+  struct route_table *er_table;
+  struct route_node *rn;
+  struct prefix_ipv4 *ipv4;
+  struct isis_ext_info *info;
+  struct ipv4_reachability *ipreach;
+  struct te_ipv4_reachability *te_ipreach;
+
+  er_table = get_ext_reach(area, AF_INET, lsp->level);
+  if (!er_table)
+    return;
+
+  for (rn = route_top(er_table); rn; rn = route_next(rn))
+    {
+      if (!rn->info)
+        continue;
+
+      ipv4 = (struct prefix_ipv4*)&rn->p;
+      info = rn->info;
+      if (area->oldmetric)
+        {
+          if (tlv_data->ipv4_ext_reachs == NULL)
+            {
+              tlv_data->ipv4_ext_reachs = list_new();
+              tlv_data->ipv4_ext_reachs->del = free_tlv;
+            }
+          ipreach = XMALLOC(MTYPE_ISIS_TLV, sizeof(*ipreach));
+
+          ipreach->prefix.s_addr = ipv4->prefix.s_addr;
+          masklen2ip(ipv4->prefixlen, &ipreach->mask);
+          ipreach->prefix.s_addr &= ipreach->mask.s_addr;
+
+          if ((info->metric & 0x3f) != info->metric)
+            ipreach->metrics.metric_default = 0x3f;
+          else
+            ipreach->metrics.metric_default = info->metric;
+          ipreach->metrics.metric_expense = METRICS_UNSUPPORTED;
+          ipreach->metrics.metric_error = METRICS_UNSUPPORTED;
+          ipreach->metrics.metric_delay = METRICS_UNSUPPORTED;
+          listnode_add(tlv_data->ipv4_ext_reachs, ipreach);
+        }
+      if (area->newmetric)
+        {
+          if (tlv_data->te_ipv4_reachs == NULL)
+            {
+              tlv_data->te_ipv4_reachs = list_new();
+              tlv_data->te_ipv4_reachs->del = free_tlv;
+            }
+          te_ipreach =
+              XCALLOC(MTYPE_ISIS_TLV,
+                      sizeof(*te_ipreach) - 1 + PSIZE(ipv4->prefixlen));
+          if (info->metric > MAX_WIDE_PATH_METRIC)
+            te_ipreach->te_metric = htonl(MAX_WIDE_PATH_METRIC);
+          else
+            te_ipreach->te_metric = htonl(info->metric);
+          te_ipreach->control = ipv4->prefixlen & 0x3f;
+          memcpy(&te_ipreach->prefix_start, &ipv4->prefix.s_addr,
+                 PSIZE(ipv4->prefixlen));
+          listnode_add(tlv_data->te_ipv4_reachs, te_ipreach);
+        }
+    }
+}
+
+static void
+lsp_build_ext_reach_ipv6(struct isis_lsp *lsp, struct isis_area *area,
+                         struct tlvs *tlv_data)
+{
+  struct route_table *er_table;
+  struct route_node *rn;
+  struct prefix_ipv6 *ipv6;
+  struct isis_ext_info *info;
+  struct ipv6_reachability *ip6reach;
+
+  er_table = get_ext_reach(area, AF_INET6, lsp->level);
+  if (!er_table)
+    return;
+
+  for (rn = route_top(er_table); rn; rn = route_next(rn))
+    {
+      if (!rn->info)
+        continue;
+
+      ipv6 = (struct prefix_ipv6*)&rn->p;
+      info = rn->info;
+
+      if (tlv_data->ipv6_reachs == NULL)
+        {
+          tlv_data->ipv6_reachs = list_new();
+          tlv_data->ipv6_reachs->del = free_tlv;
+        }
+      ip6reach = XCALLOC(MTYPE_ISIS_TLV, sizeof(*ip6reach));
+      if (info->metric > MAX_WIDE_PATH_METRIC)
+        ip6reach->metric = htonl(MAX_WIDE_PATH_METRIC);
+      else
+        ip6reach->metric = htonl(info->metric);
+      ip6reach->control_info = DISTRIBUTION_EXTERNAL;
+      ip6reach->prefix_len = ipv6->prefixlen;
+      memcpy(ip6reach->prefix, ipv6->prefix.s6_addr, sizeof(ip6reach->prefix));
+      listnode_add(tlv_data->ipv6_reachs, ip6reach);
+    }
+}
+
+static void
+lsp_build_ext_reach (struct isis_lsp *lsp, struct isis_area *area,
+                     struct tlvs *tlv_data)
+{
+  lsp_build_ext_reach_ipv4(lsp, area, tlv_data);
+  lsp_build_ext_reach_ipv6(lsp, area, tlv_data);
+}
+
 /*
  * Builds the LSP data part. This func creates a new frag whenever 
  * area->lsp_frag_threshold is exceeded.
@@ -1561,6 +1676,8 @@
 	}
     }
 
+  lsp_build_ext_reach(lsp, area, &tlv_data);
+
   lsp_debug("ISIS (%s): LSP construction is complete. Serializing...", area->area_tag);
 
   while (tlv_data.ipv4_int_reachs && listcount (tlv_data.ipv4_int_reachs))
@@ -1570,12 +1687,25 @@
       lsp_tlv_fit (lsp, &tlv_data.ipv4_int_reachs,
 		   &lsp->tlv_data.ipv4_int_reachs,
 		   IPV4_REACH_LEN, area->lsp_frag_threshold,
-		   tlv_add_ipv4_reachs);
+		   tlv_add_ipv4_int_reachs);
       if (tlv_data.ipv4_int_reachs && listcount (tlv_data.ipv4_int_reachs))
 	lsp = lsp_next_frag (LSP_FRAGMENT (lsp->lsp_header->lsp_id) + 1,
 			     lsp0, area, level);
     }
 
+  while (tlv_data.ipv4_ext_reachs && listcount (tlv_data.ipv4_ext_reachs))
+    {
+      if (lsp->tlv_data.ipv4_ext_reachs == NULL)
+	lsp->tlv_data.ipv4_ext_reachs = list_new ();
+      lsp_tlv_fit (lsp, &tlv_data.ipv4_ext_reachs,
+		   &lsp->tlv_data.ipv4_ext_reachs,
+		   IPV4_REACH_LEN, area->lsp_frag_threshold,
+		   tlv_add_ipv4_ext_reachs);
+      if (tlv_data.ipv4_ext_reachs && listcount (tlv_data.ipv4_ext_reachs))
+	lsp = lsp_next_frag (LSP_FRAGMENT (lsp->lsp_header->lsp_id) + 1,
+			     lsp0, area, level);
+    }
+
   /* FIXME: We pass maximum te_ipv4_reachability length to the lsp_tlv_fit()
    * for now. lsp_tlv_fit() needs to be fixed to deal with variable length
    * TLVs (sub TLVs!). */
diff --git a/isisd/isis_main.c b/isisd/isis_main.c
index fba7b10..3a73087 100644
--- a/isisd/isis_main.c
+++ b/isisd/isis_main.c
@@ -34,6 +34,7 @@
 #include "privs.h"
 #include "sigevent.h"
 #include "filter.h"
+#include "plist.h"
 #include "zclient.h"
 #include "vrf.h"
 
@@ -47,6 +48,7 @@
 #include "isisd/isis_dynhn.h"
 #include "isisd/isis_spf.h"
 #include "isisd/isis_route.h"
+#include "isisd/isis_routemap.h"
 #include "isisd/isis_zebra.h"
 
 /* Default configuration file name */
@@ -332,9 +334,12 @@
   memory_init ();
   access_list_init();
   vrf_init ();
+  prefix_list_init();
   isis_init ();
   isis_circuit_init ();
   isis_spf_cmds_init ();
+  isis_redist_init ();
+  isis_route_map_init();
 
   /* create the global 'isis' instance */
   isis_new (1);
diff --git a/isisd/isis_redist.c b/isisd/isis_redist.c
new file mode 100644
index 0000000..abb9ecd
--- /dev/null
+++ b/isisd/isis_redist.c
@@ -0,0 +1,821 @@
+/*
+ * IS-IS Rout(e)ing protocol - isis_redist.c
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ *
+ * This program 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 of the License, or (at your option) 
+ * any later version.
+ *
+ * This program 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 this program; 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 "if.h"
+#include "linklist.h"
+#include "memory.h"
+#include "memtypes.h"
+#include "prefix.h"
+#include "routemap.h"
+#include "stream.h"
+#include "table.h"
+#include "vty.h"
+
+#include "isisd/dict.h"
+#include "isisd/isis_constants.h"
+#include "isisd/isis_common.h"
+#include "isisd/isis_flags.h"
+#include "isisd/isis_misc.h"
+#include "isisd/isis_circuit.h"
+#include "isisd/isis_tlv.h"
+#include "isisd/isisd.h"
+#include "isisd/isis_lsp.h"
+#include "isisd/isis_route.h"
+#include "isisd/isis_zebra.h"
+
+static int
+redist_protocol(int family)
+{
+  if (family == AF_INET)
+    return 0;
+  if (family == AF_INET6)
+    return 1;
+
+  assert(!"Unsupported address family!");
+}
+
+static int
+is_default(struct prefix *p)
+{
+  if (p->family == AF_INET)
+    if (p->u.prefix4.s_addr == 0 && p->prefixlen == 0)
+      return 1;
+  if (p->family == AF_INET6)
+    if (IN6_IS_ADDR_UNSPECIFIED(&p->u.prefix6) && p->prefixlen == 0)
+      return 1;
+  return 0;
+}
+
+static struct route_table*
+get_ext_info(struct isis *i, int family)
+{
+  int protocol = redist_protocol(family);
+
+  return i->ext_info[protocol];
+}
+
+static struct isis_redist*
+get_redist_settings(struct isis_area *area, int family, int type, int level)
+{
+  int protocol = redist_protocol(family);
+
+  return &area->redist_settings[protocol][type][level-1];
+}
+
+struct route_table*
+get_ext_reach(struct isis_area *area, int family, int level)
+{
+  int protocol = redist_protocol(family);
+
+  return area->ext_reach[protocol][level-1];
+}
+
+static struct route_node *
+isis_redist_route_node_create(route_table_delegate_t *delegate,
+                              struct route_table *table)
+{
+  struct route_node *node;
+  node = XCALLOC(MTYPE_ROUTE_NODE, sizeof(*node));
+  return node;
+}
+
+static void
+isis_redist_route_node_destroy(route_table_delegate_t *delegate,
+                               struct route_table *table,
+                               struct route_node *node)
+{
+  if (node->info)
+    XFREE(MTYPE_ISIS, node->info);
+  XFREE (MTYPE_ROUTE_NODE, node);
+}
+
+static route_table_delegate_t isis_redist_rt_delegate = {
+  .create_node = isis_redist_route_node_create,
+  .destroy_node = isis_redist_route_node_destroy
+};
+
+/* Install external reachability information into a
+ * specific area for a specific level.
+ * Schedule an lsp regenerate if necessary */
+static void
+isis_redist_install(struct isis_area *area, int level,
+                    struct prefix *p, struct isis_ext_info *info)
+{
+  int family = p->family;
+  struct route_table *er_table = get_ext_reach(area, family, level);
+  struct route_node *er_node;
+
+  if (!er_table)
+    {
+      zlog_warn("%s: External reachability table of area %s"
+                " is not initialized.", __func__, area->area_tag);
+      return;
+    }
+
+  er_node = route_node_get(er_table, p);
+  if (er_node->info)
+    {
+      route_unlock_node(er_node);
+
+      /* Don't update/reschedule lsp generation if nothing changed. */
+      if (!memcmp(er_node->info, info, sizeof(*info)))
+        return;
+    }
+  else
+    {
+      er_node->info = XMALLOC(MTYPE_ISIS, sizeof(*info));
+    }
+
+  memcpy(er_node->info, info, sizeof(*info));
+  lsp_regenerate_schedule(area, level, 0);
+}
+
+/* Remove external reachability information from a
+ * specific area for a specific level.
+ * Schedule an lsp regenerate if necessary. */
+static void
+isis_redist_uninstall(struct isis_area *area, int level, struct prefix *p)
+{
+  int family = p->family;
+  struct route_table *er_table = get_ext_reach(area, family, level);
+  struct route_node *er_node;
+
+  if (!er_table)
+    {
+      zlog_warn("%s: External reachability table of area %s"
+                " is not initialized.", __func__, area->area_tag);
+      return;
+    }
+
+  er_node = route_node_lookup(er_table, p);
+  if (!er_node)
+    return;
+  else
+    route_unlock_node(er_node);
+
+  if (!er_node->info)
+    return;
+
+  route_unlock_node(er_node);
+  lsp_regenerate_schedule(area, level, 0);
+}
+
+/* Update external reachability info of area for a given level
+ * and prefix, using the given redistribution settings. */
+static void
+isis_redist_update_ext_reach(struct isis_area *area, int level,
+                             struct isis_redist *redist, struct prefix *p,
+                             struct isis_ext_info *info)
+{
+  struct isis_ext_info area_info;
+  route_map_result_t map_ret;
+
+  memcpy(&area_info, info, sizeof(area_info));
+  if (redist->metric != 0xffffffff)
+    area_info.metric = redist->metric;
+
+  if (redist->map_name)
+    {
+      map_ret = route_map_apply(redist->map, p, RMAP_ISIS, &area_info);
+      if (map_ret == RMAP_DENYMATCH)
+        area_info.distance = 255;
+    }
+
+  /* Allow synthesized default routes only on always orignate */
+  if (area_info.origin == DEFAULT_ROUTE
+      && redist->redist != DEFAULT_ORIGINATE_ALWAYS)
+    area_info.distance = 255;
+
+  if (area_info.distance < 255)
+    isis_redist_install(area, level, p, &area_info);
+  else
+    isis_redist_uninstall(area, level, p);
+}
+
+static void
+isis_redist_ensure_default(struct isis *isis, int family)
+{
+  struct prefix p;
+  struct route_table *ei_table = get_ext_info(isis, family);
+  struct route_node *ei_node;
+  struct isis_ext_info *info;
+
+  if (family == AF_INET)
+    {
+      p.family = AF_INET;
+      p.prefixlen = 0;
+      memset(&p.u.prefix4, 0, sizeof(p.u.prefix4));
+    }
+  else if (family == AF_INET6)
+    {
+      p.family = AF_INET6;
+      p.prefixlen = 0;
+      memset(&p.u.prefix6, 0, sizeof(p.u.prefix6));
+    }
+  else
+    assert(!"Unknown family!");
+
+  ei_node = route_node_get(ei_table, &p);
+  if (ei_node->info)
+    {
+      route_unlock_node(ei_node);
+      return;
+    }
+
+  ei_node->info = XCALLOC(MTYPE_ISIS, sizeof(struct isis_ext_info));
+
+  info = ei_node->info;
+  info->origin = DEFAULT_ROUTE;
+  info->distance = 254;
+  info->metric = MAX_WIDE_PATH_METRIC;
+}
+
+/* Handle notification about route being added */
+void
+isis_redist_add(int type, struct prefix *p, u_char distance, uint32_t metric)
+{
+  int family = p->family;
+  struct route_table *ei_table = get_ext_info(isis, family);
+  struct route_node *ei_node;
+  struct isis_ext_info *info;
+  struct listnode *node;
+  struct isis_area *area;
+  int level;
+  struct isis_redist *redist;
+
+  char debug_buf[BUFSIZ];
+  prefix2str(p, debug_buf, sizeof(debug_buf));
+
+  zlog_debug("%s: New route %s from %s.", __func__, debug_buf,
+             zebra_route_string(type));
+
+  if (!ei_table)
+    {
+      zlog_warn("%s: External information table not initialized.",
+                __func__);
+      return;
+    }
+
+  ei_node = route_node_get(ei_table, p);
+  if (ei_node->info)
+    route_unlock_node(ei_node);
+  else
+    ei_node->info = XCALLOC(MTYPE_ISIS, sizeof(struct isis_ext_info));
+
+  info = ei_node->info;
+  info->origin = type;
+  info->distance = distance;
+  info->metric = metric;
+
+  if (is_default(p))
+    type = DEFAULT_ROUTE;
+
+  for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+    for (level = 1; level <= ISIS_LEVELS; level++)
+      {
+        redist = get_redist_settings(area, family, type, level);
+        if (!redist->redist)
+          continue;
+
+        isis_redist_update_ext_reach(area, level, redist, p, info);
+      }
+}
+
+void
+isis_redist_delete(int type, struct prefix *p)
+{
+  int family = p->family;
+  struct route_table *ei_table = get_ext_info(isis, family);
+  struct route_node *ei_node;
+  struct listnode *node;
+  struct isis_area *area;
+  int level;
+  struct isis_redist *redist;
+
+  char debug_buf[BUFSIZ];
+  prefix2str(p, debug_buf, sizeof(debug_buf));
+
+  zlog_debug("%s: Removing route %s from %s.", __func__, debug_buf,
+             zebra_route_string(type));
+
+  if (is_default(p))
+    {
+      /* Don't remove default route but add synthetic route for use
+       * by "default-information originate always". Areas without the
+       * "always" setting will ignore routes with origin DEFAULT_ROUTE. */
+      isis_redist_add(DEFAULT_ROUTE, p, 254, MAX_WIDE_PATH_METRIC);
+      return;
+    }
+
+  if (!ei_table)
+    {
+      zlog_warn("%s: External information table not initialized.",
+                __func__);
+      return;
+    }
+
+  ei_node = route_node_lookup(ei_table, p);
+  if (!ei_node || !ei_node->info)
+    {
+      char buf[BUFSIZ];
+      prefix2str(p, buf, sizeof(buf));
+      zlog_warn("%s: Got a delete for %s route %s, but that route"
+                " was never added.", __func__, zebra_route_string(type),
+                buf);
+      if (ei_node)
+        route_unlock_node(ei_node);
+      return;
+    }
+  route_unlock_node(ei_node);
+
+  for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+    for (level = 1; level < ISIS_LEVELS; level++)
+      {
+        redist = get_redist_settings(area, family, type, level);
+        if (!redist->redist)
+          continue;
+
+        isis_redist_uninstall(area, level, p);
+      }
+
+  route_unlock_node(ei_node);
+}
+
+static void
+isis_redist_routemap_set(struct isis_redist *redist, const char *routemap)
+{
+  if (redist->map_name) {
+    XFREE(MTYPE_ISIS, redist->map_name);
+    redist->map = NULL;
+  }
+
+  if (routemap && strlen(routemap)) {
+    redist->map_name = XSTRDUP(MTYPE_ISIS, routemap);
+    redist->map = route_map_lookup_by_name(routemap);
+  }
+}
+
+static void
+isis_redist_update_zebra_subscriptions(struct isis *isis)
+{
+  struct listnode *node;
+  struct isis_area *area;
+  int type;
+  int level;
+  int protocol;
+
+  char do_subscribe[ZEBRA_ROUTE_MAX + 1];
+
+  memset(do_subscribe, 0, sizeof(do_subscribe));
+
+  for (ALL_LIST_ELEMENTS_RO(isis->area_list, node, area))
+    for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++)
+      for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
+        for (level = 0; level < ISIS_LEVELS; level++)
+          if (area->redist_settings[protocol][type][level].redist)
+            do_subscribe[type] = 1;
+
+  for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
+    {
+      /* This field is actually controlling transmission of the IS-IS
+       * routes to Zebra and has nothing to do with redistribution,
+       * so skip it. */
+      if (type == ZEBRA_ROUTE_ISIS)
+        continue;
+
+      if (do_subscribe[type])
+        isis_zebra_redistribute_set(type);
+      else
+        isis_zebra_redistribute_unset(type);
+    }
+}
+
+static void
+isis_redist_set(struct isis_area *area, int level,
+                int family, int type, uint32_t metric,
+                const char *routemap, int originate_type)
+{
+  int protocol = redist_protocol(family);
+  struct isis_redist *redist = get_redist_settings(area, family, type, level);
+  int i;
+  struct route_table *ei_table;
+  struct route_node *rn;
+  struct isis_ext_info *info;
+
+  redist->redist = (type == DEFAULT_ROUTE) ? originate_type : 1;
+  redist->metric = metric;
+  isis_redist_routemap_set(redist, routemap);
+
+  if (!area->ext_reach[protocol][level-1])
+    {
+      area->ext_reach[protocol][level-1] =
+          route_table_init_with_delegate(&isis_redist_rt_delegate);
+    }
+
+  for (i = 0; i < REDIST_PROTOCOL_COUNT; i++)
+    if (!area->isis->ext_info[i])
+      {
+        area->isis->ext_info[i] =
+            route_table_init_with_delegate(&isis_redist_rt_delegate);
+      }
+
+  isis_redist_update_zebra_subscriptions(area->isis);
+
+  if (type == DEFAULT_ROUTE && originate_type == DEFAULT_ORIGINATE_ALWAYS)
+    isis_redist_ensure_default(area->isis, family);
+
+  ei_table = get_ext_info(area->isis, family);
+  for (rn = route_top(ei_table); rn; rn = route_next(rn))
+    {
+      if (!rn->info)
+        continue;
+      info = rn->info;
+
+      if (type == DEFAULT_ROUTE)
+        {
+          if (!is_default(&rn->p))
+            continue;
+        }
+      else
+        {
+          if (info->origin != type)
+            continue;
+        }
+
+      isis_redist_update_ext_reach(area, level, redist, &rn->p, info);
+    }
+}
+
+static void
+isis_redist_unset(struct isis_area *area, int level,
+                  int family, int type)
+{
+  struct isis_redist *redist = get_redist_settings(area, family, type, level);
+  struct route_table *er_table = get_ext_reach(area, family, level);
+  struct route_node *rn;
+  struct isis_ext_info *info;
+
+  if (!redist->redist)
+    return;
+
+  redist->redist = 0;
+  if (!er_table)
+    {
+      zlog_warn("%s: External reachability table uninitialized.", __func__);
+      return;
+    }
+
+  for (rn = route_top(er_table); rn; rn = route_next(rn))
+    {
+      if (!rn->info)
+        continue;
+      info = rn->info;
+
+      if (type == DEFAULT_ROUTE)
+        {
+          if (!is_default(&rn->p))
+            continue;
+        }
+      else
+        {
+          if (info->origin != type)
+            continue;
+        }
+
+      route_unlock_node(rn);
+    }
+
+  lsp_regenerate_schedule(area, level, 0);
+  isis_redist_update_zebra_subscriptions(area->isis);
+}
+
+void
+isis_redist_area_finish(struct isis_area *area)
+{
+  int protocol;
+  int level;
+  int type;
+
+  for (protocol = 0; protocol < REDIST_PROTOCOL_COUNT; protocol++)
+    for (level = 0; level < ISIS_LEVELS; level++)
+      {
+        for (type = 0; type < ZEBRA_ROUTE_MAX + 1; type++)
+          {
+            struct isis_redist *redist;
+
+            redist = &area->redist_settings[protocol][type][level];
+            redist->redist = 0;
+            if (redist->map_name)
+              XFREE(MTYPE_ISIS, redist->map_name);
+          }
+        route_table_finish(area->ext_reach[protocol][level]);
+      }
+
+  isis_redist_update_zebra_subscriptions(area->isis);
+}
+
+DEFUN(isis_redistribute,
+      isis_redistribute_cmd,
+      "redistribute (ipv4|ipv6) " QUAGGA_REDIST_STR_ISISD
+      " (level-1|level-2) {metric <0-16777215>|route-map WORD}",
+      REDIST_STR
+      "Redistribute IPv4 routes\n"
+      "Redistribute IPv6 routes\n"
+      QUAGGA_REDIST_HELP_STR_ISISD
+      "Redistribute into level-1\n"
+      "Redistribute into level-2\n"
+      "Metric for redistributed routes\n"
+      "ISIS default metric\n"
+      "Route map reference\n"
+      "Pointer to route-map entries\n")
+{
+  struct isis_area *area = vty->index;
+  int family;
+  int afi;
+  int type;
+  int level;
+  unsigned long metric;
+  const char *routemap;
+
+  if (argc < 5)
+    return CMD_WARNING;
+
+  family = str2family(argv[0]);
+  if (family < 0)
+    return CMD_WARNING;
+
+  afi = family2afi(family);
+  if (!afi)
+    return CMD_WARNING;
+
+  type = proto_redistnum(afi, argv[1]);
+  if (type < 0 || type == ZEBRA_ROUTE_ISIS)
+    return CMD_WARNING;
+
+  if (!strcmp("level-1", argv[2]))
+    level = 1;
+  else if (!strcmp("level-2", argv[2]))
+    level = 2;
+  else
+    return CMD_WARNING;
+
+  if ((area->is_type & level) != level)
+    {
+      vty_out(vty, "Node is not a level-%d IS%s", level, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  if (argv[3])
+    {
+      char *endp;
+      metric = strtoul(argv[3], &endp, 10);
+      if (argv[3][0] == '\0' || *endp != '\0')
+        return CMD_WARNING;
+    }
+  else
+    {
+      metric = 0xffffffff;
+    }
+
+  routemap = argv[4];
+
+  isis_redist_set(area, level, family, type, metric, routemap, 0);
+  return 0;
+}
+
+DEFUN(no_isis_redistribute,
+      no_isis_redistribute_cmd,
+      "no redistribute (ipv4|ipv6) " QUAGGA_REDIST_STR_ISISD
+      " (level-1|level-2)",
+      NO_STR
+      REDIST_STR
+      "Redistribute IPv4 routes\n"
+      "Redistribute IPv6 routes\n"
+      QUAGGA_REDIST_HELP_STR_ISISD
+      "Redistribute into level-1\n"
+      "Redistribute into level-2\n")
+{
+  struct isis_area *area = vty->index;
+  int type;
+  int level;
+  int family;
+  int afi;
+
+  if (argc < 3)
+    return CMD_WARNING;
+
+  family = str2family(argv[0]);
+  if (family < 0)
+    return CMD_WARNING;
+
+  afi = family2afi(family);
+  if (!afi)
+    return CMD_WARNING;
+
+  type = proto_redistnum(afi, argv[1]);
+  if (type < 0 || type == ZEBRA_ROUTE_ISIS)
+    return CMD_WARNING;
+
+  if (!strcmp("level-1", argv[2]))
+    level = 1;
+  else if (!strcmp("level-2", argv[2]))
+    level = 2;
+  else
+    return CMD_WARNING;
+
+  isis_redist_unset(area, level, family, type);
+  return 0;
+}
+
+DEFUN(isis_default_originate,
+      isis_default_originate_cmd,
+      "default-information originate (ipv4|ipv6) (level-1|level-2) "
+        "{always|metric <0-16777215>|route-map WORD}",
+      "Control distribution of default information\n"
+      "Distribute a default route\n"
+      "Distribute default route for IPv4\n"
+      "Distribute default route for IPv6\n"
+      "Distribute default route into level-1\n"
+      "Distribute default route into level-2\n"
+      "Always advertise default route\n"
+      "Metric for default route\n"
+      "ISIS default metric\n"
+      "Route map reference\n"
+      "Pointer to route-map entries\n")
+{
+  struct isis_area *area = vty->index;
+  int family;
+  int originate_type;
+  int level;
+  unsigned long metric;
+  const char *routemap;
+
+  if (argc < 5)
+    return CMD_WARNING;
+
+  family = str2family(argv[0]);
+  if (family < 0)
+    return CMD_WARNING;
+
+  if (!strcmp("level-1", argv[1]))
+    level = 1;
+  else if (!strcmp("level-2", argv[1]))
+    level = 2;
+  else
+    return CMD_WARNING;
+
+  if ((area->is_type & level) != level)
+    {
+      vty_out(vty, "Node is not a level-%d IS%s", level, VTY_NEWLINE);
+      return CMD_WARNING;
+    }
+
+  if (argv[2] && *argv[2] != '\0')
+    originate_type = DEFAULT_ORIGINATE_ALWAYS;
+  else
+    originate_type = DEFAULT_ORIGINATE;
+
+  if (family == AF_INET6 && originate_type != DEFAULT_ORIGINATE_ALWAYS)
+    {
+      vty_out(vty, "Zebra doesn't implement default-originate for IPv6 yet%s", VTY_NEWLINE);
+      vty_out(vty, "so use with care or use default-originate always.%s", VTY_NEWLINE);
+    }
+
+  if (argv[3])
+    {
+      char *endp;
+      metric = strtoul(argv[3], &endp, 10);
+      if (argv[3][0] == '\0' || *endp != '\0')
+        return CMD_WARNING;
+    }
+  else
+    {
+      metric = 0xffffffff;
+    }
+
+  routemap = argv[4];
+
+  isis_redist_set(area, level, family, DEFAULT_ROUTE, metric, routemap, originate_type);
+  return 0;
+}
+
+DEFUN(no_isis_default_originate,
+      no_isis_default_originate_cmd,
+      "no default-information originate (ipv4|ipv6) (level-1|level-2)",
+      NO_STR
+      "Control distribution of default information\n"
+      "Distribute a default route\n"
+      "Distribute default route for IPv4\n"
+      "Distribute default route for IPv6\n"
+      "Distribute default route into level-1\n"
+      "Distribute default route into level-2\n")
+{
+  struct isis_area *area = vty->index;
+
+  int family;
+  int level;
+
+  if (argc < 2)
+    return CMD_WARNING;
+
+  family = str2family(argv[0]);
+  if (family < 0)
+    return CMD_WARNING;
+
+  if (!strcmp("level-1", argv[1]))
+    level = 1;
+  else if (!strcmp("level-2", argv[1]))
+    level = 2;
+  else
+    return CMD_WARNING;
+
+  isis_redist_unset(area, level, family, DEFAULT_ROUTE);
+  return 0;
+}
+
+int
+isis_redist_config_write(struct vty *vty, struct isis_area *area,
+                         int family)
+{
+  int type;
+  int level;
+  int write = 0;
+  struct isis_redist *redist;
+  const char *family_str;
+
+  if (family == AF_INET)
+    family_str = "ipv4";
+  else if (family == AF_INET6)
+    family_str = "ipv6";
+  else
+    return 0;
+
+  for (type = 0; type < ZEBRA_ROUTE_MAX; type++)
+    {
+      if (type == ZEBRA_ROUTE_ISIS)
+        continue;
+
+      for (level = 1; level <= ISIS_LEVELS; level++)
+        {
+          redist = get_redist_settings(area, family, type, level);
+          if (!redist->redist)
+            continue;
+          vty_out(vty, " redistribute %s %s level-%d",
+                  family_str, zebra_route_string(type), level);
+          if (redist->metric != 0xffffffff)
+            vty_out(vty, " metric %u", redist->metric);
+          if (redist->map_name)
+            vty_out(vty, " route-map %s", redist->map_name);
+          vty_out(vty, "%s", VTY_NEWLINE);
+          write++;
+        }
+    }
+
+  for (level = 1; level <= ISIS_LEVELS; level++)
+    {
+      redist = get_redist_settings(area, family, DEFAULT_ROUTE, level);
+      if (!redist->redist)
+        continue;
+      vty_out(vty, " default-information originate %s level-%d",
+              family_str, level);
+      if (redist->redist == DEFAULT_ORIGINATE_ALWAYS)
+        vty_out(vty, " always");
+      if (redist->metric != 0xffffffff)
+        vty_out(vty, " metric %u", redist->metric);
+      if (redist->map_name)
+        vty_out(vty, " route-map %s", redist->map_name);
+      vty_out(vty, "%s", VTY_NEWLINE);
+      write++;
+    }
+
+  return write;
+}
+
+void
+isis_redist_init(void)
+{
+  install_element(ISIS_NODE, &isis_redistribute_cmd);
+  install_element(ISIS_NODE, &no_isis_redistribute_cmd);
+  install_element(ISIS_NODE, &isis_default_originate_cmd);
+  install_element(ISIS_NODE, &no_isis_default_originate_cmd);
+}
diff --git a/isisd/isis_redist.h b/isisd/isis_redist.h
new file mode 100644
index 0000000..2184620
--- /dev/null
+++ b/isisd/isis_redist.h
@@ -0,0 +1,58 @@
+/*
+ * IS-IS Rout(e)ing protocol - isis_redist.h
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ *
+ * This program 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 of the License, or (at your option) 
+ * any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc., 
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+
+#ifndef ISIS_REDIST_H
+#define ISIS_REDIST_H
+
+#define REDIST_PROTOCOL_COUNT 2
+
+#define DEFAULT_ROUTE ZEBRA_ROUTE_MAX
+#define DEFAULT_ORIGINATE 1
+#define DEFAULT_ORIGINATE_ALWAYS 2
+
+struct isis_ext_info
+{
+  int origin;
+  uint32_t metric;
+  u_char distance;
+};
+
+struct isis_redist
+{
+  int redist;
+  uint32_t metric;
+  char *map_name;
+  struct route_map *map;
+};
+
+struct isis_area;
+struct prefix;
+
+struct route_table *get_ext_reach(struct isis_area *area,
+                                  int family, int level);
+void isis_redist_add(int type, struct prefix *p,
+                     u_char distance, uint32_t metric);
+void isis_redist_delete(int type, struct prefix *p);
+int isis_redist_config_write(struct vty *vty, struct isis_area *area,
+                             int family);
+void isis_redist_init(void);
+void isis_redist_area_finish(struct isis_area *area);
+
+#endif
diff --git a/isisd/isis_routemap.c b/isisd/isis_routemap.c
index 84a14ac..3e0ab04 100644
--- a/isisd/isis_routemap.c
+++ b/isisd/isis_routemap.c
@@ -1,13 +1,10 @@
 /*
- * IS-IS Rout(e)ing protocol               - isis_routemap.c
+ * IS-IS Rout(e)ing protocol - isis_routemap.c
  *
- * Copyright (C) 2001,2002   Sampo Saaristo
- *                           Tampere University of Technology      
- *                           Institute of Communications Engineering
- *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
  *
  * This program is free software; you can redistribute it and/or modify it 
- * under the terms of the GNU General Public Licenseas published by the Free 
+ * under the terms of the GNU General Public License as published by the Free 
  * Software Foundation; either version 2 of the License, or (at your option) 
  * any later version.
  *
@@ -20,18 +17,22 @@
  * with this program; if not, write to the Free Software Foundation, Inc., 
  * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  */
+
 #include <zebra.h>
 
-#include "thread.h"
+#include "command.h"
+#include "filter.h"
+#include "hash.h"
+#include "if.h"
 #include "linklist.h"
-#include "vty.h"
 #include "log.h"
 #include "memory.h"
 #include "prefix.h"
-#include "hash.h"
-#include "if.h"
-#include "table.h"
+#include "plist.h"
 #include "routemap.h"
+#include "table.h"
+#include "thread.h"
+#include "vty.h"
 
 #include "isis_constants.h"
 #include "isis_common.h"
@@ -47,60 +48,518 @@
 #include "isis_spf.h"
 #include "isis_route.h"
 #include "isis_zebra.h"
+#include "isis_routemap.h"
 
-extern struct isis *isis;
-
-/*
- * Prototypes.
- */
-void isis_route_map_upd(const char *);
-void isis_route_map_event(route_map_event_t, const char *);
-void isis_route_map_init(void);
-
-
-void
-isis_route_map_upd (const char *name)
+static route_map_result_t
+route_match_ip_address(void *rule, struct prefix *prefix,
+                       route_map_object_t type, void *object)
 {
-  int i = 0;
+  struct access_list *alist;
 
-  if (!isis)
-    return;
+  if (type != RMAP_ISIS)
+    return RMAP_NOMATCH;
 
-  for (i = 0; i <= ZEBRA_ROUTE_MAX; i++)
-    {
-      if (isis->rmap[i].name)
-	isis->rmap[i].map = route_map_lookup_by_name (isis->rmap[i].name);
-      else
-	isis->rmap[i].map = NULL;
-    }
-  /* FIXME: do the address family sub-mode AF_INET6 here ? */
+  alist = access_list_lookup(AFI_IP, (char*)rule);
+  if (access_list_apply(alist, prefix) != FILTER_DENY)
+    return RMAP_MATCH;
+
+  return RMAP_NOMATCH;
 }
 
-void
-isis_route_map_event (route_map_event_t event, const char *name)
+static void *
+route_match_ip_address_compile(const char *arg)
 {
-  int type;
-
-  if (!isis)
-    return;
-
-  for (type = 0; type <= ZEBRA_ROUTE_MAX; type++)
-    {
-      if (isis->rmap[type].name && isis->rmap[type].map &&
-	  !strcmp (isis->rmap[type].name, name))
-	{
-	  isis_distribute_list_update (type);
-	}
-    }
+  return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
 }
 
-void
-isis_route_map_init (void)
+static void
+route_match_ip_address_free(void *rule)
 {
-  route_map_init ();
-  route_map_init_vty ();
+  XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
 
-  route_map_add_hook (isis_route_map_upd);
-  route_map_delete_hook (isis_route_map_upd);
-  route_map_event_hook (isis_route_map_event);
+static struct route_map_rule_cmd route_match_ip_address_cmd =
+{
+  "ip address",
+  route_match_ip_address,
+  route_match_ip_address_compile,
+  route_match_ip_address_free
+};
+
+/* ------------------------------------------------------------*/
+
+static route_map_result_t
+route_match_ip_address_prefix_list(void *rule, struct prefix *prefix,
+                                   route_map_object_t type, void *object)
+{
+  struct prefix_list *plist;
+
+  if (type != RMAP_ISIS)
+    return RMAP_NOMATCH;
+
+  plist = prefix_list_lookup(AFI_IP, (char*)rule);
+  if (prefix_list_apply(plist, prefix) != PREFIX_DENY)
+    return RMAP_MATCH;
+
+  return RMAP_NOMATCH;
+}
+
+static void *
+route_match_ip_address_prefix_list_compile(const char *arg)
+{
+  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void
+route_match_ip_address_prefix_list_free (void *rule)
+{
+  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+struct route_map_rule_cmd route_match_ip_address_prefix_list_cmd =
+{
+  "ip address prefix-list",
+  route_match_ip_address_prefix_list,
+  route_match_ip_address_prefix_list_compile,
+  route_match_ip_address_prefix_list_free
+};
+
+/* ------------------------------------------------------------*/
+
+static route_map_result_t
+route_match_ipv6_address(void *rule, struct prefix *prefix,
+                         route_map_object_t type, void *object)
+{
+  struct access_list *alist;
+
+  if (type != RMAP_ISIS)
+    return RMAP_NOMATCH;
+
+  alist = access_list_lookup(AFI_IP6, (char*)rule);
+  if (access_list_apply(alist, prefix) != FILTER_DENY)
+    return RMAP_MATCH;
+
+  return RMAP_NOMATCH;
+}
+
+static void *
+route_match_ipv6_address_compile(const char *arg)
+{
+  return XSTRDUP(MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void
+route_match_ipv6_address_free(void *rule)
+{
+  XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static struct route_map_rule_cmd route_match_ipv6_address_cmd =
+{
+  "ipv6 address",
+  route_match_ipv6_address,
+  route_match_ipv6_address_compile,
+  route_match_ipv6_address_free
+};
+
+/* ------------------------------------------------------------*/
+
+static route_map_result_t
+route_match_ipv6_address_prefix_list(void *rule, struct prefix *prefix,
+                                     route_map_object_t type, void *object)
+{
+  struct prefix_list *plist;
+
+  if (type != RMAP_ISIS)
+    return RMAP_NOMATCH;
+
+  plist = prefix_list_lookup(AFI_IP6, (char*)rule);
+  if (prefix_list_apply(plist, prefix) != PREFIX_DENY)
+    return RMAP_MATCH;
+
+  return RMAP_NOMATCH;
+}
+
+static void *
+route_match_ipv6_address_prefix_list_compile(const char *arg)
+{
+  return XSTRDUP (MTYPE_ROUTE_MAP_COMPILED, arg);
+}
+
+static void
+route_match_ipv6_address_prefix_list_free (void *rule)
+{
+  XFREE (MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+struct route_map_rule_cmd route_match_ipv6_address_prefix_list_cmd =
+{
+  "ipv6 address prefix-list",
+  route_match_ipv6_address_prefix_list,
+  route_match_ipv6_address_prefix_list_compile,
+  route_match_ipv6_address_prefix_list_free
+};
+
+/* ------------------------------------------------------------*/
+
+static route_map_result_t
+route_set_metric(void *rule, struct prefix *prefix,
+                 route_map_object_t type, void *object)
+{
+  uint32_t *metric;
+  struct isis_ext_info *info;
+
+  if (type == RMAP_ISIS)
+    {
+      metric = rule;
+      info = object;
+
+      info->metric = *metric;
+    }
+  return RMAP_OKAY;
+}
+
+static void *
+route_set_metric_compile(const char *arg)
+{
+  unsigned long metric;
+  char *endp;
+  uint32_t *ret;
+
+  metric = strtoul(arg, &endp, 10);
+  if (arg[0] == '\0' || *endp != '\0' || metric > MAX_WIDE_PATH_METRIC)
+    return NULL;
+
+  ret = XCALLOC(MTYPE_ROUTE_MAP_COMPILED, sizeof(ret));
+  *ret = metric;
+
+  return ret;
+}
+
+static void
+route_set_metric_free(void *rule)
+{
+  XFREE(MTYPE_ROUTE_MAP_COMPILED, rule);
+}
+
+static struct route_map_rule_cmd route_set_metric_cmd =
+{
+  "metric",
+  route_set_metric,
+  route_set_metric_compile,
+  route_set_metric_free
+};
+
+/* ------------------------------------------------------------*/
+
+static int
+isis_route_match_add(struct vty *vty, struct route_map_index *index,
+                      const char *command, const char *arg)
+{
+  int ret;
+
+  ret = route_map_add_match (index, command, arg);
+  if (ret)
+    {
+      switch (ret)
+        {
+        case RMAP_RULE_MISSING:
+          vty_out (vty, "%% Can't find rule.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        case RMAP_COMPILE_ERROR:
+          vty_out (vty, "%% Argument is malformed.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        }
+    }
+  return CMD_SUCCESS;
+}
+
+static int
+isis_route_match_delete(struct vty *vty, struct route_map_index *index,
+                        const char *command, const char *arg)
+{
+  int ret;
+
+  ret = route_map_delete_match (index, command, arg);
+  if (ret)
+    {
+      switch (ret)
+        {
+        case RMAP_RULE_MISSING:
+          vty_out (vty, "%% Can't find rule.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        case RMAP_COMPILE_ERROR:
+          vty_out (vty, "%% Argument is malformed.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        }
+    }
+  return CMD_SUCCESS;
+}
+
+static int
+isis_route_set_add(struct vty *vty, struct route_map_index *index,
+                   const char *command, const char *arg)
+{
+  int ret;
+
+  ret = route_map_add_set(index, command, arg);
+  if (ret)
+    {
+      switch (ret)
+        {
+        case RMAP_RULE_MISSING:
+          vty_out (vty, "%% Can't find rule.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        case RMAP_COMPILE_ERROR:
+          vty_out (vty, "%% Argument is malformed.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        }
+    }
+
+  return CMD_SUCCESS;
+}
+
+static int
+isis_route_set_delete (struct vty *vty, struct route_map_index *index,
+		       const char *command, const char *arg)
+{
+  int ret;
+
+  ret = route_map_delete_set (index, command, arg);
+  if (ret)
+    {
+      switch (ret)
+        {
+        case RMAP_RULE_MISSING:
+          vty_out (vty, "%% Can't find rule.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        case RMAP_COMPILE_ERROR:
+          vty_out (vty, "%% Argument is malformed.%s", VTY_NEWLINE);
+          return CMD_WARNING;
+        }
+    }
+
+  return CMD_SUCCESS;
+}
+
+/* ------------------------------------------------------------*/
+
+DEFUN(match_ip_address,
+      match_ip_address_cmd,
+      "match ip address (<1-199>|<1300-2699>|WORD)",
+      MATCH_STR
+      IP_STR
+      "Match address of route\n"
+      "IP access-list number\n"
+      "IP access-list number (expanded range)\n"
+      "IP Access-list name\n")
+{
+  return isis_route_match_add(vty, vty->index, "ip address", argv[0]);
+}
+
+DEFUN(no_match_ip_address,
+      no_match_ip_address_val_cmd,
+      "no match ip address (<1-199>|<1300-2699>|WORD)",
+      NO_STR
+      MATCH_STR
+      IP_STR
+      "Match address of route\n"
+      "IP access-list number\n"
+      "IP access-list number (expanded range)\n"
+      "IP Access-list name\n")
+{
+  if (argc == 0)
+    return isis_route_match_delete(vty, vty->index, "ip address", NULL);
+  return isis_route_match_delete(vty, vty->index, "ip address", argv[0]);
+}
+
+ALIAS(no_match_ip_address,
+      no_match_ip_address_cmd,
+      "no match ip address",
+      NO_STR
+      MATCH_STR
+      IP_STR
+      "Match address of route\n"
+);
+
+/* ------------------------------------------------------------*/
+
+DEFUN(match_ip_address_prefix_list,
+      match_ip_address_prefix_list_cmd,
+      "match ip address prefix-list WORD",
+      MATCH_STR
+      IP_STR
+      "Match address of route\n"
+      "Match entries of prefix-lists\n"
+      "IP prefix-list name\n")
+{
+  return isis_route_match_add(vty, vty->index, "ip address prefix-list", argv[0]);
+}
+
+DEFUN(no_match_ip_address_prefix_list,
+      no_match_ip_address_prefix_list_cmd,
+      "no match ip address prefix-list",
+      NO_STR
+      MATCH_STR
+      IP_STR
+      "Match address of route\n"
+      "Match entries of prefix-lists\n")
+{
+  if (argc == 0)
+    return isis_route_match_delete (vty, vty->index, "ip address prefix-list", NULL);
+  return isis_route_match_delete (vty, vty->index, "ip address prefix-list", argv[0]);
+}
+
+ALIAS(no_match_ip_address_prefix_list,
+      no_match_ip_address_prefix_list_val_cmd,
+      "no match ip address prefix-list WORD",
+      NO_STR
+      MATCH_STR
+      IP_STR
+      "Match address of route\n"
+      "Match entries of prefix-lists\n"
+      "IP prefix-list name\n"
+);
+
+/* ------------------------------------------------------------*/
+
+DEFUN(match_ipv6_address,
+      match_ipv6_address_cmd,
+      "match ipv6 address WORD",
+      MATCH_STR
+      IPV6_STR
+      "Match IPv6 address of route\n"
+      "IPv6 access-list name\n")
+{
+  return isis_route_match_add(vty, vty->index, "ipv6 address", argv[0]);
+}
+
+DEFUN(no_match_ipv6_address,
+      no_match_ipv6_address_val_cmd,
+      "no match ipv6 address WORD",
+      NO_STR
+      MATCH_STR
+      IPV6_STR
+      "Match IPv6 address of route\n"
+      "IPv6 access-list name\n")
+{
+  if (argc == 0)
+    return isis_route_match_delete(vty, vty->index, "ipv6 address", NULL);
+  return isis_route_match_delete(vty, vty->index, "ipv6 address", argv[0]);
+}
+
+ALIAS(no_match_ipv6_address,
+      no_match_ipv6_address_cmd,
+      "no match ipv6 address",
+      NO_STR
+      MATCH_STR
+      IPV6_STR
+      "Match IPv6 address of route\n"
+);
+
+/* ------------------------------------------------------------*/
+
+DEFUN(match_ipv6_address_prefix_list,
+      match_ipv6_address_prefix_list_cmd,
+      "match ipv6 address prefix-list WORD",
+      MATCH_STR
+      IPV6_STR
+      "Match address of route\n"
+      "Match entries of prefix-lists\n"
+      "IP prefix-list name\n")
+{
+  return isis_route_match_add(vty, vty->index, "ipv6 address prefix-list", argv[0]);
+}
+
+DEFUN(no_match_ipv6_address_prefix_list,
+      no_match_ipv6_address_prefix_list_cmd,
+      "no match ipv6 address prefix-list",
+      NO_STR
+      MATCH_STR
+      IPV6_STR
+      "Match address of route\n"
+      "Match entries of prefix-lists\n")
+{
+  if (argc == 0)
+    return isis_route_match_delete (vty, vty->index, "ipv6 address prefix-list", NULL);
+  return isis_route_match_delete (vty, vty->index, "ipv6 address prefix-list", argv[0]);
+}
+
+ALIAS(no_match_ipv6_address_prefix_list,
+      no_match_ipv6_address_prefix_list_val_cmd,
+      "no match ipv6 address prefix-list WORD",
+      NO_STR
+      MATCH_STR
+      IPV6_STR
+      "Match address of route\n"
+      "Match entries of prefix-lists\n"
+      "IP prefix-list name\n"
+);
+
+/* ------------------------------------------------------------*/
+
+/* set metric already exists e.g. in the ospf routemap. vtysh doesn't cope well with different
+ * commands at the same node, therefore add set metric with the same 32-bit range as ospf and
+ * verify that the input is a valid isis metric */
+DEFUN(set_metric,
+      set_metric_cmd,
+      "set metric <0-4294967295>",
+      SET_STR
+      "Metric vale for destination routing protocol\n"
+      "Metric value\n")
+{
+  return isis_route_set_add(vty, vty->index, "metric", argv[0]);
+}
+
+DEFUN(no_set_metric,
+      no_set_metric_val_cmd,
+      "no set metric <0-4294967295>",
+      NO_STR
+      SET_STR
+      "Metric value for destination routing protocol\n"
+      "Metric value\n")
+{
+  if (argc == 0)
+    return isis_route_set_delete(vty, vty->index, "metric", NULL);
+  return isis_route_set_delete(vty, vty->index, "metric", argv[0]);
+}
+
+ALIAS(no_set_metric,
+      no_set_metric_cmd,
+      "no set metric",
+      NO_STR
+      SET_STR
+      "Metric vale for destination routing protocol\n"
+);
+
+void
+isis_route_map_init(void)
+{
+  route_map_init();
+  route_map_init_vty();
+
+  route_map_install_match(&route_match_ip_address_cmd);
+  install_element(RMAP_NODE, &match_ip_address_cmd);
+  install_element(RMAP_NODE, &no_match_ip_address_val_cmd);
+  install_element(RMAP_NODE, &no_match_ip_address_cmd);
+
+  route_map_install_match(&route_match_ip_address_prefix_list_cmd);
+  install_element(RMAP_NODE, &match_ip_address_prefix_list_cmd);
+  install_element(RMAP_NODE, &no_match_ip_address_prefix_list_val_cmd);
+  install_element(RMAP_NODE, &no_match_ip_address_prefix_list_cmd);
+
+  route_map_install_match(&route_match_ipv6_address_cmd);
+  install_element(RMAP_NODE, &match_ipv6_address_cmd);
+  install_element(RMAP_NODE, &no_match_ipv6_address_val_cmd);
+  install_element(RMAP_NODE, &no_match_ipv6_address_cmd);
+
+  route_map_install_match(&route_match_ipv6_address_prefix_list_cmd);
+  install_element(RMAP_NODE, &match_ipv6_address_prefix_list_cmd);
+  install_element(RMAP_NODE, &no_match_ipv6_address_prefix_list_val_cmd);
+  install_element(RMAP_NODE, &no_match_ipv6_address_prefix_list_cmd);
+
+  route_map_install_set(&route_set_metric_cmd);
+  install_element(RMAP_NODE, &set_metric_cmd);
+  install_element(RMAP_NODE, &no_set_metric_val_cmd);
+  install_element(RMAP_NODE, &no_set_metric_cmd);
 }
diff --git a/isisd/isis_routemap.h b/isisd/isis_routemap.h
new file mode 100644
index 0000000..1cb063f
--- /dev/null
+++ b/isisd/isis_routemap.h
@@ -0,0 +1,25 @@
+/*
+ * IS-IS Rout(e)ing protocol - isis_routemap.h
+ *
+ * Copyright (C) 2013-2015 Christian Franke <chris@opensourcerouting.org>
+ *
+ * This program 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 of the License, or (at your option) 
+ * any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software Foundation, Inc., 
+ * 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ */
+#ifndef ISIS_ROUTEMAP_H
+#define ISIS_ROUTEMAP_H
+
+void isis_route_map_init(void);
+
+#endif
diff --git a/isisd/isis_tlv.c b/isisd/isis_tlv.c
index 4fca072..8d4ea0e 100644
--- a/isisd/isis_tlv.c
+++ b/isisd/isis_tlv.c
@@ -993,8 +993,8 @@
   return add_tlv (LSP_ENTRIES, pos - value, value, stream);
 }
 
-int
-tlv_add_ipv4_reachs (struct list *ipv4_reachs, struct stream *stream)
+static int
+tlv_add_ipv4_reachs (u_char tag, struct list *ipv4_reachs, struct stream *stream)
 {
   struct listnode *node;
   struct ipv4_reachability *reach;
@@ -1007,7 +1007,7 @@
       if (pos - value + IPV4_REACH_LEN > 255)
 	{
 	  retval =
-	    add_tlv (IPV4_INT_REACHABILITY, pos - value, value, stream);
+	    add_tlv (tag, pos - value, value, stream);
 	  if (retval != ISIS_OK)
 	    return retval;
 	  pos = value;
@@ -1026,10 +1026,23 @@
       pos += IPV4_MAX_BYTELEN;
     }
 
-  return add_tlv (IPV4_INT_REACHABILITY, pos - value, value, stream);
+  return add_tlv (tag, pos - value, value, stream);
 }
 
 int
+tlv_add_ipv4_int_reachs (struct list *ipv4_reachs, struct stream *stream)
+{
+  return tlv_add_ipv4_reachs(IPV4_INT_REACHABILITY, ipv4_reachs, stream);
+}
+
+int
+tlv_add_ipv4_ext_reachs (struct list *ipv4_reachs, struct stream *stream)
+{
+  return tlv_add_ipv4_reachs(IPV4_EXT_REACHABILITY, ipv4_reachs, stream);
+}
+
+
+int
 tlv_add_te_ipv4_reachs (struct list *te_ipv4_reachs, struct stream *stream)
 {
   struct listnode *node;
diff --git a/isisd/isis_tlv.h b/isisd/isis_tlv.h
index f5b59a7..619003a 100644
--- a/isisd/isis_tlv.h
+++ b/isisd/isis_tlv.h
@@ -230,11 +230,13 @@
 
 /* bits in control_info */
 #define CTRL_INFO_DIRECTION    0x80
-#define DIRECTION_UP           0
-#define DIRECTION_DOWN         1
+#define DIRECTION_UP           0x00
+#define DIRECTION_DOWN         0x80
+
 #define CTRL_INFO_DISTRIBUTION 0x40
-#define DISTRIBUTION_INTERNAL  0
-#define DISTRIBUTION_EXTERNAL  1
+#define DISTRIBUTION_INTERNAL  0x00
+#define DISTRIBUTION_EXTERNAL  0x40
+
 #define CTRL_INFO_SUBTLVS      0x20
 #endif /* HAVE_IPV6 */
 
@@ -313,7 +315,8 @@
 int tlv_add_dynamic_hostname (struct hostname *hostname,
 			      struct stream *stream);
 int tlv_add_lsp_entries (struct list *lsps, struct stream *stream);
-int tlv_add_ipv4_reachs (struct list *ipv4_reachs, struct stream *stream);
+int tlv_add_ipv4_int_reachs (struct list *ipv4_reachs, struct stream *stream);
+int tlv_add_ipv4_ext_reachs (struct list *ipv4_reachs, struct stream *stream);
 int tlv_add_te_ipv4_reachs (struct list *te_ipv4_reachs, struct stream *stream);
 #ifdef HAVE_IPV6
 int tlv_add_ipv6_addrs (struct list *ipv6_addrs, struct stream *stream);
diff --git a/isisd/isis_zebra.c b/isisd/isis_zebra.c
index 1201627..8c4eef0 100644
--- a/isisd/isis_zebra.c
+++ b/isisd/isis_zebra.c
@@ -4,6 +4,7 @@
  * Copyright (C) 2001,2002   Sampo Saaristo
  *                           Tampere University of Technology      
  *                           Institute of Communications Engineering
+ * Copyright (C) 2013-2015   Christian Franke <chris@opensourcerouting.org>
  *
  * This program is free software; you can redistribute it and/or modify it 
  * under the terms of the GNU General Public Licenseas published by the Free 
@@ -525,11 +526,14 @@
   struct stream *stream;
   struct zapi_ipv4 api;
   struct prefix_ipv4 p;
+  struct prefix *p_generic = (struct prefix*)&p;
   unsigned long ifindex __attribute__ ((unused));
   struct in_addr nexthop __attribute__ ((unused));
 
   stream = zclient->ibuf;
+  memset(&api, 0, sizeof(api));
   memset (&p, 0, sizeof (struct prefix_ipv4));
+  memset(&nexthop, 0, sizeof(nexthop));
   ifindex = 0;
 
   api.type = stream_getc (stream);
@@ -554,31 +558,80 @@
     api.distance = stream_getc (stream);
   if (CHECK_FLAG (api.message, ZAPI_MESSAGE_METRIC))
     api.metric = stream_getl (stream);
-  else
-    api.metric = 0;
+
+  /*
+   * Avoid advertising a false default reachability. (A default
+   * route installed by IS-IS gets redistributed from zebra back
+   * into IS-IS causing us to start advertising default reachabity
+   * without this check)
+   */
+  if (p.prefixlen == 0 && api.type == ZEBRA_ROUTE_ISIS)
+    command = ZEBRA_IPV4_ROUTE_DELETE;
 
   if (command == ZEBRA_IPV4_ROUTE_ADD)
-    {
-      if (isis->debugs & DEBUG_ZEBRA)
-	zlog_debug ("IPv4 Route add from Z");
-    }
+    isis_redist_add(api.type, p_generic, api.distance, api.metric);
+  else
+    isis_redist_delete(api.type, p_generic);
 
   return 0;
 }
 
-#ifdef HAVE_IPV6
 static int
 isis_zebra_read_ipv6 (int command, struct zclient *zclient,
 		      zebra_size_t length, vrf_id_t vrf_id)
 {
+  struct stream *stream;
+  struct zapi_ipv6 api;
+  struct prefix_ipv6 p;
+  struct prefix *p_generic = (struct prefix*)&p;
+  struct in6_addr nexthop;
+  unsigned long ifindex __attribute__((unused));
+
+  stream = zclient->ibuf;
+  memset(&api, 0, sizeof(api));
+  memset(&p, 0, sizeof(struct prefix_ipv6));
+  memset(&nexthop, 0, sizeof(nexthop));
+  ifindex = 0;
+
+  api.type = stream_getc(stream);
+  api.flags = stream_getc(stream);
+  api.message = stream_getc(stream);
+
+  p.family = AF_INET6;
+  p.prefixlen = stream_getc(stream);
+  stream_get(&p.prefix, stream, PSIZE(p.prefixlen));
+
+  if (CHECK_FLAG(api.message, ZAPI_MESSAGE_NEXTHOP))
+    {
+      api.nexthop_num = stream_getc(stream); /* this is always 1 */
+      stream_get(&nexthop, stream, sizeof(nexthop));
+    }
+  if (CHECK_FLAG(api.message, ZAPI_MESSAGE_IFINDEX))
+    {
+      api.ifindex_num = stream_getc(stream);
+      ifindex = stream_getl(stream);
+    }
+  if (CHECK_FLAG(api.message, ZAPI_MESSAGE_DISTANCE))
+    api.distance = stream_getc(stream);
+  if (CHECK_FLAG(api.message, ZAPI_MESSAGE_METRIC))
+    api.metric = stream_getl(stream);
+
+  /*
+   * Avoid advertising a false default reachability. (A default
+   * route installed by IS-IS gets redistributed from zebra back
+   * into IS-IS causing us to start advertising default reachabity
+   * without this check)
+   */
+  if (p.prefixlen == 0 && api.type == ZEBRA_ROUTE_ISIS)
+    command = ZEBRA_IPV6_ROUTE_DELETE;
+
+  if (command == ZEBRA_IPV6_ROUTE_ADD)
+    isis_redist_add(api.type, p_generic, api.distance, api.metric);
+  else
+    isis_redist_delete(api.type, p_generic);
+
   return 0;
 }
-#endif
-
-#define ISIS_TYPE_IS_REDISTRIBUTED(T) \
-T == ZEBRA_ROUTE_MAX ? \
-  vrf_bitmap_check (zclient->default_information, VRF_DEFAULT) : \
-  vrf_bitmap_check (zclient->redist[type], VRF_DEFAULT)
 
 int
 isis_distribute_list_update (int routetype)
@@ -586,14 +639,23 @@
   return 0;
 }
 
-#if 0 /* Not yet. */
-static int
-isis_redistribute_default_set (int routetype, int metric_type,
-			       int metric_value)
+void
+isis_zebra_redistribute_set(int type)
 {
-  return 0;
+  if (type == DEFAULT_ROUTE)
+    zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_ADD, zclient, VRF_DEFAULT);
+  else
+    zclient_redistribute(ZEBRA_REDISTRIBUTE_ADD, zclient, type, VRF_DEFAULT);
 }
-#endif /* 0 */
+
+void
+isis_zebra_redistribute_unset(int type)
+{
+  if (type == DEFAULT_ROUTE)
+    zclient_redistribute_default(ZEBRA_REDISTRIBUTE_DEFAULT_DELETE, zclient, VRF_DEFAULT);
+  else
+    zclient_redistribute(ZEBRA_REDISTRIBUTE_DELETE, zclient, type, VRF_DEFAULT);
+}
 
 static void
 isis_zebra_connected (struct zclient *zclient)
diff --git a/isisd/isis_zebra.h b/isisd/isis_zebra.h
index 0011478..dd36d7d 100644
--- a/isisd/isis_zebra.h
+++ b/isisd/isis_zebra.h
@@ -28,5 +28,7 @@
 void isis_zebra_route_update (struct prefix *prefix,
 			      struct isis_route_info *route_info);
 int isis_distribute_list_update (int routetype);
+void isis_zebra_redistribute_set(int type);
+void isis_zebra_redistribute_unset(int type);
 
 #endif /* _ZEBRA_ISIS_ZEBRA_H */
diff --git a/isisd/isisd.c b/isisd/isisd.c
index 0a5286e..c446e7f 100644
--- a/isisd/isisd.c
+++ b/isisd/isisd.c
@@ -280,6 +280,8 @@
     }
 #endif /* HAVE_IPV6 */
 
+  isis_redist_area_finish(area);
+
   for (ALL_LIST_ELEMENTS (area->area_addrs, node, nnode, addr))
     {
       list_delete_node (area->area_addrs, node);
@@ -3049,6 +3051,8 @@
 	    vty_out (vty, " is-type level-2-only%s", VTY_NEWLINE);
 	    write++;
 	  }
+	write += isis_redist_config_write(vty, area, AF_INET);
+	write += isis_redist_config_write(vty, area, AF_INET6);
 	/* ISIS - Lsp generation interval */
 	if (area->lsp_gen_interval[0] == area->lsp_gen_interval[1])
 	  {
diff --git a/isisd/isisd.h b/isisd/isisd.h
index 96c3ba3..1a88efd 100644
--- a/isisd/isisd.h
+++ b/isisd/isisd.h
@@ -25,16 +25,12 @@
 
 #define ISISD_VERSION "0.0.7"
 
+#include "isisd/isis_redist.h"
+
 /* uncomment if you are a developer in bug hunt */
 /* #define EXTREME_DEBUG  */
 /* #define EXTREME_TLV_DEBUG */
 
-struct rmap
-{
-  char *name;
-  struct route_map *map;
-};
-
 struct isis
 {
   u_long process_id;
@@ -53,30 +49,7 @@
   time_t uptime;		/* when did we start */
   struct thread *t_dync_clean;	/* dynamic hostname cache cleanup thread */
 
-  /* Redistributed external information. */
-  struct route_table *external_info[ZEBRA_ROUTE_MAX + 1];
-  /* Redistribute metric info. */
-  struct
-  {
-    int type;			/* Internal or External  */
-    int value;			/* metric value */
-  } dmetric[ZEBRA_ROUTE_MAX + 1];
-
-  struct
-  {
-    char *name;
-    struct route_map *map;
-  } rmap[ZEBRA_ROUTE_MAX + 1];
-#ifdef HAVE_IPV6
-  struct
-  {
-    struct
-    {
-      char *name;
-      struct route_map *map;
-    } rmap[ZEBRA_ROUTE_MAX + 1];
-  } inet6_afmode;
-#endif
+  struct route_table *ext_info[REDIST_PROTOCOL_COUNT];
 };
 
 extern struct isis *isis;
@@ -145,6 +118,9 @@
 #endif				/* HAVE_IPV6 */
   /* Counters */
   u_int32_t circuit_state_changes;
+  struct isis_redist redist_settings[REDIST_PROTOCOL_COUNT]
+                                    [ZEBRA_ROUTE_MAX + 1][ISIS_LEVELS];
+  struct route_table *ext_reach[REDIST_PROTOCOL_COUNT][ISIS_LEVELS];
 
 #ifdef TOPOLOGY_GENERATE
   struct list *topology;
diff --git a/lib/prefix.c b/lib/prefix.c
index 63742f3..936e9fc 100644
--- a/lib/prefix.c
+++ b/lib/prefix.c
@@ -187,6 +187,17 @@
   return prefix_bit((const u_char *) &prefix->s6_addr, prefixlen);
 }
 
+int
+str2family(const char *string)
+{
+  if (!strcmp("ipv4", string))
+    return AF_INET;
+  else if (!strcmp("ipv6", string))
+    return AF_INET6;
+  else
+    return -1;
+}
+
 /* Address Famiy Identifier to Address Family converter. */
 int
 afi2family (afi_t afi)
diff --git a/lib/prefix.h b/lib/prefix.h
index bc8aebc..a517d79 100644
--- a/lib/prefix.h
+++ b/lib/prefix.h
@@ -170,6 +170,7 @@
 #endif /*s6_addr32*/
 
 /* Prototypes. */
+extern int str2family(const char *);
 extern int afi2family (afi_t);
 extern afi_t family2afi (int);
 
diff --git a/lib/routemap.h b/lib/routemap.h
index ba64553..2479c81 100644
--- a/lib/routemap.h
+++ b/lib/routemap.h
@@ -47,7 +47,8 @@
   RMAP_OSPF,
   RMAP_OSPF6,
   RMAP_BGP,
-  RMAP_ZEBRA
+  RMAP_ZEBRA,
+  RMAP_ISIS,
 } route_map_object_t;
 
 typedef enum
diff --git a/vtysh/extract.pl.in b/vtysh/extract.pl.in
index 63186d7..82532da 100755
--- a/vtysh/extract.pl.in
+++ b/vtysh/extract.pl.in
@@ -177,7 +177,7 @@
     }
 }
 
-my $bad_cli_stomps = 76;
+my $bad_cli_stomps = 89;
 # Currently we have $bad_cli_stomps.  This was determined by
 # running this script and counting up the collisions from what
 # was returned.