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:
    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
 
ATM
 
Token Ring
 

IP

     
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.)

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:
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:

This might be a good point to enumerate the SNMP traps, on p 64 of K&S:
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:
 
TAG
LEN
VALUE
 
This is applied recursively, as necessary.

The TAG identifies Type: basic ASN.1 type, class, etc
    Bits:   
7 6 5
4 3 2 1 0

    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:

hierarchical SNMP encoding
 
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⟩.