#!/usr/bin/awk -f
#
# Build a list of nodes and edges from
# cisco and Juniper router configs.
#
# This can be used for things like
# drawing maps and creating DNS zone
# files.
#
# $Id: mktop,v 1.1 2002/10/23 07:16:08 jabley Exp $

# initial state

BEGIN {
  UNKNOWN = 0;
  CISCO = 1;
  JUNOS = 2;
  config = UNKNOWN;

  DEBUG = 0;

# hacks regarding SONET/SDH naming
  sdh_name["oc3"] = "stm1";
  sdh_name["oc12"] = "stm4";
  sdh_name["oc48"] = "stm16";
  sdh_name["oc192"] = "stm64";
}


# functions used by both cisco and Juniper config handlers

# derive routername, routersite and routerregion from a router hostname
function namestuff(hostname) {
  nodeid++;
  nw = split(hostname, w, /\./);
  node_name[nodeid] = hostname;

# most people seem to have site, region and country information
# which can be extracted from the router hostname. The following,
# for example, will populate the node_site, node_region and
# node_country associative arrays from components of a hostname
# which looks like <something>.<site>.<country>, and where
# a region is derivable from a site name.
#
#  node_site[nodeid] = w[nw - 1];
#  node_region[nodeid] = w[nw - 1];
#  sub(/[0-9]+$/, "", node_region[nodeid]);
#  node_country[nodeid] = w[nw];
#
# the default behaviour here is to leave those fields blank.

  node_site[nodeid] = "";
  node_region[nodeid] = "";
  node_country[nodeid] = "";

  if (FILENAME == "-")
    node_rancidname[nodeid] = "";
  else
  {
    node_rancidname[nodeid] = FILENAME;
    sub(/^.*\//, "", node_rancidname[nodeid]);
  }
}

# add a router interface to a subnet
function addsubnet(key) {
  s = subnet[key]
  if (s)
    subnet[key] = s " " interfaceid;
  else
    subnet[key] = interfaceid;
  seen_ifaddr[ifaddr[interfaceid]]++;
} 

/RANCID-CONTENT-TYPE:/ {
  config = UNKNOWN;
  delete speed;
}


# cisco-specific patterns

/RANCID-CONTENT-TYPE: cisco/ {
  config = CISCO;
  next;
}

/^hostname / && (config == CISCO) {
  namestuff($2);
  next;
}

# here we identify a bunch of cisco parts in order to
# make a reasonable guess as to the "speed" of an interface
# later on.

# GSR junk

/^!Slot [0-9]+: [0-9]+ POS OC-192c/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $3; i++)
    speed["POS" $2 "/" i] = "oc192";
  next;
}

/^!Slot [0-9]+: [0-9]+ POS OC-48c/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $3; i++)
    speed["POS" $2 "/" i] = "oc48";
  next;
}

/^!Slot [0-9]+: [0-9]+ POS OC-12c/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $3; i++)
    speed["POS" $2 "/" i] = "oc12";
  next;
}

/^!Slot [0-9]+: [0-9]+ POS OC-3c/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $3; i++)
    speed["POS" $2 "/" i] = "oc3";
  next;
}

/^!Slot [0-9]+: [0-9] port ATM Over SONET OC-3c/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $3; i++)
    speed["ATM" $2 "/" i] = "oc3";
  next;
}

/^!Slot [0-9]: [0-9]+ Port Packet over DS3/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $3; i++)
    speed["Serial" $2 "/" i] = "ds3";
  next;
} 

/^!Slot [0-9]+: type Mx serial, [0-9] ports/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $6; i++)
    speed["Serial" $2 "/" i] = "ds1"; 
  next;
} 


# 7500 junk

/^!Slot [0-9]+\/VIP2 R5K\/PA [0-1]: type PA-POS[A-Z]+-SM/ && (config == CISCO) {
  sub(/\/VIP2/, "", $2);
  sub(/:/, "", $4);
  speed["POS" $2 "/" $4 "/0"] = "oc3";
  next;
}

/^!Slot [0-9]+\/VIP2 R5K\/PA [0-9]+: type PA-[A-Z0-9-]+-OC3[A-Z]+, [0-9]+ port/ && (config == CISCO) {
  sub(/\/VIP2/, "", $2);
  sub(/:/, "", $4);
  for (i = 0; i < $7; i++)
    speed["ATM" $2 "/" $4 "/" i] = "oc3";
  next;
}

/^!Slot [0-9]+\/VIP2 R5K\/PA [0-9]: type T3 Serial, [0-9] ports/ && (config == CISCO) {
  sub(/\/VIP2/, "", $2);
  sub(/:/, "", $4);
  for (i = 0; i < $8; i++)
    speed["Serial" $2 "/" $4 "/" i] = "ds3";
  next;
}

