ZTP – and enjoy your coffee!

Zero Touch Provisioning (ZTP) is a Junos future, very useful when deploying new devices. Basically it allows you to provision all necessary config and software versions with a single cable plug (no extra work needed).

When the new switch arrived all you need to do is take it out of the box power it up and plug it into the network (via management port or any network port).

ZTP was first introduced in Junos 12.2 (know as EZ Touchless Provisioning) since that time it is supporting wider range fo devices including EX , SRX , QFX and more on the roadmap.

I would like to describe a configuration based on Raspberry Pi box as a server. DHCP is used to instruct switch with a details related to config file and install package to fetch. I will use open source software to prepare RPI with needed tools to perform full config and image deployment.

Software needed isc-dhcp-server, vsftp, junos image (for software / junos version upgrade)

RPI network interface setup

RPI have one ethernet interface and one wi-fi connection. Ethernet is used to setup DHCP interface to propagate settings and wi-fi will be used fo management only (remote access).

nano  /etc/network/interfaces
auto wlan0
iface lo inet loopback

auto eth0
allow-hotplug eth0
iface eth0 inet static
  address 10.100.10.1
  netmask 255.255.255.0
  
allow-hotplug wlan0
iface wlan0 inet dhcp
  wpa-conf /etc/wpa_supplicant/wpa_supplicant.conf

iface default inet dhcp

DHCP sever

DHCP server is responsible for assigning IP addresses to our box (e.g. ex switch). Send instructions regarding config file / software file to upgrade (location of files on ftp server), but also can be used to setup values like hostname, ntp or dns servers.

To install isc-dhcp-server on RPI preinstalled software have to be disabled.

update-rc.d dhcpcd disable

Updating dhcpd.conf

nano /etc/dhcp/dhcpd.conf
# dhcpd.conf
#
# Setting up the options....
#

# option definitions common to all supported networks...
option domain-name "mnsbone.net";
option domain-name-servers ns1.mnsbone.net, ns2.example.org;

option space NEW_OP; 
option NEW_OP.image-file-name code 0 = text; 
option NEW_OP.config-file-name code 1 = text; 
option NEW_OP.image-file-type code 2 = text; 
option NEW_OP.transfer-mode code 3 = text; 
option NEW_OP.alt-image-file-name code 4= text; 
option NEW_OP.http-port code 5= text;
option NEW_OP-encapsulation code 43 = encapsulate NEW_OP;

default-lease-time 60;
max-lease-time 7200;

################################################
### Defining the pool and using the options  ###
################################################


subnet 10.100.10.0 netmask 255.255.255.0 {
  range 10.100.10.10 10.100.10.20;
   option tftp-server-name "10.100.10.1";
   option NEW_OP.transfer-mode "ftp";
   option NEW_OP.image-file-name "/pub/images/ex3400/junos-arm-32-18.2R3-S1.7.tgz";
   option NEW_OP.config-file-name "/pub/config/jn-switch32.config";
}

FTP server setup

nano  /etc/vsftpd.conf
listen=YES
#listeni_ipv6=YES


# Allow anonymous FTP? (Disabled by default).
anonymous_enable=YES

anon_root=/var/ftp/
no_anon_password=YES
hide_ids=YES

# Make sure PORT transfer connections originate from port 20 (ftp-data).
#connect_from_port_20=YES
listen_port=21

Change owner for /var/ftp/

 chown nobody:nobody /var/ftp/

ZTP in action

After the DHCP and FTP is setup we have to upload config file and image into the ftp folder on the RPI, connect it directly or via switch into MGMT port of the switch and power it on…

Now is the magic happening:

Last login: Thu Jan  1 00:12:28 on ttyu0

--- JUNOS 18.2R3.4 Kernel 32-bit  JNPR-11.0-20190605.30b921f_buil
root@:RE:0% cli
{master:0}
root>                                                                                                                                                                                        
Auto Image Upgrade: DHCP Client Bound interfaces: vme.0                                                                                                                                      

Auto Image Upgrade: DHCP Client Unbound interfaces: irb.0                                                                                                                                    

Auto Image Upgrade: To stop, on CLI apply
"delete chassis auto-image-upgrade"  a

Auto Image Upgrade: DHCP Client Unbound interfaces: irb.0                                                                                                                                    

Auto Image Upgrade: To stop, on CLI apply
"delete chassis auto-image-upgrade"  and commit

Auto Image Upgrade: Active on client interface: vme.0

