Browse Source

first cut at a vlan manager utility.

ssh-lenovo
John-Mark Gurney 5 years ago
commit
7479d41931
4 changed files with 178 additions and 0 deletions
  1. +3
    -0
      .gitignore
  2. +7
    -0
      Makefile
  3. +2
    -0
      requirements.txt
  4. +166
    -0
      vlanmang.py

+ 3
- 0
.gitignore View File

@@ -0,0 +1,3 @@
*.pyc
p
.coverage

+ 7
- 0
Makefile View File

@@ -0,0 +1,7 @@
MODULES=vlanmang.py

test:
(echo $(MODULES) | entr sh -c 'python -m coverage run -m unittest $(basename $(MODULES)) && coverage report --omit=p/\* -m -i')

env:
(virtualenv p && . ./p/bin/activate && pip install -r requirements.txt)

+ 2
- 0
requirements.txt View File

@@ -0,0 +1,2 @@
pysnmp
coverage

+ 166
- 0
vlanmang.py View File

@@ -0,0 +1,166 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from pysnmp.hlapi import *
from pysnmp.smi.builder import MibBuilder
from pysnmp.smi.view import MibViewController

import random
import unittest

_mbuilder = MibBuilder()
_mvc = MibViewController(_mbuilder)

# received packages
# pvid: dot1qPvid
#
# tx packets:
# dot1qVlanStaticEgressPorts
# dot1qVlanStaticUntaggedPorts
#
# vlans:
# dot1qVlanCurrentTable
# lists ALL vlans, including baked in ones
#
# note that even though an snmpwalk of dot1qVlanStaticEgressPorts
# skips over other vlans (only shows statics), the other vlans (1,2,3)
# are still accessible via that oid
#
# LLDP:
# 1.0.8802.1.1.2.1.4.1.1 aka LLDP-MIB, lldpRemTable

class SNMPSwitch(object):
def __init__(self, host, community):
self._eng = SnmpEngine()
self._cd = CommunityData(community, mpModel=0)
self._targ = UdpTransportTarget((host, 161))

def _get(self, oid):
oid = ObjectIdentity(*oid)
oid.resolveWithMib(_mvc)

errorInd, errorStatus, errorIndex, varBinds = \
next(getCmd(self._eng, self._cd, self._targ, ContextData(), ObjectType(oid)))

if errorInd: # pragma: no cover
raise ValueError(errorIndication)
elif errorStatus:
raise ValueError('%s at %s' %
(errorStatus.prettyPrint(), errorIndex and
varBinds[int(errorIndex)-1][0] or '?'))
else:
if len(varBinds) != 1: # pragma: no cover
raise ValueError('too many return values')

varBind = varBinds[0]
return varBind[1]

def _set(self, oid, value):
oid = ObjectIdentity(*oid)
oid.resolveWithMib(_mvc)

if isinstance(value, (int, long)):
value = Integer(value)
elif isinstance(value, str):
value = OctetString(value)

errorInd, errorStatus, errorIndex, varBinds = \
next(setCmd(self._eng, self._cd, self._targ, ContextData(), ObjectType(oid, value)))

if errorInd: # pragma: no cover
raise ValueError(errorIndication)
elif errorStatus: # pragma: no cover
raise ValueError('%s at %s' %
(errorStatus.prettyPrint(), errorIndex and
varBinds[int(errorIndex)-1][0] or '?'))
else:
for varBind in varBinds:
if varBind[1] != value: # pragma: no cover
raise RuntimeError('failed to set: %s' % ' = '.join([x.prettyPrint() for x in varBind]))

def _walk(self, *oid):
oid = ObjectIdentity(*oid)
# XXX - keep these, this might stop working, no clue what managed to magically make things work
# ref: http://snmplabs.com/pysnmp/examples/smi/manager/browsing-mib-tree.html#mib-objects-to-pdu-var-binds
# mibdump.py --mib-source '/Users/jmg/Nextcloud/Documents/user manuals/netgear/gs7xxt-v6.3.1.19-mibs' --mib-source /usr/share/snmp/mibs --rebuild rfc1212 pbridge vlan
#oid.addAsn1MibSource('/usr/share/snmp/mibs', '/Users/jmg/Nextcloud/Documents/user manuals/netgear/gs7xxt-v6.3.1.19-mibs')

oid.resolveWithMib(_mvc)

for (errorInd, errorStatus, errorIndex, varBinds) in nextCmd(
self._eng, self._cd, self._targ, ContextData(),
ObjectType(oid),
lexicographicMode=False):
if errorInd: # pragma: no cover
raise ValueError(errorIndication)
elif errorStatus: # pragma: no cover
raise ValueError('%s at %s' % (errorStatus.prettyPrint(), errorIndex and varBinds[int(errorIndex)-1][0] or '?'))
else:
for varBind in varBinds:
yield varBind

def findport(self, name):
return [ x[0][-1] for x in self._walk('IF-MIB', 'ifName') if str(x[1]) == name ][0]

def getvlanname(self, vlan):
v = self._get(('Q-BRIDGE-MIB', 'dot1qVlanStaticName', vlan))

return str(v).decode('utf-8')

def createvlan(self, vlan, name):
# createAndGo(4)
self._set(('Q-BRIDGE-MIB', 'dot1qVlanStaticRowStatus',
int(vlan)), 4)
self._set(('Q-BRIDGE-MIB', 'dot1qVlanStaticName', int(vlan)),
name)

def deletevlan(self, vlan):
self._set(('Q-BRIDGE-MIB', 'dot1qVlanStaticRowStatus',
int(vlan)), 6) # destroy(6)

def getvlans(self):
return (x[0][-1] for x in self._walk('Q-BRIDGE-MIB', 'dot1qVlanStatus'))

def staticvlans(self):
return (x[0][-1] for x in self._walk('Q-BRIDGE-MIB', 'dot1qVlanStaticName'))

class Test(unittest.TestCase):
def setUp(self):
args = open('test.creds').read().split()
self.switch = SNMPSwitch(*args)

def test_unpacktable(self):
pass

def test_misc(self):
switch = self.switch

self.assertEqual(switch.findport('g1'), 1)
self.assertEqual(switch.findport('l1'), 14)

def test_vlan(self):
switch = self.switch

existingvlans = set(switch.getvlans())

while True:
testvlan = random.randint(1,4095)
if testvlan not in existingvlans:
break

# Test that getting a non-existant vlans raises an exception
self.assertRaises(ValueError, switch.getvlanname, testvlan)

self.assertTrue(set(switch.staticvlans()).issubset(existingvlans))

testname = 'Sometestname'

# Create test vlan
switch.createvlan(testvlan, testname)
try:
# make sure the test vlan was created
self.assertIn(testvlan, set(switch.staticvlans()))

self.assertEqual(testname, switch.getvlanname(testvlan))
finally:
switch.deletevlan(testvlan)

Loading…
Cancel
Save