The Problem

The use of EEM scripts (or applets) on Cisco IOS-based devices has been a round for a while now.  However, I found a use case just today that I couldn’t directly get a answer to using a search engine.  (Google was not my friend today.)  🙂

By searching the web I did find a few sites on how to use variables within an EEM script but what I wanted to do was pass some values into the script from the CLI as I executed the script.  My use case is probably not very useful in an day-to-day “real world” sense but I’ll use my freshly built script to convey my thoughts and what I learned!

 

The Scenario

My use case is that I am working with DMVPN and the headend router(s) are behind a NAT device.  Everyone once in a while (often enough that I felt compelled to write this script) their Public IP addresses change.  This breaks my branch office VPN tunnels and each branch then needs to be reconfigured with the new Public IP addresses for the NHRP mappings.  For example, take the following snippet of configuration of a tunnel interface configured for DMVPN:

interface Tunnel1
description DMVPN via ISP1
bandwidth 50000
ip address 172.29.123.1 255.255.0.0
no ip redirects
ip mtu 1400
ip nbar protocol-discovery
ip flow monitor LIVEACTION-FLOWMONITOR input
ip flow monitor LIVEACTION-FLOWMONITOR output
ip nhrp authentication dCloud
ip nhrp map multicast 123.100.10.88
ip nhrp map 172.29.255.1 123.100.10.88
ip nhrp network-id 29123
ip nhrp holdtime 450
ip nhrp nhs 172.29.255.1
ip nhrp shortcut
ip nhrp redirect
ip tcp adjust-mss 1360
load-interval 30
delay 1000
qos pre-classify
tunnel source GigabitEthernet0/0
tunnel mode gre multipoint
tunnel key 1234567890
tunnel vrf ISP1
tunnel protection ipsec profile ISP1

 

If the headend router’s public IP (123.100.10.88 in this example) changed to, say 123.100.10.222, then the tunnel would have to be reconfigured with the new IP being referenced.  As it happens there is no “easy” way to do this other than to say “no” to the two lines in the config that reference the 123.100.10.88 IP.  Additionally there is no “shortened” version of the command to remove both (or each) without typing out the WHOLE command (i.e. no ip nhrp map doesn’t work).  So not only do you have to issue the command show run interface tunnel 1 to get the lines to remove, you have to go into interface configuration mode and issue “no” to those two lines.  Then you have to re-issue those two commands referencing the new IP address.  This results in no less than something to this effect:

sh ip int tu1

conf t

int tu1

no ip nhrp map multicast 123.100.10.88

no ip nhrp map 172.29.255.1 123.100.10.88

ip nhrp map multicast 123.100.10.222

ip nhrp map 172.29.255.1 123.100.10.222

end

And you have to do that each time the head end router’s IP changes (no typos allowed).

 

The Solution

On to my solution using an EEM script.  I’ll post the whole EEM script at the bottom of this post but for now I’m going to just talk about sections at a time.  The first, and most important, to this blog is passing the new IP address into the EEM script from the command line.  I was unable to find any examples or even good documentation on how to do this (hence this post).  The syntax I wanted was something like so:

Router#event manager run ConfigDMVPN 1 123.100.10.222

The first part, event manager run ConfigDMVPN, is what is needed to call the EEM script.  The two items thereafter are the tunnel number, 1 in this example, and the new public IP address, 123.100.10.222.  These two command line arguments will be referenced as _none_arg1 and _none_arg2 in the EEM script and this little piece of information was what I couldn’t find using the “interwebs”.  The closest I found was a page on Cisco’s site that listed all the Embedded Event Manager Built-In Environment Variables.

Passing Values in from CLI and Assigning Variables Some Values

Let’s begin configuring the EEM applet.

event manager applet ConfigDMVPN
event none sync yes
action a0001 syslog msg “EEM applet ConfigDMVPN running”
action a0002 set ExternalIP “$_none_arg2”
action a0003 set Interface “tunnel $_none_arg1”

In global configuration you issue the command event manager applet ConfigDMVPN to create the applet and enter its configuration mode.  From here we need to describe when this event will be called.  In my case I just want to issue it from the CLI so the command event none sync yes tells the script that there is no automatic way to call this event (i.e. it isn’t tied to the state of a track statement like the EEM script I described in an early post).

The remaining lines in the EEM script will all be action lines.  Each action line is “organized” my a line id.  Notice I didn’t say line number.  I stress this point because action lines are alphanumerically organized.  This can cause a heap of problems it you “number” your action lines and think that the line action 10 will occur after the line action 2.  To further remind myself of this oddity in organization within a programming language I start all my lines with an alpha character and then “number” them with a few zeros padding.