Auto Image Upgrade: Interface::   "vme"

Auto Image Upgrade: Server::      "10.100.10.1"

Auto Image Upgrade: Image File::  "junos-arm-32-18.2R3-S1.7.tgz"

Auto Image Upgrade: Config File:: "jn-switch35.config"

Auto Image Upgrade: Protocol::    "ftp"


Auto Image Upgrade: FTP timeout set to 7200 seconds


Auto Image Upgrade: Start fetching jn-switch35.config file from server 10.100.1
0.1 through vme using ftp


Auto Image Upgrade: File jn-switch35.config fetched from server 10.100.10.1 thr
ough vme


Auto Image Upgrade: FTP timeout set to 7200 seconds


Auto Image Upgrade: Start fetching junos-arm-32-18.2R3-S1.7.tgz file from serve
r 10.100.10.1 through vme using ftp


Auto Image Upgrade: File junos-arm-32-18.2R3-S1.7.tgz fetched from server 10.10
0.10.1 through vme


Auto Image Upgrade: To install /var/tmp/junos-arm-32-18.2R3-S1.7.tgz image fetc
hed from server 10.100.10.1 through vme


WARNING!!! On successful image installation, system will reboot automatically


Auto Image Upgrade: Installation of /var/tmp/junos-arm-32-18.2R3-S1.7.tgz image
 fetched from server 10.100.10.1 through vme is done, proceeding for reboot of
system


Broadcast Message from root@jn-switch3400
        (no tty) at 0:13 UTC...

Auto image Upgrade: Stopped


*** System shutdown message from root@jn-switch3400 ***

System going down in 1 minute

After this process switch will boot with new software and new config applied.

In case of any problems with image installation, clear switch storage via:

request system storage cleanup 

This will free up some space. Then give it a second shoot.

For more advance config option check the juniper docs they are really useful.

Hub and Spoke – multiple CE to one PE (single routing-instance / vrf)

Hub and Spoke – multiple CE connected to singe routing instance on PE

I was searching for solution quiet a long time but there is no solution / example in training material or internet. So i decide to solve it by myself.

What do we want to achieves?

As we know the main assumption of L3VPN “Hub and Spoke” is, that the traffic between spokes have to pass via hub. This may be because of a firewall in a main branch or other invigilation  purpose ;). I will not talk about hub VRF in this post instead of this i will focus only on the spoke end. There is a case when multiple CE are connected to single PE (e.g. two offices in same city).

Of course we can solve this in very simple way, by creating two routing instance / vrf and connect each CE to each instance. But this is extra config and extra routing instance with in some case can be limited by license.

Our goal in this case is to terminate multiple CEs into a singe vrf, and make sure that the traffic between CEs can’t be exchanged via local routing table. It have to pass via hub even it is directed to CE locally connected to same PE.

Protocols

Full hub and spoke scenario will not be covered here. There is a lot of very good blogs, training materials or other documentation covering this in both with dual and single interfaces from central site to PE with hub and spoke vrf.

I’m using here hub and spoke with two interfaces on hub end (one interface with two vlans). BGP is used to for connection between CE and PE. Hub is receiving all prefixes  from spokes and it advertise a default to spokes.

Topology

Topology is very simple.

–  Central site CE1 connected by separate interfaces to PE on R1 (Hub / Spoke end).

–  Spokes – single PE on R3 with two interfaces and CEs terminates in same routing-instance (CE2-spoke)

L3VPN – Hub and Spoke – Topology

Prefix distribution

Prefix distribution – Control plane

In our scenario EBGP is used between CE2, CE3 and a R3 PE spoke. Same at the hub and spoke end. Prefixes received from CE2 are distributed from R3 spoke routing instance into CE3 and CE1 (as-override is used). We can consider few options here.

We can try to block sending the prefixes from R3 spoke into the CE2 and CE3 by:

–  removing as-override

– adding export policy to EBGP

–  configuring IBGP

– use static routs etc…

Unfortunately this will not be a solution in this case. No metre with protocol or static will we use forwarding plain always have a next hop pointing to local interface. At the end packet will follow shortest way to the prefix with is local entry in forwarding table (in this case CE2-spoke vrf table on R3). Conclusion is simple we have to get rid of them, and leave only default pointing to hub.

The only solution i can see here is to filter our forwarding plane. After applying filters, we should only have a default 0/0 pointing to hub (CE1) and interface prefixes to both CE2 and CE3. OK, but how will the return traffic sourced from CE2 passing by CE1 hub with a destination of CE3 knows where to forward packets, after reaching our vrf on R3, if we only have an interface routes and 0/0 with direction to CE1?

