How Does a SysAdmin Can Apply a Python Skills To His Daily Work? Part2

System Administration & Programming Main Logo 2

How Does a SysAdmin Can Apply 

a Python Skills To His Daily Work? (Part2)

In the first part, we showed you how the script learned to collect hostname equipment by SNMP! 

In this part, we going to show you how to collect and process the remaining data necessary for the formation of zone files.

Let’s formulate the task, which must be solved: it is necessary to form domain names for routers and their interfaces. The script should come from a similar configuration (in this case Cisco one):

hostname R1.greenhouse
domain service.prov.com

interface FastEthernet0/1.24
description Line to Red House
ip address 10.24.1.1 255.255.255.254

We have to form two records, for the direct zone and for the reverse zone:

Fa0/1.24.Line_to_Red_House.R1.greenhouse.service.prov.com. IN A 10.24.1.1

1.1.24.10.in-addr.arpa. IN PTR Fa0/1.24.Line_to_Red_House.R1.greenhouse.service.prov.com.

There is a bad news: these are the wrong domain names. By the rules, you can only use numbers, letters and “-“. And the hyphen cannot stand first and last. To comply with the rules, the entry should look something like this:

Fa0-1-24.Line-to-Red-House.R1.greenhouse.service.prov.com. IN A 10.24.1.1

There are obvious problems with readability. And if the name of the interface does not start with non-zero digits, and even with a colon? After bringing this to the standard, understand what and wherein these figures and letters becomes quite difficult. Therefore, bind was configured with an option that allows you to work with nonstandard names. And the script, accordingly, makes the most readable names. Docking DNS networkers and global DNS is a bit of a separate conversation, but it’s easiest to allocate a separate domain for network equipment, for example, network.megaproduct.org.

To generate zone files, we need to collect router hostname values, interface names, description value on these interfaces and ip-addresses of these interfaces. The only exception is a record containing the ip-address belonging to the interface Loopback0. This entry will not contain the interface name and value of its description since this is the domain name of the router.

How to find the correct OID?

Let’s define with OIDs to get the names of interfaces, ip addresses and description from these interfaces. Our goal is universality, so we will use OID standard MIB files, which are implemented by all vendors. Typically, the easiest way to find the correct OID is not reading the documentation or google, but using snmpwalk or any MIB browser that you like best. Let see what might give us the concrete equipment, then choose the necessary one. By running snmpwalk, you get all the OIDs supported by the device with their values at the time of polling.

In order to get started with snmpwalk, you need to install the SNMP and SNMP-mibs-downloader package:

sudo apt-get install SNMP

sudo apt-get install SNMP-mibs-downloader

If it is necessary for the utilities to work with named OID in the configuration file /etc/SNMP/SNMP.conf, add the used mib or the keyword ALL.
Of course, using ALL is not recommended, the number of files is huge, searching all files in a row in many cases is clearly not a good idea. However, in the case of writing a program and searching for the desired MIB file, this is permissible. Do not forget to leave only the necessary things.

$ cat /etc/snmp/snmp.conf
# As the snmp packages come without MIB files due to license reasons, loading
# of MIBs is disabled by default. If you added the MIBs you can reenable
# loading them by commenting out the following line.
Mibs: SNMPv2-MIB: RFC1213-MIB: IF-MIB

#mibs: ALL
zw@note:~$

Check to see if snmp saw our MIB:

host$ snmptranslate 1.3.6.1.2.1.31.1.1.1.18
IF-MIB::ifAlias

host$ snmptranslate -On IF-MIB::ifAlias
.1.3.6.1.2.1.31.1.1.1.18

Now, making a request to the real host:

host$ snmpget -v 2c -c tilitili 10.239.192.2 1.3.6.1.2.1.31.1.1.1.18.1
IF-MIB::ifAlias.1 = STRING: Line_to_OUT

The last digit (1) that we added is ifindex. Ifndex is the interface number assigned by snmp to the hardware operating system agent.

Numbering is performed when the agent is initialized (when the OS boots). After each reboot, it will be different if you do not tell the router to remember the assigned ifindex (google for ifindex persist keywords).

In working with interfaces, everything is logical, take OID, add ifindex, make a query – get the description of the interface with this Ifindex.

Now, we need to request snmpwalk, which will request all available OIDs and their current values from the router:

host$ snmpwalk -v 2c -c tilitili 10.239.192.2

