Programmering for nettverksteknikere

Anders Låstad den 18. mars 2016
Programmering for nettverksteknikere.png

Programmering har alltid vært en nyttig egenskap, og trolig blir det i framtiden en mye større del av hverdagen til mange.For de som ikke har fått prøvd seg på programmering eller skripting enda,er det kanskje på tide å gi det en sjanse.

De siste årene har det skjedd en rivende utvikling på programvare-siden i nettverksverden. Openflow, CiscoOnePK, Cisco ACI og VMware NSX er noen eksempler på nye API som kan benyttes for å utøve kontroll over nettverket. Dette har potensialet til å endre måten vi jobber på som nettverksteknikere. Cisco IOS har i lengre tid støttet Tcl skript direkte på ruteren/switchen og Cisco Nexus har støtte for Python. 

Hvilket språk skal man lære?

Dette er egentlig ikke så viktig, mange av konseptene er ofte de samme uavhengig av språk, så man tilegner seg generell forståelse for programmeringsprinsipper og kontroll strukturer.
Har man en kollega eller venn man kan støtte seg på, kan det være en god idé å velge språket de kan. Ellers er Python en god kandidat da det er relativt enkelt, kraftig, godt dokumentert og er støttet blant annet i CiscoOnePK.

Hvor skal man begynne?

Begynn i det små. Lag enkle verktøy, automatiser enkle rutine oppgaver. Hva med ett skript som automatisk lagrer konfigurasjonen fra Cisco enheten til TFTP server basert på SNMP? Når dette fungerer kan man enkelt integrere det med syslog serveren som automatisk kjører skriptet når den får melding om konfigurasjonsendring på enheten.

Ganske enkelt, men veldig nyttig.
Små enkle steg mot en bedre hverdag er bedre enn ett gigantisk hopp man aldri når.

«Stjel» mye og ofte!

Ett godt prinsipp som vi alle burde følge. Vi når alle høyere om vi kan stå på skuldrene til de som har gått foran oss.Trolig har noen allerede gjort deler av det vi prøver å løse, kanskje har de også gjort det på en smartere måte en hva en selv kunne kommet på.Det har selvsagt en verdi å lage ting selv fra ett læringsperspektiv, men ikke føl at du trenger å lage alt selv. Forstå hva du trenger og støtt deg på andres bidrag, dette gjør deg selv mer effektiv. På sikt kan du selv også bidra til fellesskapet.

Eksempel

Jeg var ute hos en kunde som hadde behov for å få registrert switcher og rutere i DNS. Selvsagt kunne vi lagt inn enhetene manuelt, men hva når det skjer en endring i nettverket? Da må man manuelt oppdatere DNS, ikke den mest spennende oppgaven. Erfaringsmessig er også slike «kjedelige» manuelle rutineoppgaver ofte plaget med feil registreringer.

Løsningen ble å lage ett skript i PowerShell.(Powershell fordi det var tilgjengelig i server miljøet til kunden). Jeg har ingen tidligere efaring med powershell, og følte derfor at det kunne bli ett godt eksempel som introduksjon til skripting.

Bevæpnet kun med generell skript forståelse og det (nærmest allmektige) Internett søket, satte jeg i gang.

Powershell har støtte for kommentarer over flere linjer, disse kommentarene omkranses av <# og #>
Det er en god ide å legge med en kort beskrivelse av hva koden gjør og eventuelt hvem som har skrevet den.nettverk tekniker script1

I første del av skriptet har jeg lagt inn noen brukerdefinerte variabler, slik at man relativt enkelt kan endre DNS server navn.
Hvilken bruker som skal logge inn med Telnet og tilhørende passord.
Passordet er her sikret med «over skulderen sikkert» passord.
Det betyr at passordet er «skjult» på en slik måte at man aktivt må gå inn for å hente passordet.
Slike skript som dette bør uansett sikres mot tilgang fra uatuorisert personell.
Switch subnet og routersubnet er nettverk med fastsatt lengde (/24 bit) i dette skriptet, dette kan enkelt endres ved behov, men passet hos den gitte oppdragsgiver.

Nettverk tekniker script2