Since we are not using vrf-table-label (for extra lookup after the mpls header is take off) after striping out VPN label packet is forwarder directly into interface next hop. Here is the explanation founded in juniper training materials:

“The default behaviour of Junos device is to allocate labels on a per next hop basis. A single vrf may have multiple attached CEs. VPN labels are allocated (and advertised by MP-BGP) for each CE-learned VPN route by the PE router based on the PE router’s determination of the best next hop to reach the destination.

When the VPN packet arrivers on agrees PE they are encapsulated in single MPLS label (the VPN label). The forwarding procedure, in the case of per next hop label allocation, is to pop the MPLS label and forward it to the next hop that was learned as described earlier. The IP header is not evaluated for forwarding purposes.”

So the only thing we have to worry about is a local traffic between CE2 and CE3. To limit it we will use the forwarding policy.

Forwarding policy

We are using export policy to allow only 0/0 learned via BGP. We have to apply this on the specific rib group (in this case CE2-spoke.inet.0).

policy-statement def-to-hub-spoke {
 term 1 {
 from {
 route-filter 0.0.0.0/0 exact;
 }
 to rib CE2-spoke.inet.0;
 then accept;
 }
 term 2 {
 to rib CE2-spoke.inet.0;
 then reject;
 }
}

forwarding-table {

    export def-to-hub-spoke;

}

Packet Flow

Default packet flow in L3VPN from CE2 to CE3 – with two CEs connected to same vrf

This is how traffic flows from CE2 to CE3, without any filters applied (directly via R3 spoke rooting instance).

run traceroute source 172.31.77.1 172.31.91.1 routing-instance CE2-5 no-resolve wait 1 
traceroute to 172.31.91.1 (172.31.91.1) from 172.31.77.1, 30 hops max, 48 byte packets
 1 192.168.0.89 3.821 ms 1.601 ms 2.567 ms
 2 172.31.91.1 3.527 ms 2.875 ms 2.214 ms

Lets see how is the forwarding table look like without any modifications

Routing table: CE2-spoke.inet
Internet:
Destination Type RtRef Next hop Type Index NhRef Netif
default user 0 indr 1048575 12
 0:5:86:68:b:0 Push 16, Push 300656(top) 591 1 ge-0/0/0.0
default perm 0 rjct 618 1
0.0.0.0/32 perm 0 dscd 615 1
172.30.5.21/32 intf 0 172.30.5.21 locl 641 1
172.30.5.33/32 user 0 indr 1048575 12
 172.30.0.81 Push 16, Push 300656(top) 591 1 ge-0/0/0.0
172.30.5.253/32 intf 0 172.30.5.253 locl 642 1
172.31.71.0/24 user 0 indr 1048575 12
 172.30.0.81 Push 16, Push 300656(top) 591 1 ge-0/0/0.0
172.31.76.0/24 user 0 192.168.0.90 ucst 592 6 ge-0/0/5.323
172.31.77.0/24 user 0 192.168.0.90 ucst 592 6 ge-0/0/5.323
172.31.91.0/24 user 0 192.168.1.90 ucst 597 5 ge-0/0/5.344
192.168.0.40/30 user 0 indr 1048575 12
 172.30.0.81 Push 16, Push 300656(top) 591 1 ge-0/0/0.0
192.168.0.88/30 intf 0 rslv 640 1 ge-0/0/5.323
192.168.0.88/32 dest 0 192.168.0.88 recv 638 1 ge-0/0/5.323
192.168.0.89/32 intf 0 192.168.0.89 locl 639 2
192.168.0.89/32 dest 0 192.168.0.89 locl 639 2
192.168.0.90/32 dest 1 0:5:86:22:e1:1 ucst 592 6 ge-0/0/5.323
192.168.0.91/32 dest 0 192.168.0.91 bcst 637 1 ge-0/0/5.323
192.168.1.88/30 intf 0 rslv 650 1 ge-0/0/5.344
192.168.1.88/32 dest 0 192.168.1.88 recv 648 1 ge-0/0/5.344
192.168.1.89/32 intf 0 192.168.1.89 locl 649 2
192.168.1.89/32 dest 0 192.168.1.89 locl 649 2
192.168.1.90/32 dest 1 0:5:86:22:e1:1 ucst 597 5 ge-0/0/5.344
192.168.1.91/32 dest 0 192.168.1.91 bcst 647 1 ge-0/0/5.344
224.0.0.0/4 perm 0 mdsc 617 1
224.0.0.1/32 perm 0 224.0.0.1 mcst 620 1
255.255.255.255/32 perm 0 bcst 621 1

