Campus and Branch
Campus and Branch

How-To: Junos Automation Helps Dynamic Provisioning of Access Ports in Campus Networks

by Juniper Employee on ‎02-10-2016 02:38 AM - edited on ‎09-19-2017 11:02 AM by Administrator Administrator
02.10.16   |   02:38 AM

With Juniper's Event Policies and SLAX Scripts, one can pre-provision configuration templates, and depending on what is connected to the access port, the configuration relevant for that end device can be dynamically applied. This not only reduces the manual errors, but also saves manual effort in initial deployment period. This article takes VoIP phone and WLAN Access Points as examples, and presents a sample set of configuration policies and templates.

 

Provisioning access ports in a huge campus network is a cumbersome process for any network administrator.  If you can just define "templates" and carve out some rules as to which template is applied for which port, wouldn't it be great? And add to it, the all new  Junos Fusion Enterprise® architecture, where you can manage the whole building as a single switch, now all you have to do is define a few templates and rules, that too on a single switch for the whole network/building. The Junos automation framework will take care of dynamically changing the interface configuration.

 

Junos ships with cutting-edge automation tools, and Junos as an operating system was built with automation in mind, so organically Junos architecture has been built with the capability to receive RPCs (Remote Procedure Calls) from scripts and automation tools.  Using these tools, one can build powerful provisioning model that can help save manual effort and cut down human errors and bring in predictability.  In this article, let's see how to achieve this using the popular SLAX scripts, a unique framework that Junos supports.

 

SLAX stands for Stylesheet Language Alternative syntaX. It is an abstracted form of XSLT, much simplified and easy to use compared to the underlying XSLT language.  If you are first time user of SLAX, I would encourage you to start from here, an excellent resource to help you write your first SLAX script.  However, you don't have to master all the constructs of SLAX language to provision your network.  An example provisioning script is given in this article, and as you would come to know, it is easy to modify this example to suit your provisioning requirements.

 

Any provisioning model would work on the basis of type of device connected to the wiring closet, and have a pre-defined configuration to be applied on the port based on the device type. In this example, we are going to use LLDP to discover the device type, and use the information to apply configuration via the SLAX scripts. e.g. When a VoIP phone is connected to the wiring closet (usually to one of the LFE devices in Junos Fusion Enterprise architecture), LLDP will report that a neighbour has been discovered, and it is a "Telephone" device.  We will use this information and apply the "VoIP Phone" template on the access port.

 

Components that we will need:  An Event policy to capture LLDP events, configuration template and SLAX script to apply the template. The interaction is explained in the figure below:

 

slax.png.png

Let's build a sample solution with LLDP as the event, and VoIP and WLAN Access Point as the possible end devices.

 

First, the logic:

 

We write an event policy to get notified whenever an LLDP neighbour shows up or disappears.  When notified the event infrastructure calls the provisioning script. Provisioning script retrieves the device type, interface name, the voice vlan etc, and fills the template config and commits the final configuration.

 

Now the Junos CLI configuration (Defining Event policies):

 

root@bng-9208# show event-options
policy lldp_trigger_up {
    events lldp_neighbor_up;
    then {
        event-script provision_access_port.slax {
            arguments {
                interface "{$$.interface-name}";
                voice-vlan voice-vlan;
            }
        }
    }
}
 
policy lldp_trigger_down {
    events lldp_neighbor_down;
    then {
        event-script provision_access_port.slax {
            arguments {
                delete true;
                interface "{$$.interface-name}";
            }
        }
    }
}
event-script {
    file provision_access_port.slax;
}

 

 

Here what we telling JUNOS is that we define an event policy, and register for LLDP_NEIGHBOR_UP/DOWN events, and call the SLAX script provision_access_port.slax. We also want to pass some arguments to the script: the interface name and the VoIP VLAN to be used on the interface. Here it is assumed that user has configured a vlan named "voice-vlan" for this purpose.

 

Second, the SLAX script

 

Store the following as /var/db/scripts/event/provision_access_port.slax. The VoIP phone template config used here is to add access-vlan, voice-vlan and enable IEEE 802.1x authentication in multiple supplicant mode.  The template config takes interface-name, access-vlan, voice-vlan as inputs. It is also possible to associate default values for the parameters.  Similarly the Access Point template configures the given port as a trunk with membership to all the VLANs.

 

/* provision_access_port.slax *>
version 1.0;
ns junos = "http://xml.juniper.net/junos/*/junos";
ns xnm = "http://xml.juniper.net/xnm/1.1/xnm";
ns jcs = "http://xml.juniper.net/junos/commit-scripts/1.0";
import "../import/junos.xsl";
param $interface;
param $voice-vlan;
param $access-vlan;
param $delete;
 
template cleanup_config ($interface) {
    expr jcs:syslog("external.err", "End device disconnected, cleaning up config for ", $interface);
    var $configuration = jcs:invoke( "get-configuration" );
    var $change = {
        <configuration> {
            <switch-options> {
                <voip> {
                    <interface delete="delete"> {
                        <name> $interface;
                    }
                }
            }
            <protocols> {
                <dot1x> {
                    <authenticator> {
                        <interface delete="delete"> {
                            <name> $interface;
                        }
                    }
                }
            }
            <interfaces> {
                <interface > {
                    <name> $interface;
                    <unit delete = "delete">;
                 }
            }
        }
    }
    var $connection = jcs:open();
    var $results := {
        call jcs:load-configuration( $connection, $configuration = $change);
    }
    var $close-results = jcs:close( $connection );
    /* Report either errors or success */
    if( $results//xnm:error ) {
        for-each( $results//xnm:error ) {
            expr jcs:syslog( "external.error", "Commit Error: ", message );
        }
    }
}
 