Funksjonen under er «stjålet» fra det store Internet, riktignok med tillatelse fra Martin Pugh.
Jeg har valgt å legge inn en liten beskrivelse i funksjonen med referanse til originalforfatter. Dette blir å regne som normal høflighet.

Nettverk programmering script3

Den lille kodebiten under her tar det «skjulte» passordet og dekoder det slik at vi kan benytte det i Telnet funksjonen. $password blir nå klartekst passordet til Telnet brukeren.

Nettverk programmering script4

Vi pinger noder i nettverkene som skal scannes. Dette er mye raskere en å gjøre telnet til alle tenkelige adresser i disse nettene. Vi forsøker kun Telnet dersom noden svarer på ping.
Vi gjør klar en «range» med adresser fra 1-254
I foreach løkken under «hopper» vi fra switchsubnet til routesubnet for hver adresse vi prøver. På den måten pinger vi alle addressene i begge nettverkene.
En del ganger rekker ikke noden å svare på første ping, pga ARP oppslag, vi sender derfor 1 ping ekstra dersom noen ikke svarer på første.

Nettverk programmering script5

Når vi så har laget en liste over noder som svarer på ping, er vi klar til å kjøre telnet mot dem. Her går vi i foreach løkken igjennom alle adressene og kjører Get-Telnet funksjonen mot dem.
Vi sender noen kommandoer for å hente ut interface og IP addresser. Select-string tar «output» fra funksjonen og kjører gjennom ett filter. Her er vi kun ute etter linjer som har relevante interfacenavn eller nodenavnet i seg.

Nettverk programmering script6Etter at informasjonen er hentet ut og filtrert, trenger vi å hente ut de ulike feltene slik at vi kan lage DNS oppslag på dem.
Her går løkken igjennom hver linje i «output» og leter etter gitte tekst strenger og henter ut feltene med navn, interface nummer og IP addresse. Søkestrengene som benyttes her er såkalte regulære uttrykk.
Til sist bytter vi ut / i interface nummer med -, da / ikke er gyldig i DNS navn.

Nettverk programmering script7

Reversoppslag i DNS skrives baklengs, altså en IP addresse 10.1.2.3 blir i revers DNS til 3.2.1.10.in-addr.arp
Vi må derfor gjøre om rekkefølgen på IP addressen
Nettverk programmering script8

Når reversoppslaget så er klart gjør vi en sjekk om det allerede finnes i DNS. Dette gjør vi ved hjelp av DNS «commandlets» som er ferdige «kommandosett» som er laget for oss.

Nettverk programmering script9Dersom DNS objektet ikke finnes fra før oppretter vi det

Nettverk programmering script10

Dersom DNS objektet finnes fra før, må vi sjekke om det er korrekt eller refererer til ett gammelt navn

Nettverk programmering script12

Når det er gjennomført for alle interface er vi ferdig. Normal høflighet tilsier at vi nå gir beskjed at vi er ferdig

Nettverk programmering script13

Hele skriptet ligger tilgjengelig for nedlasting, for dem som måtte være interessert. Regulære uttrykk er en måte å skildre ett søk på, hvor man bare har kjennskap til deler av søkestrengen, eller strukturen på det man leter etter.
Regulære uttrykk kan være veldig enkle men også veldig kompliserte og det kan ta noe tid før man blir fortrolig med dem. Eksemplene i dette skriptet er nokså enkle.Men jeg tar meg ikke tid til å forklare dem her, dersom det er ønsker om det kan det i framtiden dukke opp en egen post om regulære uttrykk.
Ett godt sted å starte for å lære litt om regulære uttrykk er her: http://regexone.com/

Dette skriptet ble til på en del timer med prøv og feil metoden, samt god støtte i online dokumentasjon. Jeg håper at dette har gjort deg nysgjerrig på scripting og kanskje også fjernet noe av redselen for det ukjente.
Skript trenger ikke være store, vanskelig og kompliserte. Kan jeg lære meg powershell godt nok til dette på under en dag, så kan saktens du også lære deg skripting. Det fine er når man først har lært seg ett skriptspråk, blir det mye lettere å lære flere. Get-Telnet powershell funksjonen brukt her med tillatelse fra Martin Pugh.

Original script fra Martin Pugh: https://community.spiceworks.com/scripts/show/1887-get-telnet-telnet-to-a-device-and-issue-commands

