Adding new CLI commands to Junos via op scripts

One of the biggest intuitions early Junos designers had was to make their network OS automation-friendly since day 0.

This meant implementing a native XML schema that worked as the foundation for an easy and smooth integration with all the automation tools that became popular over the years (netconf, python, rest API, ansible, salt, etc…).

Part of this clever automation-friendly approach was the development of scripting capabilities able to enrich Junos behavior. Three kind of scripts were introduced:

  • op, scripts running on-demand by users
  • event, scripts running after an event occurs (e.g. upon a BGP peer down event, run this script)
  • commit, scripts executed after a commit is issued

Today, I’m going to focus on op scripts. As briefly said above, op scripts are run by the user in operation mode. For example, if we had a script called “umberto”, we would run it by typing:

user@router> op umberto

If you think about it, running an op script is not different from running a CLI command. For this reason, I tend to say that op scripts allow you to add new customized CLI commands.

Op scripts might be written in either python or slax.

As I have always worked with python, this time I wanted to go with slax. SLAX “is a language for writing Junos OS commit scripts, op scripts, event scripts, and SNMP scripts. It is an alternative to Extensible Stylesheet Language Transformations (XSLT). SLAX has a distinct syntax similar to that of C and Perl, but the same semantics as XSLT.”

Before creating my own slax op script I thought about, from my limited experience, which CLI command is missing (or partly missing) and might be useful. Recently, I’ve worked on improving my routing skills and did few labs about MPLS-BGP vpns. Often, I found myself checking the backbone status. By that, I mean verifying IGP works, label exchange protocol works and iBGP exchanging routes between PEs is up and running. To achieve that, I needed to run at least three different CLI commands. In order to make this verification easier, I thought to create an op script giving all that information in a single shot.

I called my op script “backbone”.

Let’s go through it, step by step.

First, we add some default lines, specifying slax version and importing namespaces needed to use slax functions:

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";

Next, we define arguments:

var $arguments = {
    <argument> {
        <name> "community";
        <description> "Community of a VPN";
    }
}
param $community;

In this case, we define a single argument, community. The idea is to give the user the ability to provide a community when running the script in order to see all the vpn routes tagged with it.

As a result, when we will run the script, Junos will treat that argument like any other CLI argument (auto-completion included):

root@pe1> op backbone ?
Possible completions:
  <[Enter]>            Execute this command
  <name>               Argument name
  community            Community of a VPN

Back to our script.

Op scripts typically start like this:

match / {
    <op-script-results> {

We get a copy of the configuration:

        var $config = jcs:invoke("get-configuration");

That is achieved by using the invoke function, defined within the jcs namespace.
Invoke function is passed a RPC as argument; “get-configuration” is the RPC of the cli command “show configuration”.
The result is a variable called config containing the device configuration in xml format.

Then, we check if an IGP is configured (either ospf or isis):

        if (jcs:empty($config/protocols/isis) and jcs:empty($config/protocols/ospf)){
            <xnm:error> {
                <message>"NO IGP was found";
            }
            call exit-script();
        }

That is done by using the jcs:empty function. Take jcs:empty($config/protocols/isis): the script will navigate down the xml formatted config up to the “protocols isis” stanza. If that stanza is empty, it means isis is not configured on the device and jcs:empty returns True.
If both ospf and isis were not configured, we call another function, named exit-script:


template exit-script(){
    <xsl:message terminate="yes"> "Script aborted";
}

The function simply prints a message and aborts script execution.

Be aware, we only check that at least one IGP is configured but we do not store any information about which igp protocol is used.

We do the same thing with label exchange protocol and iBGP:

        if (jcs:empty($config/protocols/rsvp) and jcs:empty($config/protocols/ldp)){
            <xnm:error> {
                <message>"NO labeling protocol was found";
            }
            call exit-script();
        }
        if (not($config/protocols/bgp//group[type="internal"]/family/inet-vpn)){
            <xnm:error> {
                <message>"NO iBGP inet-vpn enabled group was found";
            }
            call exit-script();
        }

Notice, for iBGP we check if there is at least one bgp group with type internal (iBGP) and family inet-vpn configured (VPN routes).

At this point, we start getting some data about the IGP.

The following block collects ospf information if that protocol is configured:

        if (not(jcs:empty($config/protocols/ospf))){
            <output> "IGP: OSPF\nneighbors status";
            var $res = jcs:invoke("get-ospf-neighbor-information");
            for-each($res/ospf-neighbor){
                <output> neighbor-address _ " : " _ interface-name _ " : " _ ospf-neighbor-state;
            }
        }

Many things there! Let’s try to understand them all 🙂

First, the if block gets executed if “edit protocols ospf” is not empty (ospf configured).
If so, we run the “get-ospf-neighbor-information” RPC and store the result in variable res.

Remember, RPC can be fond via CLI:

root@pe1> show ospf neighbor | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <rpc>
        <get-ospf-neighbor-information>
        </get-ospf-neighbor-information>
    </rpc>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

Then, we have a for cycle. In order to understand how it works, we need to look at the RPC reply body:

root@pe1> show ospf neighbor | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <ospf-neighbor-information xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
        <ospf-neighbor>
            <neighbor-address>192.168.12.1</neighbor-address>
            <interface-name>ae12.0</interface-name>
            <ospf-neighbor-state>Full</ospf-neighbor-state>
            <neighbor-id>2.2.2.2</neighbor-id>
            <neighbor-priority>128</neighbor-priority>
            <activity-timer>32</activity-timer>
        </ospf-neighbor>
        <ospf-neighbor>
            <neighbor-address>192.168.13.1</neighbor-address>
            <interface-name>ae13.0</interface-name>
            <ospf-neighbor-state>Full</ospf-neighbor-state>
            <neighbor-id>3.3.3.3</neighbor-id>
            <neighbor-priority>128</neighbor-priority>
            <activity-timer>39</activity-timer>
        </ospf-neighbor>
        <ospf-neighbor>
            <neighbor-address>192.168.14.1</neighbor-address>
            <interface-name>ae14.0</interface-name>
            <ospf-neighbor-state>Full</ospf-neighbor-state>
            <neighbor-id>4.4.4.4</neighbor-id>
            <neighbor-priority>128</neighbor-priority>
            <activity-timer>37</activity-timer>
        </ospf-neighbor>
    </ospf-neighbor-information>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

We easily notice a recurring pattern: ospf-neighbor. That is why we use this:

            for-each($res/ospf-neighbor){

Path $res/ospf-neighbor identifies those blocks. Be aware that, the xml root of the rpc reply is the one after <rpc-reply>, in this case <ospf-neighbor-information>. As a result, in order to reach <ospf-neighbor>,we only have to go down one step, hence $res/ospf-neighbor ($res by itself, of course, is the root).

Within the ospf-neighbor block, we want to extract

  • neighbor-address
  • interface-name
  • ospf-neighbor-state

Now, the for cycle should be clearer:

            for-each($res/ospf-neighbor){
                <output> neighbor-address _ " : " _ interface-name _ " : " _ ospf-neighbor-state;
            }

Tag <output> simply adds an output node to the script reply (what the user will see on screen). We can see <output> as a sort of print function. Within that node, we define what to print. Strings can be concatenated by using _. Notice, we do not need to descend the whole tree here (e.g. $res/ospf-neighbor/neighbor-address) as, at each for iteration, we already are inside a specific ospf-neighbor block!

Similarly, we have the ISIS block:

        if (not(jcs:empty($config/protocols/isis))){
            <output> "ISIS LOGIC";
        }

Here, it is just a sort of placeholder as my lab uses ospf and I simply wanted to test the script but you can easily imagine what would be inside it (invoke on the “show isis adjacency” RPC, for cycle through each adjacency, print adj info).

Using the same approach, we get information for rsvp and ldp:

        if (not(jcs:empty($config/protocols/ldp))){
            <output> "\n\nLABELS: LDP\nneighbors status";
            var $res = jcs:invoke("get-ldp-session-information");
            for-each($res/ldp-session){
                <output> ldp-neighbor-address _ " : " _ ldp-session-state;
            }
        }
        if (not(jcs:empty($config/protocols/rsvp))){
            <output> "LDP LOGIC";
        }

How those lines work should be very clear by now 🙂

We checked the IGP and the label exchange protocols. We only miss BGP:

        if ($config/protocols/bgp//group[type="internal"]/family/inet-vpn){
            <output> "\n\nROUTING: BGP";
            for-each($config/protocols/bgp/group[type="internal"]/family/inet-vpn/../..){
                <output> "Group " _ name;
                var $bgpr =  <get-bgp-summary-information> {
                     <group> ./name;
                }
                var $res = jcs:invoke($bgpr);
                <output> "Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...";
                <bgp-information> {
                    for-each ($res/bgp-peer){
                        <bgp-peer junos:style="terse">{
                            copy-of ./*;
                        }
                    }
                }
                <output> "\n";
            }
        }

We a have a for cycle going through all the suitable groups (iBGP and family inet-vpn configured).

To better understand that path ” $config/protocols/bgp/group[type=”internal”]/family/inet-vpn/../.. “, let’s have a look at rpc reply:

root@pe1> show configuration protocols bgp | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <configuration junos:commit-seconds="1615977534" junos:commit-localtime="2021-03-17 03:38:54 PDT" junos:commit-user="root">
            <protocols>
                <bgp>
                    <group>
                        <name>vpn</name>
                        <type>internal</type>
                        <local-address>1.1.1.1</local-address>
                        <family>
                            <inet-vpn>
                                <unicast>
                                </unicast>
                            </inet-vpn>
                        </family>
                        <neighbor>
                            <name>2.2.2.2</name>
                        </neighbor>
                        <neighbor>
                            <name>5.5.5.5</name>
                        </neighbor>
                        <neighbor>
                            <name>6.6.6.6</name>
                        </neighbor>
                    </group>
                </bgp>
            </protocols>
    </configuration>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

Family inet-vpn information is 2 steps down the hierarchy from the <group> node. As we want to work on the group block, similarly to what we do with unix paths, we have to go back two levels in the hierarchy so to reach <group> again.

Next, we get bgp information for that group. This time, we need to provide an additional argument to the RPC request. First, we find the RPC:

root@pe1> show bgp summary group vpn | display xml rpc
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <rpc>
        <get-bgp-summary-information>
                <group>vpn</group>
        </get-bgp-summary-information>
    </rpc>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

Next, we model that information in to slax language:

                var $bgpr =  <get-bgp-summary-information> {
                     <group> ./name;
                }
                var $res = jcs:invoke($bgpr);

We “print” the well-known bgp summary header:

                <output> "Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...";

That is taken from the real “show bgp summary” rpc reply:

root@pe1> show bgp summary group vpn | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <bgp-information xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
        ...
        <bgp-peer junos:style="terse" heading="Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...">

Last piece of bgp-related code to analyze is this one:

                <bgp-information> {
                    for-each ($res/bgp-peer){
                        <bgp-peer junos:style="terse">{
                            copy-of ./*;
                        }
                    }
                }

Remember, with slax we are basically writing a xml document. In addition, we have all the functions, the conditional blocks, that’s true…but, at the end of the day, they all take part to writing that xml tree that will be given as an output to the user.

Knowing that, it should be easier to understand why we put <bgp-information> into our code. It means the op script output will contain a <bgp-information> node. Ok…but why?

That tag belongs to Junos “get-bgp-summary-information” reply:

root@pe1> show bgp summary group vpn | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <bgp-information xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">

We want our script to print bgp information using standard junos syntax for bgp information. When Junos sees the <bgp-information> tag, it knows what to do, it knows how to display the data enclosed within that tag. That’s why we use it.

Inside that tag, we have a for cycle that gets one bgp neighbor block at a time and simply copies that “sub-xml” tree into script output.

Consider this stripped “show bgp summary group vpn” xml output:

root@pe1> show bgp summary group vpn | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <bgp-information xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
        <group-count>1</group-count>
        <peer-count>3</peer-count>
        <down-peer-count>0</down-peer-count>
        ...
        <bgp-peer junos:style="terse" heading="Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...">
            <peer-address>2.2.2.2</peer-address>
            <peer-as>100</peer-as>
            <input-messages>7326</input-messages>
            <output-messages>7334</output-messages>
            <route-queue-count>0</route-queue-count>
            <flap-count>0</flap-count>
            <elapsed-time junos:seconds="199260">2d 7:21:00</elapsed-time>
            <peer-state junos:format="Establ">Established</peer-state>
            <bgp-rib junos:style="terse">
                <name>bgp.l3vpn.0</name>
                <active-prefix-count>0</active-prefix-count>
                <received-prefix-count>0</received-prefix-count>
                <accepted-prefix-count>0</accepted-prefix-count>
                <suppressed-prefix-count>0</suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>
        <bgp-peer junos:style="terse">
            <peer-address>5.5.5.5</peer-address>
            <peer-as>100</peer-as>
            <input-messages>7327</input-messages>
            <output-messages>7333</output-messages>
            <route-queue-count>0</route-queue-count>
            <flap-count>0</flap-count>
            <elapsed-time junos:seconds="199256">2d 7:20:56</elapsed-time>
            <peer-state junos:format="Establ">Established</peer-state>
            <bgp-rib junos:style="terse">
                <name>bgp.l3vpn.0</name>
                <active-prefix-count>2</active-prefix-count>
                <received-prefix-count>2</received-prefix-count>
                <accepted-prefix-count>2</accepted-prefix-count>
                <suppressed-prefix-count>0</suppressed-prefix-count>
            </bgp-rib>
            <bgp-rib junos:style="terse">
                <name>noodles.inet.0</name>
                <active-prefix-count>2</active-prefix-count>
                <received-prefix-count>2</received-prefix-count>
                <accepted-prefix-count>2</accepted-prefix-count>
                <suppressed-prefix-count>0</suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>
        <bgp-peer junos:style="terse">
            <peer-address>6.6.6.6</peer-address>
            <peer-as>100</peer-as>
            <input-messages>7327</input-messages>
            <output-messages>7333</output-messages>
            <route-queue-count>0</route-queue-count>
            <flap-count>0</flap-count>
            <elapsed-time junos:seconds="199254">2d 7:20:54</elapsed-time>
            <peer-state junos:format="Establ">Established</peer-state>
            <bgp-rib junos:style="terse">
                <name>bgp.l3vpn.0</name>
                <active-prefix-count>0</active-prefix-count>
                <received-prefix-count>0</received-prefix-count>
                <accepted-prefix-count>0</accepted-prefix-count>
                <suppressed-prefix-count>0</suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>
    </bgp-information>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

The for cycle will identify three <bgp-peer> blocks. It will take each of them and simply copy it into script xml output. For example, at the first iteration, it will copy:

        <bgp-peer>
            <peer-address>2.2.2.2</peer-address>
            <peer-as>100</peer-as>
            <input-messages>7326</input-messages>
            <output-messages>7334</output-messages>
            <route-queue-count>0</route-queue-count>
            <flap-count>0</flap-count>
            <elapsed-time junos:seconds="199260">2d 7:21:00</elapsed-time>
            <peer-state junos:format="Establ">Established</peer-state>
            <bgp-rib junos:style="terse">
                <name>bgp.l3vpn.0</name>
                <active-prefix-count>0</active-prefix-count>
                <received-prefix-count>0</received-prefix-count>
                <accepted-prefix-count>0</accepted-prefix-count>
                <suppressed-prefix-count>0</suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>

Last section of the script makes use, if provided, of the community argument:

        if(not(jcs:empty($community))){
            <output> jcs:printf("\n\nVPN routes tagged with community %s", $community);
            var $router =  <get-route-information> {
                <community> $community;
                <table> "bgp.l3vpn.0";
            }
            var $res = jcs:invoke($router);
            for-each ($res/route-table/rt){
                <output> "\tVPN ROUTE: " _ rt-destination _ "/" _ rt-prefix-length _ " from " _ rt-entry/learned-from;
            }
        }

Everything should be clear by now. Checking if argument was provided, running the RPC to get route information, cycling through routes and print information.

Our script is ready.

We have to copy the file into our device and configure it under “system”:

root@pe1> file list /var/db/scripts/op/

/var/db/scripts/op/:
backbone.slax
sdg-inservice.slax@ -> /packages/mnt/junos-runtime-mx/var/db/scripts/op/sdg-inservice.slax
sdg-oos.slax@ -> /packages/mnt/junos-runtime-mx/var/db/scripts/op/sdg-oos.slax
services-oids.slax@ -> /packages/mnt/junos-runtime-mx/var/db/scripts/op/services-oids.slax
srd-status.slax@ -> /packages/mnt/junos-runtime-mx/var/db/scripts/op/srd-status.slax

root@pe1> show configuration system scripts
op {
    file backbone.slax;
}

Finally, we can run it:

root@pe1> op backbone community target:100:1
IGP: OSPF
neighbors status
192.168.12.1 : ae12.0 : Full
192.168.13.1 : ae13.0 : Full
192.168.14.1 : ae14.0 : Full

LABELS: LDP
neighbors status
2.2.2.2 : Operational
3.3.3.3 : Operational
4.4.4.4 : Operational

ROUTING: BGP
Group vpn
Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...
2.2.2.2                 100       7359       7366       0       0  2d 7:35:45 Establ
  bgp.l3vpn.0: 0/0/0/0
5.5.5.5                 100       7360       7366       0       0  2d 7:35:41 Establ
  bgp.l3vpn.0: 2/2/2/0
  noodles.inet.0: 2/2/2/0
6.6.6.6                 100       7360       7366       0       0  2d 7:35:39 Establ
  bgp.l3vpn.0: 0/0/0/0


VPN routes tagged with community target:100:1
        VPN ROUTE: 20.20.20.20:1:20.20.20.20/32 from 5.5.5.5
        VPN ROUTE: 20.20.20.20:1:172.30.201.0/31 from 5.5.5.5

That’s it! Everything in one single command.

And check the bgp information. It is the same formatting we are used to and this was possible by relying on tags that junos knows how to treat and by copying xml sub-trees with bgp peer information that, again, junos knows how to display.

One more thing, remember when I was saying that with slax, even if cooked and mixed with functions and cycles, we are simply writing a xml file? Well, check this:

root@pe1> op backbone community target:100:1 | display xml
<rpc-reply xmlns:junos="http://xml.juniper.net/junos/17.2R2/junos">
    <output>
        IGP: OSPF
        neighbors status
    </output>
    <output>
        192.168.12.1 : ae12.0 : Full
    </output>
    <output>
        192.168.13.1 : ae13.0 : Full
    </output>
    <output>
        192.168.14.1 : ae14.0 : Full
    </output>
    <output>

        LABELS: LDP
        neighbors status
    </output>
    <output>
        2.2.2.2 : Operational
    </output>
    <output>
        3.3.3.3 : Operational
    </output>
    <output>
        4.4.4.4 : Operational
    </output>
    <output>

        ROUTING: BGP
    </output>
    <output>
        Group vpn
    </output>
    <output>
        Peer                     AS      InPkt     OutPkt    OutQ   Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped...
    </output>
    <bgp-information>
        <bgp-peer junos:style="terse">
            <peer-address xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                2.2.2.2
            </peer-address>
            <peer-as xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                100
            </peer-as>
            <input-messages xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                7365
            </input-messages>
            <output-messages xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                7373
            </output-messages>
            <route-queue-count xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                0
            </route-queue-count>
            <flap-count xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                0
            </flap-count>
            <elapsed-time xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:seconds="200306">
                2d 7:38:26
            </elapsed-time>
            <peer-state xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:format="Establ">
                Established
            </peer-state>
            <bgp-rib xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:style="terse">
                <name>
                    bgp.l3vpn.0
                </name>
                <active-prefix-count>
                    0
                </active-prefix-count>
                <received-prefix-count>
                    0
                </received-prefix-count>
                <accepted-prefix-count>
                    0
                </accepted-prefix-count>
                <suppressed-prefix-count>
                    0
                </suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>
        <bgp-peer junos:style="terse">
            <peer-address xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                5.5.5.5
            </peer-address>
            <peer-as xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                100
            </peer-as>
            <input-messages xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                7366
            </input-messages>
            <output-messages xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                7372
            </output-messages>
            <route-queue-count xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                0
            </route-queue-count>
            <flap-count xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                0
            </flap-count>
            <elapsed-time xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:seconds="200302">
                2d 7:38:22
            </elapsed-time>
            <peer-state xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:format="Establ">
                Established
            </peer-state>
            <bgp-rib xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:style="terse">
                <name>
                    bgp.l3vpn.0
                </name>
                <active-prefix-count>
                    2
                </active-prefix-count>
                <received-prefix-count>
                    2
                </received-prefix-count>
                <accepted-prefix-count>
                    2
                </accepted-prefix-count>
                <suppressed-prefix-count>
                    0
                </suppressed-prefix-count>
            </bgp-rib>
            <bgp-rib xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:style="terse">
                <name>
                    noodles.inet.0
                </name>
                <active-prefix-count>
                    2
                </active-prefix-count>
                <received-prefix-count>
                    2
                </received-prefix-count>
                <accepted-prefix-count>
                    2
                </accepted-prefix-count>
                <suppressed-prefix-count>
                    0
                </suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>
        <bgp-peer junos:style="terse">
            <peer-address xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                6.6.6.6
            </peer-address>
            <peer-as xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                100
            </peer-as>
            <input-messages xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                7366
            </input-messages>
            <output-messages xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                7372
            </output-messages>
            <route-queue-count xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                0
            </route-queue-count>
            <flap-count xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing">
                0
            </flap-count>
            <elapsed-time xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:seconds="200300">
                2d 7:38:20
            </elapsed-time>
            <peer-state xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:format="Establ">
                Established
            </peer-state>
            <bgp-rib xmlns="http://xml.juniper.net/junos/17.2R2/junos-routing" junos:style="terse">
                <name>
                    bgp.l3vpn.0
                </name>
                <active-prefix-count>
                    0
                </active-prefix-count>
                <received-prefix-count>
                    0
                </received-prefix-count>
                <accepted-prefix-count>
                    0
                </accepted-prefix-count>
                <suppressed-prefix-count>
                    0
                </suppressed-prefix-count>
            </bgp-rib>
        </bgp-peer>
    </bgp-information>
    <output/>
    <output>

        VPN routes tagged with community target:100:1
    </output>
    <output>
                VPN ROUTE: 20.20.20.20:1:20.20.20.20/32 from 5.5.5.5
    </output>
    <output>
                VPN ROUTE: 20.20.20.20:1:172.30.201.0/31 from 5.5.5.5
    </output>
    <cli>
        <banner></banner>
    </cli>
</rpc-reply>

If you have time and patience to go through it, you will see all those tags we defined in our script and how for cycles resulted in xml nodes being added multiple times with the adequate text.

If, for example, there is no IGP configured, as said at the beginning, script will relaize it and abort;

root@pe1> op backbone community target:100:1
error: Script aborted
error: NO IGP was found

We made it! We combined existing CLI comannds and made our own, providing all the data needed to understand the status of our backbone device.

The script can be found here https://raw.githubusercontent.com/iosonoumberto/slax_scripts/main/backbone.slax .

Ciao
IoSonoUmberto

Leave a comment