marți, 30 aprilie 2013

MPLS - roots bloody roots

Multiprotocol Label Switching este o functionalitate foarte intalnita in routerele ISP, prin care acestea nu mai verifica propria tabela de rutare pt a gasi calea catre destinatia unui mesaj, ci utilizeaza o noua tabela, care contine corespondente intre etichete (un numar pe 20 de biti) si interfete de iesire. Avantajele sunt multiple si le voi puncta pe parcursul fiecarui post ce va urma acestuia.

Acest post are mai mult valoare teoretica decat practica, totusi trebuie sa raminem cu ideea ca pt. configurarea acestei noi metode de comutare a pachetelor avem nevoie de routere, protocoale de rutare IGP, de BGP (in urmatorul post) si de ceva in plus pe care incerc sa il evidentiez aici.

Ideea consta in asocierea unei etichete MPLS, de exemplu,  cu o adr IP si anuntarea acestei etichete printr-un prot. de rutare destinat acestei sarcini, LDP (Label Distribuition Protocol).
Totodata si adr. IP este anuntata printr-un prot. de rutare IGP. Corespondenta intre eticheta MPLS si adresa IP este importanta doar la intrarea pachetului IP in routerele PE, apoi conteaza numai corespondenta intre etichete.
Presupunem ca un pachet IP are sursa 192.168.2.x si destinatia 192.168.1.x (cele doua retele, in acest caz, sunt direct conectate de router PE2 si respectiv PE1). Routerul PE2 incapsuleaza pachetul IP intr-un frame MPLS (prin adaugarea unui header MPLS) si apoi intr-un frame corespunzator prot. Layer 2 OSI de pe interfata de iesire  (in cazul meu HDLC). In figura de mai jos observa ca intre headerul HDLC si cel IP se gaseste headerul MPLS, in care valoarea etichetei este 18 si TTL=255.
Tabela LFIB a routerului PE2 din imaginea de mai jos confirma ca eticheta corespunzatoare subnetului destinatie 192.168.10.0/24 este 18 (outgoing tag). Acest router a primit aceasta eticheta, prin prot. de rutare LDP, de la routerul P2.
Acum mesajul a ajuns in routerul P2 si are eticheta 18. Acest router nu se mai uita in tabela si de rutare, ci numai in LFIB. Se observa ca eticheta 18 (local tag) trebuie inlocuita cu 17 (outgoing tag) si frame-ul MPLS trebuie trimis pe interfara Se 1/1. In plus, TTL se decrementeaza cu 1.
Astfel, din router P2 pleaca mesajul in forma de mai jos (vezi eticheta si TTL).
Acum mesajul a ajuns in routerul P1 si are eticheta 17. Nici acest router nu se uita in tabela de rutare, ci numai in LFIB. De aceasta data etichetei 17 (local tag) ii corespunde o eticheta speciala (valoarea numerica 3) care inseamna 'imposition-null' si care determina routerul P1 sa elimine headerul MPLS (pop tag) si sa trimita spre PE1 pachetul IP asa cum l-a primit initial routerul PE2, cu exceptia TTL (acum 253).
Mesajul trimis de catre P1 nu mai contine headerul MPLS, iar TTL este 253.
Dupa ce mesajul a ajuns in PE1, doar acest router se mai uita in tabela de rutare proprie pt a determina intf. de iesire a pachetului.

Ca sa se intample toate acestea, trebuie sa configuram interfetele de leg. dintre routere cu comanda mpls ip si un prot de rutare IGP (OSPF, ISIS, EIGRP) cu timp de convergenta mic. Trebuie sa analizati daca este necesar sa mariti si MTU, un header MPLS are exact 20+3+1+8=32 biti (eticheta MPLS + Experimental_bits + Stack_bit + MPLS_TTL).
Experimental_bits -3 biti care se folosesc pt marcarea frame-urilor in vederea implementarii QoS.
Stack bit - bitul care indica daca acest header este ultimul din stiva (S=1) sau mai urmeaza si alte headere MPLS (S=0).
TTL_MPLS - campul de 8 biti care previne intrarea in bucla a unui frame MPLS.

Etichetele MPLS ajung de la un router la altul prin prot de rutare special, standard, denumit LDP - Label Distribution Protocol, care se activeaza automat dupa ce se configureaza routerele cum am precizat anterior.
Se formeaza in mod automat adiacente LDP intre routere; mesajele LDP sunt incapsulate in TCP/646.

De exemplu configuratia routerului P1 este:
interface Loopback1
 ip address 1.1.1.1 255.255.255.255