RFC1213-MIB::ifDescr.1 = STRING: «FastEthernet0/0»
RFC1213-MIB::ifDescr.2 = STRING: «FastEthernet0/1»
RFC1213-MIB::ifDescr.3 = STRING: «Null0»
RFC1213-MIB::ifDescr.36 = STRING: «Loopback0»
RFC1213-MIB::ipAdEntAddr.10.239.192.2 = IpAddress: 10.239.192.2
RFC1213-MIB::ipAdEntAddr.10.239.192.90 = IpAddress: 10.239.192.90
RFC1213-MIB::ipAdEntAddr.11.0.0.1 = IpAddress: 11.0.0.1
IF-MIB::ifName.1 = STRING: Fa0/0
IF-MIB::ifName.2 = STRING: Fa0/1
IF-MIB::ifName.3 = STRING: Nu0
IF-MIB::ifName.36 = STRING: Lo0
IF-MIB::ifAlias.1 = STRING: Line_to_OUT
IF-MIB::ifAlias.2 = STRING: Line_to_poligon
IF-MIB::ifAlias.3 = STRING:
IF-MIB::ifAlias.36 = STRING:

The snmpwalk output has something to read, it’s very verbose, so it’s better to filter it.

If you are paying attention, FastEthernet interfaces come with 1 ifindex and Loopback with 36. An interesting detail: ifDesc – gives the full names of interfaces, ifAlias – gives a description of interfaces, and ifName – produces abbreviated names of interfaces. Using the full names of interfaces was discarded at once, names due to the contents of the description and so will be long, and the full names of the interfaces will make them even longer.

If we need OIDs whose values we know, it’s easier to search them from the snmpwalk or MIB browser output using search or filtering. If we need an OID, for example, the current CPU utilization value, then it’s easier to search in google or in the documentation.

Let’s look for the necessary OIDs for the values of ip addresses that we know:

host$ snmpwalk -v 2c -c tilitili 10.239.192.2 | grep 10.239.192.2

RFC1213-MIB::ipAdEntAddr.10.239.192.2 = IpAddress: 10.239.192.2
RFC1213-MIB::ipAdEntIfIndex.10.239.192.2 = INTEGER: 36
RFC1213-MIB::ipAdEntNetMask.10.239.192.2 = IpAddress: 255.255.255.255
RFC1213-MIB::ipAdEntBcastAddr.10.239.192.2 = INTEGER: 1
RFC1213-MIB::ipAdEntReasmMaxSize.10.239.192.2 = INTEGER: 18024
RFC1213-MIB::ipRouteDest.10.239.192.2 = IpAddress: 10.239.192.2
RFC1213-MIB::ipRouteIfIndex.10.239.192.2 = INTEGER: 36

And from the output you can immediately see that there is no OID, adding to it ifindex, we would get ip interface addresses with this ifindex.

RFC1213-MIB :: ipAdEntAddr. + ip address – returns ip address.
RFC1213-MIB :: ipAdEntIfIndex. + ip address – returns ifindex

Thus, we first need to compile ip addresses and ifndex interfaces with these ip addresses, and then, ifndex, collect names and description interfaces.

Let’s move on to data collection: SNMPGETNEXT and Python

Previously used to collect hostname method snmpget to solve this problem is completely not suitable. This method needs an exact OID, but we do not. In such situations, another method is used – snmpgetnext. In snmp utilities, the snmpgetnext utility responds to this method.

It works like this: SNMPGETNEXT takes an OID and returns the next OID and its value.

host$ snmpgetnext -v 2c -c tilitili 10.239.192.2 1.3.6.1.2.1.4.20.1.1
RFC1213-MIB::ipAdEntAddr.10.239.192.2 = IpAddress: 10.239.192.2

host$ snmpgetnext -v 2c -c tilitili 10.239.192.2 1.3.6.1.2.1.4.20.1.1.10.239.192.2
RFC1213-MIB::ipAdEntAddr.10.239.192.90 = IpAddress: 10.239.192.90

And so on. Note that we do not know the address of the router on the first request, so the first access goes just to the value of OID 1.3.6.1.2.1.4.20.1.1

Let’s try to automate these requests. Write the function and call it several times:


def snmp_getnextcmd (community, ip, port, OID):
# type class 'generator' errorIndication, errorStatus, errorIndex, result [3]
# next method to get the values in order, one by one with next ()
return (nextCmd (SnmpEngine (),
CommunityData (community),
UdpTransportTarget ((ip, port)),
ContextData (),
ObjectType (ObjectIdentity (OID))))

g = (snmp_getnextcmd (community_string, ip_address_host, port_snmp, OID_ipAdEntAddr))
print (g)
errorIndication, errorStatus, errorIndex, varBinds = next (g)
for name, val in varBinds:
print (name.prettyPrint (), '======', val.prettyPrint ())

errorIndication, errorStatus, errorIndex, varBinds = next (g)
for name, val in varBinds:
print (name.prettyPrint (), '======', val.prettyPrint ())

errorIndication, errorStatus, errorIndex, varBinds = next (g)
for name, val in varBinds:
print (name.prettyPrint (), '======', val.prettyPrint ())