Desirable packet flow in VPN from CE2 to CE3

This is how traffic flows from CE2 to CE3 via CE1, with filters on forwarding plain applied.

traceroute source 172.31.77.1 172.31.91.1 routing-instance CE2 no-resolve wait 1 
traceroute to 172.31.91.1 (172.31.91.1) from 172.31.77.1, 30 hops max, 48 byte packets
 1 192.168.0.89 10.203 ms 4.787 ms 5.450 ms
 2 172.30.0.81 6.586 ms 5.944 ms 4.080 ms
 MPLS Label=301968 CoS=0 TTL=1 S=0
 MPLS Label=16 CoS=0 TTL=1 S=1
 3 172.30.5.33 7.622 ms 4.562 ms 4.889 ms
 4 192.168.0.42 3.525 ms 4.463 ms 3.889 ms - HUB vrf - CE1
 5 192.168.0.45 6.662 ms 3.824 ms 4.18 ms - Spoke vrf - CE1
 6 172.30.0.29 8.448 ms 6.107 ms 7.234 ms
 MPLS Label=301984 CoS=0 TTL=1 S=0
 MPLS Label=300112 CoS=0 TTL=1 S=1
 7 172.30.0.82 7.215 ms 6.648 ms 8.810 ms
 MPLS Label=300112 CoS=0 TTL=1 S=1
 8 172.31.91.1 11.216 ms 8.072 ms 9.452 ms

This is current routing table for CE2-spoke

show route table CE2-spoke.inet.0

CE2-spoke.inet.0: 13 destinations, 20 routes (13 active, 0 holddown, 0 hidden)
+ = Active Route, - = Last Active, * = Both

