Network Management
Week 4, Feb 14
LT-412, Mon 4:15-6:45 pm
Finish reading M&S chapter 2
Continued overview of networks
Ethernet
Failures/config issues:
- unique addresses
- b'cast traffic
- ethernet "meltdown"
- runt packets
- late collisions
- bad CRC
- collisions
- too-big-packets
- bad jitter (relatively obscure)
total bandwidth consumption
hub v switch
Hubs
setup: via rs-232 port, telnet, web-based,
snmp. Note: hubs do NOT automatically get IP address!!
Switches
- what is the difference between a switch and a hub?
- setup: similar
- additional item: ⟨dest,port⟩ tables
- port speed (though some hubs have partial support for this)
- monitor: one port replicates another
- all statistics are per-port!
- redundant links and the switch-to-switch spanning-tree protocol
ATM
- virtual circuit maintenance
- virtual-circuit performance
- ATM-ARP: given an IP addr, what VC is or needs to be set up to get you there?
Token Ring
- Token maintenance protocols
- ring disconnects
IP
- Addressing
- Routing tables: ⟨dest,next_hop⟩, where dest represents a network address.
- ARP, ARP storms
- ICMP responses
- fragmentation/reassembly timers
- packet loss rates
- packet delay (very important for VOIP)
IP support:
IP routing versus switching
Ethernet switching is very fast, and table sizes of up to 100,000 are rather easily managed.
Ultimately, the limiting factor is table size, table maintenance traffic, and broadcast traffic.
IP routing is much more flexible, but is often quite a bit slower.
SNMPv1 operations & formats
(See M&S p 37, though they mix SNMPv2 in as well. We will do v2 separately.)
-
SET
- GET
- GET-NEXT
- GET-RESPONSE
- TRAP
A GET request contains a list of OIDs being requested,
OID1, OID2, ... OID_N
The corresponding response is then a list of N ⟨OID,VALUE⟩ pairs. Such a list is called a VarBind list.
Often the request list is also called a varbind list, with the values
NULL. Very often, the list has length 1, that is, only a single OID
value is requested.
Note that the set of all OIDs, including proper prefixes, form a tree. SNMP tree traversal is generally depth-first, also known as lexicographic order. From any node on the tree, there is a well-defined next node, and a well-defined next leaf node.
Both the next node and the next leaf node for an interior node are
always below it (ie their OIDs represent proper extensions of the
original OID). The next and next-leaf nodes of a leaf node involve some
backtracking back up the tree, to the next "right branch", and then
taking that.
Here are a few hypothetical OIDs; leaf nodes are in bold.
1.5.1
1.5.1.0
1.5.2
1.5.2.0
1.5.3
1.5.3.1
1.5.3.1.1
1.5.3.1.1.7
1.5.3.1.1.9
1.5.3.1.2
1.5.3.1.2.28
From 1.5.2, the next and next-leaf nodes are both 1.5.2.0. From 1.5.3, the next node is 1.5.3.1; the next leaf is 1.5.3.1.1.7.
A GET-NEXT request contains, like a GET request, a list of OIDs. The agent then, for each OID in the list, finds the next leaf node properly following the received OID (regardless of whether OID is a leaf node or not). GET-NEXT(OID) returns the varbind pair ⟨OIDnextleaf, value⟩, where OIDnextleaf
is the next leaf node in the OID strictly after OID (whether or not OID
itself is a leaf node). Note that OIDnextleaf will always
be different from the OID included in the request (and further on in
the tree traversal). The GET-NEXT mechanism gives us an easy way of
depth-first
traversal of all the leaf nodes (the only ones with values attached) of
the OID tree.
Note that, if we're in an SNMP table, not on the last row, then the next-leaf node in the tree sense is the same column but down one row, in the table sense. If we were on the last row, then the tree next-leaf is the table's top of the next column.
Consider GET-NEXT in the system group. A GET-NEXT of system.1 will return sysDescr.0, ie system.1.0, while a GET-NEXT of system.1.0 will return sysObjectID.0, or system.2.0. If there is no next OID, a special end-of-MIB value is returned.
GET-NEXT is at the heart of snmpwalk: we ask for OIDs in sequence,
until we get to an OID "above" (in the tree) our starting value. We get
each leaf node below our starting value, in turn.
Normal practice is for GET-NEXT requests to request one OID at a
time, though for tables it is possible to request several. In that
case, each OID supplied has its "next" OIDleaf returned.
For SNMP tables, GET-NEXT will traverse down each column of the table, and then move on to subsequent columns.
sample: what will GET-NEXT return at the last leaf node of the interfaces table?
It is also possible to use GET-NEXT to traverse down a table "in
parallel". We start with a sequence of each "column head"; for the interfaces table (which has 22 columns numbered 1 through 22) this would be
⟨ifEntry.1, ifEntry.2, ifEntry.3, ..., ifEntry.21, ifEntry.22⟩
or, using the names for the "column heads",
⟨ifIndex, ifDescr, ifType, ..., ifOutQLen, ifSpecific⟩
The next-leaf node is, in each case, the physical entry for the first row:
⟨ifIndex.1, ifDescr.1, ifType.1, ..., ifOutQLen.1, ifSpecific.1⟩
Using these in a second GET-NEXT returns the second entire row:
⟨ifIndex.2, ifDescr.2, ifType.2, ..., ifOutQLen.2, ifSpecific.2⟩
Now, let us suppose that these two rows are all there is in the interfaces
table for this agent. Then the next GET-NEXT, with OIDlist the row
above, will go "up" the table each time to find the next leaf. The next
leaf following ifIndex.2 is ifDescr.1, that is, there is nothing more
in the ifIndex column so we go up into the first row of the ifDescr
column. The next-leaf following ifSpecific.2 is the first leaf node in
the next group altogether, the atTable:
⟨ifDescr.1, ifType.1, ifMtu.1, ..., ifSpecific.1, atTable.whatever⟩
At this point the manager knows that traversal of the interfaces table is completed.
Note that fetching specific entries from a table with get-next is a bit tricky: we have to be at the previous value.
SNMP packet formats:
GET-request
GET-NEXT-request
SET-request
GET-response,
TRAP
format of one of first four:
ReqID
|
ErrStatus
|
ErrIndex
|
VarBind
List
|
ErrorStatus: 0 in request, 0 in response if no error. Other error codes:
1: PDU has too many bytes
2. no object with given OID
3. PDU type field is bad
4. readonly
5. other errors
Demo with snmpget; note the need for the -Cf option because otherwise
snmpget will attempt to recover from the error by retransmitting the
request with the bad entry deleted:
snmpget -Cf -v 1 -c public localhost system.3.0 system.4.1
(called badoid in my file)
VarBindList: list of ⟨OID,Value⟩ pairs
(values are Null in requests)
GET-req: send out
VarBindList with OIDs and null values. Multiple values are requested with
a VarBindList of length > 1; some common tools don't do that
however.
GET-response: return same OIDs, with values filled in
SNMPv1 semantics: if there is an error in a single request entry, the
response is simply "error"; there are no partial answers. There is a
table of all six error codes on page 62 of Mauro & Schmidt:
- noError
- tooBig (response was too big to fit in one packet)
- noSuchName (the case above for system.4.1; same if we don't have permission!)
- badValue (for SET requests)
- readOnly seldom used; noSuchName is preferred even if you are trying to SET a readonly value
- genErr when nothing else fits
This list is pretty spare.
SNMPv2c introduced partial answers, and the concept of ErrorIndex (1st
entry in the VarBindList that caused an error). Note that there are
still no partial SET actions; those remain all-or-nothing. This is important in ensuring transaction integrity.
SNMPv2 added some more error types, extending the list above:
- noAccess you tried to SET something that was not-accessible
- wrongType for SETs
- wrongLength for SETs, eg involving strings of explicitly limited length
- wrongEncoding for SETs; obscure
- wrongValue for SETs; example: using an illegal value for an enumerated-type object
- noCreation setting a non-existent OID
- inconsistentValue
- resourceUnavailable
- commitFailed
- undoFailed a SET failed and we couldn't roll back all the previous SETs of that request
- authorizationErrror
- notWriteable even if it should have been
- inconsistentName
This might be a good point to enumerate the SNMP traps, on p 64 of K&S:
- coldStart counters will reset to 0
- warmStart counters will not be reset
- linkDown
- linkUp
- authenticationFailure so you can keep track of password crackers
- egpNeighborLoss EGP is obsolete! Replaced by BGP.
- enterpriseSpecific ymmv
The linkUp/linkDown and enterpriseSpecific ones are the most useful.
K&S give an example of an enterpriseSpecific trap on p 65: rdbmsOutOfSpace, part of RFC 1697. Here is the trap definition:
rdbmsOutOfSpace NOTIFICATION-TYPE
OBJECTS { rdbmsSrvInfoDiskOutOfSpaces }
STATUS current
DESCRIPTION
"An rdbmsOutOfSpace trap signifies that one of the database
servers managed by this agent has been unable to allocate
space for one of the databases managed by this agent. Care
should be taken to avoid flooding the network with these traps."
::= { rdbmsTraps 2 }
Earlier, there is a definition of rdbmsTraps ::= { rdbmsMIB 2 }. Note that this is slightly different from K&S.
And here is the mib definition of rdbmsSrvInfoDiskOutOfSpaces (RFC 1697, p 22)
rdbmsSrvInfoDiskOutOfSpaces OBJECT-TYPE
SYNTAX Counter32
MAX-ACCESS read-only
STATUS current
DESCRIPTION
"The total number of times the server has been unable to
obtain disk space that it wanted, since server startup. This
would be inspected by an agent on receipt of an
rdbmsOutOfSpace trap."
::= { rdbmsSrvInfoEntry 9 }
Note that trap flooding would be a serious concern here.
net-snmp configuration (snmpd)
A given SNMP request from a manager is identified by its community (also known as community string),
which by default (and which will remain in our case to the irritation
of network security people everywhere) the string "public". Actually,
the string "public" corresponds to the "readonly" group, with access to
(in our case) well nigh everything but we could change that.
It is possible to create several communities for the same agent. Each
community is associated on the agent with a given set of privileges.
Note that it is not possible
to have two separate communities with the same community name, as the
name is the lookup key. However, it is possible to configure net-snmp
this way, in which case the given community string will receive the
privileges of the first definition.
Here is an example of adding a new community, foo, to the snmpd.conf file. I did this on ulam3 (10.38.2.42).
Goal: add a new community named "foo" that can see the system and interface groups of mib-2.
Step 1: com2sec
We use the com2sec directive to associate our community name foo with the security name foosec.
# sec.name source community
# com2sec paranoid default public
# pld
com2sec readonly default public
# pld demo community "foo"
com2sec foosec default foo
#com2sec readwrite default private
The "default" refers to the request source.
Step 2: group (security name to group). This associates the security name foosec with the group name MyROFoo. Separate entries are needed for SNMPv1 and SNMPv2c.
# sec.model sec.name
group MyROSystem v1 paranoid
group MyROSystem v2c paranoid
group MyROSystem usm paranoid
group MyROGroup v1 readonly
group MyROGroup v2c readonly
group MyROGroup usm readonly
group MyRWGroup v1 readwrite
group MyRWGroup v2c readwrite
group MyRWGroup usm readwrite
group MyROFoo v1 foosec
group MyROFoo v2 foosec
Step 3: creating a view. These two lines create a named view fooview that says we can see mib-2.1 and mib-2.2. The "included" means that we allow these OID prefixes.
#
incl/excl
subtree
mask
view all
included
.1
80
view system included .iso.org.dod.internet.mgmt.mib-2.system
view fooview included .1.3.6.1.2.1.1
view fooview included .1.3.6.1.2.1.2
Step 4: granting access. Here we connect the MyROFoo group, defined above to correspond to the security name foosec and thus to the community foo, to the view defined by fooview. We also specify here that fooview represents the read view, and there is no write view.
#
context sec.model sec.level match read write
notif
access MyROSystem
"" any
noauth exact system none none
access MyROGroup
""
any noauth
exact all none none
access MyRWGroup
""
any noauth
exact all all none
access
MyFOOgroup ""
any noauth
exact fooview none none
Note that we have two view statements, adding two specific subtrees to fooview. If we then do an snmpwalk to retrieve everything, using community "foo", we get these two subtrees.
snmpwalk -v 1 -c foo ulam3 1.3.6.1.2.1
Compare:
snmpwalk -v 1 -c public ulam3 1.3.6.1.2.1
Side note: explain the mask option in the view
statement. An OID has multiple levels. We must match all the levels
specified, unless some of the levels are "unselected" via a mask. A
mask is a bitstring of the same length as the OID; multiple bytes are
separated by ":". OID levels corresponding to a mask bit of 1 must
match; however, OID levels corresponding to a mask bit of 0 need not
match (that is, 0 entries in the mask mean that the corresponding OID
entry is treated as a "wildcard"). Thus
view VNAME included .1.3.6.1.2.1 F4
would match 1.3.6.1.2.1 and also 1.3.6.1.4.1, 1.3.6.1.6.1, etc. Note
that the F4 bits are 1.1.1.1.0.1.0.0, so level 5 (the first 0 of the
mask) need not match in the OID.
This is more likely to be useful in masking to allow access to a specific row of a table, while allowing access to all columns of that row. Recall that the OID of a table entry is Entry.column.row.
ASN.1/BER syntax
Basic issue: we need a machine-independent way of describing complex datatypes. Host byte order cannot matter!
(Actually, SNMP only uses atomic data types as OID values; records and
arrays of records (ie tables) are implemented as OID tree structures.)
Data described with ASN.1, used to define languages (in effect)
ASN.1 basic data item encoding:
This is applied recursively, as necessary.
The TAG identifies Type: basic ASN.1 type, class, etc
Bits:
1st two bits (7 & 6): the "class":
class:
universal
|
00
|
application
|
01
|
context-specific
|
10
|
private
|
11
|
Bit 5 indicates whether the type is "simple" or is a constructed type:
0: simple
1: constructed
The remaining bits indicate the actual type.
Universal:
00001: boolean (simple)
00010: integer (simple)
00011: bit string (simple)
00100: octet string (simple)
00101: Null (simple)
00110: OID (simple)
01001: floating point
10000: Sequence / Sequence Of (constructed)
10110: IA5string (aka ASCII)
Here are a few context-specific type tags, all constructed types:
00000 Get-Request PDU (10-1-00000)
00001 Get-Next-Request PDU
00010 Get-Response PDU
00011 Set-Request PDU
00100 Trap PDU
Here are some application types, specific to SNMP
00000 IpAddress
00001 Counter/Counter32
00010 Gauge/Gauge32
00011 TimeTicks
00100 Opaque
00101 NsapAddress
00110 Counter64 (SNMPv2 only)
(There are some more examples in Burke, Network Management, Appendix D)
SNMP packet:
SNMP message tag | SNMP msg LENGHT | SNMP message VALUE
Value:
version, community name, PDU
version: integer
community: octet_string
PDU: varbindlist: has TAG, LEN, and then a whole series of
TLVs for ReqID, ErrorStatus, ErrorIndex, VarBindList
VarBindList: has a TLV for the
whole list, and then individual TLVs for each item in the list.
Here's a picture from http://www.rane.com/n161fig5.gif:

Note that there are 46 bytes in all, or 44 bytes in the top-level message. 44 = 0x2C.
LEN field:
1-byte length: 1st bit is 0, remaining bits indicate length up to a maximum of 127
bytes
multi-byte len: 1st bit 1, remaining seven bits indicate the number of following octets that hold the length.
Example: a data length of 128 would be specified
with two LEN bytes: 1000 0001 1000 0000
Encoding of 17, 132, and 65540
17: 2, 1, 17
260: 2, 2, 1, 4
260 = 1*256 + 4s
65540: 2, 3, 1, 0, 4
65540 = 1*2562 + 0*256 + 4
Note this is intrinsically variable-length. The len field is used even for integers; may hold 1 (byte) or 4 (bytes) or more.
Data alignment (eg on word boundaries) is nonexistent.
SNMP
message
|
tag=SEQUENCE
|
|
|
|
|
len=44
|
|
|
|
|
value #1:
SNMP version
|
tag=02
|
|
|
|
len=01
|
|
|
|
value=0
|
|
|
|
value #2:
community string
|
tag=04
|
|
|
|
len=7
|
|
|
|
'p'
|
|
|
|
'r'
|
|
|
|
'i'
|
|
|
|
'v'
|
|
|
|
'a'
|
|
|
|
't'
|
|
|
|
'e'
|
|
|
|
value #3:
SNMP PDU
Type=GetRequest
|
tag=10-1-00000
|
|
|
|
len=30
|
|
|
|
value #1: ReqID
|
tag=INTEGER
|
|
|
len=1
|
|
|
value=1
|
|
|
value #2: Error
|
tag=INTEGER
|
|
|
len=1
|
|
|
val=0
|
|
|
value #3: ErrIndex
|
tag=INTEGER
|
|
|
len=1
|
|
|
val=0
|
|
|
value #4: VarbindList
(a list of
VarBindPairs)
|
tag=SEQUENCE
|
|
|
len=19
|
|
|
value: VarBindPair
|
type=SEQUENCE
|
|
len=17
|
|
value #1:
|
type=OID
|
len=13
|
2B = .1.3
|
.6
|
.1
|
.4
|
.1
|
148
|
120
|
.1
|
.2
|
.7
|
.3
|
.2
|
.0
|
value #2:
|
type=NULL
|
Len=0
|
Note that in the rightmost column, bytes 148 (0x94) & 120 (0x78)
encode the integer 2680 (0x0A78), a single level in the OID. Here's the
explanation for OID encoding:
OID encoding
The tag is 00-0-00110, or 0x06, followed by a length byte.
The first two levels x.y of an OID are encoded as a single byte: 40*x+y. For "1.3" this is 43, or 0x2B.
Subsequent levels are encoded as one-byte numbers, if less than 128.
For numbers greater than or equal to 128, the the 8th bit is used as a
"more" flag. (1=more, 0=end), so 2680 = 20*128 + 120 would encode as
the two seven-bit pieces ⟨20,120⟩, or ⟨0x14, 0x78⟩, but the
high bit would be set on the 0x14 byte making it 0x94. The final
encoding would be ⟨0x94,0x78⟩.