errorIndication, errorStatus, errorIndex, varBinds = next (g)
for name, val in varBinds:
print (name.prettyPrint (), '======', val.prettyPrint ())

errorIndication, errorStatus, errorIndex, varBinds = next (g)
for name, val in varBinds:
print (name.prettyPrint (), '======', val.prettyPrint ())

Run, watch the result:

<generator object nextCmd at 0x7f960364f8e0>
SNMPv2-SMI::mib-2.4.20.1.1.10.239.192.2 ====== 10.239.192.2
SNMPv2-SMI::mib-2.4.20.1.1.10.239.192.90 ====== 10.239.192.90
SNMPv2-SMI::mib-2.4.20.1.1.11.0.0.1 ====== 11.0.0.1
SNMPv2-SMI::mib-2.4.20.1.2.10.239.192.2 ====== 36
SNMPv2-SMI::mib-2.4.20.1.2.10.239.192.90 ====== 1

The variable g is our generator. What does python tell us when it executes print (g)?
And then, using the built-in function next, the script iterates through the OID for the OID. The error values are added to errorIndication, errorStatus, errorIndex, and the result is added to the varBinds list. To get values from varBinds, use the for loop:


for name,val in varBinds:
print(name.prettyPrint(),' ====== ',val.prettyPrint())

And now we have a very simple sequence of actions:

  1. We get addresses for OID 1.3.6.1.2.1.4.20.1.1, sorting through the next, we write down the addresses on the list. For example, we will track the address 10.239.192.90.
  2. Get ifindex by OID 1.3.6.1.2.1.4.20.1.2, write to the list. For the address 10.239.192.90, the ifindex value of the interface is 1.
    indicating the value of ifindex. For the address 10.239.192.90, the final design will be 1.3.6.1.2.1.31.1.1.1.18.1.
  3. We collect interface names using OID 1.3.6.1.2.1.31.1.1.1.1 + .ifindex.

It remains to compile the first two items into the function with snmpgetnext, and we’ll compile the remaining two items with snmpget. The function itself will walk through the OID, sort through the values and stop when all the OIDs have run out. OID will be loaded in a list. List with OID is over – we finish the work. The result will be returned by a two-dimensional list.


def snmp_getnextcmd_next (community, ip, port, OID, file):
# method handles the class generator from def snmp_getnext
# OID is an OID list in the form list_OID = [OID_ipAdEntAddr, OID_ipAdEntIfIndex, OID_ipAdEntNetMask], where variable string values
# in the form '1.2.3.4'
# return a two-dimensional list with values, by the number of OIDs
list_result = [] # to generate first-level lists
list_result2 = [] # summary list
g = (snmp_getnextcmd (community, ip, port, OID [0])) # start with the first OID
varBinds = 0
flag = True
for oid in list_OID:
if varBinds! = 0:
for name, val in varBinds:
list_result2.append (list_result)
list_result = []
list_result.append (val.prettyPrint ())
i = 0
while i <= 0: # on the list
errorIndication, errorStatus, errorIndex, varBinds = next (g)
if errors (errorIndication, errorStatus, errorIndex, ip_address_host, file):
if str (varBinds) .find (oid)! = -1:
i = 0
for name, val in varBinds:
list_result.append (val.prettyPrint ())
else:
i = i + 1
flag = False
else:
file.write (datetime.strftime (datetime.now (), "% Y.% m.% d% H:% M:% S") + ':' + 'Error snmp_getnextcmd_next ip =' + ip + 'OID = '+ OID [0] +' \ n ')
print ('Error snmp_getnextcmd_next', False)
i = i + 1
flag = False
list_result2.append (list_result)
return list_result2

We get a list containing ip addresses, ifindex, masks. The masks remained after the experiments, a heavy legacy of the past. To proceed to the next steps to collect interface names, description, you need to filter the received data. For example, equipment Huawei, Cisco will give out addresses from the network 127.0.0.0/8, to form for the domain names is meaningless. In the current version of the script, there is filtering over the permissible range of IPv4, everything that does not fall into the range is deleted. Obviously, for your networks, the filtering rules will be different.

Now, we collect interface names, description with snmpget and proceed to create zone files. To form the reverse zone file, you need to change the order of the octets, for which you can use reverse_pointer from the ipaddress library or write something of your own.

print(ipaddress.ip_address(‘194.4.5.1’).reverse_pointer)

1.5.4.194.in-addr.arpa

Our script calls reverse zone files based on the addresses it receives from the router, it creates a dictionary in which the key consists of the first two octets of the ip address. Then from these keys file names are formed. To do this, you still need to parse the address in octets. We apply a script which, having received a list of router addresses, should give out the correct zone files.

The whole script in one