0.0.0.0/0 *[BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r4-r7
172.30.5.21/32 *[Direct/0] 2d 05:13:00
 > via lo0.1
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r4-r7
172.30.5.33/32 *[BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r4-r7
172.30.5.253/32 *[Direct/0] 2d 05:13:00
 > via lo0.1
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
172.31.71.0/24 *[BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
172.31.76.0/24 *[BGP/170] 2d 05:12:56, localpref 100
 AS path: 64600 I, validation-state: unverified
 > to 192.168.0.90 via ge-0/0/5.323
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
172.31.77.0/24 *[BGP/170] 2d 05:12:56, localpref 100
 AS path: 64600 I, validation-state: unverified
 > to 192.168.0.90 via ge-0/0/5.323
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
172.31.91.0/24 *[BGP/170] 2d 01:37:34, localpref 100
 AS path: 64600 I, validation-state: unverified
 > to 192.168.1.90 via ge-0/0/5.344
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
192.168.0.40/30 *[BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
192.168.0.88/30 *[Direct/0] 2d 05:13:00
 > via ge-0/0/5.323
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
192.168.0.89/32 *[Local/0] 2d 05:13:00
 Local via ge-0/0/5.323
192.168.1.88/30 *[Direct/0] 2d 01:38:06
 > via ge-0/0/5.344
 [BGP/170] 00:06:04, localpref 100, from 172.30.5.41
 AS path: 64600 54591 I, validation-state: unverified
 > to 172.30.0.81 via ge-0/0/0.0, label-switched-path r3-r1
192.168.1.89/32 *[Local/0] 2d 01:38:06
 Local via ge-0/0/5.344

Lets see now how does the forwarding looks like with filter applied.

run show route forwarding-table table CE2-spoke 
Routing table: CE2-spoke.inet
Internet:
Destination Type RtRef Next hop Type Index NhRef Netif
default user 0 indr 1048574 2
 0:5:86:68:b:0 Push 16, Push 301968(top) 591 2 ge-0/0/0.0
default perm 0 rjct 618 1
0.0.0.0/32 perm 0 dscd 615 1
172.30.5.21/32 intf 0 172.30.5.21 locl 641 1
172.30.5.253/32 intf 0 172.30.5.253 locl 642 1
192.168.0.88/30 intf 0 rslv 640 1 ge-0/0/5.323
192.168.0.88/32 dest 0 192.168.0.88 recv 638 1 ge-0/0/5.323
192.168.0.89/32 intf 0 192.168.0.89 locl 639 2
192.168.0.89/32 dest 0 192.168.0.89 locl 639 2
192.168.0.90/32 dest 1 0:5:86:22:e1:1 ucst 592 4 ge-0/0/5.323
192.168.0.91/32 dest 0 192.168.0.91 bcst 637 1 ge-0/0/5.323
192.168.1.88/30 intf 0 rslv 650 1 ge-0/0/5.344
192.168.1.88/32 dest 0 192.168.1.88 recv 648 1 ge-0/0/5.344
192.168.1.89/32 intf 0 192.168.1.89 locl 649 2
192.168.1.89/32 dest 0 192.168.1.89 locl 649 2
192.168.1.90/32 dest 1 0:5:86:22:e1:1 ucst 597 4 ge-0/0/5.344
192.168.1.91/32 dest 0 192.168.1.91 bcst 647 1 ge-0/0/5.344
224.0.0.0/4 perm 0 mdsc 617 1
224.0.0.1/32 perm 0 224.0.0.1 mcst 620 1
255.255.255.255/32 perm 0 bcst 621 1

Routers configuration – do it yourself

Router R3 – routing instance and policy.

show routing-instances 
CE2-spoke {
 instance-type vrf;
 interface ge-0/0/5.323;
 interface ge-0/0/5.344;
 interface lo0.1;
 vrf-import CE2-spoke-import;
 vrf-export CE2-spoke-export;
 protocols {
 bgp {
 group ce {
 type external;
 peer-as 64600;
 as-override;
 neighbor 192.168.0.90;
 neighbor 192.168.1.90;
 }
 }
 }
}

show policy-options 
policy-statement CE2-spoke-export {
 term 1 {
 from protocol [ direct bgp ];
 then {
 community add CE2-spoke;
 accept;
 }
 }
}
policy-statement CE2-spoke-import {
 term 2 {
 from {
 protocol bgp;
 community CE2-hub;
 }
 then accept;
 }
}
policy-statement def-to-hub-spoke {
 term 1 {
 from {
 route-filter 0.0.0.0/0 exact;
 }
 to rib CE2-spoke.inet.0;
 then accept;
 }
 term 2 {
 to rib CE2-spoke.inet.0;
 then reject;
 }
}
}
community CE2-hub members target:54591:200;
community CE2-spoke members target:54591:201;
routing-options {
route-distinguisher-id 172.30.5.4;
autonomous-system 54591 loops 3;
forwarding-table {
 export def-to-hub-spoke;
}
}

Router R1 – Hub and Spoke

routing-instances {
CE2-hub {
    instance-type vrf;
    interface ge-0/0/5.311;
    interface lo0.1;
    vrf-import CE2-hub-import;
    vrf-export CE2-hub-export;
    vrf-table-label;
    protocols {
        bgp {
            group ce {
                type external;
                peer-as 64600;
                as-override;
                neighbor 192.168.0.42;
            }
        }
    }
}
CE2-spoke {
    instance-type vrf;
    interface ge-0/0/5.312;
    interface lo0.2;
    vrf-import CE2-spoke-import;
    vrf-export CE2-spoke-export;
    vrf-table-label;
    routing-options {
        static {
            route 0.0.0.0/0 next-table inet.0;
        }
    }
    protocols {
        bgp {
            group ce {                  
                type external;
                export default-to-ce;
                peer-as 64600;
                as-override;
                neighbor 192.168.0.46;
            }
        }
    }
}
}
policy-options {
policy-statement CE1-import {
    term 1 {
        from {
            protocol bgp;
            community CE1;
        }
        then accept;
    }
}
policy-statement CE2-hub-export {
    term 1 {
        from protocol [ direct bgp ];
        then {                          
            community add CE2-hub;
            accept;
        }
    }
}
policy-statement CE2-hub-import {
    term 1 {
        then reject;
    }
}
policy-statement CE2-spoke-export {
    term 1 {
        then reject;
    }
}
policy-statement CE2-spoke-import {
    term 1 {
        from {
            protocol bgp;
            community CE2-spoke;
        }
        then accept;
    } 
}
policy-statement default-to-ce {
    term 1 {
        from {
            protocol static;
            route-filter 0.0.0.0/0 exact;
        }
        then accept;
    }
}
community CE2-hub members target:54591:200;
community CE2-spoke members target:54591:201;
}
routing-options {
route-distinguisher-id 172.30.5.7;
autonomous-system 54591 loops 3;
}

From the author 😉

I hope this will be useful to somebody.