http://www.perlmonks.org?node_id=868986

ccie has asked for the wisdom of the Perl Monks concerning the following question:

Hi, my first post, so have mercy please ;-) Goal: compare the configuration template (I call it MASK) against actual device configurations to detect anomalies. Here's a sample device config:
============== mpls ldp router-id 192.168.130.5 nsr graceful-restart backoff 5 10 session protection neighbor 192.168.130.1 password encrypted pwd1 neighbor 192.168.130.2 password encrypted pwd2 neighbor 192.168.130.25 password encrypted pwd3 igp sync delay 10 label allocate for LLAF ! log neighbor nsr ! interface TenGigE0/0/0/0 ! interface TenGigE0/1/0/0 ! interface TenGigE0/7/0/0 ! ! ==============
And now my template/MASK:
====================================================================== =|MASK|mpls ldp|= ====================================================================== (mpls ldp) (router-id) (__REGEX__) nsr graceful-restart backoff 5 10 session protection (neighbor) (__REGEX__) password encrypted __REGEX__ (neighbor) (__REGEX__) password encrypted __REGEX__ (neighbor) (__REGEX__) password encrypted __REGEX__ igp sync delay 10 label allocate for LLAF ! log neighbor nsr ! interface (__REGEX__) ! interface (__REGEX__) ! ! ======================================================================
So I read all device config in a string and match with /..../s against template above; before actual match the template updated with $mask_tmp =~ s/__REGEX__/^\\n+/g; Easy part is to match the template against the configuration when eg. number of neighbor or interface statements is the same on all devices. But that is not the case, so I'm looking for idea how to match (and capture necessary parameters such as IP numbers of all neighbors) irrespective of number of neighbors/interfaces in actual config. TIA

Replies are listed 'Best First'.
Re: Template-based router configuration audit
by McDarren (Abbot) on Nov 02, 2010 at 14:26 UTC
    Not a direct answer to your question, but you might want to check out RANCID (Really Awesome New Cisco config Differ).

    I believe that it does exactly what you are trying to do, plus a whole lot more.

    HTH,
    Darren

Re: Template-based router configuration audit
by jethro (Monsignor) on Nov 02, 2010 at 14:50 UTC

    What exactly do you want to test? The templating engine?

    If you want to test a self-written templating engine, do yourself a favor and use an already tested one from CPAN: There is Template::Toolkit or the smaller Text::Template or Template::Simple. They should already be well tested

    What do you want to do? Revert the config file to the template and compare? Or change the template to a config file and compare?

    If you have the actual data that was used to create the config file, just use the second version and a different template package to create the same config file and compare

    The first version on the other hand does not need the actual data. But your regex makes no sense:$mask_tmp =~ s/__REGEX__/[^\\n]+/g; (I repeat it here because you forgot code tags arround it in your post). You can't substitute __REGEX__ with a match expression. How should the machine know what you want to put in there.

    If you meant $mask_tmp =~ s/[^\\n]+/__REGEX__/g; instead, it would replace anything, so lets change that to $mask_tmp =~ s/interface \(\n+/interface (__REGEX__/g;

    The additional problem that you might have more interfaces in the filled in config file could be corrected with the following hacks for example:

    1. Count lines that begin with interface in the config file and add lines appropriately to the template file.

    2. Parse the config file line by line and match it against the template. This would mean "don't ever change your template or you have to edit your script too"

    OR you could do a clean solution: Use a template library. Or if you are paranoid, use two and compare their output

      Sorry, I realize my explanation was not clear enough.

      Inventory, such as IPs, neighbors, interfaces, etc., is not known in advance. So the Template module not really useful IMO. I want to collect inventory from device configurations, and later import as csv in excel, where simple sort by desired column could outline anomalies. By scanning device configurations against "templates" I make sure that configuration sections are correct, and at the same time acquire inventory.

      I wanted this task to b simple for end user, hence no regex in the "template" (or mask) file.

      The template example above is transformed in following regex string $mask_tmp:

      (mpls ldp) (router-id) ([^\n]+) nsr graceful-restart backoff 5 10 session protection (neighbor) ([^\n]+) password encrypted [^\n]+ (neighbor) ([^\n]+) password encrypted [^\n]+ (neighbor) ([^\n]+) password encrypted [^\n]+ igp sync delay 10 label allocate for LLAF ! log neighbor nsr ! interface ([^\n]+) ! interface ([^\n]+) ! !
      And then I loop (mask can be found multiple times in the configuration) this mask through device configuration stored in $current_cfg as follows:
      while (@MASK_VARIABLES = $current_cfg =~ /$mask_tmp/s) { $MASK_VARIABLES = join(",",@MASK_VARIABLES); $MASK_SEEN{$hostname}{$mask} = "YES"; $MASK_COUNT{$hostname}{$mask}++; # ($MASK_VARIABLES[1] =~ /\w+/) && do { print "$hostname,$MASK_VARIABLES\n"; }; # # remove identified cfg section from router configuration # $current_cfg =~ s/$mask_tmp//s; }
      Result is the following:
      - inventories in .csv on stdout (for further analyzing in excel)
      - summary view (code not shown above) where I see which and how many times given template has been matched in each device configuration
      - remaining of $current_cfg stored in a "leftover" file, where I can see all commands not identifed by above process (this allows network operator to asses any anomalies - ie. config sections not compliant with the template)

        Ok, now I get it.

        Your idea is clever but smells a bit like a hack. Whenever the config file changes minimally (for example an additional space or empty line somewhere) your method breaks. And as you have already found out it can't cope with a variable number of lines.

        With a bit more complexity you might still get this to work, by doing the parsing piece by piece (aka chunk by chunk). Your end user would have to split the static lines from the dynamic and give you something like this:

        Chunk 1: (mpls ldp) Chunk 2: (router-id) (__REGEX__) ... Chunk 6: ! interface (__REGEX__)

        Your end user would have to know that Chunk 6 has to include the line with the '!' because it is part of the repeating pattern.

        Then your script should match chunk after chunk. And try to repeat matching any chunks with __REGEX__ in them until they don't match anymore. And naturally it should warn if it doesn't get at least one match