template config_access_point ($interface) {
    var $configuration = jcs:invoke( "get-configuration" );
    copy-of $configuration;
    var $change = {
        <configuration> {
            <interfaces> {
                <interface> {
                   <name> $interface;
                   <unit> {
                       <name> "0";
                       <family> {
                           <ethernet-switching> {
                               <interface-mode> "trunk";
                                   <vlan> {
                                       <members> "all";
                                    }
                            }
                        }
                    }
                }
            }
        }
    }
    var $connection = jcs:open();
    var $results := {
        call jcs:load-configuration( $connection, $configuration = $change );
    }
    var $close-results = jcs:close( $connection );
    /* Report either errors or success */
    if( $results//xnm:error ) {
        for-each( $results//xnm:error ) {
            expr jcs:syslog( "external.error", "Commit Error: ", message );
        }
    }
}
 
template config_phone ($interface, $access-vlan, $voice-vlan) {
    var $configuration = jcs:invoke( "get-configuration" );
    copy-of $configuration;
    var $change = {
        <configuration> {
            <switch-options> {
                <voip> {
                    <interface> {
                        <name> $interface;
                        <vlan> $voice-vlan;
                        <forwarding-class> "assured-forwarding";
                    }
                }
            }
            <interfaces> {
                <interface> {
                    <name> $interface;
                    <unit> {
                        <name> "0";
                        <family> {
                            <ethernet-switching> {
                                <vlan> {
                                    <members> "access-vlan";
                                }
                            }
                        }
                    }
                }
            }
            <protocols> {
                <dot1x> {
                    <authenticator> {
                        <interface> {
                            <name> $interface;
                            <supplicant> "multiple";
                        }
                    }
                }
            }
        }
    }
    var $connection = jcs:open();
    var $results := {
        call jcs:load-configuration( $connection, $configuration = $change );
    }
    var $close-results = jcs:close( $connection );
   /* Report either errors or success */
    if( $results//xnm:error ) {
        for-each( $results//xnm:error ) {
            expr jcs:syslog( "external.error", "Commit Error: ", message );
        }
    }
}
 
match / {
    <event-script-results> {
        if (contains ($delete, "true")) {
            call cleanup_config($interface);
        }  else {
            var $show_lldp_neighbor = {
                <get-lldp-interface-neighbors> {
                    <interface-device> $interface;
                }
            }
            var $show_results = jcs:invoke($show_lldp_neighbor);
            var $capabilities = $show_results/lldp-neighbor-information/lldp-remote-system-capabilities-supported;
            expr jcs:syslog("external.err", "End device detected with capabilities: ", $capabilities);
            if( contains($capabilities, "Telephone")) {
                call config_phone($interface, $access-vlan = "access-vlan", $voice-vlan);
            } else if (contains($capabilities, "WLAN Access Point")) {
                call config_access_point($interface);
            }
        }
    }
}

 

Modifying configuration section in the script to suit your requirements

 

a) Do a "show configuration | display xml" on the piece of configuration you want to use. e.g.

 

root@bng-9208# show interfaces ge-120/0/8 | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/15.1I0/junos">
    <configuration junos:changed-seconds="1455016240" junos:changed-localtime="2016-02-09 11:10:40 UTC">
        <interfaces>
            <interface>
                <name>ge-120/0/8</name>
                <unit>
                    <name>0</name>
                    <family>
                        <ethernet-switching>
                            <vlan>
                                <members>access-vlan</members>
                            </vlan>
                        </ethernet-switching>
                    </family>
                </unit>
            </interface>
        </interfaces>
    </configuration>
    <cli>
        <banner>[edit]</banner>
    </cli>
</rpc-reply>

 

b) Apply the following simple rules:

 

  1. Extract the section between <configuration> and </configuration>
  2. After every start tag, insert a opening curly brace "{"
  3. Replace every end tag with a closing curly brace "}"
  4. If start and end tags are in the same line, then just replace the end tag with a "';". No curly braces required.

 

c) After applying the rules, you will get the modified section as:

 

   <interfaces>
            <interface>
                <name>ge-120/0/8;
                <unit>
                    <name>0;
                    <family>
                        <ethernet-switching>
                            <vlan>
                                <members>access-vlan;
                            }
                        }
                    }
                }
            }
        }

 

d) Copy paste this in the configuration section of the script, to use this as the configuration instead of the default configuration.

 

We can extend the solution to include more events like DHCP options, IEEE802.1x authentication response from RADIUS server, MAC OUI pattern matching, MAC Authentication Bypass etc. We can also cover any other types of devices such as IP Camera. 

 

Please try this in your network and let me know your comments/feedback!

'
Comments
03.09.16
Juniper Employee

Great article.

 

You can use the "slaxproc" utility to convert XML into slax syntax.  The command is:

 

    slaxproc --xslt-to-slax --partial foo.xml

 

slaxproc can be found on your JUNOS box in /usr/libexec/ui/slaxproc or you can install it on any unix box from the sources available on github:

 

    https://github.com/Juniper/libslax

 

Thanks,

 Phil