!
interface Serial1/0
 description link_2_PE1
 ip address 172.16.1.1 255.255.255.252
 ip router isis IGP
 mpls ip
 !
interface Serial1/1
 description link_2_P2
 ip address 172.16.0.1 255.255.255.252
 ip router isis IGP
 mpls ip
!
router isis IGP
 net 49.0001.0010.0100.1001.00
 is-type level-2-only
 log-adjacency-changes
 redistribute connected

Adiacentele LDP pe care le formeaza cu routerele PE1 si P2:
P1#sh mpls ldp neighbor
    Peer LDP Ident: 2.2.2.2:0; Local LDP Ident 1.1.1.1:0
        TCP connection: 2.2.2.2.34835 - 1.1.1.1.646
        State: Oper; Msgs sent/rcvd: 13/11; Downstream
        Up time: 00:00:52
        LDP discovery sources:
          Serial1/1, Src IP addr: 172.16.0.2
        Addresses bound to peer LDP Ident:
          2.2.2.2         172.16.0.2
    Peer LDP Ident: 192.168.10.1:0; Local LDP Ident 1.1.1.1:0
        TCP connection: 192.168.10.1.31261 - 1.1.1.1.646
        State: Oper; Msgs sent/rcvd: 10/9; Downstream
        Up time: 00:00:41
        LDP discovery sources:
          Serial1/0, Src IP addr: 172.16.1.2
        Addresses bound to peer LDP Ident:
          172.16.1.2      10.0.0.1        192.168.10.1

Tabela de rutare:
P1#sh ip route
Codes: C - connected, S - static, R - RIP, M - mobile, B - BGP
       D - EIGRP, EX - EIGRP external, O - OSPF, IA - OSPF inter area
       N1 - OSPF NSSA external type 1, N2 - OSPF NSSA external type 2
       E1 - OSPF external type 1, E2 - OSPF external type 2
       i - IS-IS, su - IS-IS summary, L1 - IS-IS level-1, L2 - IS-IS level-2
       ia - IS-IS inter area, * - candidate default, U - per-user static route
       o - ODR, P - periodic downloaded static route

Gateway of last resort is not set

     1.0.0.0/32 is subnetted, 1 subnets
C       1.1.1.1 is directly connected, Loopback1
     2.0.0.0/32 is subnetted, 1 subnets
i L2    2.2.2.2 [115/10] via 172.16.0.2, Serial1/1
i L2 192.168.10.0/24 [115/10] via 172.16.1.2, Serial1/0
     172.16.0.0/30 is subnetted, 3 subnets
i L2    172.16.20.0 [115/20] via 172.16.0.2, Serial1/1
C       172.16.0.0 is directly connected, Serial1/1
C       172.16.1.0 is directly connected, Serial1/0
i L2 192.168.20.0/24 [115/20] via 172.16.0.2, Serial1/1
     10.0.0.0/32 is subnetted, 2 subnets
i L2    10.0.0.2 [115/20] via 172.16.0.2, Serial1/1
i L2    10.0.0.1 [115/10] via 172.16.1.2, Serial1/0

Tabela LFIB:
P1#sh mpls forwarding-table
Local  Outgoing    Prefix            Bytes tag  Outgoing   Next Hop
tag    tag or VC   or Tunnel Id      switched   interface
16     Pop tag     2.2.2.2/32                         0          Se1/1      point2point
17     Pop tag     172.16.20.0/30               0          Se1/1      point2point
18     Pop tag     10.0.0.1/32                      0          Se1/0      point2point
19     Pop tag     192.168.10.0/24             0          Se1/0      point2point
20     20          10.0.0.2/32                           0          Se1/1      point2point
21     21          192.168.20.0/24                 0          Se1/1      point2point

Urmatorul post va fi despre VPN MPLS Layer 3, practic vom vedea frame-uri MPLS cu doua headere MPLS si un singur header IP, adrese VPNv4, VRF-uri si cum putem configura intraneturile a doi clienti care au acelasi plan de adresare privat, fara ca routerele sa fie deranjate ca pe doua interfete au aceeasi adresa IP.

joi, 25 aprilie 2013

... cu de toate (Linux, Expect, Cisco)


Ideea de la care am pornit a fost sa fac un script in BASH si cu ajutorul limbajul EXPECT sa execut comenzi, unele interactive (adica trebuie sa introduci parametrii, de ex. comanda enable
solicita introducerea unui parole s.a.m.d), pe un numar de elemente de retea (aici Cisco) a.i. sa pot configura la orice moment un numar cat mai mare din acestea fara sa ma conectez eu pe fiecare in parte.

