|
|
|
#!/usr/bin/python
|
|
|
|
|
|
|
|
# This file is part of Networklore's snmp library for Ansible
|
|
|
|
#
|
|
|
|
# The module is free software: you can redistribute it and/or modify
|
|
|
|
# it under the terms of the GNU General Public License as published by
|
|
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
|
|
# (at your option) any later version.
|
|
|
|
#
|
|
|
|
# The module is distributed in the hope that it will be useful,
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
# GNU General Public License for more details.
|
|
|
|
#
|
|
|
|
# You should have received a copy of the GNU General Public License
|
|
|
|
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
|
|
DOCUMENTATION = '''
|
|
|
|
---
|
|
|
|
module: snmp_facts
|
|
|
|
version_added: "1.9"
|
|
|
|
author: "Patrick Ogenstad (@ogenstad)"
|
|
|
|
short_description: Retrive facts for a device using SNMP.
|
|
|
|
description:
|
|
|
|
- Retrieve facts for a device using SNMP, the facts will be
|
|
|
|
inserted to the ansible_facts key.
|
|
|
|
requirements:
|
|
|
|
- pysnmp
|
|
|
|
options:
|
|
|
|
host:
|
|
|
|
description:
|
|
|
|
- Set to target snmp server (normally {{inventory_hostname}})
|
|
|
|
required: true
|
|
|
|
version:
|
|
|
|
description:
|
|
|
|
- SNMP Version to use, v2/v2c or v3
|
|
|
|
choices: [ 'v2', 'v2c', 'v3' ]
|
|
|
|
required: true
|
|
|
|
community:
|
|
|
|
description:
|
|
|
|
- The SNMP community string, required if version is v2/v2c
|
|
|
|
required: false
|
|
|
|
level:
|
|
|
|
description:
|
|
|
|
- Authentication level, required if version is v3
|
|
|
|
choices: [ 'authPriv', 'authNoPriv' ]
|
|
|
|
required: false
|
|
|
|
username:
|
|
|
|
description:
|
|
|
|
- Username for SNMPv3, required if version is v3
|
|
|
|
required: false
|
|
|
|
integrity:
|
|
|
|
description:
|
|
|
|
- Hashing algoritm, required if version is v3
|
|
|
|
choices: [ 'md5', 'sha' ]
|
|
|
|
required: false
|
|
|
|
authkey:
|
|
|
|
description:
|
|
|
|
- Authentication key, required if version is v3
|
|
|
|
required: false
|
|
|
|
privacy:
|
|
|
|
description:
|
|
|
|
- Encryption algoritm, required if level is authPriv
|
|
|
|
choices: [ 'des', 'aes' ]
|
|
|
|
required: false
|
|
|
|
privkey:
|
|
|
|
description:
|
|
|
|
- Encryption key, required if version is authPriv
|
|
|
|
required: false
|
|
|
|
'''
|
|
|
|
|
|
|
|
EXAMPLES = '''
|
|
|
|
# Gather facts with SNMP version 2
|
|
|
|
- snmp_facts: host={{ inventory_hostname }} version=2c community=public
|
|
|
|
connection: local
|
|
|
|
|
|
|
|
# Gather facts using SNMP version 3
|
|
|
|
- snmp_facts:
|
|
|
|
host={{ inventory_hostname }}
|
|
|
|
version=v3
|
|
|
|
level=authPriv
|
|
|
|
integrity=sha
|
|
|
|
privacy=aes
|
|
|
|
username=snmp-user
|
|
|
|
authkey=abc12345
|
|
|
|
privkey=def6789
|
|
|
|
delegate_to: localhost
|
|
|
|
'''
|
|
|
|
|
|
|
|
from ansible.module_utils.basic import *
|
|
|
|
from collections import defaultdict
|
|
|
|
|
|
|
|
try:
|
|
|
|
from pysnmp.entity.rfc3413.oneliner import cmdgen
|
|
|
|
has_pysnmp = True
|
|
|
|
except:
|
|
|
|
has_pysnmp = False
|
|
|
|
|
|
|
|
class DefineOid(object):
|
|
|
|
|
|
|
|
def __init__(self,dotprefix=False):
|
|
|
|
if dotprefix:
|
|
|
|
dp = "."
|
|
|
|
else:
|
|
|
|
dp = ""
|
|
|
|
|
|
|
|
# From SNMPv2-MIB
|
|
|
|
self.sysDescr = dp + "1.3.6.1.2.1.1.1.0"
|
|
|
|
self.sysObjectId = dp + "1.3.6.1.2.1.1.2.0"
|
|
|
|
self.sysUpTime = dp + "1.3.6.1.2.1.1.3.0"
|
|
|
|
self.sysContact = dp + "1.3.6.1.2.1.1.4.0"
|
|
|
|
self.sysName = dp + "1.3.6.1.2.1.1.5.0"
|
|
|
|
self.sysLocation = dp + "1.3.6.1.2.1.1.6.0"
|
|
|
|
|
|
|
|
# From IF-MIB
|
|
|
|
self.ifIndex = dp + "1.3.6.1.2.1.2.2.1.1"
|
|
|
|
self.ifDescr = dp + "1.3.6.1.2.1.2.2.1.2"
|
|
|
|
self.ifMtu = dp + "1.3.6.1.2.1.2.2.1.4"
|
|
|
|
self.ifSpeed = dp + "1.3.6.1.2.1.2.2.1.5"
|
|
|
|
self.ifPhysAddress = dp + "1.3.6.1.2.1.2.2.1.6"
|
|
|
|
self.ifAdminStatus = dp + "1.3.6.1.2.1.2.2.1.7"
|
|
|
|
self.ifOperStatus = dp + "1.3.6.1.2.1.2.2.1.8"
|
|
|
|
self.ifAlias = dp + "1.3.6.1.2.1.31.1.1.1.18"
|
|
|
|
|
|
|
|
# From IP-MIB
|
|
|
|
self.ipAdEntAddr = dp + "1.3.6.1.2.1.4.20.1.1"
|
|
|
|
self.ipAdEntIfIndex = dp + "1.3.6.1.2.1.4.20.1.2"
|
|
|
|
self.ipAdEntNetMask = dp + "1.3.6.1.2.1.4.20.1.3"
|
|
|
|
|
|
|
|
|
|
|
|
def decode_hex(hexstring):
|
|
|
|
|
|
|
|
if len(hexstring) < 3:
|
|
|
|
return hexstring
|
|
|
|
if hexstring[:2] == "0x":
|
|
|
|
return hexstring[2:].decode("hex")
|
|
|
|
else:
|
|
|
|
return hexstring
|
|
|
|
|
|
|
|
def decode_mac(hexstring):
|
|
|
|
|
|
|
|
if len(hexstring) != 14:
|
|
|
|
return hexstring
|
|
|
|
if hexstring[:2] == "0x":
|
|
|
|
return hexstring[2:]
|
|
|
|
else:
|
|
|
|
return hexstring
|
|
|
|
|
|
|
|
def lookup_adminstatus(int_adminstatus):
|
|
|
|
adminstatus_options = {
|
|
|
|
1: 'up',
|
|
|
|
2: 'down',
|
|
|
|
3: 'testing'
|
|
|
|
}
|
|
|
|
if int_adminstatus in adminstatus_options.keys():
|
|
|
|
return adminstatus_options[int_adminstatus]
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def lookup_operstatus(int_operstatus):
|
|
|
|
operstatus_options = {
|
|
|
|
1: 'up',
|
|
|
|
2: 'down',
|
|
|
|
3: 'testing',
|
|
|
|
4: 'unknown',
|
|
|
|
5: 'dormant',
|
|
|
|
6: 'notPresent',
|
|
|
|
7: 'lowerLayerDown'
|
|
|
|
}
|
|
|
|
if int_operstatus in operstatus_options.keys():
|
|
|
|
return operstatus_options[int_operstatus]
|
|
|
|
else:
|
|
|
|
return ""
|
|
|
|
|
|
|
|
def main():
|
|
|
|
module = AnsibleModule(
|
|
|
|
argument_spec=dict(
|
|
|
|
host=dict(required=True),
|
|
|
|
version=dict(required=True, choices=['v2', 'v2c', 'v3']),
|
|
|
|
community=dict(required=False, default=False),
|
|
|
|
username=dict(required=False),
|
|
|
|
level=dict(required=False, choices=['authNoPriv', 'authPriv']),
|
|
|
|
integrity=dict(required=False, choices=['md5', 'sha']),
|
|
|
|
privacy=dict(required=False, choices=['des', 'aes']),
|
|
|
|
authkey=dict(required=False),
|
|
|
|
privkey=dict(required=False),
|
|
|
|
removeplaceholder=dict(required=False)),
|
|
|
|
required_together = ( ['username','level','integrity','authkey'],['privacy','privkey'],),
|
|
|
|
supports_check_mode=False)
|
|
|
|
|
|
|
|
m_args = module.params
|
|
|
|
|
|
|
|
if not has_pysnmp:
|
|
|
|
module.fail_json(msg='Missing required pysnmp module (check docs)')
|
|
|
|
|
|
|
|
cmdGen = cmdgen.CommandGenerator()
|
|
|
|
|
|
|
|
# Verify that we receive a community when using snmp v2
|
|
|
|
if m_args['version'] == "v2" or m_args['version'] == "v2c":
|
|
|
|
if m_args['community'] == False:
|
|
|
|
module.fail_json(msg='Community not set when using snmp version 2')
|
|
|
|
|
|
|
|
if m_args['version'] == "v3":
|
|
|
|
if m_args['username'] == None:
|
|
|
|
module.fail_json(msg='Username not set when using snmp version 3')
|
|
|
|
|
|
|
|
if m_args['level'] == "authPriv" and m_args['privacy'] == None:
|
|
|
|
module.fail_json(msg='Privacy algorithm not set when using authPriv')
|
|
|
|
|
|
|
|
|
|
|
|
if m_args['integrity'] == "sha":
|
|
|
|
integrity_proto = cmdgen.usmHMACSHAAuthProtocol
|
|
|
|
elif m_args['integrity'] == "md5":
|
|
|
|
integrity_proto = cmdgen.usmHMACMD5AuthProtocol
|
|
|
|
|
|
|
|
if m_args['privacy'] == "aes":
|
|
|
|
privacy_proto = cmdgen.usmAesCfb128Protocol
|
|
|
|
elif m_args['privacy'] == "des":
|
|
|
|
privacy_proto = cmdgen.usmDESPrivProtocol
|
|
|
|
|
|
|
|
# Use SNMP Version 2
|
|
|
|
if m_args['version'] == "v2" or m_args['version'] == "v2c":
|
|
|
|
snmp_auth = cmdgen.CommunityData(m_args['community'])
|
|
|
|
|
|
|
|
# Use SNMP Version 3 with authNoPriv
|
|
|
|
elif m_args['level'] == "authNoPriv":
|
|
|
|
snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], authProtocol=integrity_proto)
|
|
|
|
|
|
|
|
# Use SNMP Version 3 with authPriv
|
|
|
|
else:
|
|
|
|
snmp_auth = cmdgen.UsmUserData(m_args['username'], authKey=m_args['authkey'], privKey=m_args['privkey'], authProtocol=integrity_proto, privProtocol=privacy_proto)
|
|
|
|
|
|
|
|
# Use p to prefix OIDs with a dot for polling
|
|
|
|
p = DefineOid(dotprefix=True)
|
|
|
|
# Use v without a prefix to use with return values
|
|
|
|
v = DefineOid(dotprefix=False)
|
|
|
|
|
|
|
|
Tree = lambda: defaultdict(Tree)
|
|
|
|
|
|
|
|
results = Tree()
|
|
|
|
|
|
|
|
errorIndication, errorStatus, errorIndex, varBinds = cmdGen.getCmd(
|
|
|
|
snmp_auth,
|
|
|
|
cmdgen.UdpTransportTarget((m_args['host'], 161)),
|
|
|
|
cmdgen.MibVariable(p.sysDescr,),
|
|
|
|
cmdgen.MibVariable(p.sysObjectId,),
|
|
|
|
cmdgen.MibVariable(p.sysUpTime,),
|
|
|
|
cmdgen.MibVariable(p.sysContact,),
|
|
|
|
cmdgen.MibVariable(p.sysName,),
|
|
|
|
cmdgen.MibVariable(p.sysLocation,),
|
|
|
|
lookupMib=False
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if errorIndication:
|
|
|
|
module.fail_json(msg=str(errorIndication))
|
|
|
|
|
|
|
|
for oid, val in varBinds:
|
|
|
|
current_oid = oid.prettyPrint()
|
|
|
|
current_val = val.prettyPrint()
|
|
|
|
if current_oid == v.sysDescr:
|
|
|
|
results['ansible_sysdescr'] = decode_hex(current_val)
|
|
|
|
elif current_oid == v.sysObjectId:
|
|
|
|
results['ansible_sysobjectid'] = current_val
|
|
|
|
elif current_oid == v.sysUpTime:
|
|
|
|
results['ansible_sysuptime'] = current_val
|
|
|
|
elif current_oid == v.sysContact:
|
|
|
|
results['ansible_syscontact'] = current_val
|
|
|
|
elif current_oid == v.sysName:
|
|
|
|
results['ansible_sysname'] = current_val
|
|
|
|
elif current_oid == v.sysLocation:
|
|
|
|
results['ansible_syslocation'] = current_val
|
|
|
|
|
|
|
|
errorIndication, errorStatus, errorIndex, varTable = cmdGen.nextCmd(
|
|
|
|
snmp_auth,
|
|
|
|
cmdgen.UdpTransportTarget((m_args['host'], 161)),
|
|
|
|
cmdgen.MibVariable(p.ifIndex,),
|
|
|
|
cmdgen.MibVariable(p.ifDescr,),
|
|
|
|
cmdgen.MibVariable(p.ifMtu,),
|
|
|
|
cmdgen.MibVariable(p.ifSpeed,),
|
|
|
|
cmdgen.MibVariable(p.ifPhysAddress,),
|
|
|
|
cmdgen.MibVariable(p.ifAdminStatus,),
|
|
|
|
cmdgen.MibVariable(p.ifOperStatus,),
|
|
|
|
cmdgen.MibVariable(p.ipAdEntAddr,),
|
|
|
|
cmdgen.MibVariable(p.ipAdEntIfIndex,),
|
|
|
|
cmdgen.MibVariable(p.ipAdEntNetMask,),
|
|
|
|
|
|
|
|
cmdgen.MibVariable(p.ifAlias,),
|
|
|
|
lookupMib=False
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
if errorIndication:
|
|
|
|
module.fail_json(msg=str(errorIndication))
|
|
|
|
|
|
|
|
interface_indexes = []
|
|
|
|
|
|
|
|
all_ipv4_addresses = []
|
|
|
|
ipv4_networks = Tree()
|
|
|
|
|
|
|
|
for varBinds in varTable:
|
|
|
|
for oid, val in varBinds:
|
|
|
|
current_oid = oid.prettyPrint()
|
|
|
|
current_val = val.prettyPrint()
|
|
|
|
if v.ifIndex in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['ifindex'] = current_val
|
|
|
|
interface_indexes.append(ifIndex)
|
|
|
|
if v.ifDescr in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['name'] = current_val
|
|
|
|
if v.ifMtu in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['mtu'] = current_val
|
|
|
|
if v.ifMtu in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['speed'] = current_val
|
|
|
|
if v.ifPhysAddress in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['mac'] = decode_mac(current_val)
|
|
|
|
if v.ifAdminStatus in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['adminstatus'] = lookup_adminstatus(int(current_val))
|
|
|
|
if v.ifOperStatus in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['operstatus'] = lookup_operstatus(int(current_val))
|
|
|
|
if v.ipAdEntAddr in current_oid:
|
|
|
|
curIPList = current_oid.rsplit('.', 4)[-4:]
|
|
|
|
curIP = ".".join(curIPList)
|
|
|
|
ipv4_networks[curIP]['address'] = current_val
|
|
|
|
all_ipv4_addresses.append(current_val)
|
|
|
|
if v.ipAdEntIfIndex in current_oid:
|
|
|
|
curIPList = current_oid.rsplit('.', 4)[-4:]
|
|
|
|
curIP = ".".join(curIPList)
|
|
|
|
ipv4_networks[curIP]['interface'] = current_val
|
|
|
|
if v.ipAdEntNetMask in current_oid:
|
|
|
|
curIPList = current_oid.rsplit('.', 4)[-4:]
|
|
|
|
curIP = ".".join(curIPList)
|
|
|
|
ipv4_networks[curIP]['netmask'] = current_val
|
|
|
|
|
|
|
|
if v.ifAlias in current_oid:
|
|
|
|
ifIndex = int(current_oid.rsplit('.', 1)[-1])
|
|
|
|
results['ansible_interfaces'][ifIndex]['description'] = current_val
|
|
|
|
|
|
|
|
interface_to_ipv4 = {}
|
|
|
|
for ipv4_network in ipv4_networks:
|
|
|
|
current_interface = ipv4_networks[ipv4_network]['interface']
|
|
|
|
current_network = {
|
|
|
|
'address': ipv4_networks[ipv4_network]['address'],
|
|
|
|
'netmask': ipv4_networks[ipv4_network]['netmask']
|
|
|
|
}
|
|
|
|
if not current_interface in interface_to_ipv4:
|
|
|
|
interface_to_ipv4[current_interface] = []
|
|
|
|
interface_to_ipv4[current_interface].append(current_network)
|
|
|
|
else:
|
|
|
|
interface_to_ipv4[current_interface].append(current_network)
|
|
|
|
|
|
|
|
for interface in interface_to_ipv4:
|
|
|
|
results['ansible_interfaces'][int(interface)]['ipv4'] = interface_to_ipv4[interface]
|
|
|
|
|
|
|
|
results['ansible_all_ipv4_addresses'] = all_ipv4_addresses
|
|
|
|
|
|
|
|
module.exit_json(ansible_facts=results)
|
|
|
|
|
|
|
|
|
|
|
|
main()
|