<strong>cisco2dns.ps1 :</strong>
 
&lt;#
.SYNOPSIS
    Simple script to find routers/switches through ping sweep and connect to them using Telnet, issue commands and
    using the output to create revers pointer records for DNS
    Some userchangeable variables are placed at the top for ease of management.
.Author
    Anders Låstad
#&gt;
 
#Userchangeable variables
 
$dnsServer = "ad-dns-server.company.local"
$dnsSuffix = "net.comapny.local."
$username = 'script-ad-user'
$epassword = '66 70 83 90 55 20 13 81 84 99 50 109'
 
$switchSubnet = "192.0.2"
$routerSubnet = "198.51.100"
 
 
 
#Rest of script
 
Function Get-Telnet
&lt;#
    Author:            Martin Pugh
    Twitter:           @thesurlyadm1n
    Spiceworks:        Martin9700
    Blog:              www.thesurlyadmin.com
    Modified:          Anders Låstad
#&gt;
 
{   Param (
        [Parameter(ValueFromPipeline=$true)]
        [String[]]$Commands = @("username","password","term len 0","sh ip int brie"),
        [string]$RemoteHost = "HostnameOrIPAddress",
        [string]$Port = "23",
        [int]$WaitTime = 1000
    )
    #Attach to the remote device, setup streaming requirements
    $Socket = New-Object System.Net.Sockets.TcpClient($RemoteHost, $Port)
    If ($Socket)
    {   $Stream = $Socket.GetStream()
        $Writer = New-Object System.IO.StreamWriter($Stream)
        $Buffer = New-Object System.Byte[] 1024
        $Encoding = New-Object System.Text.AsciiEncoding
 
        #Now start issuing the commands
        ForEach ($Command in $Commands)
        {   $Writer.WriteLine($Command)
            $Writer.Flush()
            Start-Sleep -Milliseconds $WaitTime
        }
        #All commands issued, but since the last command is usually going to be
        #the longest let's wait a little longer for it to finish
        Start-Sleep -Milliseconds ($WaitTime * 4)
        $Result = ""
        #Save all the results
        While($Stream.DataAvailable)
        {   $Read = $Stream.Read($Buffer, 0, 1024)
            $Result += ($Encoding.GetString($Buffer, 0, $Read))
        }
    }
    Else
    {   $Result = "Unable to connect to host: $($RemoteHost):$Port"
    }
    ($Result -split '[rn]') |? {$_}
}
 
 
 
$password = ''
$epassword.Trim().Split(" ") | Foreach { $password = $password + [CHAR][BYTE](($_)-1) }
 
$scanNet = ($switchSubnet,$routerSubnet)
 
$remoteHosts = @()
#Do a pingscan to find switches and routers
$target = 1
$min = 1
$max = 254
$target = $min
 
$ping = new-object system.net.networkinformation.ping
Write-Host "---------Starting Discovery Process---------" -foregroundcolor red
while ($target -le $max)
{
 
   if ($target -eq [int](($max - $min) * 0.1)) { Write-Host "10% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.2)) { Write-Host "20% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.3)) { Write-Host "30% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.4)) { Write-Host "40% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.5)) { Write-Host "50% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.6)) { Write-Host "60% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.7)) { Write-Host "70% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.8)) { Write-Host "80% Complete" -foregroundcolor red }
   elseif ($target -eq [int](($max - $min) * 0.9)) { Write-Host "90% Complete" -foregroundcolor red }
 
   foreach ($net in $scanNet)
   {
      $pingreturn = $ping.send("$($net).$($target)", "100")
      if ($pingreturn.status -ne "success")
      {
         $pingreturn = $ping.send("$($net).$($target)", "200")
      }
      if ($pingreturn.status -eq "success")
      {
         Write-Host "Discovered Host: $($net).$($target)"
         $remoteHosts += "$($net).$($target)"
      }
   }
   $target++
}
Write-Host "---------Finished Discovery Process---------" -foregroundcolor red
Write-Host "Found $($remoteHosts.Count) devices to connect to" -foregroundcolor red
 
