Network Management Week 4
Fall 2009; LT-412, Wed 4:15-6:45 pm
Network Planning
SNMP packet format
error codes
trap codes
net-snmp agent configuration
BER
OID encoding
ireasoning walk
bulkget
Additional ASN.1 features
mib-2 groups
address translation (at)
IP
TCP
UDP
importing MIB files into iReasoning
Network planning checklists, Burke, 2004
SNMP packet formats:
GET-request
GET-NEXT-request
SET-request
GET-response,
TRAP
format of one of first four:
ReqID ErrStatus ErrIndex VarBindList
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, null values. Multiple values are requested with
a VarBindList of length > 1; some common tools don't do that
however.
GET-resp: 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.
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)
Here is an example of adding a new community, foo, to the snmpd.conf file. I did this on my laptop; that is, localhost. I add
# First, map the community name (COMMUNITY) into a security name
# (local and mynetwork, depending on where the request is coming
# from):
# sec.name source community
# com2sec paranoid default public
# pld
com2sec readonly default public
# pld demo community "foo"
com2sec foocom default foo
#com2sec readwrite default private
####
# Second, map the security names into group names:
# 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 MyFOOgroup v1 foocom
####
# Third, create a view for us to let the groups have rights to:
#
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
####
# Finally, grant the 2 groups access to the 1 view with different
# write permissions:
#
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 localhost .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
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 examples in Burke, 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 len < 128
bytes
multi-byte len: 1st bit 1, remaining seven bits indicate # of
following octets are 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 is nonexistent.
OID encoding
The tag is 00-0-00110, or 0x06, followed by a length byte. The tag for a SEQUENCE is 00-1-10000, or 0x30.
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 7-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>. Similarly, to encode 2021 (the private
number for ucd-snmp), we would split it into 7-bit pieces
<0x0f,0x65> and set the high bit on the first piece to get
<0x8f,0x65>. Or, for 8072 (the private number for net-snmp), the
7-bit pieces are 0x1f and 0x88; setting the first byte's high bit
leaves us <0x9f,0x88>.
Thus, to encode 1.3.6.1.2.1.1.1.0, use (length byte is in bold)
0x06 0x08 0x2b 0x06 0x01 0x02 0x01 0x01 0x01 0x00
Decoding of my big-request demo packet:
loopback.wireshark: send request with 128 entries in it; get back huge response packet of size 11085. This would be fragmented if sent on a regular ethernet.
Actually, on a regular ethernet (see big128ulam3.wireshark) it is very
fragmented. The request is 1835 bytes, and the response is10667 bytes.
ireasoning walk
If we enable wireshark and do a walk on the iftable, we see lots of
packets. There are 68 get-next-requests and 68 get-responses. Note in
particular the final get-response. There are 66 data items in the
table; we do a get-next on each of them (remember that the following OID is actually retrieved), plus one on the interfaces group mib-2.2 and one on ifNumber.
The wireshark file is iftable.ireasoning.wireshark.
bulkget
Next we try SNMPv2c's bulkget operation. See bulkget.wireshark. Note
that there is a single request and a single response; the response is a
single 497-byte packet.
The bulkget request is generated with:
snmpbulkget -v2c -Cn1 -Cr18 -c public localhost system ifTable
the -Cn1 means that there is 1 nonrepeater; in this case the system OID. The remaining OIDs on the line are the repeaters; there is only the one here, ifTable. The -Cr18 means that we are to request it (with get-next semantics) 18 times, with all the repetition done on the agent side.
Why do all this encoding? So any data can be received and decoded without knowledge
of semantics.
Why do that? Look at the ireasoning mib browser!
If we didn't have embedded types, most of the data would print in binary format. At least this way, we can try to figure out what some of the data values mean.
Actually, compatibility between big-endian and wrong-endian (little-endian) hardware is probably more important.
Further ASN.1 specifications
ASN.1 primitive types are
BOOLEAN, INTEGER, REAL, OID, OCTET STRING, ENUMERATED
Type constructors: SEQUENCE, SEQUENCE OF, SET, SET OF, CHOICE
Some ASN.1 structured data types
(not used for SNMP data, but used within SNMP MIB files for syntax definition)
1. PersonnelRecord on page 120 of Mani Subramanian, Network Management, 1999
(Note: could use SEQUENCE instead of SET; use of SET means data can be encoded in any order)
Note the numeric tags in square brackets, which get encoded in BER in
the type bits. The data itself is then stored or transmitted with a tag
field. This allows
us to define CHOICE structures, as receiver can look at data tag
to figure out which option it is.
division CHOICE {
marketing [0] SEQUENCE
{Sector, State},
research [1] CHOICE
{product [0] NULL,
basic [1] NULL},
production [2] SEQUENCE
{product-line, Country}
}
Trade-Message::= SEQUENCE {
invoice-no INTEGER,
name GraphicString,
details SEQUENCE OF
SEQUENCE {partno INTEGER, quan
INTEGER},
cost REAL
}
Trade-Message: uses SEQUENCE and SEQUENCE OF, also more basic types
name GraphicString,
details SEQUENCE OF
SEQUENCE {part-no INTEGER, quantity
INTEGER},
charge REAL
The SEQUENCE OF creates an ARRAY of homogeneous records.
The SEQUENCE {part-no ...} creates a RECORD of two fields in a
specified order. The two (or more) fields need not have the same type.
details of SET/SEQUENCE
what if we replaced SEQUENCE OF with SET OF above?
could we replace SEQUENCE {...} with SET {...}??
PageNum ::= INTEGER
ChapNum ::= INTEGER
BookPageNum ::= SEQUENCE {ChapNum, Separator, PageNum}
BookPages ::= SEQUENCE OF {BookPageNum}
What if we replace the above with
BookPages ::= SET OF {BookPageNum}
BookPageNumber
BookPages
structured types: BookPageNumber, etc
BookPages::= SEQUENCE OF { BookPageNumber}
or
::= SEQ. OF { SEQ { chapternumber, separator, pagenumber} }
We could use SET OF instead of SEQUENCE OF, if we do not care about
the order of the BookPages. However, we can't replace the inner SEQ
with SET unless we have some way (perhaps tags?) of telling which
is the chapternumber and which is the pagenumber.
more on tags
Accessing table as a tree: indexing is not needed
accessing using the index
MIB files
Case studies
AT group
The Address Translation group contains a single table, with three entries per row:
atTable OBJECT-TYPE
SYNTAX SEQUENCE OF AtEntry
...
::= { at 1 }
atEntry OBJECT-TYPE
SYNTAX AtEntry
ACCESS not-accessible
STATUS deprecated
DESCRIPTION
"Each entry contains one NetworkAddress to
`physical' address equivalence."
INDEX { atIfIndex, atNetAddress }
::= { atTable 1 }
AtEntry ::=
SEQUENCE {
atIfIndex INTEGER,
atPhysAddress PhysAddress,
atNetAddress NetworkAddress
}
atIfIndex OBJECT-TYPE
SYNTAX INTEGER
ACCESS read-write
STATUS deprecated
DESCRIPTION
"The interface on which this entry's equivalence
is effective. The interface identified by a
particular value of this index is the same
interface as identified by the same value of ifIndex."
::= { atEntry 1 }
atPhysAddress OBJECT-TYPE
SYNTAX PhysAddress
ACCESS read-write
STATUS deprecated
DESCRIPTION
"The media-dependent `physical' address.
.."
::= { atEntry 2 }
atNetAddress OBJECT-TYPE
SYNTAX NetworkAddress
ACCESS read-write
STATUS deprecated
DESCRIPTION
"The NetworkAddress (e.g., the IP address)
corresponding to the media-dependent `physical' address."
::= { atEntry 3 }
Look at the actual data, and how it is indexed. This could be called
"sparse indexing", versus the "dense" indexing of the interfaces table.
Note that the actual data from localhost is likely to be pretty
limited; try the actual data from ulam3. Or try the localhost data
after executing the pingsubnet script.