That said, the action lines shown of the action lines show so far the a0002 and a0003 are the key lines to discuss.  These two lines are taking the passed variables _none_arg1 and _none_arg2 and assigning them to variable names.  Technically you could reference the $_none_arg1 variable throughout your script but if for some reason you want to reorder your passed variables you’d have to carefully change all the references accordingly, which is a recipe for disaster if you ask me.  So, I “convert” the _none_arg1 variable into a meaningful variable to which I’ll reference through the rest of the script.  These two lines also show how to assign values to variables.

action a1000 if $TunnelNum eq “1”
action a1001 set InternalIP “172.29.255.1”
action a1002 end
action a2000 if $TunnelNum eq “2”
action a2001 set InternalIP “172.30.255.1”
action a2002 end

The command action <id> set <variable name> <value> could also be used to assign a variable name to some string value.  In action lines a1001 and a2001 I’m using just that to reference the head end router’s tunnel IP address for the respective tunnel.  Note: The variable name, as it is being set, does not have a dollar sign ($) in front of it.  However, as it is being used later in the script it will have a dollar sign preceding it.

 Using Conditionals in EEM

The previous snippet of code also showed the action <id> if/action <id> end syntax.  Each if needs a corresponding end and that goes for nested if/ends too.  This next snippet of code shows this in addition to the use of the action <id> else syntax:

action a4000 syslog msg “Tunnel: $Interface InternalIP: $InternalIP ExternalIP: $ExternalIP”
action b0000 cli command “enable”
action b0001 cli command “show run int interface $TunnelNum | in ip nhrp map multicast”
action b0002 regexp ” [0-9\.]+” “$_cli_result” RemoveIP
action b1000 if $_regexp_result eq “1”
action b1001 cli command “config t”
action b1002 cli command “int $Interface”
action b1003 cli command “no ip nhrp map multicast $RemoveIP”
action b1004 cli command “no ip nhrp map $InternalIP $RemoveIP”
action b1005 cli command “end”
action b1006 puts “\nMap statements on interface $Interface removed.”
action b2000 else
action b2001 puts “\nNo NHRP Map Exists on $Interface”
action b3000 end

Changing the Router’s Config

The first line, action a4000 syslog, is use as kind of a sanity check to output to the console the tunnel, internal and external IPs that are being used during this EEM script’s execution.  From there it is time to unconfigure the old/bad public IP.

I don’t like it but you must issue the enable command as the action <id> cli command command starts in UserExec mode.  Oddly, even if you have an enable password set you don’t need it.  Arguably this is because it is possible the EEM script wouldn’t need PrivExec or Global Config modes but I forget this step often enough to be annoying.

Once at PrivExec mode the command action b0001 cli command is issuing a show statement and parsing that show statement for a particular subset of code.  In this case I’m showing the running configuration for interface tunnel X (the tunnel you are interested in changing) and then parsing that output for the line(s) that include the text “ip nhrp map multicast”.  In my case this will yield only one line, however, I’m fully aware that this could yield multiple lines if the referenced tunnel interface had multiple lines that included the text “ip nhrp map multicast”.  How should I deal with this?  Well, technically I should fix the EEM script to run action lines b0001 through b1006 over and over until $_regexp_result doesn’t contain “1”.  Since EEM scripting doesn’t include any looping commands, like “while” or “foreach”, I decided to accept the risk of having the script not remove all the pertinent command lines.  I should also note that action line b1005 is not the “end” for the if statement but it is a command issued at the CLI.  This is because I want to return to PrivExec mode so that the mode I end up with is the same whether the if statement does something (i.e. go into interface configuration mode and remove the ip nhrp map lines) or not.  This is important because the next snippet of code will issue more CLI commands and we need to know what mode we are in prior to doing so.

action c0000 cli command “config t”
action c0001 cli command “int $Interface”
action c0002 cli command “ip nhrp map multicast $ExternalIP”
action c0003 cli command “ip nhrp map $InternalIP $ExternalIP”
action c0004 cli command “end”

We are down to the final lines of the script.  These last 5 action lines return to interface configuration mode and program the tunnel interface with the new public ip address in the ip nhrp map commands.  Arguably I don’t need the “end” CLI action command but I do that for clarity and in case I add further commands to this script OR if I reference this script within some other script later on.

 

Proof is in the Pudding

This section is just to show you that my madness actually works.

First the tunnel’s configuration prior to executing the EEM script:

Router#sh run int tu1
Building configuration…

Current configuration : 688 bytes
!
interface Tunnel1
description DMVPN via ISP1
bandwidth 50000
ip address 172.29.123.1 255.255.0.0
no ip redirects
ip mtu 1400
ip nbar protocol-discovery
ip flow monitor LIVEACTION-FLOWMONITOR input
ip flow monitor LIVEACTION-FLOWMONITOR output
ip nhrp authentication dCloud
ip nhrp map multicast 123.100.10.88
ip nhrp map 172.29.255.1 123.100.10.88
ip nhrp network-id 29123
ip nhrp holdtime 450
ip nhrp nhs 172.29.255.1
ip nhrp shortcut
ip nhrp redirect
ip tcp adjust-mss 1360
load-interval 30
delay 1000
qos pre-classify
tunnel source GigabitEthernet0/0
tunnel mode gre multipoint
tunnel key 1234567890
tunnel vrf ISP1
tunnel protection ipsec profile ISP1
end