/^!Slot [0-9]+\/VIP2\/PA [0-9]: type T3 Serial, [0-9] ports/ && (config == CISCO) {
  sub(/\/VIP2\/PA/, "", $2);
  sub(/:/, "", $3); 
  for (i = 0; i < $7; i++)
    speed["Serial" $2 "/" $3 "/" i] = "ds3";
  next;
} 

/^!Slot [0-9]\/VIP2 R5K\/PA [0-9]: type Mx Serial, [0-9] ports/ && (config == CISCO) {
  sub(/\/VIP2/, "", $2);
  sub(/:/, "", $4); 
  for (i = 0; i < $8; i++)
    speed["Serial" $2 "/" $4 "/" i] = "ds1";
  next;
}


# 7200 junk

/^!Slot [0-9]+: type POS/ && (config == CISCO) {
  sub(/:/, "", $2);
  speed["POS" $2 "/0"] = "oc3";
  next;
} 
    
/^!Slot [0-9]+: type T3\+ PA, [0-9] ports/ && (config == CISCO) {
  sub(/:/, "", $2);
  for (i = 0; i < $6; i++)
    speed["Serial" $2 "/" i] = "ds3";
  next;
}


# 2600 junk

/^!Interface: Serial[0-9]\/[0-9].*Integrated FT1/ && (config == CISCO) {
  sub(/,/, "", $2);
  speed[$2] = "ds1";
  next;
}


# more interface speed hacks in here

/^interface / && (config == CISCO) {
  gsub(/:/, "-", $2);
  new_ifname = $2;
  new_ifnode = nodeid;

  if (speed[$2] == "") {
    if ($2 ~ /^FastEthernet/)
      new_ifspeed = "fe";
    else if ($2 ~ /^GigabitEthernet/)
      new_ifspeed = "ge";
    else if ($2 ~ /^Ethernet/)
      new_ifspeed = "eth";
    else if ($2 ~ /^Fddi/)
      new_ifspeed = "fddi";
    else if ($2 ~ /^Hssi/)
      new_ifspeed = "ds3";
    else if ($2 ~ /^Tunnel/)
      new_ifspeed = "tunnel";
    else
      new_ifspeed = "unknown";
  } else
    new_ifspeed = speed[$2];

  new_ifdesc = "";
  new_ifaddr = "";
  new_ifmask = "";

  inif = 1;
  disabled = 0;
  next;
}

/^ description / && (config == CISCO) && (inif == 1) {
  sub(/^ description /, "");
  new_ifdesc = $0;
  next;
}

# SDH hack
/^ pos framing sdh/ && (config == CISCO) && (inif == 1) {
  new_ifspeed = sdh_name[new_ifspeed];
  next;
}

/^!/ && (config == CISCO) && (inif == 1) {
  if (disabled == 0 && new_ifaddr != "") {
    interfaceid++;
    ifname[interfaceid] = new_ifname;
    ifnode[interfaceid] = new_ifnode;
    ifspeed[interfaceid] = new_ifspeed;
    ifdesc[interfaceid] = new_ifdesc;
  
    ifaddr[interfaceid] = new_ifaddr;
    split(new_ifmask, a, /\./);
    split(new_ifaddr, b, /\./);
    prefix = b[4] + 256*b[3] + 65536*b[2] + 16777216*b[1];
    modulus = 1 + (255 - a[4]) + 256*(255 - a[3]) + 65536*(255 - a[2]) + \
      16777216*(255 - a[1]);
    masklen = 32 - (log(modulus)/log(2));
    prefix = prefix - (prefix % modulus);
  
    key = int(prefix/16777216) "." int(prefix/65536) % 256 "." \
      int(prefix/256) % 256 "." prefix % 256 "/" masklen;

    addsubnet(key);
  }

  inif = 0;
  disabled = 0;

  next;
}

/^ shutdown/ && (config == CISCO) && (inif == 1) {
  disabled = 1;
  next;
}

/^ ip address [0-9\.]+ [0-9\.]+$/ && (config == CISCO) && (inif == 1) {
  new_ifaddr=$3;
  new_ifmask=$4;

  if ($3 == "208.184.102.29")
  next;
}


# juniper-specific patterns

/RANCID-CONTENT-TYPE: juniper/ {
  config = JUNOS;
  indent = 0;
}

# juniper POS interface types

# M160
/^# FPC [0-9]+ / && (config == JUNOS) {
  fpc = $3;
  next;
}

# M20
/^# Slot card [0-9]+/ && (config == JUNOS) {
  fpc = $4;
  next;
}

