The Simple Network Management Protocol (SNMP) is a system for monitoring and controlling other software and hardware systems. It can be used to monitor anything from the state of a running Network Time Protocol daemon, to the level of fuel in a storage tank. It exposes the information in a large hierarchical tree structure, with numerous "Management Information Base" files defining what data is expected in different parts of the tree. Because of the large number of different entities monitored by SNMP it is useful to split the work between a "master agent" that handles external communication and security, and one or more "sub-agents" which focus on their monitoring task and only communicate with the master agent.
AgentX is the protocol used for master to sub-agent communication, designed around the turn of the century as an open standard to replace the myriad protocols that sprung up over the years. Sadly, both SNMP and AgentX suffer from the Curse of Systems Programming: it is decidedly uncool and of niche interest. The result is that documentation outside of RFCs is hard to find at best. Often the only way to figure out for certain what the protocol wants is to simply poke pre-existing agent with a stick and see what happens. Hence this series; all of the information in this can be gleaned from the AgentX RFC (RFC2741), but RFCs are not particularly newbie friendly. And their sheer terseness means that sometimes it can be difficult to discover what is defined in the standard if someone is not already familiar with the subject.
AgentX packets consist of a 20-byte header, an optional context string, and an optional payload. Nearly all packet types have a payload, and many can have the context string. Padding bytes are inserted to align values to 4-byte word boundaries.
The packet header starts with 4 bytes:
- 1 byte defining the AgentX version, currently always has a value of 1
- 1 byte specifying the packet type
- 1 byte for option flags
- 1 reserved byte (padding)
Most flags are specific to certain packet types, but the Network Byte Order bit (bit 4) is relevant to all packets. If bit 4 is true, all data is given in big endian format. Any implementation must be able to handle either endianness.
The rest of the header is 16 bytes divided into four 4-byte fields:
Session ID: Uniquely identifies a single session between a master and sub-agent. A sub-agent may have more than one session open with a master at a given time.
Transaction ID: Identifies a single logical command across multiple packets.
Packet ID: Identifies a specific packet.
Payload Length: Length of the data after the header.
For some packet types the user may provide a non default context for indexing into extra configuration information. If a packet type has this option the Non-default Context bit in the flags header (bit 3) enables the option, if enabled the context string immediately follows the packet header. The context string is implemented as an Octet String.
Packet types with context string option: Register Unregister Get GetNext GetBulk TestSet Ping Notify IndexAllocate IndexDeallocate AddAgentCapabilities RemoveAgentCapabilities
Packet types without context string option: Open Close CommitSet UndoSet CleanupSet Response
|CommitSet, UndoSet, CleanupSet, Ping||Nothing|
|Unregister||Padding byte, Priority|
|Get and GetNext||Search Range (all ending OIDs are null in Get packets)|
|TestSet, IndexAllocate, IndexDeallocate, Notify||VarBind List|
|[optional VarBind List]|
An Octet String consists of a 4-byte length followed by a blob of bytes. If the bytes are not a multiple of 4 then null pad bytes will be added to the end, but not counted as part of the length. The Octet String is the actual form of many SNMP/AgentX types including Context Strings, Descriptions, the BITS type, and the OPAQUE type.
An Object Identifier (OID) consists of 4 bytes of header followed by zero or more 4-byte unsigned integers (subids). The header is 1 byte for the number of subids (max 128), 1 byte for the prefix, 1 byte used as an include flag, and one alignment byte. If the prefix field is non-zero, it and the internet prefix is prepended to the rest of the subids in the format (
A VarBind consists of a 4-byte header, an OID, and an optional data blob. The header is 2-bytes to specify the value type, plus 2 alignment bytes. For types that have associated data the blob is used.
A Search Range is simply two OIDs back to back. The first OID specifies the beginning of the range in- or exclusive depending on the inclusive flag. The second OID determines the non-inclusive end of the range (include is always false), or unbounded if it is empty.
In future posts I plan on describing other parts of the AgentX and SNMP standards that have gotchas or suffer from unclear documentation. Definite topics include the agent-to-master connection sequence, and how data types work.