Ziceam pe cand predam cursuri - si in continuare cred in faptul - ca si lenea a dus la progres.

Ceea ce am scris mai jos nu necesita cunostinte avansate de BASH scripting si de EXPECT, eu nu stiu mai mult de genunchiul broastei; totusi in 3-4 zile am reusit sa inteleg sintaxa de baza pt BASH si EXPECT si iata ce a iesit.

Scriptul se lanseaza cu comanda sh <numescript>.sh <inputFile>.csv

Fisierul cu datele de intrare <inputFile>.csv este in format csv, fiecare linie este de forma:
ipaddr,<numefisier>.txt (de ex: 192.168.1.2,routerBackbone.txt)

<numefisier>.txt contine pe fiecare linie comenzile pe care doriti sa le executati pe fiecare element de retea identificat cu ipaddr, de ex.:

show clock
conf t
interface Gi 0/0/0
no shutdown
ip address 10.0.0.0.1 255.255.255.0
exit
exit
write memory

De la linia de comanda se introduce user/parola, parola VTY si enable secret; doar userul este afisat pe ecran
Erorile se salveaza in fisierul <numefisier>.error.log, iar outputul comenzilor executate pe fiecare element de retea se savleaza in fisierul <numefisier>.report

Va rog sa inlocuiti <numefisier> cu ceva care are sens pt fiecare.

Continutul fisierului <numescript>.sh este:


#!/bin/bash

#functia de mai jos se apeleaza cu 3 parametrii si practic trimite 5 mesaje ICMP echo de marime 512 octeti 
#si asteapta raspuns timp de 10 sec
#nu ma intereseaza sa capturez outputul comenzii ping si nici eventualele erori

isAlive() {
host=$1
contor=$2
rows=$3
         echo -e "\n$contor/$rows#####start probing...$host"
         ping -c 5 -s 512 -w 10 $host > /dev/null 2>&1
}

#IFS este parametrul prin care ii transmit BASH ca Internal Field Separator este ,
IFS=","

#mai jos ii transmit BASH ca fisierul de intrare este primul argument din comanda de lansare
inputFile=$1

#ma asigur ca fisierul de intrare exista (-f) si ca are o lungime diferita de 0 octeti (-s)
if [ -f $inputFile -a -s $inputFile ]
then

#mai jos solicit utilizatorului sa introduca user/parola, VTY line password si enable secret
 stty echo
 echo username:
 read userName
 echo password:
 stty -echo
 read passWord
 stty echo
 echo VTY password:
 stty -echo
 read VTYpassWord
 stty echo
 echo ROOT/ENABLE password:
 stty -echo
 read RootPassWord
 stty echo

#mai jos ma asigur ca s-au introdus niste valori si nu s-a apasat doar Enter
 if [ "x$userName" != x -a "x$passWord" != x -a "x$VTYpassWord" != x -a "x$RootPassWord" != x ]
 then

#numar cate elemente de retea sunt in total
rows=`cat $inputFile | wc -l`

#sterg continutul fisierului cu erori si al celui cu outputul comenzilor
echo > <numefisier>.error.log
echo > <numefisier>.report




#initializez un contor care imi raporteaza indexul elementului de retea la care a ajuns scriptul 


i=1





#parcurg fisierul inputFile linie cu linie pana la sfarsitul acestuia

while read line

        do

#varialbila host va contine adresa IP a elementului de retea
                host=`echo $line | awk '{print $1}'`

#variabila inputCmdFile va contine numele fisierului cu comenzile de executat
                inputCmdFile=`echo $line | awk '{print $2}'`

#daca fisierul cu comenzi exista (-f) si are marimea diferita de 0 octeti (-s)
                if [ -f $inputCmdFile -a -s $inputCmdFile ]
                then

#variabila NOW contine momentul de timp, folositor pt raportare erori
                 NOW=$(date +"%c")

#apelez functia prin care testez ca elementul de retea este pornit
                 isAlive $host $i $rows

#daca rezultatul de executie al functiei isAlive este 0 (in BASH valoarea 0 inseamna SUCCESS)
                 if [ $? -eq "0" ]
                 then

#apelez scriptul <numescriptEXPECT>.expect pe care il gasiti mai jos (eu am facut acest script ca fiind 
#executabil) si outpul executiei il documentez in fisierul <numefisier>.report
./<numescriptEXPECT>.expect $inputCmdFile $host $userName $passWord $VTYpassWord $RootPassWord $NOW >> <numefisier>.report