Now let’s execute the EEM script passing the tunnel number “1” and the corrected IP address:

Router#event manager run ConfigDMVPN 1 123.100.10.222
Map statements on interface tunnel 1 removed.

Router#
*Jan 29 00:19:29.463: %HA_EM-6-LOG: ConfigDMVPN: EEM applet ConfigDMVPN running
*Jan 29 00:19:29.463: %HA_EM-6-LOG: ConfigDMVPN: Tunnel: tunnel 1 InternalIP: 172.29.255.1 ExternalIP: 123.100.10.222
Router#
*Jan 29 00:19:29.651: %SYS-5-CONFIG_I: Configured from console by on vty0 (EEM:ConfigDMVPN)
*Jan 29 00:19:29.715: %SYS-5-CONFIG_I: Configured from console by on vty0 (EEM:ConfigDMVPN)
Router#

*Jan 29 00:20:36.183: %DUAL-5-NBRCHANGE: EIGRP-IPv4 1: Neighbor 172.29.255.1 (Tunnel1) is up: new adjacency

As you can see the tunnel came up an my EIGRP routing protocol established an adjacency with the head end router!  Yay!

And finally the configuration of interface tunnel 1 after running the EEM script:

Router#sh run int tu1
Building configuration…

Current configuration : 686 bytes
!
interface Tunnel1
description DMVPN via ISP1
bandwidth 50000
ip address 172.29.123.1 255.255.0.0
no ip redirects
ip mtu 1400
ip nbar protocol-discovery
ip flow monitor LIVEACTION-FLOWMONITOR input
ip flow monitor LIVEACTION-FLOWMONITOR output
ip nhrp authentication dCloud
ip nhrp map multicast 123.100.10.222
ip nhrp map 172.29.255.1 123.100.10.222
ip nhrp network-id 29123
ip nhrp holdtime 450
ip nhrp nhs 172.29.255.1
ip nhrp shortcut
ip nhrp redirect
ip tcp adjust-mss 1360
load-interval 30
delay 1000
qos pre-classify
tunnel source GigabitEthernet0/0
tunnel mode gre multipoint
tunnel key 1234567890
tunnel vrf ISP1
tunnel protection ipsec profile ISP1
end

 

In Summary

I’m not guru coder.  I’ve mentioned at least one place where I can see a possibility of my script failing.  Another area that could break my script is in initial command being called.  What if I didn’t pass any variables, or what if instead of referencing “1” or “2” for my tunnel I put in a word, like “jelly”?  What if I passed in an improperly formatted IP address?  In short there are a dozen (at least) ways this script is not fool proof.  That said, it works for the scenario I use it in.  Should you use it for your own purposes… consider yourself forewarned.  🙂

As promised here is the script in its entirety:

event manager applet ConfigDMVPN
event none sync yes
action a0001 syslog msg “EEM applet ConfigDMVPN running”
action a0002 set ExternalIP “$_none_arg2”
action a0003 set TunnelNum “$_none_arg1”
action a1000 if $TunnelNum eq “1”
action a1001 set InternalIP “172.29.255.1”
action a1002 end
action a2000 if $TunnelNum eq “2”
action a2001 set InternalIP “172.30.255.1”
action a2002 end
action a4000 syslog msg “Tunnel: $Interface InternalIP: $InternalIP ExternalIP: $ExternalIP”
action b0000 cli command “enable”
action b0001 cli command “show run int interface $TunnelNum | in ip nhrp map multicast”
action b0002 regexp ” [0-9\.]+” “$_cli_result” RemoveIP
action b1000 if $_regexp_result eq “1”
action b1001 cli command “config t”
action b1002 cli command “int $Interface”
action b1003 cli command “no ip nhrp map multicast $RemoveIP”
action b1004 cli command “no ip nhrp map $InternalIP $RemoveIP”
action b1005 cli command “end”
action b1006 puts “\nMap statements on interface $Interface removed.”
action b2000 else
action b2001 puts “\nNo NHRP Map Exists on $Interface”
action b3000 end
action c0000 cli command “config t”
action c0001 cli command “int $Interface”
action c0002 cli command “ip nhrp map multicast $ExternalIP”
action c0003 cli command “ip nhrp map $InternalIP $ExternalIP”
action c0004 cli command “end”

As a closing comment I’ll put out there that we here at Best Path Consulting are available to assist you with your routing and switching needs including writing custom EEM scripts.  Feel free to contact us for further discussion if you desire.  Thanks for your time!