#Loop through the hosts to collect interfaces and addresses
[int]$collectCount = 0
Write-Host "---------Starting Interface collection---------" -foregroundcolor red
foreach ($remoteHost in $remoteHosts)
{
 
   $collectCount += 1
   if ($collectCount -eq [int](($remoteHosts.Count) * 0.1)) { Write-Host "10% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.2)) { Write-Host "20% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.3)) { Write-Host "30% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.4)) { Write-Host "40% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.5)) { Write-Host "50% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.6)) { Write-Host "60% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.7)) { Write-Host "70% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.8)) { Write-Host "80% Complete" -foregroundcolor red }
   elseif ($collectCount -eq [int](($remoteHosts.Count) * 0.9)) { Write-Host "90% Complete" -foregroundcolor red }
 
 
   #Reset hostname before each run
   $hostname = $null
 
   $output = Get-Telnet `
                -RemoteHost $remoteHost `
                -Commands `
                    $username, `
                    $password, `
                    "term len 0", `
                    "show start | inc ^hostname", `
                    "sh ip int brie | exc down|unass" `
                | Select-String "^(Fast|Giga|Loop|Vlan).*s+d+.d+.d+.d+s+|^hostname "
 
   #Loop through each output line for data
   ForEach ($line in $output)
   {
      #Get hostname
      if ($line -match "^hostname (S+)")
      {
         $hostname = $matches[1]
         continue
      }
      Elseif ($line -match "^GigaD+(d+S+)s+(S+)")
      {
         $intType = "gi"
         $intId = $matches[1]
         $ip = $matches[2]
      }
      Elseif ($line -match "^FastD+(d+S+)s+(S+)")
      {
         $intType = "fa"
         $intId = $matches[1]
         $ip = $matches[2]
      }
      Elseif ($line -match "^LoopD+(d+)s+(S+)")
      {
         $intType = "lo"
         $intId = $matches[1]
         $ip = $matches[2]
      }
      Elseif ($line -match "^VlanD*(d+)s+(S+)")
      {
         $intType = "vl"
         $intId = $matches[1]
         $ip = $matches[2]
      }
      Else
      {
         continue
      }
      $intId = $intId.replace('/','-') #Replace / with - as these are not allowed in DNS names
 
      #Split ip address and reverse order
      $ipOctets = $ip.split('.')
      [array]::Reverse($ipOctets)
      $ipName = $ipOctets[0]
      $ipZone = $ipOctets[-3..-1] -Join "."
 
      $newObj = $oldObj = Get-DnsServerResourceRecord `
                            -ComputerName $dnsServer `
                            -Node $ipName `
                            -ZoneName "$($ipZone).in-addr.arpa" `
                            -RRType "Ptr" `
                            -ErrorAction SilentlyContinue
      if ($oldObj -eq $null)
      {
        Write-Host "New DNS object created for $($ip) - $($hostname)_$($intType)$($intId)"
        Add-DnsServerResourceRecordPtr `
            -ComputerName $dnsServer `
            -Name $ipName `
            -ZoneName "$($ipZone).in-addr.arpa" `
            -AllowUpdateAny `
            -TimeToLive 01:00:00 `
            -AgeRecord `
            -PtrDomainName "$($hostname)_$($intType)$($intId).$($dnsSuffix)"
      }
      else
      {
        if ($newObj.RecordData.PtrDomainName -ne "$($hostname)_$($intType)$($intId).$($dnsSuffix)")
        {
            Write-Host "Updated DNS object for $($ip) - $($hostname)_$($intType)$($intId)"
            $newObj.RecordData.PtrDomainName = "$($hostname)_$($intType)$($intId).$($dnsSuffix)"
            set-DnsServerResourceRecord `
                -NewInputObject $NewObj `
                -OldInputObject $OldObj `
                -ZoneName "$($ipZone).in-addr.arpa" `
                -PassThru
        }
        else
        {
            Write-Host "DNS record already up to date: $($ip)"
        }
      }
   }
}
Write-Host "---------Finished---------" -foregroundcolor red

pencode.ps1 (genererer «over skulderen sikkert» passord)

$strEncoded = ""
$strToEncode = "passordetsomskalkodes"
$strToEncode.ToCharArray() | Foreach { $strEncoded = $strEncoded + ([BYTE][CHAR]($_)+1)  + " " }
Write-Host $strEncoded

 

 

 

 

 

 

 

Tags: Teknisk blogg