#incrementez contor 
                       let i++

#daca elementul de retea nu raspunde la ping, documentez eroarea in fisierul cu erori si incrementez contor
                else
                        echo $NOW,$host",INACCESIBILl!" >> sendCmdCisco.error.log
                        let i++
                 fi

#daca un fisier cu comenzi nu exista sau are dimensiune 0 octeti, trec la urmatorul element


                else

                 continue
                fi
        done < $inputFile



#daca nu s-a introdus una  valorile user/password/VTYpassword/enableSecret, se iese din script
else echo "EROARE: Nu ati introdus user/password/VTYpassword/rootpassword!"; exit 1
 fi



#daca nu s-a introdus argumentul pt lansarea scriptului (cel cu datele de intrare), se iese din script

else

 echo "EROARE: Verificati fisierul de intrare $inputFile !"; exit 1
fi


Scriptul scris in EXPECT (<numescriptEXPECT>.expect) arata dupa cum urmeaza:

#!/usr/local/bin/expect

#parcurg fisierul cu comenzi si introduc fiecare comanda in lista inputCmd
set inputCmd [split [read [open [lindex $argv 0] r]] "\n"]

#deschid fisierul cu erori in modul append
set errorFile [open "<numefiser>.error.log" a]

#initializez variablilele host, userName, passWord, VTYpassword, RootPassWord si recordTime
set host [lindex $argv 1]
set userName [lindex $argv 2]
set passWord [lindex $argv 3]
set VTYpassWord [lindex $argv 4]
set RootPassWord [lindex $argv 5]
set recordTime [lindex $argv 6]

#initiez sesiunea Telnet
spawn telnet $host

#daca se solicita username atunci trimit valorile variabilelor userName si apoi passWord
#daca se solicita numai parola atunci trimit valorea variabilelei VTYpassWord
#daca nu se solicita nimic timp de 10 sec atunci documentez eroarea
expect {
"*sername: " {send "$userName\n"; expect "*assword: "; send "$passWord\n"}
"*assword: " {send "$VTYpassWord\n"}
timeout {puts $errorFile "$recordTime,$host,TIMEOUT 1"; exit 1}
}

#daca nu primesc promptul specific user-EXEC (adica >) pt a trece mai departe si se solicita din nou user 
#sau parola VTY atunci documentez eroarea (poate ati gresit cand ati introdus user/parola etc)
expect {
"*sername: " {puts $errorFile "$recordTime,$host,contul de acces $userName INCORECT!!!"; exit 1}
"*assword: " {puts $errorFile "$recordTime,$host,parola de acces VTY INCORECTA!!!"; exit 1}

#SUCCESS, suntem in modul user-EXEC
"*>"
}

#trimit comanda enable pt a ajunge in privileged-EXEC
send "enable\n"

#se solicita parola de escaladare a niv de privilegii (enable secret)
expect "*assword: "

#trimit valoarea variabilei care contine aceasta parola
send "$RootPassWord\n"

#daca nu primesc promptul specific priviled-EXEC (adica #) pt a trece mai departe si solicita din nou parola 
#atunci documentez eroarea (poate ati gresit cand ati introdus enable secret etc)
expect {
"*>" {puts $errorFile "$recordTime,$host,parola de acces in privilegiul 15 INCORECTA!!!"; exit 1}
"*assword: " {puts $errorFile "$recordTime,$host,parola de acces in privilegiul 15 INCORECTA!!!"; exit 1}

#SUCCESS, suntem in modul privileged-EXEC
"*#"
}

#se trimite fiecare comanda din lista comenzi si apoi se inchide sesiunea Telnet,
#parasind astfel si scriptul scris in EXPECT
foreach cmd $inputCmd {
send "$cmd\n"
expect "*#"
}
exit 0

Ca sa recapitulam:
1. trebuie sa aveti instalat EXPECT
2. va trebuie fisierul <numefisier>.EXPECT - vezi mai sus
3. va trebuie fisierul <numefisier>.sh - vezi mai sus
4. va trebuie <numefisier>.csv - vezi mai sus
5. va trebuie <numefisier>.txt (atatea fisiere cate hosturi aveti, daca nu rulati aceleasi comenzi, altfel numai unul singur) - vezi mai sus

Eu am rulat cu sucess si cu BASH si cu KSH (cu mici modificari) atat din RHEL, Debian, Sun Solaris.
Spor in tot ce faceti si sper sa am vesti cat de curand pt cei care le asteapta (acum suntem cu actele la judecatorie - greu la deal cu boii mici si vale tre' sa-mpingi).