(/^# +PIC [0-9]+ .* OC-3 / || /^# +PIC [0-9]+ .* STM-1 / || \
 /^# +Port card [0-9]+ .* OC-3/ || /^# +Port card [0-9]+ .* STM-1 /) && \
 (config == JUNOS) {
  sub(/ card/, ""); /* hack hack */
  for (i = 0; i < 8; i++)
    speed["so-" fpc "/" $3 "/" i] = "oc3";
  next;
}

(/^# +PIC [0-9]+ .* OC-12 / || /^# +PIC [0-9]+ .* STM-4 / || \
 /^# +Port card [0-9]+ .* OC-12/ || /^# +Port card [0-9]+ .* STM-4 /) && \
 (config == JUNOS) {
  sub(/ card/, ""); /* hack hack */
  for (i = 0; i < 8; i++)
    speed["so-" fpc "/" $3 "/" i] = "oc12";
  next;
}

(/^# +PIC [0-9]+ .* OC-48 / || /^# +PIC [0-9]+ .* STM-16 / || \
 /^# +Port card [0-9]+ .* OC-48/ || /^# +Port card [0-9]+ .* STM-16 /) && \
 (config == JUNOS) {
  sub(/ card/, ""); /* hack hack */
  for (i = 0; i < 8; i++)
    speed["so-" fpc "/" $3 "/" i] = "oc48";
  next;
}

(/^# +PIC [0-9]+ .* OC-192 / || /^# +PIC [0-9]+ .* STM-64 / || \
 /^# +Port card [0-9]+ .* OC-192/ || /^# +Port card [0-9]+ .* STM-64 /) && \
  (config == JUNOS) {
  sub(/ card/, ""); /* hack hack */
  for (i = 0; i < 8; i++)
    speed["so-" fpc "/" $3 "/" i] = "oc192";
  next;
}


/\{/ && (config == JUNOS) {
  indent++;
  section[indent] = $0;
  sub(/^[[:space:]]+/, "", section[indent]);
  sub(/[[:space:]]+\{.*$/, "", section[indent]);
  if (indent == 2 && section[1] == "interfaces") disabled = 0;
  next;
}

/\}/ && (config == JUNOS) {
  indent--;
  next;
}

/ host-name / && (indent == 1) && (section[1] == "system") && \
  (config == JUNOS) {
  sub(/;/, "", $2);
  namestuff($2);
  next;
}

/^ +disable;/ && (indent > 1) && (section[1] == "interfaces") && \
  (config == JUNOS) {
  disabled = 1;
  next;
}

/description "/ && (indent > 1) && (section[1] == "interfaces") && \
  (config == JUNOS) {
  sub(/^ +description +"/, "");
  sub(/\";$/, "");
  new_ifdesc = $0;
  next;
}

/address [0-9.]+\/[0-9]+;/ && (indent > 1) && (section[1] == "interfaces") && \
    (config == JUNOS) && (disabled == 0) {
  sub(/;/, "", $2);
  split($2, a, /\//);
  ifaddr[++interfaceid] = a[1];

  split(a[1], b, /\./);
  modulus = 2^(32 - a[2]);
  prefix = b[4] + 256*b[3] + 65536*b[2] + 16777216*b[1];
  prefix = prefix - (prefix % modulus);

  key = int(prefix/16777216) "." int(prefix/65536) % 256 "." \
    int(prefix/256) % 256 "." prefix % 256 "/" a[2];

  addsubnet(key);

  ifdesc[interfaceid] = new_ifdesc;
  ifname[interfaceid] = section[2];

  if (speed[ifname[interfaceid]] == "") {
    if (ifname[interfaceid] ~ /^ge-/)
      ifspeed[interfaceid] = "ge";
    else if (ifname[interfaceid] ~ /^fxp/) 
      ifspeed[interfaceid] = "fe";
    else
      ifspeed[interfaceid] = "unknown";
  } else
    ifspeed[interfaceid] = speed[ifname[interfaceid]];

  ifnode[interfaceid] = nodeid;

  next;
}


function shownode(n) {
  if (node_shown[n] != 1) {
    node_shown[n] = 1;
    printf "node:%s:%s:%s:%s:%s:%s\n", n, node_country[n], node_region[n], \
      node_site[n], node_name[n], node_rancidname[n];
  }
}

function showedge(s, t) {
  if (edge_shown[s " " t] != 1) {
    edge_shown[s " " t] = 1;
    edge_shown[t " " s] = 1; /* undirected graph */
    printf "edge:%s:%s:%s:%s:%s:%s:%s:%s\n", \
      sb, \
      ifname[s], ifaddr[s], ifnode[s], \
      ifname[t], ifaddr[t], ifnode[t], \
      (ifspeed[s] == "unknown" ? ifspeed[t] : ifspeed[s]);
  }
}

# final processing and output

END {
  for (sb in subnet) {
    n = split(subnet[sb], ifi, / /);

# only concern ourselves with subnets that link at least
# two devices, since we want a consistent graph
    if (n > 1) {
      for (i = 1; i <= n; i++)
        for (j = 1; j <= n; j++)
          if (i != j)
          {
            s = ifi[i];
            t = ifi[j];

            if (seen_ifaddr[ifaddr[s]] == 1 && seen_ifaddr[ifaddr[t]] == 1) {
              shownode(ifnode[s]);
              shownode(ifnode[t]);
              showedge(s, t);
            }
          }
    }
  }
}

