RFC 4011

Policy Based Management MIB

๐Ÿ“… published March 2005, from ietf

RFC 4011: Policy Based Management MIB ๐ŸŽ€

okay bestie, this one is the Policy Based Management MIB ๐Ÿ’… It's an Internet standards track spec from March 2005, so it's officially on the proper standards path ๐Ÿ”ฅ It's from the Network Working Group and it's by S. Waldbusser (Nextbeacon), J. Saperia (JDS Consulting, Inc.), and T. Hongal (Riverstone Networks, Inc.) ๐Ÿซถ The document is the Internet Society's, copyright 2005 โœจ

Status of This Memo โœจ

this doc is an Internet standards track protocol spec for the Internet community, and it's basically asking for discussion and suggestions to make it better ๐Ÿฆ‹ if you want the official standardization state of this protocol, go check the current "Internet Official Protocol Standards" (STD 1) ๐Ÿ“š and yes, distribution of this memo is unlimited ๐ŸŒˆ

Copyright (C) The Internet Society (2005).

Abstract ๐ŸŒธ

this memo defines a piece of the Management Information Base (MIB) for network management protocols in TCP/IP-based internets ๐Ÿงš specifically, it has objects that let you do policy-based monitoring and management of Simple Network Management Protocol (SNMP) infrastructures, plus a scripting language and a script execution environment ๐Ÿ’– it's giving full toolkit energy ๐Ÿ˜ญ

Table of Contents ๐Ÿ“š

1.  The Internet-Standard Management Framework ..................   3
2.  Overview ....................................................   4
3.  Policy-Based Management Architecture ........................   4
4.  Policy-Based Management Execution Environment ...............  10
    4.1.  Terminology ...........................................  10
    4.2.  Execution Environment - Elements of Procedure .........  10
    4.3.  Element Discovery .....................................  11
          4.3.1.  Implementation Notes ..........................  12
    4.4.  Element Filtering .....................................  13
          4.4.1.  Implementation Notes ..........................  13
    4.5.  Policy Enforcement ....................................  13
          4.5.1.  Implementation Notes ..........................  14
5.  The PolicyScript Language ...................................  14
    5.1.  Formal Definition .....................................  15
    5.2.  Variables .............................................  18
          5.2.1.  The Var Class .................................  19
    5.3.  PolicyScript QuickStart Guide .........................  23
          5.3.1.  Quickstart for C Programmers ..................  25
          5.3.2.  Quickstart for Perl Programmers ...............  25
          5.3.3.  Quickstart for TCL Programmers ................  25
          5.3.4.  Quickstart for Python Programmers .............  26
          5.3.5.  Quickstart for JavaScript/ECMAScript/JScript
                  Programmers ...................................  26
    5.4.  PolicyScript Script Return Values .....................  26
6.  Index Information for `this element' ........................  27
7.  Library Functions ...........................................  28
8.  Base Function Library .......................................  29
    8.1.  SNMP Library Functions ................................  29
          8.1.1.  SNMP Operations on Non-Local Systems ..........  30
          8.1.2.  Form of SNMP Values ...........................  32
          8.1.3.  Convenience SNMP Functions ....................  34
                  8.1.3.1.  getVar() ............................  34
                  8.1.3.2.  exists() ............................  34
                  8.1.3.3.  setVar() ............................  35
                  8.1.3.4.  searchColumn() ......................  36
                  8.1.3.5.  setRowStatus() ......................  38
                  8.1.3.6.  createRow() .........................  39
                  8.1.3.7.  counterRate() .......................  42
          8.1.4.  General SNMP Functions ........................  44
                  8.1.4.1.  newPDU() ............................  45
                  8.1.4.2.  writeVar() ..........................  45
                  8.1.4.3.  readVar() ...........................  46
                  8.1.4.4.  snmpSend() ..........................  47
                  8.1.4.5.  readError() .........................  48
                  8.1.4.6.  writeBulkParameters() ...............  48
          8.1.5.  Constants for SNMP Library Functions ..........  49
    8.2.  Policy Library Functions ..............................  51
          8.2.1.  elementName() .................................  51
          8.2.2.  elementAddress() ..............................  51
          8.2.3.  elementContext() ..............................  52
          8.2.4.  ec() ..........................................  52
          8.2.5.  ev() ..........................................  52
          8.2.6.  roleMatch() ...................................  52
          8.2.7.  Scratchpad Functions ..........................  53
          8.2.8.  setScratchpad() ...............................  55
          8.2.9.  getScratchpad() ...............................  56
          8.2.10. signalError() .................................  57
          8.2.11. defer() .......................................  57
          8.2.12. fail() ........................................  58
          8.2.13. getParameters() ...............................  58
    8.3.  Utility Library Functions .............................  59
          8.3.1.  regexp() ......................................  59
          8.3.2.  regexpReplace() ...............................  60
          8.3.3.  oidlen() ......................................  60
          8.3.4.  oidncmp() .....................................  60
          8.3.5.  inSubtree() ...................................  60
          8.3.6.  subid() .......................................  61
          8.3.7.  subidWrite() ..................................  61
          8.3.8.  oidSplice() ...................................  61
          8.3.9.  parseIndex() ..................................  62
          8.3.10. stringToDotted() ..............................  63
          8.3.11. integer() .....................................  64
          8.3.12. string() ......................................  64
          8.3.13. type() ........................................  64
          8.3.14. chr() .........................................  64
          8.3.15. ord() .........................................  64
          8.3.16. substr() ......................................  65
    8.4.  General Functions .....................................  65
9.  International String Library ................................  65
    9.1.  stringprep() ..........................................  66
          9.1.1.  Stringprep Profile ............................  66
    9.2.  utf8Strlen() ..........................................  67
    9.3.  utf8Chr() .............................................  68
    9.4.  utf8Ord() .............................................  68
    9.5.  utf8Substr() ..........................................  68
10. Schedule Table ..............................................  69
11. Definitions .................................................  70
12. Relationship to Other MIB Modules ........................... 113
13. Security Considerations ..................................... 114
14. IANA Considerations ......................................... 117
15. Acknowledgements ............................................ 118
16. References .................................................. 118
    16.1. Normative References .................................. 118
    16.2. Informative References ................................ 119
Authors' Addresses .............................................. 120
Full Copyright Statement ........................................ 121

1. The Internet-Standard Management Framework ๐Ÿฆ‹

if you want the full scoop on the documents that describe the current Internet-Standard Management Framework, check out section 7 of RFC 3410 [16] ๐Ÿ“–

managed objects get accessed through a virtual information store called the Management Information Base, or MIB ๐Ÿงš you usually reach MIB objects through the Simple Network Management Protocol (SNMP) ๐Ÿ’œ the objects themselves are defined using the mechanisms in the Structure of Management Information (SMI) โœจ this memo specifies a MIB module that's compliant with SMIv2, which is described in STD 58, RFC 2578 [2], STD 58, RFC 2579 [3], and STD 58, RFC 2580 [4] ๐Ÿ’…

2. Overview ๐ŸŒˆ

okay so big IT orgs have come up with management strategies to handle how massive and complicated today's networks are ๐Ÿ˜ญ instead of managing device by device (where you can make orders of magnitude more decisions, and more mistakes, lol ๐Ÿ™ˆ), they try to configure the whole network by describing and implementing high-level business policies ๐Ÿ‘‘

here are some examples of "business policies" ๐Ÿ“

  • All routers will run code version 6.2.
  • On-site contractors will only be connected to ports that are configured with special security restrictions.
  • All voice over cable ports in California must provide free local calling.
  • Apply special forwarding to all ports whose customers have paid for premium service.

each one of these could be an action applied to hundreds of thousands of variables ๐Ÿคฏ

to automate all this, customers need software tools that carry out business policies across their networks, plus standard protocols that make sure policies can be applied to all their devices, no matter the vendor โœจ this practice is called Policy-Based Management, and this document defines the managed objects for SNMP that get used to distribute policies in a common form throughout the network ๐Ÿ’–

3. Policy-Based Management Architecture โœจ

okay so policy-based management is basically applying management operations globally across ALL the managed elements that share certain attributes ๐Ÿ’… it's giving "set it once, it hits everything that matches" energy ๐Ÿซถ

Policies are meant to express this vibe:

if (an element has certain characteristics) then (apply an
operation to that element)

and they take this normal form:

if (policyCondition) then (policyAction)

A policyCondition is a script that results in a boolean ๐Ÿ”ฅ it decides whether an element is a member of the set of elements that an action should be performed on. A policyAction is an operation performed on an element or a set of elements.

These policies are most often run on or near the managed devices, where the elements live ๐Ÿ  that way their characteristics can be inspected easily, and the operations on those elements get performed right there. The management station is responsible for distributing an organization's policies to all the managed devices in the infrastructure ๐Ÿ“ฌ and the pmPolicyTable gives you managed objects for representing a policy on a managed device ๐ŸŽ€

An element is an instance of a physical or logical entity, and it's embodied by a group of related MIB variables (like all the variables for interface 7 ๐Ÿ‘€). This makes policies way more efficient and concise, and elements can also model circuits, CPUs, queues, processes, systems, etc. ๐ŸŒˆ

Conceptually, policies run like this:

for each element for which policyCondition returns true, execute
   policyAction on that element

For example:

If (interface is fast ethernet)       then (apply full-duplex mode)
If (interface is access)              then (apply security filters)
If (circuit w/gold service paid for)  then (apply special queuing)

Each unique combo of policy and element is called an execution context โœจ inside one, the phrase 'this element' is used to refer to the associated element, since most policy operations get applied to 'this element'. The address of 'this element' holds the object identifier of any attribute of the element, the SNMP context the element was discovered in, and the address of the system it was discovered on ๐Ÿฆ‹

Policies can manage elements on the same system:

     -----------------------------------------------------
     |                                                   |
     |              Managed System                       |
     |                                                   |
     |                                                   |
     |   ------------------             Managed Elements |
     |   |                |               interfaces     |
     |   | Policy Manager | manages...    circuits       |
     |   |                |               queues         |
     |   ------------------               processes      |
     |                                    ...            |
     |                                                   |
     -----------------------------------------------------

or they can manage elements on other systems:

                                        --------------------------
                                        |  Managed System        |
 --------------------------             |    Managed Elements    |
 |                        |             |      interfaces        |
 |  Management Station or |             |      circuits          |
 |    Mid-Level Manager   |             |      ...               |
 |                        |             --------------------------
 |   ------------------   | manages...
 |   | Policy Manager |   |             --------------------------
 |   ------------------   |             |  Managed System        |
 |                        |             |    Managed Elements    |
 --------------------------             |      interfaces        |
                                        |      circuits          |
                                        |      ...               |
                                        --------------------------

                                        ...

PolicyConditions can do comparison operations on SNMP variables, logical expressions, and other functions ๐Ÿง  a bunch of device characteristics are already defined in MIB Modules and are easy to drop into policyCondition expressions, like ifType == ethernet or frCircuitCommittedBurst < 128K ๐Ÿ’– BUT (and this is the tea โ˜•) there are important characteristics that aren't in MIB objects yet, and it's not current practice to store that info on managed devices. So this document defines MIB objects for that info. To meet today's needs, there are three missing areas: roles, capabilities, and time ๐Ÿ‘‘

Roles ๐ŸŽ€

A role is an administratively specified characteristic of a managed element. As a selector for policies, it decides whether a policy applies to a particular managed element ๐Ÿ’…

Roles are things like political, financial, legal, geographical, or architectural characteristics, which usually can't be directly derived from the info stored on the managed system. So "paid for premium service" or "is plugged into a UPS" are roles, but "percent utilization of a link" is not, because you can just read that one ๐Ÿ™ˆ

Here are the kinds of info you'd put into a role:

  • political - describes the role of a person or group of people, or of a service that a group of people uses. Examples: executive, sales, outside-contractor, customer.
If (attached user is executive) then (apply higher bandwidth)
If (attached user is outside-contractor) then (restrict access)
  • financial/legal - describes what financial consideration was received. Could also include contractual or legal considerations. Examples: paid, gold, free, trial, demo, lifeline.
If (gold service paid for) then (apply special queuing)
  • geographical - describes the location of an element. Examples: California, Headquarters, insecure conduit.
If (interface leaves the building) then (apply special security)
  • architectural - describes the network architects "intent" for an element. Examples: backup, trunk.
If (interface is backup) then (set ifAdminStatus = down)

Roles in this model are human-defined strings that policy code can reference ๐Ÿงš the role table in this MIB may be used to assign role strings to elements and to view all role string assignments. Implementation-specific mechanisms may also be used to assign role strings, however, those assignments must be visible in the role table ๐Ÿ‘€ Multiple roles can be assigned to each element.

Here's the tea: policy code can already read MIB objects that show the current state of the system, while role strings are more static. So it is recommended that role strings not duplicate information available in MIB objects ๐Ÿ™…โ€โ™€๏ธ role strings generally should be used to describe info that isn't accessible in MIB objects.

Policy scripts may inspect role assignments to make decisions based on whether an element has a particular role assigned to it ๐Ÿฆ‹

The pmRoleTable lets a management station learn what roles exist on a managed system. The management station may choose not to install policies that depend on a role that doesn't exist on any elements in the system. It can also register for notifications of new roles, and when it receives a pmNewRoleNotification, it may choose to install new policies that make use of that new role ๐Ÿ””

Capabilities ๐ŸŒธ

The capabilities table lets a management station learn what capabilities exist on a managed system. The management station may choose not to install policies that depend on a capability that doesn't exist on any elements in the system. It can then register for notifications of new capabilities ๐Ÿซถ and upon receipt of a pmNewCapabilityNotification, it may choose to install new policies that make use of that new capability โœจ

Time โฐ

Managers may wish to define policies that are meant to apply for certain periods of time ๐Ÿ•ฐ๏ธ this might mean a policy is installed and dormant for a while, becomes ready, and then later goes dormant again. Sometimes these time periods are regular (Monday-Friday 9-5), and sometimes they're ad hoc. This MIB provides a schedule table that can schedule when a policy is ready and when it's dormant ๐Ÿ“…


A policy manager contains the following pieces ๐Ÿงฉ

   -------------------------------------------------------
   | Policy Manager                                      |
   |                                                     |
   |   ----------------------------------------          |
   |   | Agent                                |          |
   |   |                                      |          |
   |   |  ---------------------------------   |          |
   |   |  | Policy Download and Control   |   |          |
   |   |  |   pmPolicyTable               |   |          |
   |   |  |   pmElementTypeRegTable       |   |          |
   |   |  |   pmSchedTable                |   |          |
   |   |  ---------------------------------   |          |
   |   |                                      |          |
   |   |  ---------------------------------   |          |
   |   |  | Policy Environment Control    |   |          |
   |   |  |   pmRoleTable                 |   |          |
   |   |  |   pmCapabilitiesTables        |   |          |
   |   |  ---------------------------------   |          |
   |   |                                      |          |
   |   |  ---------------------------------   |          |
   |   |  | Policy Monitoring             |   |          |
   |   |  |   pmTrackingTables            |   |          |
   |   |  |   pmDebuggingTable            |   |          |
   |   |  ---------------------------------   |          |
   |   ----------------------------------------          |
   |                                                     |
   |   --------------------------------                  |
   |   | Execution Environment        |                  |
   |   |                              |                  |
   |   |  -----------------------     |                  |
   |   |  | Policy Scheduler    |     |                  |
   |   |  -----------------------     |                  |
   |   |  -----------------------     |                  |
   |   |  | Language            |     |                  |
   |   |  -----------------------     |                  |
   |   |  -----------------------     |                  |
   |   |  | Function Library    |     |                  |
   |   |  -----------------------     |                  |
   |   --------------------------------                  |
   -------------------------------------------------------

4. Policy-Based Management Execution Environment ๐ŸŒˆ

4.1. Terminology ๐Ÿ“–

Active Schedule ๐Ÿ—“๏ธ - okay so a schedule lists the times it's considered active, and it's active during exactly those times. Simple, iconic. ๐Ÿ’…

Valid Policy โœ… - a valid policy is one that's fully configured and enabled to run. It can run, unless it's linked to a schedule entry that says it's not active right now. Not the vibe, but it's the rule. ๐Ÿ™ˆ

Ready Policy ๐Ÿš€ - a ready policy is a valid policy that either has no schedule at all, or is linked to a schedule that's active at this moment. Ready = good to go, bestie. โœจ

Precedence Group ๐Ÿ‘‘ - you can put multiple policies into one precedence group. The result is that for each element, out of the ready policies that match the condition, only the one with the highest precedence value gets to be active. Main character energy, one policy wins. ๐Ÿ’– For example, say there's a default bronze policy that applies to any interface, and a special policy for gold interfaces. The higher precedence of the gold policy means it runs on the gold ports, and the bronze policy doesn't. ๐Ÿฅ‡

Active Execution Context ๐ŸŽฏ - this is a pairing of a ready policy with an element that matches both the element type filter and the policy condition. If there are multiple policies in the precedence group, no higher precedence policy in that group can match the policy condition too. It's a whole lineup, and only the top match gets the spotlight. ๐ŸŒธ

Run-Time Exception (RTE) ๐Ÿ’€ - a run-time exception is a fatal error caused in language or function processing. If one happens while a script is being invoked, that script is immediately terminated, no cap. If a policyCondition hits a run-time exception while processing an element, that element is not matched by the condition, and the associated action won't be run on it. An RTE can also add an entry to the pmDebuggingTable, and it shows up in the pmTrackingPEInfo object. ๐Ÿ˜ญ


4.2. Execution Environment - Elements of Procedure ๐Ÿงš

Okay, so there are three steps the environment goes through, in order, to execute policies:

  • Element Discovery
  • Element Filtering
  • Policy Enforcement

4.3. Element Discovery ๐Ÿ”

An element is an instance of a physical or logical entity. Examples include interfaces, circuits, queues, CPUs, and processes. Sometimes different attributes of one entity get described through tables in several standard and proprietary MIB Modules. As long as the indexing is consistent across those tables, the entity can be modeled as one element. For example, the ifTable and the dot3Stats table both contain attributes of interfaces and share the same index (ifIndex), so they can be modeled as one element type. It's giving teamwork. ๐Ÿซถ

The Element Type Registration table lets the manager learn which element types the system is managing, and register new types if needed. An element type gets registered by providing the OID of an SNMP object (without the instance). Each SNMP instance that exists under that object is a distinct element. The index part of the discovered OID gets supplied to policy conditions and actions, so that code can inspect and configure the element. The agent can figure out the index portion of discovered OIDs from the length of the pmElementTypeRegOIDPrefix, for the part of the MIB being retrieved. For example, if the OIDPrefix is 'ifEntry', which has 9 subids, the index starts at the 11th subid (skipping the subidentifier for the column; e.g., ifSpeed). Math that's actually serving. ๐Ÿงฎ

For each element that gets discovered, the policy condition is called with the element's name as an argument, to see whether the element is a member of the set the policy acts upon. ๐ŸŽ€

Note that agents may automatically configure entries in this table for frequently used element types (interfaces, circuits, etc.). In particular, they may configure elements for which discovery is optimized in one or both of these ways:

  1. The agent may discover elements by scanning internal data structures, as opposed to issuing local SNMP requests. It is possible to recreate the exact semantics described in this table even if local SNMP requests are not issued.
  2. The agent may receive asynchronous notification of new elements (for example, "card inserted") and use that information to create elements instantly, rather than through polling. A similar feature might be available for the deletion of elements.

Note that upon restart, the disposition of agent-installed entries is described by the pmPolicyStorageType object. ๐Ÿ”„

A special element type "0.0" represents the "system element". "0.0" represents the single instance of the system itself, and provides an execution context for policies to operate on "the system" and on MIB objects modeled as scalars. For example, "0.0" gives an execution context for policy-based selection of the operating system code version (likely modeled as a scalar MIB object). The element type "0.0" always exists. As a consequence, no actual discovery takes place, and the pmElementTypeRegMaxLatency object has no effect for the "0.0" element type. However, if the "0.0" element type is not registered in the table, policies will not be executed on the "0.0" element. The system element is the main character who's always there. ๐Ÿ‘‘

If the agent is discovering elements by polling, it should check for new elements no less frequently than pmElementTypeRegMaxLatency would dictate. When an element is first discovered, all policyConditions are run immediately, and any policyConditions that match will have the associated policyAction run immediately. After that, the policyCondition gets run regularly for the element, with no more than pmPolicyConditionMaxLatency milliseconds elapsing between each invocation. Note that if an implementation can be alerted immediately when a particular type of element is created, it is urged to discover that type of element this way rather than through polling, so the discovered element gets configured instantly. โšก


4.3.1. Implementation Notes ๐Ÿ› ๏ธ

Although the external behavior of this registration process is defined in terms of walking MIB tables, implementation strategies may differ. For example, commonly used element types (such as interface) may have purpose-built element discovery capability built in, and advertised to managers through an entry in the pmElementTypeRegTable. ๐Ÿฆ‹

Before registering an element type, a manager is responsible for inspecting the table to see whether it's already registered, either by the agent or by another manager. Note that entries that differ only in the last subid (which specifies which object is an entry) are effectively duplicates, and should be treated as such by the manager. Double entries are not it. ๐Ÿ™…โ€โ™€๏ธ

The system that implements the Policy-Based Management MIB may not know the format of object identifiers in other MIB Modules. So it's inappropriate for it to check these OIDs for errors. It's the management station's job to register well-formed object identifiers. For example, if an extra sub-identifier is supplied when the ifTable is registered, no elements will be discovered. Similarly, if a sub-identifier is missing, every element will be discovered numerous times (once per column), and none of the element addresses will be well formed. Read your OIDs carefully, bestie. ๐Ÿ’€


4.4. Element Filtering ๐Ÿงน

The first step in executing a policy is to see whether the policy is ready to run based on its schedule. If the pmPolicySchedule object is equal to zero, there's no schedule defined, and the policy is always ready. If the pmPolicySchedule object is non-zero, then the policy is ready only if the referenced schedule group contains at least one valid schedule entry that's active at the current time. ๐Ÿ—“๏ธ

If the policy is ready, the next step is to see which elements match the policy condition. The policy condition is called once for each element, and runs to completion. The element's name is the only argument passed to the condition code for each invocation. No state is remembered within the policy script from the previous invocation of 'this element', or from the previous invocation of the policy condition, except for state accessible through library functions. Two notable examples are the scratchpad functions, which explicitly provide for storing state, and the SNMP functions, which can store state in local or remote MIB objects. If any run-time exception occurs, the condition terminates immediately for 'this element'. If the condition returns non-zero, the corresponding policy action gets executed for 'this element'. ๐ŸŽฏ

If an element matches a condition and it hadn't matched that condition the last time it was checked (or if it's a newly discovered element), the associated policyAction gets executed immediately. If the element matched the condition at the last check, it stays in the set of elements whose policyAction will be run within the policyActionMaxLatency. ๐ŸŒˆ


4.4.1. Implementation Notes ๐Ÿ’œ

Whether policy conditions are multi-tasked is an implementation-dependent matter. Each condition/element combination is conceptually its own process, and can be scheduled sequentially, or two or more could run simultaneously. Choose your fighter, the implementation decides. ๐Ÿคทโ€โ™€๏ธ


4.5. Policy Enforcement ๐Ÿšจ

For each element that has returned non-zero from the policy condition, the corresponding policy action is called. The element's name is the only argument passed to the policy action for each invocation. Except for state accessible from library functions, no state is remembered from the policy condition evaluation, from the previous condition/action invocation of 'this element', or from the previous invocation of the policy condition or action on any other element. If any run-time exception occurs, the action terminates immediately for 'this element'. ๐Ÿ’…


4.5.1. Implementation Notes ๐ŸŒŸ

How policy actions are multi-tasked is an implementation-dependent matter. Each condition/element combination is conceptually its own process, and can be scheduled sequentially, or two or more could be run simultaneously. Same energy as the conditions, different spotlight. โœจ

5. The PolicyScript Language โœจ

okay so, policy conditions and policy actions are written in the PolicyScript language ๐Ÿ’– it's meant to be a small interpreted language that's simple to understand and simple to implement, because it's built for tiny scripts that make up conditions and actions. it's giving lightweight and focused ๐Ÿฆ‹

PolicyScript is meant to feel familiar to programmers who already know a common language like Perl or C ๐ŸŒธ nominally, policyScript is a subset of C. but the authors wanted C++'s operator overloading (mostly just to make the docs easier to write) ๐Ÿ’… so PolicyScript is formally defined as a subset of C++, where a lot of the operators are overloaded as part of the "var" class. heads up though: a PolicyScript program can't overload operators itself, because the syntax for doing that isn't part of PolicyScript. the subset exists so that low-cost interpreters are easy to build, and so the C/C++ quirks get left behind ๐Ÿงš the goal is that both C and Perl people will recognise what's allowed.

some C/C++ features are NOT available, like function definitions, pointer variables, structures, enums, typedefs, floating point, and preprocessor functions (except for comments). no cap, it's a streamlined vibe โœจ

this language is formally defined as a subset of ISO C++ [10], but it only allows constructs that can be expressed in the Extended Backus-Naur Form (EBNF) written down here. EBNF doesn't fully pin down syntax (it allows some things that are actually invalid) and it doesn't cover semantics, but it's enough to define the subset you need for conformance ๐ŸŽ€ unless it's explicitly described here, the meaning of any EBNF construct is found by looking at the ISO C++ standard.

comments and newlines are allowed and actually encouraged, since they make PolicyScript code easier to read ๐Ÿซถ comments start with '/' and end with '/', or start with '//' and run to the end of the line.

one thing EBNF can't express: all variables inside one instance of a PolicyScript script share the same scope. so variables defined in a block between '{' and '}' are NOT in their own separate scope from the enclosing block. wild, right? ๐Ÿ˜ญ

PolicyScript code must be written in the ASCII character set ๐Ÿ“

in the EBNF used here, terminals are character set members (on their own or in a sequence) wrapped in single quotes, or described as a phrase between '<' and '>'. nonterminals are sequences of letters and underscores. a colon (:) after a nonterminal introduces its definition (its production). inside a production, '|' separates alternatives, and '(' and ')' group things together. '[' and ']' mean the enclosed items are optional, and so does a '?' after an item. a '*' after an item means zero or more repeats, and a '+' means one or more. and '--' starts a comment that runs to the end of the line ๐ŸŒˆ

5.1. Formal Definition ๐Ÿ’…

The PolicyScript language follows the syntax and semantics of ISO C++ [10], but it's limited to what can be expressed in the EBNF below.

the following keywords are reserved words and can't be used in any policy script ๐Ÿ™ˆ this stops someone from using a keyword from another language as an identifier and accidentally changing what a script means. the reserved words are:

   auto, case, char, const, default, do, double, enum, extern, float,
   goto, inline, int, long, register, short, signed, sizeof, static,
   struct, switch, typedef, union, unsigned, void, and volatile.

any syntax error, use of a reserved keyword, reference to an unknown identifier, wrong number of function arguments, error in coercing an argument to the proper type, going over the local limit on string length, or going over the local limit on total storage used by local variables will cause an RTE ๐Ÿ’€

PolicyScript allows comments using the comment delimiters '/' to '/', or the start-of-comment symbol '//' โœจ

-- Lexical Grammar

   letter:       '_' | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
               | 'g' | 'h' | 'i' | 'j' | 'k' | 'l' | 'm'
               | 'n' | 'o' | 'p' | 'q' | 'r' | 's' | 't'
               | 'u' | 'v' | 'w' | 'x' | 'y' | 'z'
               | 'A' | 'B' | 'C' | 'D' | 'E' | 'F'
               | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M'
               | 'N' | 'O' | 'P' | 'Q' | 'R' | 'S' | 'T'
               | 'U' | 'V' | 'W' | 'X' | 'Y' | 'Z'

   digit:        '0' | '1' | '2' | '3' | '4'
               | '5' | '6' | '7' | '8' | '9'

   non_zero:   '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'

   oct_digit:  '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7'

   hex_digit:    digit | 'a' | 'b' | 'c' | 'd' | 'e' | 'f'
                       | 'A' | 'B' | 'C' | 'D' | 'E' | 'F'

   escape_seq:    '\''   |   '\"'   |   '\?'   |   '\\'
                | '\a'   |   '\b'   |   '\f'   |   '\n'
                | '\r'   |  '\t'    |   '\v'
                | '\' oct_digit+    | '\x' hex_digit+

   non_quote:  Any character in the ASCII character set
               except single quote ('), double quote ("),
               backslash ('\'), or newline.

   c_char:            non_quote | '"' | escape_seq

   string_literal:    '"' s_char* '"'

   s_char:            non_quote | ''' | escape_seq

   char_constant:     ''' c_char '''

   decimal_constant:  non_zero digit*
   octal_constant:    '0' oct_digit*

   hex_constant:      ( '0x' | '0X' ) hex_digit+

   integer_constant:  decimal_constant | octal_constant | hex_constant

   identifier:        letter ( letter | digit )*

-- Phrase Structure Grammar

   -- Expressions

   primary_expr:      identifier | integer_constant | char_constant
                    | string_literal  |  '(' expression ')'

   postfix_expr:      primary_expr
                    | identifier '(' argument_expression_list? ')'
                    | postfix_expr '++'
                    | postfix_expr '--'
                    | postfix_expr '[' expression ']'

   argument_expression_list:
                      assignment_expr
                    | argument_expression_list ',' assignment_expr

   unary_expr:        postfix_expr  |  unary_op unary_expr

   unary_op:          '+' | '-' | '~' | '!' | '++' | '--'

   binary_expr:  unary_expr | binary_expr binary_op unary_expr

   binary_op:       '||' | '&&' | '|'  | '^'  | '&'  | '!='
                  | '==' | '>=' | '<=' | '>'  | '<'  | '>>'
                  | '<<' |  '-' | '+'  | '%'  | '/'  |  '*'

   assignment_expr:      binary_expr
                       | unary_expr assignment_op assignment_expr

   assignment_op:     '=' | '*='  | '/=' | '%=' | '+=' | '-='
                  | '<<=' | '>>=' | '&=' | '^=' | '|='

   expression:    assignment_expr | expression ',' assignment_expr

   -- Declarations

   declaration:       'var' declarator_list ';'
   declarator_list:   init_declarator
                    | declarator_list ',' init_declarator

   init_declarator:   identifier [ '=' assignment_expr ]

   -- Statements

   statement:   declaration
              | compound_statement
              | expression_statement
              | selection_statement
              | iteration_statement
              | jump_statement

   compound_statement:    '{' statement* '}'

   expression_statement:  expression? ';'

   selection_statement:
           'if' '(' expression ')' statement
         | 'if' '(' expression ')' statement 'else' statement

   iteration_statement:
           'while' '(' expression ')' statement
         | 'for' '(' expression? ';' expression? ';' expression? ')'
               statement

   jump_statement:    'continue' ';'
                    | 'break' ';'
                    | 'return' expression? ';'

   -- Root production

   PolicyScript:     statement*

5.2. Variables ๐ŸŽ€

to keep scripts short and easy to write, PolicyScript gives you a loosely typed data class called "var" ๐Ÿฆ‹ it can hold both integer and string values. so the native C++ types (char, int, and so on) aren't needed, and they didn't make it into this subset. the semantics of the "var" type are modeled on ECMAScript [17] ๐Ÿ’œ

for example:

   var number = 0, name = "IETF";

this language runs in an environment where the following typedef is declared. (note that this typedef won't be visible in the policyCondition or policyAction code ๐Ÿ‘€)

   typedef ... var;

even though this is written here as a typedef, the 'typedef' keyword itself is NOT available to use in PolicyScript code ๐Ÿ™…โ€โ™€๏ธ

5.2.1. The Var Class ๐Ÿ’–

okay so bestie, a value is an entity that is always one of two types: string or integer ๐ŸŽ€

The String type is every finite ordered sequence of zero or more 8-bit unsigned integer values (we call each one an "element") ๐ŸŒธ Strings can hold text AND binary data, so they're super versatile. Each element sits at a position in the sequence, and positions are numbered with nonnegative integers. The first element (if there is one) is at position 0, the next at 1, and so on. The length of a string is how many elements (8-bit values) it has. The empty string has length 0, so it has no elements at all ๐Ÿซถ

The integer type is every integer from -9223372036854775808 (-2^63) up to 18446744073709551615 (2^64-1) ๐Ÿ”ฅ If an integer operation overflows in the positive direction, the result is taken modulo 2^64. If it underflows in the negative direction, the result is undefined (yikes ๐Ÿ’€). Integer division rounds toward zero.

Before a var object gets initialized, it has type String and a length of zero ๐Ÿงš

The policy script runtime does automatic type conversion whenever it's needed โœจ To make the semantics of some constructs easier to explain, the spec defines a set of conversion operators: ToInteger(), ToString(), ToBoolean(), and Type(). These are NOT part of the language itself, they just exist on paper to help describe how things behave ๐Ÿ“ They're polymorphic, meaning they accept a value of any standard type ๐Ÿฆ‹

ToInteger

ToInteger converts its argument to a value of type Integer, following this table:

Integer            The result equals the input argument
                   (no conversion).
String             See grammar and note below.
integer_constant   The result equals the input argument
                   (no conversion).
string_literal     See grammar and note below.
char_constant      See grammar and note below.

ToInteger Applied to Strings

For string_literal and char_constant inputs, ToInteger runs the grammar below over the input. If the grammar can't read the string as an expansion of numeric_string, then an RTE gets generated ๐Ÿ™ˆ Also, a numeric_string that's empty or only whitespace converts to 0. Lowkey a fun little fact โœจ

-- EBNF for numeric_string

numeric_string : white_space* numeric? white_space*

white_space :      <TAB> |  <SP> |  <NBSP> |  <FF> |  <VT>
                 | <CR>  |  <LF> |  <LS>   |  <PS> |  <USP>

numeric :        signed_decimal |  hex_constant | octal_constant |
                 enum_decimal

signed_decimal:  [ '-' | '+' ] decimal_constant

enum_decimal:    [ letter | digit | '-' ]* '(' decimal_constant ')'

-- decimal_constant, hex_constant, and octal_constant are defined
-- in the PolicyScript EBNF described earlier.

Now the enum_decimal form is the main vibe ๐Ÿ’… When it converts, everything before the parenthesis AND the parentheses themselves get completely ignored, and only the decimal_constant inside the parentheses gets converted. So "frame-relay(32)" turns into the integer 32 ๐Ÿฆ‹

Writing 32 directly would be more readable, sure, but the code writer has to be accurate, because "ethernet-csmacd(32)" and "frame-relay(999)" will ALSO be accepted ๐Ÿ˜ญ Be careful, bestie.

ToString

ToString converts its argument to a value of type String, following this table:

Integer           Return the string containing the decimal
                  representation of the input argument in
                  the form of signed_decimal, except that
                  no leading '+' will be used.
String            Return the input argument (no conversion)
integer_constant  Return the string containing the decimal
                  representation of the input argument in the
                  form of signed_decimal except that no
                  leading '+' will be used.
string_literal    Return the input argument (no conversion)
char_constant     Return the string of length one containing
                  the value of the input argument.

ToBoolean

ToBoolean converts its argument to a value of type Integer, following this table:

Integer            The result is 0 if the argument is 0.
                   Otherwise the result is 1.
String             The results is 0 if the argument is the
                   empty string.  Otherwise the result is 1.
integer_constant   The result is 0 if the argument is 0.
                   Otherwise the result is 1.
string_literal     The result is 0 if the argument is the
                   empty string.  Otherwise the result is 1.
char_constant      The result is 1.

Operators

These rules spell out how the type conversions work for each operator ๐ŸŒˆ Read them carefully, they're the heart of the whole thing ๐Ÿซถ

  A++:   A = ToInteger(A); A++;
  A--:   A = ToInteger(A); A--;
  ++A:   A = ToInteger(A); ++A;
  --A:   A = ToInteger(A); --A;
  +A:    ToInteger(A);
  -A:     -1 * ToInteger(A);
  ~A:    ToInteger(A);
  !A:    !ToBoolean(A);
  A * B, A - B, A & B, A ^ B , A | B, A << B, A >> B:
         ToInteger(A) <operator> ToInteger(B)

  A / B, A % B:
         if (ToInteger(B) == 0)
           RTE, terminate;
         else
           ToInteger(A) <operator> ToInteger(B)
  A + B:
         if (Type(A) == String || Type(B) == String)
           ToString(A) concatenated with ToString(B)
         else
           A + B
  Compound Assignment (<operator>=):
          Simply follow rules above.  Note that type of LHS (Left
          Hand Side) may be changed as a result.

  A < B, A > B, A <= B, A >= B, A == B, A != B:
         if (Type(A) == String && Type(B) == String)
             lexically compare strings with strcmp() logic
         else
             ToInteger(A) <operator> ToInteger(B)
   A && B:
          if (ToBoolean(A))
              ToBoolean(B);
          else
              false;
   A || B:
          if (ToBoolean(A))
              true;

          else
              ToBoolean(B);

   if(A):
          if (ToBoolean(A))
   while(A):
          while(ToBoolean(A))
   for(...; A; ...):
         for(...; ToBoolean(A); ...)

   A[B] as a RHS (Right Hand Side) value:
         if (Type(A) != String
              || ToInteger(B) >= strlen(A))
            RTE, terminate;
         A[ ToInteger(B) ]
         The contents are returned as a string of length one

    A[B] = C as a LHS value:
         if (Type(A) != String
              || ToInteger(B) >= strlen(A))

            RTE, terminate;
         if (strlen(ToString(C)) == 0)
            RTE, terminate
         A[ ToInteger(B) ] = First octet of ToString(C)

         Note that this is only applicable in a simple assignment.

Here's a little example to see it all click โœจ Take this expression:

"getVar("ifSpeed.1") < 128000"

getVar ALWAYS returns a string, and '128000' gets implicitly treated as an integer ๐Ÿง  The < rules say that if either argument is an integer, you do a "numeric less than" on ToInteger(A) and ToInteger(B).

So if "getVar("ifSpeed.1")" returns "64000", the expression can be translated like this:

ToInteger("64000") < ToInteger(128000); or,
64000 < 128000; or,
True

5.3. PolicyScript QuickStart Guide ๐ŸŽ€

okay so PolicyScript is built so that programmers who already know other languages can jump in and start writing scripts right away ๐Ÿ’… Honestly it's giving approachable ๐ŸŒธ

The fastest way to get a language is to just see it in action โœจ This totally nonsensical script uses most of the PolicyScript constructs (it skips some usage options and a bunch of arithmetic operators though ๐Ÿ™ˆ):

var x, index = 7, str = "Hello World", oid = "ifSpeed.";

x = 0;
while(x < 10){
    if (str < "Goodbye") /* string comparison */
        continue;
    else
        break;
    x++;
}
if (oidlen(oid) == 10)
    oid += "." + index; // append index to oid
for(x = 0; x < 7; x++){
      str += "a";
      var y = 12;
      index = ((x * 7) + y) % 3;
      if (str[6] == 'W')
          return index;
}
return;

Now for some more practical stuff ๐Ÿฆ‹

For a condition:

// Return 1 if this is an interface and it is tagged
// with the role "gold"
return (inSubtree(elementName(), "ifEntry")
    && roleMatch("gold"))

A condition/action pair: first, you register the Host Resources MIB hrSWRunEntry as a new element in the pmElementTypeRegTable. This makes the policy run for every process on the system ๐ŸŒˆ The token $* gets replaced by the script interpreter with a process index (see Section 7 for a definition of the $* token).

The condition:

// if it's a process and it's an application and it's
// consumed more than 5 minutes of CPU time
return (inSubtree(elementName(), "hrSWRunEntry")
        && getVar("hrSWRunType.$*") == 4  // app, not OS or driver
        && getVar("hrSWRunPerfCPU.$*") > 30000) // 300 seconds

The action:

// Kill it
setVar("hrSWRunStatus.$*", 4, Integer); // invalid(4) kills it

A more substantial action, to start an RMON2 host table on the interfaces that match the condition ๐Ÿงš

var pdu, index;

pdu = newPDU();
writeVar(pdu, 0, "hlHostControlDataSource.*",
         "ifIndex." + ev(0), Oid);
writeVar(pdu, 1, "hlHostControlNlMaxDesiredEntries.*", 1000,
         Integer);
writeVar(pdu, 2, "hlHostControlAlMaxDesiredEntries.*", 1000,
         Integer);
writeVar(pdu, 3, "hlHostControlOwner.*", "policy", String);
writeVar(pdu, 4, "hlHostControlStatus.*", "active(1)", Integer);
if (createRow(pdu, 5, 4, 20, 65535, index) == 0
    || index == -1)
    return;

Because PolicyScript is a least common denominator, it has nothing that would shock programmers who know C, C++, Perl, Tcl, JavaScript, or Python ๐Ÿ˜ญ A new programmer might try language constructs that PolicyScript doesn't have, but they should still be able to understand any existing PolicyScript, and they'll probably know how to use anything that IS valid in it. The lists below quickly point out the notable changes for people coming from particular languages. They don't describe the unavailable constructs, but you can easily tell what's available from the definition above ๐Ÿ’œ

5.3.1. Quickstart for C Programmers ๐Ÿ“

  • Character constants (i.e., 'c') are treated as one-character strings, not as integers. So operations such as ('M' - 'A') or (x + 'A') will not perform as expected ๐Ÿ’€
  • Functions can change the value of arguments even though they are not pointers (or called like &arg). ๐Ÿ‘‘
  • All variables are in the same scope. ๐Ÿซถ

5.3.2. Quickstart for Perl Programmers ๐ŸŒธ

  • Comments are /* comment */ and // till end of line, not #. โœจ
  • No need to put a $ in front of variables. ๐Ÿ’…
  • Strings are compared with ==, <=, <, etc. (details in Sec. 6.2.1). ๐Ÿฆ‹
  • Strings are concatenated with + (details in Sec. 6.2.1). ๐ŸŽ€
  • No variable substitution in "" strings. '' strings are 1 char only. ๐Ÿ™ˆ
  • Variables must be declared before use (but no type is necessary). ๐Ÿงš
  • All variables are in the same scope. iconic ๐ŸŒˆ

5.3.3. Quickstart for TCL Programmers ๐Ÿ’…

okay bestie, TCL people, this one's for you ๐Ÿงš here's what's different:

  • Comments are /* comment */ and // till end of line, not # ๐Ÿ’œ
  • No need to put a $ in front of variables โœจ
  • Function calls are func-name(arg1, arg2, ...) ๐Ÿซถ
  • Square braces [] don't interpret their contents ๐Ÿ™ˆ
  • Double quotes "" surround a string, but no substitutions are performed ("" is like { } in TCL ) ๐Ÿ’–
  • Statements are terminated by a semicolon (;) ๐ŸŒธ
  • Instead of "Set a b", use "b = a;" ๐Ÿ’…
  • Strings are concatenated with + (details in Sec. 6.2.1) ๐Ÿฆ‹
  • All variables are in the same scope ๐Ÿ‘‘

5.3.4. Quickstart for Python Programmers ๐Ÿ

Python girlies, read this part ๐Ÿซถ the vibes are a little different:

  • Comments are /* comment */ and // till end of line, not # ๐Ÿ’œ
  • Single quotes can be used only for single-character strings ('a') ๐ŸŒธ
  • Indentation doesn't matter. Braces { } define blocks โœจ
  • Variables must be declared before use (but no type is necessary) ๐Ÿฆ‹
  • The expressions for if and while are always surrounded by parenthesis, as in "if (x < 5)" ๐Ÿ’…
  • for syntax is "for(expression; expression; expression)" (see EBNF) ๐ŸŽ€
  • All variables are in the same scope ๐Ÿ‘‘

5.3.5. Quickstart for JavaScript/ECMAScript/JScript Programmers ๐ŸŒˆ

  • Variables must be declared before use ๐Ÿ’–
  • Functions can change the value of arguments ๐Ÿ”ฅ
  • All variables are in the same scope ๐Ÿ‘‘

5.4. PolicyScript Script Return Values ๐ŸŽ€

okay so a PolicyScript script usually ends one of two ways: either it hits a return statement, or the flow of execution reaches the end of the final statement in the script ๐Ÿ’… a normal script execution always returns a Boolean value โœจ if there's no explicit value in the return statement, or the flow goes right through the end of the script, the return value is implicitly zero ๐Ÿ™ˆ and if you do give the return statement an expression, that expression gets evaluated, and its result is implicitly converted with the ToBoolean operator before it goes back to the script execution environment ๐Ÿซถ

the return value of a policyCondition script decides whether its policyAction script runs ๐Ÿฆ‹ if the returned value is zero, the policyAction script is not executed ๐Ÿ’” if the returned value is one, the policyAction script will be executed ๐Ÿฅณ

the return value of a policyAction script is ignored ๐Ÿคทโ€โ™€๏ธ

and heads up, an RTE or an invocation of the fail() function sets the return value of the script to zero ๐Ÿ˜ญ but! running defer() or fail() might set the defer attribute, so the lower precedence script may get executed anyway ๐Ÿ‘‘ that part has nothing to do with the return value of the policy script execution, so keep them separate in your head ๐Ÿง 


6. Index Information for 'this element' ๐Ÿงญ

PolicyScript code needs a convenient way to grab the components of the index for 'this element', so it can do SNMP operations on it or on related elements ๐Ÿ’– there are two mechanisms for this, and they're both super useful ๐Ÿซถ

  1. For all OID input parameters to all SNMP Library Functions (but not OID utility functions), the token "$n" ($ followed by an integer between 0 and 128) can be used in place of any decimal sub-identifier โœจ the agent expands this token at execution time to contain the nth subid of the index for the current element ๐Ÿฆ‹ for example, if the element is interface 7, and the objectIdentifier is "1.3.6.1.2.1.2.2.1.3.$0", it gets expanded to "1.3.6.1.2.1.2.2.1.3.7" ๐ŸŒธ the special token "$*" expands to contain all of the subidentifiers of the index of the current element, separated by . characters ๐Ÿ’…

It is an RTE if a token is specified that is beyond the length of the index for the current element ๐Ÿ˜ฌ

Note that the "$n" convention is only active within strings ๐Ÿ™ˆ

  1. The ec() and ev() functions let you access the components of the index for 'this element' ๐ŸŽ€ ec() takes no argument and returns the number of index components that exist ๐Ÿ’œ ev() takes an integer argument specifying which component of the index (numbered starting at 0), and returns an integer containing the value of the n'th subidentifier โœจ refer to the Library functions section for the complete definition of ec() and ev() ๐Ÿ“–
         For example, if 'this element' is frCircuitDLCI.5.57
                                           (ifIndex = 5, DLCI = 57)
               then ec()  returns 2
                    ev(0) returns 5
                    ev(1) returns 57

This is helpful when one wishes to address a related element ๐Ÿงš extending the previous example, to find the port speed of the port, the circuit (above) runs over:

      portSpeed = getVar("ifSpeed." + ev(0));

A script may check the type of 'this element' by calling the elementName() function ๐Ÿ” it's possible to write a script that works with different types of elements, but honestly, lots of scripts will assume one particular element type. if they get used on a different element type, they'll work incorrectly ๐Ÿ˜ญ so be careful, bestie ๐Ÿ’…


7. Library Functions ๐Ÿ“š

Library functions are built-in functions, mainly there to give you access to information on the local system or to manipulate that information more efficiently โœจ a group of functions is organized into a library, and the library is the unit of conformance for function implementation ๐Ÿซถ to claim conformance to a library, an implementation must implement all functions in that library to the specifications of the library ๐Ÿ’–

so a management station or a condition or action can figure out whether a certain library is implemented ๐Ÿ•ต๏ธโ€โ™€๏ธ each library has a name that it registers in the role table as a characteristic of the system element ("0.0") in the default SNMP context ๐ŸŒˆ that means you can test conformance to a library with the roleMatch library function (in the base library) with the call roleMatch ("libraryName", "0.0") ๐ŸŽ€

in the descriptions of these functions below, the function prototype describes the type of argument expected ๐Ÿ“ even though variables aren't declared with a particular type, their contents must be appropriate for each function argument ๐Ÿ˜ค if the type is variable, the keyword 'var' will be used. if only a string is appropriate, the keyword 'string' will be used. if only an integer is appropriate, the keyword 'integer' will be used ๐Ÿฆ‹ if the argument is declared as 'string' or 'integer' and a value of a different type is passed, the argument will be coerced with ToInteger() or ToString() ๐Ÿ”„ any failure of this coercion will cause an RTE (in particular for ToInteger(), which will fail if its string-valued argument is not a well-formed integer) ๐Ÿ’€

in the function prototype, if the '&' character precedes the identifier for an argument, that argument may be modified by the function (e.g., "integer &result, ...)") โœ๏ธ arguments without the '&' character cannot be modified by the function ๐Ÿ”’ in a script, modifiable arguments don't have to be preceded by a '&' ๐Ÿ’… it is an RTE if a constant is passed to a modifiable function argument (regardless of whether the function actually writes to the argument) ๐Ÿ˜ฑ

in the function prototype, the '[' and ']' characters surround arguments that are optional ๐ŸŽ€ in PolicyScript code, the optional argument may only be included if all optional arguments to the left of it are included ๐Ÿง  the function may place restrictions on when an optional argument must, or must not, be included ๐Ÿšซ

in the function prototype, if a type is listed before the name of the function, the function returns a value of that type ๐ŸŽ if no type is listed, the function returns no value ๐Ÿซฅ


8. Base Function Library ๐ŸŒธ

a standard base library of functions is available to all systems that implement this specification ๐Ÿ’– this library is registered with the name "pmBaseFunctionLibrary" โœจ the specification of this library is modularized into 4 separate sections, but conformance to the library requires implementation of all functions in all sections ๐Ÿซถ the sections are:

  • SNMP library functions ๐Ÿ’œ
  • Policy library functions ๐Ÿฆ‹
  • Utility functions ๐Ÿงฐ
  • Library Functions ๐Ÿ“š

8.1. SNMP Library Functions ๐Ÿ“ก

there are two sets of SNMP Library functions, each one built for a different situation ๐Ÿงš

  • Convenience SNMP Functions

these are all about keeping simple things simple โœจ they're easy to use and the code is easy to understand ๐Ÿ’… they'll suffice for the majority of situations, where a single variable is referenced and the desired error recovery is simply (and immediately) to give up and move to the next policy-element combination ๐Ÿ™ƒ in more complex cases, the General SNMP Functions can be used, at the cost of several times the code complexity ๐Ÿ˜ฎโ€๐Ÿ’จ

the convenience SNMP functions are getVar, exists, setVar, setRowStatus, createRow, counterRate, and searchColumn ๐ŸŽ€

  • General SNMP Functions

the General SNMP functions let you generate nearly any legal SNMP Message, including ones with multiple varbinds, getNext operations, notifications, and messages with explicit addressing or security specifications ๐Ÿ”ฅ they're the heavy hitters ๐Ÿ‘‘

the general SNMP functions are writeVar, readVar, snmpSend, readError, and writeBulkParameters ๐ŸŒˆ

8.1.1. SNMP Operations on Non-Local Systems โœจ

Okay so sometimes a script has to do something on a different SNMP system than the one 'this element' lives on ๐Ÿ‘€. Sometimes it also needs different security parameters. For that, the SNMP library functions get some extra optional arguments ๐Ÿ’…:

snmp-function(...[, integer mPModel,
                    string tDomain, string tAddress,
                    integer secModel, string secName,
                    integer secLevel, string contextEngineID
])

For example:

    getVar("sysDescr.0", "", SNMPv3, "transportDomainUdpIpv4",
           "192.168.1.1:161", USM, "joe", NoAuthNoPriv);

When a function uses these, its definition calls them 'NonLocalArgs' ๐Ÿฆ‹. Here's what each one means:

  • 'mPModel' is the integer value of the SnmpMessageProcessingModel to use for this operation.
  • 'tDomain' is a string containing an ASCII dotted-decimal object identifier representing the transport domain to use for this operation.
  • 'tAddress' is a string containing the transport address formatted according to the 'tDomain' argument. The ASCII formats for each 'tDomain' value come from the DISPLAY-HINT of a TEXTUAL-CONVENTION that represents an address of that type. The DISPLAY-HINTs used are:
   tDomain                    Source of DISPLAY-HINT [5] [11]
   -------                    ----------------------
   transportDomainUdpIpv4     TransportAddressIPv4
   transportDomainUdpIpv6     TransportAddressIPv6
   transportDomainUdpDns      TransportAddressDns
   snmpCLNSDomain             snmpOSIAddress
   snmpCONSDomain             snmpOSIAddress
   snmpDDPDomain              snmpNBPAddress
   snmpIPXDomain              snmpIPXAddress
   rfc1157Domain              snmpUDPAddress
   Other                      Use DISPLAY-HINT "1x:"
  • 'secModel' is the integer value of the SnmpSecurityModel to use for this operation.
  • 'secName' is a string value representing the SnmpSecurityName to use for this operation.
  • 'secLevel' is the integer value of the SnmpSecurityLevel to use for this operation.

Now the important bit ๐Ÿšจ. An SNMP operation gets sent to the target system using security parameters pulled from a local configuration datastore, based on 'secModel', 'secName', and 'secLevel'. The agent is responsible for making sure that sensitive info in that local datastore gets used on behalf of the correct principals. Those are whoever is identified by the security credentials of the last entity to modify the pmPolicyAdminStatus for a policy.

Let's do an example ๐Ÿงš. 'joe' installs a policy on 'PMAgent' that periodically configures objects on 'TargetAgent' using the credentials of 'Operator'. For this policy to run with the right privileges, all of these must be true:

  • 'Operator's security credentials for TargetAgent must be installed in PMAgent's local configuration datastore (e.g., usmUserTable [6]) indexed by TargetAgent's engineID and 'Operator'.
  • VACM [9] must be configured on PMAgent so that 'joe' has access to the above entry in the appropriate MIB for the local configuration datastore (e.g., usmUserTable).
  • 'joe' must be the last user to modify the pmPolicyAdminStatus object for the policy.

See the Security Considerations section for more information. Honestly, read it ๐Ÿ™ˆ.

For convenience, constants for 'mPModel', 'secModel', and 'secLevel' are defined in the "Constants" section below.

'contextEngineID' is a string representing the contextEngineID of the SNMP entity targeted by this operation. It's encoded as a pair of hex digits for each octet of the contextEngineID (upper- and lowercase are both valid). If 'tDomain' and 'tAddress' are provided but 'contextEngineID' isn't, the operation goes to the SNMP entity reachable at 'tDomain' and 'tAddress'.

Okay, the rules for stacking these args ๐Ÿ“‹: for PolicyScript code to use any of these arguments, all optional arguments to the left must be included. 'mPModel', 'tDomain', 'tAddress', 'secModel', 'secName', and 'secLevel' must be used as a group. If one is specified, they must all be specified. 'contextEngineID' may only be specified if all others are specified.

Also, a function that uses NonLocalArgs must provide a parameter for the contextName, which is required whenever the NonLocalArgs are present. Lots of functions follow this logic:

ContextName NonLocalArgs
Supplied    Supplied

No          No            Addressed to default context on
                          local system.
Yes         No            Addressed to named context on
                          local system.
Yes         Yes           Addressed to named context on
                          potentially remote system.
No          Yes           Not allowed.

8.1.2. Form of SNMP Values

Lots of library functions have input or output parameters that can be one of the many SMI data types ๐ŸŒˆ. The actual type isn't encoded in the value. It's specified somewhere else, sometimes just by the situation it's used in. Here's exactly how input and output work for each one:

Any Integer value (INTEGER, Integer32, Counter32, Counter64, Gauge32, Unsigned32, TimeTicks, Counter64):

  • On input: an Integer, or a String that can be successfully coerced to an Integer with the ToInteger() operator. It's an RTE if a string is passed that ToInteger() can't convert into an integer.

A string of the form

enum_decimal: [ letter | digit | '-' ]* '(' decimal_constant ')'

is also accepted. In that case, the characters before the parentheses and the parentheses themselves are completely ignored, and the decimal_constant inside the parentheses gets converted. So "frame-relay(32)" becomes the integer 32 โœจ.

  • On output: an Integer containing the returned value.

Octet String

  • On input: either a String or an Integer. If it's an Integer, it gets coerced to a String with the ToString() function. This string is used as an unencoded representation of the octet string value.
  • On output: a String containing the unencoded value of the octet string.

Object Identifier

  • On input and on output: a String containing a decimal ASCII encoded object identifier of this form:
oid:       subid [ '.' subid ]* [ '.' ]
subid:     '0' | decimal_constant

It's an RTE if an Object Identifier argument isn't in the form above. A trailing '.' is acceptable and just gets ignored. (BUT ๐Ÿ˜ค, a trailing dot could make a strncmp() comparison of two otherwise-identical OIDs fail. Use oidncmp() instead.)

ASCII descriptors (e.g., "ifIndex") are never used in these encodings "over the wire". They're never returned from library functions, and library functions never accept them. NMS user interfaces are encouraged to let humans view object identifiers with ASCII descriptors. But they must translate those descriptors to dotted-decimal format before sending them in MIB objects to policy agents.

Null

  • On input: the input is ignored.
  • On output: a zero length string.

8.1.3. Convenience SNMP Functions

8.1.3.1. getVar()

The getVar() function retrieves the value of an SNMP MIB object instance ๐Ÿ“ฆ.

string getVar(string oid [, string contextName, NonLocalArgs])
  • 'oid' is a string containing an ASCII dotted-decimal representation of an object identifier (e.g., "1.3.6.1.2.1.1.1.0").
  • The optional 'contextName' argument contains the SNMP context to operate on. If 'contextName' isn't present, the contextName of 'this element' gets used. If 'contextName' is the zero-length string, the default context is used.
  • The optional 'NonLocalArgs' provide addressing and security info to perform an SNMP operation on a system different from 'this element'.

It's an RTE if the queried object identifier value doesn't exist ๐Ÿ’€.

This function returns a string containing the returned value, encoded according to the returned type. No actual SNMP PDU has to be generated and parsed when the policy MIB agent lives on the same system as the managed elements, so that's a nice bonus ๐Ÿ’–.

It is recommended that NMS user interfaces display and allow input of MIB object names by their descriptor values, followed by the index in dotted-decimal form (e.g., "ifType.7") ๐ŸŽ€.

8.1.3.2. exists()

The exists() function checks whether an SNMP MIB object instance exists ๐Ÿ”.

integer exists(string oid [, string contextName, NonLocalArgs])
  • 'oid' is a string containing an ASCII dotted-decimal representation of an object identifier (e.g., "1.3.6.1.2.1.1.1.0").
  • The optional 'contextName' argument contains the SNMP context to operate on. If 'contextName' isn't present, the contextName of 'this element' gets used. If 'contextName' is the zero-length string, the default context is used.
  • The optional 'NonLocalArgs' provide addressing and security info to perform an SNMP operation on a system different from 'this element'.

This function returns the value 1 if the SNMP instance exists, and 0 if it doesn't. Again, no actual SNMP PDU has to be generated and parsed when the policy MIB agent is on the same system as the managed elements ๐Ÿง .

It is recommended that NMS user interfaces display and allow input of MIB object names by their descriptor values, followed by the index in dotted-decimal form (e.g., "ifType.7") ๐ŸŒธ.

8.1.3.3. setVar() ๐Ÿ’…

okay so setVar() is how you SET a MIB object instance to a specific value ๐ŸŽ€ It's only valid inside policyActions, so keep it there, bestie ๐Ÿซถ

setVar(string oid, var value, integer type
       [, string contextName, NonLocalArgs] )
  • oid is a string holding an ASCII dotted-decimal version of an object identifier, like "1.3.6.1.2.1.1.1.0" โœจ
  • value is a string encoded in whatever format matches the type parameter. The agent then sets the variable named by oid to that value ๐Ÿ’–
  • type is the type of the value parameter, and it's one of the DataType Constants ๐Ÿฆ‹
  • contextName is optional and holds the SNMP context to work on. If you leave it out, the contextName of 'this element' gets used. If it's the zero-length string, the default context is used ๐ŸŒธ
  • NonLocalArgs is optional too, and it's giving addressing and security info so you can do an SNMP operation on a system that isn't 'this element' ๐Ÿ‘‘ Also, no actual SNMP PDU has to be generated and parsed when the policy MIB agent lives on the same system as the managed elements. Iconic efficiency ๐Ÿ˜ญ
If the set hits any error at all, it's an RTE. Not me getting an RTE in my own script ๐Ÿ’€

It's recommended that NMS user interfaces show and accept MIB object names by their descriptor values, followed by the index in dotted-decimal form, like "ifType.7" ๐ŸŒˆ

8.1.3.4. searchColumn() ๐Ÿ”

integer searchColumn(string columnoid, string &oid,
                     string pattern, integer mode
                     [, string contextName, NonLocalArgs])

okay this one is a whole vibe ๐Ÿงš searchColumn does an SNMP walk on part of the MIB and looks for objects whose values equal the pattern parameter ๐Ÿ’œ

  • columnoid limits the search to variables that share the same OID prefix. Basically, anything living beneath it in the OID tree ๐ŸŒณ
  • A getnext request gets sent for the object identifier oid. If oid is an empty string, the value of columnoid gets sent instead ๐Ÿ’…
  • Every value that comes back in a response packet gets turned into a string. First it's put into the form dictated by the "Form of SNMP Values" rules, and then the ToString() function runs on it. That result is called 'SearchString' ๐Ÿฆ‹
  • mode decides what kind of match to do against that 'SearchString'. There are 6 possibilities, and the constants for them live in the 'Constants' section below โœจ
Mode               Search Action
ExactMatch         Case sensitive exact match of 'pattern'
                   and 'SearchString'.
ExactCaseMatch     Case insensitive exact match of 'pattern'
                   and 'SearchString'.
SubstringMatch     Case sensitive substring match, finding
                   'pattern' in 'SearchString'.
SubstringCaseMatch Case insensitive substring match, finding
                   'pattern' in 'SearchString'.
RegexpMatch        Case sensitive regular expression match,
                   searching 'SearchString' for the regular
                   expression given in 'pattern'.

RegexpCaseMatch    Case insensitive regular expression match,
                   searching 'SearchString' for the regular
                   expression given in 'pattern'.

searchColumn uses the POSIX extended regular expressions defined in POSIX 1003.2 ๐ŸŽ€

  • contextName is optional and holds the SNMP context to work on. If it's left out, the contextName of 'this element' is used. If it's the zero-length string, the default context is used ๐ŸŒธ
  • NonLocalArgs is optional, and it provides addressing and security info so you can run SNMP operations on a system that isn't 'this element' ๐Ÿ‘‘

If a match shows up, oid gets set to the OID of the matched value and you get back 1. If the search goes past columnoid, or it hits an error without finding anything, you get back zero and oid stays untouched ๐Ÿฅบ

To find the first match, the caller sets oid to the empty string. To find more matches, later calls to searchColumn should have oid set to the OID of the last match. searchColumn does that bit automatically for you, bestie ๐Ÿ’–

For example, to find an ethernet interface:

oid = "";
searchColumn("ifType", oid, "6", 0);

This sends a getnext request for ifType and keeps walking the tree until it finds a value matching 6, or until it returns a variable that isn't in the 'ifType' subtree ๐Ÿ˜ญ

To find the next ethernet interface, assuming interface 3 was the first one found:

oid = "ifType.3";
searchColumn("ifType", oid, "6", 0);

And if you want all the ethernet interfaces in a loop, it looks like this:

oid = "";
while(searchColumn("ifType", oid, "6", 0)){
  /* Do something with oid */
}

Quick note: "ifType" is just a notational convenience in those examples. The actual code downloaded to the policy MIB agent has to use the string "1.3.6.1.2.1.2.2.1.3", because there might not be a MIB compiler (or MIB file) on the policy MIB agent ๐Ÿ™ˆ

Also, if columnoid is too short and references too much of the object identifier tree (like "1.3.6"), then searchColumn could end up searching a huge number of variables. If the value was "1.3.6", it would search ALL variables on the agent ๐Ÿ’€ It's the caller's job to make sure columnoid is set appropriately ๐Ÿซถ

8.1.3.5. setRowStatus() ๐Ÿงš

integer setRowStatus(string oid, integer maxTries
                     [, integer freeOnException , integer seed
                      , string contextName, NonLocalArgs])

setRowStatus automates finding an unused row in a read-create table that uses RowStatus, where the index has an arbitrary integer component for uniqueness ๐Ÿฆ‹ So it's basically doing the hunt for you โœจ

  • oid is a string holding an ASCII dotted-decimal version of an object identifier, with one of the subids swapped out for a '' character. For example, "1.3.6.1.3.1.99.1.2.1.9." ๐ŸŒธ oid has to reference an 'instance' of the RowStatus object, and the '*' has to stand in for any integer index item that can be set to some random value ๐Ÿ’…

setRowStatus picks a number for the selected index item and tries to create the instance with the createAndWait state. If that attempt fails, it tries again with a different random index value. It'll try no more than maxTries times ๐Ÿ”ฅ

  • freeOnException is optional. If it's present and equal to 1, the agent frees this row by setting RowStatus to 'destroy' if, later in the same script invocation, the script dies with a run-time exception or by a call to fail() ๐Ÿ’€ Note that this doesn't apply to exceptions in later invocations of the script ๐Ÿ™ˆ
  • seed is optional. If it's present, the initial index gets set to seed. Otherwise it's random ๐ŸŽ€ seed may not be present if the freeOnException argument isn't present either ๐Ÿฅบ
  • contextName is optional and holds the SNMP context to work on. If it's left out, the contextName of 'this element' is used. If it's the zero-length string, the default context is used ๐ŸŒธ
  • NonLocalArgs is optional, and it provides addressing and security info for doing an SNMP operation on a system that isn't 'this element' ๐Ÿ‘‘

setRowStatus returns the successful integer value for the index. If it's unsuccessful after maxTries, or if zero or more than one '*' is in OID, -1 gets returned ๐Ÿ˜ญ

The createRow function can also be used when adding rows to tables. createRow has more functionality, but setRowStatus might be preferable in certain situations, like when you want the chance to inspect the default values the agent created ๐Ÿงš๐Ÿ’œ

8.1.3.6. createRow() ๐ŸŽ€

integer createRow(integer reqPDU, integer reqNumVarbinds,
                  integer statusColumn, integer maxTries,
                  integer indexRange,
                  integer &respPDU, integer &respNumVarbinds,
                  integer &index
                  [, integer freeOnException, string contextName,
                  NonLocalArgs])

okay so createRow is the helper that does the annoying work of making a row in a read-create table whose index has a random integer in it ๐Ÿฆ‹ It basically wraps up the createAndWait or createAndGo mechanism AND the hunt for an unused row, so you don't have to. It is not useful for tables whose indexes don't have an arbitrary integer component, so don't reach for it there ๐Ÿ’…

createRow sends 'reqPDU' and drops the results into 'respPDU'. Both of those must have been allocated with newPDU first ๐ŸŒธ They're allowed to be the same PDU handle too, in which case 'reqPDU' gets sent and then swapped out for whatever the received PDU contains. Iconic recycling โœจ

'reqNumVarbinds' is an integer bigger than zero that says which varbinds in the PDU get used. The first 'reqNumVarbinds' ones are the ones that count. Each of those varbinds has to be a special form, where one of the subids in the object name is replaced with a * character. For example, "1.3.6.1.3.1.99.1.2.1.9.*". The subid that gets the * is an integer index item that can be set to some random value, and you should pick the same subid in every varbind in the PDU. Consistency is everything bestie ๐Ÿซถ

'respNumVarbinds' gets updated to hold the number of varbinds received in the last response PDU ๐Ÿ’œ

'statusColumn' tells you which varbind in 'pdu' should be treated as the RowStatus column, where 0 is the 1st varbind ๐Ÿ“

So here's the vibe. createRow comes up with a random integer index value and swaps it in for the * subid in each varbind. Then it sets the RowStatus column to pick the createAndGo mechanism and runs the set. If that fails because createAndGo isn't available, it retries with createAndWait selected ๐Ÿ”ฅ If it fails because that index is already taken, it tries again with a different random index. It keeps going like this until it succeeds, until it has made 'maxTries' attempts, or until it runs into an error. Honestly, 'maxTries' should be picked high enough that, as the table fills up, creating a new entry doesn't 'collide' too often and fail. Nobody wants a row that keeps getting ghosted ๐Ÿ’€

Every random index value has to land between 1 and 'indexRange', inclusive. That way it never tries an index outside that index's restricted range, like 1..65535 ๐Ÿ‘‘

If the optional 'freeOnException' argument is present and equal to 1, the agent will free this row by setting RowStatus to destroy if, later in the same script invocation, the script dies with a run-time exception or with a call to fail(). Just know this doesn't apply to exceptions that happen in later invocations of the script ๐Ÿ™ˆ

The optional 'contextName' argument holds the SNMP context to operate on. If 'contextName' isn't present, the contextName of 'this element' gets used. If 'contextName' is the zero-length string, the default context is used โœจ

The optional 'NonLocalArgs' give you the addressing and security info needed to run an SNMP operation on a system that is different from 'this element' ๐ŸŒˆ

Here's a plot twist. No actual SNMP PDU has to be generated or parsed when the policy MIB agent lives on the same system as the managed elements. But if no PDU gets generated, the agent must correctly simulate the behavior of the SNMP Response PDU, especially when there's an error. Main character energy means not skipping the fake response ๐Ÿงš

This function returns zero unless an error happens. If one does, it returns the proper SNMP Error Constant. When there's an error, respPDU holds the last response PDU as received from the agent, unless no response PDU came back at all, in which case respNumVarbinds will be 0. Either way you can call readError on the PDU to get the error info for the transaction ๐Ÿ˜ญ

The 'index' parameter hands back the chosen index. If it works, 'index' gets set to the successful integer index ๐Ÿฆ‹ If there's no SNMP error but the operation still doesn't succeed, 'index' gets set to -1, and that happens for these reasons:

  1. Unsuccessful after 'maxTries'.
  2. An object name had no * in it.
  3. An object name had more than one * in it.

Here's how you'd use createRow():

var index, pdu = newPDU(), nVars = 0;

writeVar(pdu, nVars++, "hlHostControlDataSource.*",
         "ifIndex." + ev(0), Oid);
writeVar(pdu, nVars++, "hlHostControlNlMaxDesiredEntries.*",
         1000, Integer);
writeVar(pdu, nVars++, "hlHostControlAlMaxDesiredEntries.*",
         1000, Integer);
writeVar(pdu, nVars++, "hlHostControlOwner.*", "policy",
         String);
writeVar(pdu, nVars++, "hlHostControlStatus.*", "active(1)",
         Integer);
if (createRow(pdu, nVars, 4, 20, 65535,
              pdu, nVars, index) != 0

    || index == -1)
    return;
// index now contains index of new row

8.1.3.7. counterRate() ๐Ÿ’…

When a policy wants to make a decision based on the rate of a counter, it runs into a couple of problems ๐Ÿ˜ค

  1. It may have to run every X minutes but need to make decisions on rates calculated over at least Y minutes, where Y > X. That would require the complexity of managing a queue of old counter values.
  2. The policy script has no control over exactly when it will run.

The counterRate() function is built to surmount these problems easily. Slay ๐Ÿ‘‘

integer counterRate(string oid, integer minInterval
                    [, integer 64bit,
                    string discOid, integer discMethod,
                    string contextName, NonLocalArgs])

'counterRate' retrieves the variable specified by oid once per invocation. It keeps track of timestamped values retrieved on previous invocations by this execution context, so it can calculate a rate over a period longer than the time since the last invocation ๐Ÿฆ‹

'oid' is the object identifier of the counter value that will be retrieved. The most recent previously saved value of that same object identifier that is at least 'minInterval' seconds old gets subtracted from the newly retrieved value, giving you a delta. If 'minInterval' is zero, that delta is returned. Otherwise, the delta gets divided by the number of seconds elapsed between the two retrievals, and the integer-valued result is returned (rounding down when necessary) ๐Ÿ“

If there was no previously saved retrieval older than 'minInterval' seconds, then -1 is returned. It is an RTE if the query returns noSuchName, noSuchInstance, or noSuchObject, or an object that is not of type Counter32 or Counter64 ๐Ÿ™ˆ

The delta calculation allows for 32-bit counter semantics if it runs into a rollover between the two retrievals. The exception is when the optional argument '64bit' is present and equal to 1, in which case it allows for 64-bit counter semantics ๐ŸŒธ

'discOid' and 'discMethod' may only be present together. 'discOid' contains an object identifier of a discontinuity indicator value that is retrieved at the same time as each counter value:

  1. If 'discMethod' is equal to 1 and the discontinuity indicator is less than the last one retrieved, then a discontinuity is indicated.
  2. If 'discMethod' is equal to 2 and the discontinuity indicated is different from the last one retrieved, then a discontinuity is indicated.

If this value indicates a discontinuity, this counter value (and its timestamp) gets stored. All previously stored counter values get invalidated, and -1 is returned ๐Ÿ˜ญ

The implementation has to store a number of timestamped counter values. It must keep all values that are newer than minInterval seconds, plus the newest value that is older than minInterval seconds. Apart from that one older value, the implementation should discard any older values ๐Ÿงš

Here are some examples:

Policy that executes every 60 seconds:
    rate = counterRate("ifInOctets.$*", 300);
    if (rate > 1000000)
        ...

Another example, with a discontinuity indicator:

Policy that executes every 60 seconds:
    rate = counterRate("ifInOctets.$*", 300, 0,
                       "sysUpTime.0", 1);
    if (rate > 1000000)
        ...

Another example, with zero minInterval:

Policy that executes every 60 seconds:
    delta = counterRate("ifInErrors.$*", 0);
    if (delta > 100)
        ...

The optional 'contextName' argument holds the SNMP context to operate on. If 'contextName' isn't present, the contextName of 'this element' gets used. If 'contextName' is the zero-length string, the default context is used ๐Ÿ’–

8.1.4. General SNMP Functions ๐ŸŒธ

okay so a general SNMP interface really should be able to poke at a bunch of variables in one go, and let multiple varbind lists exist at the same time ๐Ÿงš newPDU, readVar, and writeVar are here to make that happen in a language with no pointers, no arrays, and no memory allocators ๐Ÿ’–

newPDU lets you allocate a PDU and get back an integer handle for it. PDUs get freed automatically when the script exits and you can reuse them during execution, so there is no freePDU() ๐Ÿซถ

readVar and writeVar work on a variable-length varbind list inside a PDU. Every readVar and writeVar call needs the PDU handle and the index of the variable inside that PDU. Once you've fully set up a PDU with one or more writeVar calls, you pass it to snmpSend (by referencing the handle) along with the number of varbinds to include. When the response comes back, its contents land in another PDU, and you read them out with one or more readVar calls. Error info comes from the readError function ๐Ÿ’… And since GetBulk PDUs carry extra info in the SNMP header, the writeBulkParameters function lets you configure those parameters ๐ŸŽ€

Varbinds in this data store get created automatically whenever they're written by any writeVar or snmpSend operation โœจ

Here's an example:

var pdu = newPDU();
var nVars = 0, oid, type, value;

writeVar(pdu, nVars++, "sysDescr.0", "", Null);
writeVar(pdu, nVars++, "sysOID.0", "", Null);
writeVar(pdu, nVars++, "ifNumber.0", "", Null);
if (snmpSend(pdu, nVars, Get, pdu, nVars))
    return;
readVar(pdu, 0, oid, value, type);
readVar(pdu, 1, oid, value, type);
readVar(pdu, 2, oid, value, type);
...

or,

var pdu = newPDU();
var nVars = 0, oid1, oid2;

writeVar(pdu, nVars++, "ifIndex", "", Null);
writeVar(pdu, nVars++, "ifType", "", Null);
while(!done){
  if (snmpSend(pdu, nVars, Getnext, pdu, nVars))
      continue;
  readVar(pdu, 0, oid1, value, type);
  readVar(pdu, 1, oid2, value, type);
  /* leave OIDs alone, now PDU #0 is set up for next step
     in table walk. */
  if (oidncmp(oid1, "ifIndex", oidlen("ifIndex")))
    done = 0;
  ...
}

Quick heads-up: in those examples, ifType and sysDescr are only used in object identifiers as a notational convenience ๐Ÿ™ˆ The actual code downloaded to the policy MIB agent must use dotted decimal notation only, because there might be no MIB compiler (or MIB file) on the policy MIB agent ๐Ÿค“

To conform to this spec, implementations must allow each policy script invocation to allocate at least 5 PDUs with at least 64 varbinds per list ๐Ÿ’œ It's suggested that implementations cap the total number of PDUs per invocation so one malfunctioning script (like one that calls newPDU() in a loop) can't wreck everyone else's scripts ๐Ÿ’€

8.1.4.1. newPDU()

integer newPDU()

newPDU will allocate a new PDU and return a handle to it ๐Ÿฆ‹ If it can't allocate a PDU, -1 gets returned. The PDU's initial values of nonRepeaters and maxRepetitions are zero.

8.1.4.2. writeVar()

writeVar(integer pdu, integer varBindIndex,
         string oid, var value, integer type)

writeVar stores 'oid', 'value', and 'type' in the specified varbind ๐ŸŒˆ

'pdu' is the handle to a PDU that newPDU() allocated.

'varBindIndex' is a non-negative integer that identifies which varbind inside the specified PDU this call modifies. The first varbind is number 0.

'oid' is a string holding an ASCII dotted-decimal representation of an object identifier (e.g., "1.3.6.1.2.1.1.1.0").

'value' is the value to be stored, and it has to be a type that matches the 'type' parameter.

'type' is the type of the value parameter, and it gets set to one of the values from DataType Constants.

It's an RTE if any of the parameters don't follow the rules above. Bestie, don't skip this one ๐Ÿ˜ญ

8.1.4.3. readVar()

readVar(integer pdu, integer varBindIndex, string &oid,
        var &value, integer &type)

readVar pulls the oid, the value, and its type out of the specified varbind โœจ

'pdu' is the handle to a PDU that newPDU() allocated.

'varBindIndex' is a non-negative integer that identifies which varbind inside the specified PDU this call reads. The first varbind is number 0.

The object identifier of the referenced varbind gets copied into the 'oid' parameter, formatted as an ASCII dotted-decimal representation (e.g., "1.3.6.1.2.1.1.1.0").

'value' is the value retrieved, and it's a type that matches the 'type' parameter.

'type' is the type of the value parameter, and it gets set to one of the values from DataType Constants.

It's an RTE if 'pdu' doesn't reference a valid PDU or if 'varBindIndex' doesn't reference a valid varbind ๐Ÿ™…โ€โ™€๏ธ

8.1.4.4. snmpSend()

integer snmpSend(integer reqPDU, integer reqNumVarbinds,
                 integer opcode,
                 integer &respPDU, integer &respNumVarbinds,
                 [, string contextName , NonLocalArgs] )

snmpSend performs an SNMP operation by sending 'reqPDU' and dropping the results into 'respPDU' ๐Ÿš€ Both 'reqPDU' and 'respPDU' have to have been allocated with newPDU first. They're allowed to be the same PDU handle, in which case 'reqPDU' gets sent and then replaced with the contents of the received PDU. If the opcode is a Trap or V2trap, 'respPDU' won't be modified.

'reqNumVarbinds' is an integer greater than zero that says which varbinds in the PDU get used in this operation. The first 'reqNumVarbinds' varbinds in the PDU are the ones used. 'respNumVarbinds' gets updated to hold the number of varbinds in the response PDU, which, for a GetBulk or an error, can be wildly different from reqNumVarbinds ๐Ÿ‘€

'opcode' is the type of SNMP operation to perform, and it must be one of the values from the SNMP Operation Constants listed in the 'Constants' section below.

The optional 'contextName' argument holds the SNMP context to operate on. If 'contextName' isn't there, the contextName of 'this element' gets used. If 'contextName' is the zero-length string, the default context is used ๐ŸŒธ

Heads up: no actual SNMP PDU has to be generated and parsed when the policy MIB agent lives on the same system as the managed elements ๐Ÿง  If no PDU gets generated, the agent must correctly simulate the behavior of the SNMP Response PDU, especially in the case of an error.

This function returns zero unless an error happens, in which case it returns the proper SNMP Error Constant. If an error occurred, respPDU holds the response PDU as received from the agent, unless no response PDU came back, in which case respNumVarbinds will be 0. Either way, you can call readError on the PDU to find out the error info for the transaction ๐Ÿ’…

If an SNMP Version 1 trap is requested (the opcode is Trap(4)), then SNMP Version 2 trap parameters are supplied and converted according to the rules of RFC 3584 [8], section 3.2. The first variable binding must be sysUpTime.0, and the second must be snmpTrapOID.0, as per RFC 3416 [7], section 4.2.6. Any variable bindings after that get copied into the SNMP Version 1 trap PDU in the usual fashion ๐Ÿฆ‹

8.1.4.5. readError()

readError(integer pdu, integer numVarbinds, integer &errorStatus,
          integer &errorIndex, integer &hasException)

Returns the error information in a PDU ๐ŸŽ€

'errorStatus' holds the error-status field from the response PDU, or a local error constant if the error was generated locally. If no error was experienced, or no PDU was ever copied into this PDU, this value will be 0.

'errorIndex' holds the error-index field from the response PDU. If no PDU was ever copied into this PDU, this value will be 0.

'hasException' will be 1 if any of the first 'numVarbinds' varbinds in the PDU contain an exception (Nosuchobject, Nosuchinstance, Endofmibview). Otherwise it will be 0 ๐ŸŒˆ

It's an RTE if 'pdu' doesn't reference a valid PDU, or if 'numVarbinds' references varbinds that aren't valid.

8.1.4.6. writeBulkParameters()

writeBulkParameters(integer pdu, integer nonRepeaters,
                    integer maxRepetitions)

This modifies the parameters in a PDU for any later GetBulk operation sent by that PDU ๐Ÿ“ 'nonRepeaters' gets copied into the PDU's non-repeaters field, and 'maxRepetitions' gets copied into the max-repetitions field.

writeBulkParameters can be called before or after writeVar adds varbinds to the PDU, but it must be called before the PDU is sent. Otherwise it won't have any effect. A new PDU starts with nonRepeaters set to zero and maxRepetitions set to zero. If a Bulk PDU gets sent before writeBulkParameters is called, those default values get used. If writeBulkParameters is called to modify a PDU, it's okay if that PDU is later sent as a type other than bulk. The writeBulkParameters call only affects later sends of Bulk PDUs. If a PDU is used to receive the contents of a response, the values of nonRepeaters and maxRepetitions are never modified ๐Ÿ’–

8.1.5. Constants for SNMP Library Functions โœจ

okay so these are the constants you can use with all the SNMP Library Functions ๐Ÿ’– policy code runs somewhere these are already declared, but heads up: the declarations themselves won't be visible in policyCondition or policyAction code ๐Ÿ™ˆ. They're reserved words, so you can't name any variable or function with them. Also, even though they're written as C consts here, the const keyword itself isn't available in policy code. bestie, just use the values ๐ŸŽ€

// Datatype Constants

// From RFC 2578 [2]
const integer Integer       = 2;
const integer Integer32     = 2;
const integer String        = 4;
const integer Bits          = 4;
const integer Null          = 5;
const integer Oid           = 6;
const integer IpAddress     = 64;
const integer Counter32     = 65;
const integer Gauge32       = 66;
const integer Unsigned32    = 66;
const integer TimeTicks     = 67;
const integer Opaque        = 68;
const integer Counter64     = 70;

// SNMP Exceptions from RFC 3416 [7]
const integer NoSuchObject         = 128;
const integer NoSuchInstance       = 129;
const integer EndOfMibView         = 130;

// SNMP Error Constants from RFC 3416 [7]
const integer NoError              = 0;
const integer TooBig               = 1;
const integer NoSuchName           = 2;
const integer BadValue             = 3;
const integer ReadOnly             = 4;
const integer GenErr               = 5;
const integer NoAccess             = 6;
const integer WrongType            = 7;
const integer WrongLength          = 8;
const integer WrongEncoding        = 9;
const integer WrongValue           = 10;
const integer NoCreation           = 11;
const integer InconsistentValue    = 12;
const integer ResourceUnavailable  = 13;
const integer CommitFailed         = 14;
const integer UndoFailed           = 15;
const integer AuthorizationError   = 16;
const integer NotWritable          = 17;
const integer InconsistentName     = 18;

// "Local" Errors
// These are also possible choices for errorStatus returns
// For example: unknown PDU, maxVarbinds is bigger than number
// written with writeVar, unknown opcode, etc.
const integer BadParameter         = 1000;

// Request would have created a PDU larger than local limitations
const integer TooLong              = 1001;

// A response to the request was received but errors were encountered
// when parsing it.
const integer ParseError           = 1002;

// Local system has complained of an authentication failure
const integer AuthFailure          = 1003;

// No valid response was received in a timely fashion
const integer TimedOut             = 1004;

// General local failure including lack of resources
const integer GeneralFailure       = 1005;

// SNMP Operation Constants from RFC 3416 [7]
const integer Get                  = 0;
const integer Getnext              = 1;
const integer Set                  = 3;
const integer Trap                 = 4;
const integer Getbulk              = 5;
const integer Inform               = 6;
const integer V2trap               = 7;

// Constants from RFC 3411 [1] for SnmpMessageProcessingModel
const integer SNMPv1              = 0;
const integer SNMPv2c             = 1;
const integer SNMPv3              = 3;
// Constants from RFC 3411 [1] for SnmpSecurityModel
const integer SNMPv1              = 1;
const integer SNMPv2c             = 2;
const integer USM                 = 3;

// SnmpSecurityLevel Constants from RFC 3411 [1]
const integer NoAuthNoPriv        = 1;
const integer AuthNoPriv          = 2;
const integer AuthPriv            = 3;

// Constants for use with searchColumn
const integer ExactMatch          = 0;
const integer ExactCaseMatch      = 1;
const integer SubstringMatch      = 2;
const integer SubstringCaseMatch  = 3;
const integer RegexpMatch         = 4;
const integer RegexpCaseMatch     = 5;

8.2. Policy Library Functions ๐Ÿ’…

okay now we're in the policy library era ๐ŸŒˆ these functions give you info that's specifically about running policies, so they're basically your cheat sheet for the policy itself ๐Ÿงš

8.2.1. elementName() ๐Ÿฆ‹

elementName() tells you what the current element is ๐Ÿ‘€ and it can also give you info about what type of element it is and how it's indexed. it's giving detective energy, no cap.

string elementName()

elementName returns a string holding an ASCII dotted-decimal representation of an object identifier (e.g., 1.3.6.1.2.1.1.1.0) ๐Ÿ’œ that OID identifies an instance of a MIB object that's an attribute of 'this element'. โœจ

8.2.2. elementAddress() ๐ŸŒธ

elementAddress(&tDomain, &tAddress)

elementAddress finds a domain/address pair you can use to reach 'this element', and it hands the values back in 'tDomain' and 'tAddress'. bestie that's literally how you find your way to the element ๐Ÿ—บ๏ธ๐Ÿ’–

8.2.3. elementContext() ๐ŸŽ€

string elementContext()

elementContext() returns a string with the SNMP contextName of 'this element'. simple, iconic, done ๐Ÿ’…

8.2.4. ec() ๐Ÿ“

okay so ec() and ev() are the element count and element value functions, and they're super handy for getting at the pieces of the index for 'this element' ๐Ÿซถ they're especially useful when you're building the index for other, related elements.

integer ec()

ec() returns an integer count of how many index subidentifiers exist in the index for 'this element'. โœจ

8.2.5. ev() ๐Ÿงš

integer ev(integer n)

ev() returns the value of the nth subidentifier in the index for 'this element' ๐Ÿ’œ heads up, the first subidentifier is at index 0, not 1 ๐Ÿ™ˆ and if n points past the last subidentifier, that's an RTE. ๐Ÿ˜ญ

8.2.6. roleMatch() ๐Ÿ‘‘

roleMatch() is how you check whether an element has been given a particular role. it's main character energy ๐Ÿ”ฅ

integer roleMatch(string roleString [, string element,
                  string contextName, string contextEngineID])

'roleString' is a string. the optional 'element' argument holds the OID name of an element, and if you don't pass it, it defaults to the current element ๐ŸŒˆ if roleString exactly matches (content and length, no cap) any role assigned to the specified element, the function returns 1. if no roles match, it returns 0. โœจ

the optional 'contextName' argument is the SNMP context you want to operate on ๐Ÿฆ‹ if you leave it out, the contextName of 'this element' gets used. if 'contextName' is the zero-length string, the default context is used. โœจ

'contextEngineID' holds the contextEngineID of the remote system where 'element' lives ๐Ÿ’– it's encoded as a pair of hex digits for each octet of the contextEngineID, and upper- and lowercase are both valid. if you skip it, the contextEngineID of 'this element' gets used. and here's the rule, periodt: 'contextEngineID' can only be present if the 'element' and 'context' arguments are also present. ๐Ÿซถ

8.2.7. Scratchpad Functions ๐Ÿงš

okay so every maxLatency period, every policy runs once for each element ๐Ÿซถ when setScratchpad runs, it stores a value under a string name, and getScratchpad() can still grab it even after that policy code has exited โœจ this is how a condition and an action can share data, or two conditions running on different elements can, or even two totally different policies ๐Ÿ”ฅ

the scope decides which policy/element combos can retrieve a 'varName'/'value' pair ๐Ÿ’… here are the scope options:

Global The 'varName'/'value' pair is available in the condition or action of any policy, while that policy is running on any element ๐ŸŒ but heads up bestie: anything stored here is visible to every other script on the system, no matter whose authority they have ๐Ÿ˜ญ sensitive info should not be put in global scratchpad variables.

Policy The pair is available in any future run of the current policy's condition or action, on whatever element it's running on ๐Ÿ’œ if the policy gets deleted, or its condition or action code gets modified, every value in its 'Policy' scope is deleted ๐Ÿ—‘๏ธ

PolicyElement The pair is available in future runs of the current policy's condition or action, but only when that policy is running on the current element ๐Ÿฆ‹ same deal: deleting the policy or editing its code wipes its 'PolicyElement' values. the agent may also periodically delete 'PolicyElement' values if the element doesn't exist anymore. so if an element vanishes and then comes back, those values may or may not still be there ๐Ÿ™ˆ

setScratchpad's 'storageType' argument controls how long a variable lives in the scratchpad ๐Ÿ“ if storageType is the constant 'volatile', the variable must be deleted on a reboot. if it's 'nonVolatile', the variable should be stored in non-volatile storage, so it sticks around after a reboot โœจ if the 'storageType' argument isn't there at all, the variable is volatile and gets erased on reboot.

if the optional 'freeOnException' argument is there and equals 1, the agent frees the variable when, later in that same script run, the script dies with a run-time exception or by calling fail() ๐Ÿ’€ (this doesn't apply to exceptions in later runs of the script, just to be clear ๐Ÿซถ)

there may be implementation-specific limits on how many scratchpad variables you can make ๐ŸŒธ the limit can be different for each scope or storageType. it's suggested that implementations cap the total number of scratchpad variables per script, so one broken script can't mess up everyone else ๐Ÿ›ก๏ธ also, compliant implementations must support at least 50 Global variables, 5 Policy variables per policy, and 5 PolicyElement variables per policy-element pair ๐Ÿ‘‘

Scratchpad Usage Examples ๐ŸŽ€

Policy  Element    Action
A       ifIndex.1  setScratchpad(Global, "foo", "55")
A       ifIndex.1  getScratchpad(Global, "foo", val) --> 55
A       ifIndex.2  getScratchpad(Global, "foo", val) --> 55
B       ifIndex.2  getScratchpad(Global, "foo", val) --> 55
B       ifIndex.2  setScratchpad(Global, "foo", "16")
A       ifIndex.1  getScratchpad(Global, "foo", val) --> 16
Policy  Element    Action
A       ifIndex.1  setScratchpad(Policy, "bar", "75")
A       ifIndex.1  getScratchpad(Policy, "bar", val) --> 75
A       ifIndex.2  getScratchpad(Policy, "bar", val) --> 75
B       ifIndex.1  getScratchpad(Policy, "bar", val) not found
B       ifIndex.1  setScratchpad(Policy, "bar", "20")
A       ifIndex.2  getScratchpad(Policy, "bar", val) --> 75
B       ifIndex.2  getScratchpad(Policy, "bar", val) --> 20
Policy  Element    Action
A       ifIndex.1  setScratchpad(PolicyElement, "baz", "43")
A       ifIndex.1  getScratchpad(PolicyElement, "baz", val) --> 43
A       ifIndex.2  getScratchpad(PolicyElement, "baz", val) not found
B       ifIndex.1  getScratchpad(PolicyElement, "baz", val) not found
A       ifIndex.2  setScratchpad(PolicyElement, "baz", "54")
B       ifIndex.1  setScratchpad(PolicyElement, "baz", "65")
A       ifIndex.1  getScratchpad(PolicyElement, "baz", val) --> 43
A       ifIndex.2  getScratchpad(PolicyElement, "baz", val) --> 54
B       ifIndex.1  getScratchpad(PolicyElement, "baz", val) --> 65
Policy  Element    Action
A       ifIndex.1  setScratchpad(PolicyElement, "foo", "11")
A       ifIndex.1  setScratchpad(Global,        "foo", "22")
A       ifIndex.1  getScratchpad(PolicyElement, "foo", val) --> 11
A       ifIndex.1  getScratchpad(Global,        "foo", val) --> 22

Constants ๐ŸŒˆ

the following constants are defined for the scratchpad functions ๐Ÿ’– policy code runs in an environment where these constants are declared. (heads up: these declarations won't be visible in the policyCondition or policyAction MIB objects ๐Ÿ™ˆ)

even though they're written here as C consts, the const construct itself isn't available inside policy code ๐Ÿ’…

// Scratchpad Constants

// Values of scope
const integer Global           = 0;
const integer Policy           = 1;
const integer PolicyElement    = 2;

// Values of storageType
const integer Volatile         = 0;
const integer NonVolatile      = 1;

8.2.8. setScratchpad() ๐Ÿฆ‹

setScratchpad(integer scope, string varName [, string value,
                integer storageType, integer freeOnException ])

okay the setScratchpad function stores a value that can still be retrieved after this policy code exits ๐Ÿซถ the scope options are Global, Policy, and PolicyElement, which control who can see it ๐Ÿ’œ

'varName' is the string that identifies the value. later lookups using the same 'varName' in the right scope will get the stored value back ๐ŸŽ€ the namespace for 'varName' is separate for each scope, and 'varName' is case sensitive, so foo and Foo are different babes ๐Ÿ™ˆ

'value' is the string you want to store. ToString(value) gets called on it first to turn it into a string before it's saved โœจ

if the 'value' argument is missing, the 'varName' in the given 'scope' gets deleted (if it exists) ๐Ÿ—‘๏ธ

if the optional 'storageType' argument is there and equals the constant Volatile, the variable must be deleted on a reboot. if it equals NonVolatile, the variable should be stored in non-volatile storage, where it's still available after a reboot ๐Ÿ“ if 'storageType' isn't there, the variable is volatile and gets erased on reboot. 'storageType' may not be present if 'value' isn't present either ๐Ÿ’… and if the variable already existed, its old storageType gets updated to whatever the current 'storageType' argument says ๐Ÿ”„

if the optional 'freeOnException' argument is there and equals 1, the agent frees the variable if, later in the same script run, the script dies with a run-time exception or by calling fail() ๐Ÿ’€ (again, subsequent runs of the script don't count ๐Ÿซถ)


8.2.9. getScratchpad() ๐Ÿ”

integer getScratchpad(integer scope, string varName,
                         string &value)

the getScratchpad function lets you pull back values that were stored earlier, either in this execution context or in other ones ๐ŸŒธ the scope decides which execution contexts can pass a value into this one. the options are Global, Policy, and PolicyElement ๐Ÿ’œ

'varName' identifies the value, and later lookups with the same 'varName' in the proper scope return what was stored โœจ the namespace for varName is separate per scope, so getScratchpad can't force access to a variable in a scope you can't reach ๐Ÿ™…โ€โ™€๏ธ it can only retrieve variables by naming the right scope where they were set. 'varName' is case sensitive too ๐Ÿ’–

on a successful return, 'value' gets set to the value that was stored before. otherwise 'value' isn't modified ๐Ÿซถ

this function returns 1 if a value was stored earlier, and 0 if not ๐ŸŽ€


8.2.10. signalError() ๐Ÿšจ

the signalError() function is how the script tells a management station that it's acting up ๐Ÿ˜ญ calling it turns on the conditionUserSignal(3) or actionUserSignal(5) bit in the associated pmTrackingPEInfo object. calling it again does nothing extra ๐Ÿ’… that bit is cleared at the start of each execution. if a later execution finishes without calling signalError, the bit gets cleared โœจ

signalError()

the signalException function takes no arguments and returns no value ๐Ÿงš


8.2.11. defer() ๐Ÿƒโ€โ™€๏ธ

okay so precedence groups enforce one rule: for each element, out of the ready policies that match the condition, only the one with the highest precedence value is active ๐Ÿ‘‘ the problem is that once the winner gets picked and its action starts, the script can figure out it can't finish its job ๐Ÿ˜ญ in lots of those cases, it's better for the next runner-up ready policy to run. like in the earlier example, if gold is right but gold isn't possible, at least bronze behavior should be configured ๐Ÿฅ‰

when a policy defers, it exits, and the ready, condition-matching policy with the next-highest precedence runs immediately ๐Ÿ” that one might defer too, so the execution environment has to remember where it is in the precedence chain and keep going down until an action finishes without deferring, or until the precedence group runs out of policies ๐Ÿซถ once a policy finishes successfully, the next iteration starts back at the top of the chain ๐ŸŒˆ

there are two ways to defer ๐Ÿ’… a script can exit by calling fail() and say it should defer right away. or a script can tell the execution environment to defer automatically if a run-time exception happens ๐Ÿ’€

defer(integer deferOnRTE)

the defer function changes how the script behaves on run-time exceptions ๐Ÿฆ‹ by default, a script does not defer when it hits an RTE. if defer(1) is called, the exit behavior changes so the script defers when it's terminated due to an RTE. if defer(0) is called, the script goes back to its default and won't defer ๐Ÿ’œ

heads up: calling defer doesn't make the script exit ๐Ÿ™ˆ it only changes the default behavior if an RTE happens later in this same invocation โœจ

8.2.12. fail() ๐Ÿ’ฅ

fail(integer defer, integer free [, string message] )

Okay bestie, fail() is the "pls stop the script rn" button ๐Ÿ’€. It can optionally do some cleanup and then the script exits.

If defer is 1, the script defers to the next lower precedence ready policy in the same precedence group whose condition matches ๐Ÿซถ. If defer isn't 1, it does not defer. Ngl, a condition that defers is basically the same thing as a condition returning false ๐Ÿ’….

If free is 1, certain registered resources get freed ๐Ÿงน. If earlier in this script invocation any rows were created by createRow with the freeOnException option, the execution environment sets the RowStatus of each row to destroy, which deletes the row. Also, if earlier in this invocation any scratchpad variables were created or modified with the freeOnException option, they get deleted too.

If the optional message argument is present, it gets logged to the debugging table, but only if pmPolicyDebugging is turned on for this policy ๐Ÿ”.

This function does not return. The script just terminates, no encore ๐Ÿ‘‹.

8.2.13. getParameters() ๐Ÿงš

Policy scripts can be parameterized, which means they get handed one or more parameters, like site-specific constants โœจ. These parameters can be installed in the pmPolicyParameters object, and the script can reach them through the getParameters() function. If you need to pass multiple parameters, the script can choose whatever encoding or delimiting method is most appropriate, so the parameters can all be stored in the associated instance of pmPolicyParameters ๐ŸŽ€.

string getParameters()

The getParameters function takes no arguments. It returns a string containing the value of the pmPolicyParameters object for the running policy ๐Ÿ’œ.

For example, say a policy is supposed to apply to "slow speed interfaces", and the cutoff point for slow speed should be parameterized. The policy filter should be:

getVar("ifSpeed.$*") == getParameters()

In this example, you store the string "128000" in the policy's pmPolicyParameters object, and that makes this policy act on all 128 Kbps interfaces ๐ŸŽ๏ธ๐Ÿ’จ.


8.3. Utility Library Functions ๐Ÿงฐ

Utility Library Functions are provided to help you write more efficient policy scripts. Lowkey my favourite part of the whole library ๐ŸŒธ.

8.3.1. regexp() ๐Ÿ”Ž

integer regexp(string pattern, string str,
               integer case [, string &match])

regexp searches str for matches to the regular expression given in pattern. It uses the POSIX extended regular expressions defined in POSIX 1003.2 โœจ.

If case is 0, the search is case insensitive. Otherwise, it is case sensitive.

If a match is found, 1 is returned. Otherwise, 0 is returned.

If the optional argument match is provided and a match is found, match is replaced with the text of the first substring of str that matches pattern. If no match is found, match is left unchanged.

8.3.2. regexpReplace() ๐Ÿช„

string regexpReplace(string pattern, string replacement,
                      string str, integer case)

regexpReplace searches str for matches to the regular expression given in pattern, and replaces each occurrence of matched text with replacement. It uses the POSIX extended regular expressions defined in POSIX 1003.2 ๐Ÿ’….

If case is 0, the search is case insensitive. Otherwise, it is case sensitive.

The modified string is returned. If no matches were found, it is the same as the original string ๐Ÿซง.

8.3.3. oidlen() ๐Ÿ”ข

integer oidlen(string oid)

oidlen returns the number of subidentifiers in oid. oid is a string containing an ASCII dotted-decimal representation of an object identifier, like "1.3.6.1.2.1.1.1.0" ๐Ÿฆ‹.

8.3.4. oidncmp() โš–๏ธ

integer oidncmp(string oid1, string oid2, integer n)

The arguments oid1 and oid2 are strings containing ASCII dotted-decimal representations of object identifiers, like "1.3.6.1.2.1.1.1.0".

oidcmp compares no more than n subidentifiers of oid1 and oid2. It returns -1 if oid1 is less than oid2, 0 if they are equal, and 1 if oid1 is greater than oid2 ๐Ÿ™ˆ.

8.3.5. inSubtree() ๐ŸŒณ

integer inSubtree(string oid, string prefix)

The arguments oid and prefix are strings containing ASCII dotted-decimal representations of object identifiers, like "1.3.6.1.2.1.1.1.0".

inSubtree returns 1 if every subidentifier in prefix equals the corresponding subidentifier in oid. Otherwise, it returns 0. This is equivalent to oidncmp(oid1, prefix, oidlen(prefix)), but it's provided anyway because it's an idiom, and because it avoids evaluating prefix twice if it's an expression ๐Ÿง โœจ.

8.3.6. subid() ๐Ÿ”‘

integer subid(string oid, integer n)

subid returns the value of the nth subidentifier of oid, counting from zero. oid is a string containing an ASCII dotted-decimal representation of an object identifier, like "1.3.6.1.2.1.1.1.0" ๐Ÿ’–.

If n specifies a subidentifier beyond the length of oid, a value of -1 is returned.

8.3.7. subidWrite() โœ๏ธ

integer subidWrite(string oid, integer n, integer subid)

subidWrite sets the value of the nth subidentifier of oid to subid, counting from zero. oid is a string containing an ASCII dotted-decimal representation of an object identifier, like "1.3.6.1.2.1.1.1.0".

If n specifies a subidentifier beyond the length of oid, a value of -1 is returned. Also, appending subidentifiers can be done with the string concatenation + operator ๐ŸŽ€. If no error occurs, zero is returned ๐ŸŒˆ.

8.3.8. oidSplice() โœ‚๏ธ

string oidSplice(string oid1, integer offset, integer len, string
   oid2)

oidSplice returns an OID formed by replacing len subidentifiers in oid1 with all of the subidentifiers from oid2, starting at offset in oid1. The first subidentifier is at offset 0 ๐Ÿ‘‘. If offset + len extends beyond the end of oid1, the OID length is extended as needed. If offset is larger than the length of oid1, then an RTE occurs ๐Ÿ’€.

The resulting OID is returned.

For example:

oidSplice("1.3.6.1.2.1", 5, 1, "7")     => "1.3.6.1.2.7"
oidSplice("1.3.6.1.2.1", 4, 2, "7.7")   => "1.3.6.1.7.7"
oidSplice("1.3.6.1.2.1", 4, 3, "7.7.7") => "1.3.6.1.7.7.7"

8.3.9. parseIndex() ๐Ÿงฉ

ParseIndex is provided to make it easy to pull index values from OIDs into variables ๐Ÿซถ.

var parseIndex(string oid, integer &index, integer type,
               integer len)

parseIndex pulls values from the instance identification portion of oid, encoded as per Section 7.7, "Mapping of the INDEX Clause", of the SMIv2 [2] ๐Ÿ“š.

oid is the OID to be parsed.

index describes which subid to begin parsing at. index is modified to indicate the subid after the last one parsed, even if that points past the last subid. The first subid is index 0. If any error occurs, index is set to -1 on return. If the input index is less than 0 or refers past the end of the OID, index is set to -1 on return and the function returns 0 ๐Ÿ˜ต.

If type is Integer, len is not consulted. The return value is the integer value of the next subid ๐Ÿ”ข.

If type is String and len is greater than zero, len subids are parsed. For each subid parsed, the chr() value of the subid is appended to the returned string. If any subid is greater than 255, index is set to -1 on return, and an empty string is returned. If there are fewer than len subids left in oid, index is set to -1 on return, but a string is still returned containing a character for each subid that was left ๐Ÿ“.

If type is String and len is zero, the next subid is parsed to find N, the length of the string. Then, that many subids are parsed. For each subid parsed, the chr() value of the subid is appended to the returned string. If any subid is greater than 255, index is set to -1 on return, and an empty string is returned. If there are fewer than N subids left in oid, index is set to -1 on return, but a string is still returned containing a character for each subid that was left ๐Ÿง.

If type is String and len is -1, subids are parsed until the end of oid. For each subid parsed, the chr() value of the subid is appended to the returned string. If any subid is greater than 255, index is set to -1 on return, and an empty string is returned ๐ŸŒธ.

If type is Oid and len is greater than zero, len subids are parsed. For each subid parsed, the decimal-encoded value of the subid is appended to the returned string, with a '.' character appended between each output subid, but not after the last subid. If there are fewer than len subids left in oid, index is set to -1 on return, but a string is still returned containing an encoding for each subid that was left ๐Ÿ’….

If type is Oid and len is zero, the next subid is parsed to find N, the number of subids to parse. For each subid parsed, the decimal-encoded value of the subid is appended to the returned string, with a '.' character appended between each output subid but not after the last subid. If there are fewer than N subids left in oid, index is set to -1 on return, but a string is still returned containing an encoding for each subid that was left ๐Ÿฆ‹.

If type is Oid and len is -1, subids are parsed until the end of oid. For each subid parsed, the decimal-encoded value of the subid is appended to the returned string, with a '.' character appended between each output subid, but not after the last subid โœจ.

For example, to decode the index component of an instance of the ipForward table:

oid = "ipForwardIfIndex.0.0.0.0.13.0.192.168.1.1";
index = 11;
dest   =  parseIndex(oid, index, String, 4);
proto  =  parseIndex(oid, index, Integer, 0);
policy =  parseIndex(oid, index, Integer, 0);
nextHop = parseIndex(oid, index, String, 4);
// proto and policy now contain integer values
// dest and nextHop now contain 4 byte IP addresses.  Use
// stringToDotted to get them to dotted decimal notation:
// e.g.: stringToDotted(nextHop) => "192.168.1.1"

8.3.10. stringToDotted() ๐ŸŽ€

okay so stringToDotted() is the function for turning a string into something that fits in the index part of an OID, or for turning the binary bytes of an IP address into dotted-decimal form. super handy ๐Ÿ’…

string stringToDotted(string value)

If value is the zero-length string, the zero-length string is returned. ๐Ÿซถ

Otherwise, the decimal encoding of the first byte of value gets added to the output string. Then, for every extra byte in value, a . gets tacked on, followed by that byte's decimal encoding. so it ends up looking like a dotted list of numbers โœจ

8.3.11. integer() ๐Ÿ”ข

integer integer(var input)

integer takes input and turns it into an integer using the rules from ToInteger(), then hands back the integer-typed result. easy peasy ๐Ÿฆ‹

8.3.12. string() ๐Ÿงต

string string(var input)

string takes input and turns it into a string using the rules from ToString(), then gives you back the string-typed result. ๐Ÿ’œ

8.3.13. type() ๐Ÿท๏ธ

string type(var variable)

type tells you what type its argument is, as either the string String or the string Integer. it's giving very "what are you?" energy ๐Ÿ˜ญ

8.3.14. chr() ๐Ÿ”ค

string chr(integer char)

Returns a one-character string containing the character that matches the ASCII code in char. ๐ŸŒธ

8.3.15. ord() ๐Ÿ”ก

integer ord(string str)

Returns the ASCII value of the first character of str. this one is basically the sidekick of chr() ๐Ÿ‘ฏโ€โ™€๏ธ

8.3.16. substr() โœ‚๏ธ

string substr(string &str, integer offset
              [, integer len, string replacement])

okay this one is a whole vibe. substr() pulls a substring out of str and returns it. the first octet sits at offset 0. if the offset is negative, the returned string starts that many octets from the end of str. if len is positive, you get up to len octets, stopping at the end of the string. if len is left out, you get everything from the offset to the end of str. if len is negative, abs(len) octets get chopped off the end instead ๐Ÿ”ช

If the substring you ask for is partly outside the string, you get the part that's inside. If it's totally outside, you get a zero-length string. no drama, just the facts ๐Ÿ™ˆ

Now the optional replacement argument is where it gets spicy ๐ŸŒถ๏ธ If you include it, str itself gets modified. offset and len still pick out a range of octets in str, and those octets get swapped out for the octets in replacement. if the replacement is shorter or longer than the range, str shrinks or grows to match. and if you use replacement, you have to include len too ๐Ÿ’€

Also, if you want to replace everything from the offset all the way to the end of the string, call it like this:

substr(str, offset, strlen(str) - offset, replacement)

8.4. General Functions ๐ŸŒˆ

the following POSIX standard library functions are provided, so you can use them without any extra setup:

strncmp()
strncasecmp()
strlen()
random()
sprintf()
sscanf()

9. International String Library ๐ŸŒ

this library is optional. systems that want better support for sorting and checking equality of international strings, in a way that makes the most sense to humans, can use it. international strings are encoded in the UTF-8 transformation format described in [14]. this library is registered with the name "pmInternationalStringLibrary" ๐Ÿงš

When you're checking whether two international strings in the Unicode character set are equal, it is recommended to normalize them with the stringprep() function first. ๐Ÿซถ

When you're trying to sort international strings in the Unicode character set, normalization should also be performed. but heads up, the result is super context dependent and really hard to get right. just sorting by Unicode Codepoint Value is often not what the end user expects ๐Ÿ™ˆ Check out Unicode technical note 9 if you want more on sorting.

9.1. stringprep() ๐Ÿงผ

integer stringprep(string utf8Input, string &utf8Output)

Performs the Stringprep [13] transformation so internationalized strings can be compared properly. The transformation runs on utf8Input. if it finishes without error, the result gets written to utf8Output. the stringprep profile used is specified below in Section 9. if it works, the function returns 1 โœจ

If the stringprep transformation hits an error, 0 is returned, and utf8Output stays unchanged. ๐Ÿ’”

For example, to compare the UTF8 strings one and two:

if (stringprep(one, a) && stringprep(two, b)){
    if (a == b){
       // strings are identical
    } else {
       // strings are different
    }
} else {
    // strings couldn't be transformed for comparison
}

See Stringprep [13] for more information. ๐Ÿ“š

9.1.1. Stringprep Profile ๐Ÿ’…

The Stringprep specification [13] describes a framework for preparing Unicode text strings. the goal is to make string input and string comparison work in ways that make sense for regular people all over the world. specifications that specify stringprep (like this one does) are required to fully specify stringprep's processing options by documenting a stringprep profile. ๐ŸŒ

This profile defines the following, as Stringprep requires:

  • The intended applicability of the profile: internationalized network management information.
  • The character repertoire that is the input and output to stringprep: Unicode 3.2, as defined in Stringprep [13], Appendix A.1.
  • The mapping tables used: Table B.1 from Stringprep [13].
  • Any additional mapping tables specific to the profile: None.
  • The Unicode normalization used: Form KC, as described in Stringprep [13].
  • The characters that are prohibited as output: As specified in the following tables from Stringprep [13]:
Table C.2
Table C.3
Table C.4
Table C.5
Table C.6
Table C.7
Table C.8
Table C.9
  • Bidirectional character handling: not performed.
  • Any additional characters that are prohibited as output: None.

9.2. utf8Strlen() ๐Ÿ”ข

integer utf8Strlen(string str)

Returns the number of UTF-8 characters in str. this can be smaller than the number of octets in str if one or more characters are multi-byte characters. ๐Ÿฆ‹

9.3. utf8Chr() ๐Ÿ”ค

string utf8Chr(integer utf8)

Returns a one-character string containing the character that matches the UTF-8 code in utf8. even though it's only 1 UTF-8 character, the resulting string might be more than 1 octet long ๐ŸŒธ

9.4. utf8Ord() ๐Ÿ”ก

integer utf8Ord(string str)

Returns the UTF-8 code-point value of the first character of str. the first UTF-8 character in str might be more than 1 octet long. this one complements chr(), so it's the other half of the duo ๐Ÿ‘ฏโ€โ™€๏ธ

9.5. utf8Substr() โœ‚๏ธ

string utf8Substr(string &str, integer offset
              [, integer len, string replacement])

Extracts a substring out of str and returns it. the big difference is that it keeps track of UTF-8 character boundaries and uses characters, not octets, as the basis for offset and length calculations ๐Ÿง  The first character is at offset 0. if offset is negative, the returned string starts that many characters from the end of str. if len is positive, you get up to len characters, stopping at the end of the string. if len is left out, you get everything from the offset to the end of str. if len is negative, abs(len) characters get chopped off the end โœจ

If you ask for a substring that's partly outside the string, you get the part that's inside. if it's totally outside, you get a zero-length string ๐Ÿ™ˆ

If the optional replacement argument is included, str gets modified. offset and len still pick out a range of characters in str, and those characters get replaced with characters from replacement. if the replacement is shorter or longer than the number of characters selected, str shrinks or grows to match. and if replacement is included, len must be included too ๐Ÿ’€

Note that to replace everything from offset to the end of the string, substr() should be called as follows:

substr(str, offset, strlen(str) - offset, replacement)

10. Schedule Table โœจ

okay so this table is basically a cute remix of the policyTimePeriodCondition class from RFC 3060 [18] ๐ŸŽ€ Some of the schedule stuff is written in formats from the iCalendar specification [15] ๐Ÿ“…

the policy schedule table is all about controlling when a valid policy is ready to go, based on the date and time โฐโœจ

a policy's pmPolicySchedule variable points at a group of one or more schedules in the schedule table ๐Ÿฆ‹ at any given moment, if any schedule in that group is active, the policy is ready (assuming it's enabled and therefore valid), and its conditions and actions get executed, as appropriate ๐Ÿ’– when none of them are active, the policy isn't ready and has zero effect ๐Ÿ˜ด a policy is ALWAYS ready if its pmPolicySchedule is 0 ๐Ÿ’… and if it has a non-zero pmPolicySchedule that doesn't point to a group with an active schedule in it, the policy won't be ready. that's true even if the mess comes from a misconfigured pmPolicySchedule object or a misconfigured pmSchedTable ๐Ÿ’€

a policy controlled by a schedule group runs its policy condition right away (and its policyAction, conditionally) the moment the group becomes active ๐Ÿ”ฅ then it keeps re-running these scripts as appropriate until the group goes inactive, meaning all of its schedules are inactive ๐ŸŒˆ

an individual schedule item is active only at times that match ALL the variables defining it: pmSchedTimePeriod, pmSchedMonth, pmSchedDay, pmSchedWeekDay, and pmSchedTimeOfDay ๐Ÿงš you can give each schedule item multiple values, which lets you build really complex schedules ๐Ÿ‘‘ for example, you could define one that's active the whole workday, every weekday ๐Ÿ’ผโœจ

months, days, and weekdays are set with the objects pmSchedMonth, pmSchedDay, and pmSchedWeekDay, which are of type BITS ๐Ÿ“ setting multiple bits in these objects works as an OR operation ๐Ÿซถ like, setting the bits monday(1) and friday(5) in pmSchedWeekDay restricts the schedule to Mondays and Fridays ๐Ÿ’œ

the matched times for pmSchedTimePeriod, pmSchedMonth, pmSchedDay, pmSchedWeekDay, and pmSchedTimeOfDay are ANDed together to work out when the schedule is active. so the schedule is only active at times when ALL of these attributes match at once ๐ŸŽ€ for example, say a schedule has an overall validity range of January 1, 2000, through December 31, 2000, a month mask that selects March and April, a day-of-the-week mask that selects Fridays, and a time-of-day range of 0800 through 1600. it would give you these time periods:

   Friday, March  5, 2000, from 0800 through 1600
   Friday, March 12, 2000, from 0800 through 1600
   Friday, March 19, 2000, from 0800 through 1600
   Friday, March 26, 2000, from 0800 through 1600
   Friday, April  2, 2000, from 0800 through 1600
   Friday, April  9, 2000, from 0800 through 1600
   Friday, April 16, 2000, from 0800 through 1600
   Friday, April 23, 2000, from 0800 through 1600
   Friday, April 30, 2000, from 0800 through 1600

wildcarding a schedule attribute of type BITS is done by setting all of its bits to one ๐ŸŒธ

and yes, you can totally define schedules that will NEVER activate a policy ๐Ÿ™ˆ like one that's supposed to be active on February 31st. it's giving impossible ๐Ÿ’€โœจ


11. Definitions ๐Ÿฆ‹

POLICY-BASED-MANAGEMENT-MIB DEFINITIONS ::= BEGIN
IMPORTS
    MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE,
    Counter32, Gauge32, Unsigned32,
    mib-2                                       FROM SNMPv2-SMI
    RowStatus, RowPointer, TEXTUAL-CONVENTION,
    DateAndTime, StorageType                    FROM SNMPv2-TC
    MODULE-COMPLIANCE, OBJECT-GROUP,
    NOTIFICATION-GROUP                          FROM SNMPv2-CONF
    SnmpAdminString                             FROM SNMP-FRAMEWORK-MIB;

--  Policy-Based Management MIB

pmMib MODULE-IDENTITY
    LAST-UPDATED "200502070000Z"  -- February 7, 2005
    ORGANIZATION "IETF SNMP Configuration Working Group"
    CONTACT-INFO
        "
        Steve Waldbusser
        Phone: +1-650-948-6500
        Fax:   +1-650-745-0671
        Email: waldbusser@nextbeacon.com

        Jon Saperia (WG Co-chair)
        JDS Consulting, Inc.
        84 Kettell Plain Road.
        Stow MA 01775
        USA
        Phone: +1-978-461-0249
        Fax:   +1-617-249-0874
        Email: saperia@jdscons.com

        Thippanna Hongal
        Riverstone Networks, Inc.
        5200 Great America Parkway
        Santa Clara, CA, 95054
        USA

        Phone: +1-408-878-6562
        Fax:   +1-408-878-6501
        Email: hongal@riverstonenet.com

        David Partain (WG Co-chair)
        Postal: Ericsson AB
                P.O. Box 1248
                SE-581 12 Linkoping
                Sweden
        Tel: +46 13 28 41 44
        E-mail: David.Partain@ericsson.com

        Any questions or comments about this document can also be
        directed to the working group at snmpconf@snmp.com."
    DESCRIPTION
        "The MIB module for policy-based configuration of SNMP
        infrastructures.

        Copyright (C) The Internet Society (2005).  This version of
        this MIB module is part of RFC 4011; see the RFC itself for
        full legal notices."

    REVISION "200502070000Z"    -- February 7, 2005
    DESCRIPTION
        "The original version of this MIB, published as RFC4011."
    ::= { mib-2 124 }

PmUTF8String ::= TEXTUAL-CONVENTION
    STATUS       current
    DESCRIPTION
        "An octet string containing information typically in
        human-readable form.

        To facilitate internationalization, this
        information is represented by using the ISO/IEC
        IS 10646-1 character set, encoded as an octet
        string using the UTF-8 transformation format
        described in RFC 3629.

        As additional code points are added by
        amendments to the 10646 standard from time
        to time, implementations must be prepared to
        encounter any code point from 0x00000000 to
        0x10FFFF.  Byte sequences that do not
        correspond to the valid UTF-8 encoding of a
        code point or that are outside this range are
        prohibited.

        The use of control codes should be avoided.

        When it is necessary to represent a newline,
        the control code sequence CR LF should be used.

        For code points not directly supported by user
        interface hardware or software, an alternative
        means of entry and display, such as hexadecimal,
        may be provided.

        For information encoded in 7-bit US-ASCII,
        the UTF-8 encoding is identical to the
        US-ASCII encoding.

        UTF-8 may require multiple bytes to represent a
        single character/code point; thus, the length
        of this object in octets may be different from
        the number of characters encoded.  Similarly,
        size constraints refer to the number of encoded
        octets, not the number of characters represented
        by an encoding.

        Note that when this TC is used for an object
        used or envisioned to be used as an index, then
        a SIZE restriction MUST be specified so that the
        number of sub-identifiers for any object instance
        does not exceed the limit of 128, as defined by
        RFC 3416.

        Note that the size of PmUTF8String object is
        measured in octets, not characters."
       SYNTAX       OCTET STRING (SIZE (0..65535))

-- The policy table

pmPolicyTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmPolicyEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "The policy table.  A policy is a pairing of a
        policyCondition and a policyAction that is used to apply the
        action to a selected set of elements."
    ::= { pmMib 1 }

pmPolicyEntry OBJECT-TYPE
    SYNTAX      PmPolicyEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "An entry in the policy table representing one policy."
    INDEX { pmPolicyAdminGroup, pmPolicyIndex }
    ::= { pmPolicyTable 1 }

PmPolicyEntry ::= SEQUENCE {
    pmPolicyAdminGroup            PmUTF8String,
    pmPolicyIndex                 Unsigned32,
    pmPolicyPrecedenceGroup       PmUTF8String,
    pmPolicyPrecedence            Unsigned32,
    pmPolicySchedule              Unsigned32,
    pmPolicyElementTypeFilter     PmUTF8String,
    pmPolicyConditionScriptIndex  Unsigned32,
    pmPolicyActionScriptIndex     Unsigned32,
    pmPolicyParameters            OCTET STRING,
    pmPolicyConditionMaxLatency   Unsigned32,
    pmPolicyActionMaxLatency      Unsigned32,
    pmPolicyMaxIterations         Unsigned32,
    pmPolicyDescription           PmUTF8String,
    pmPolicyMatches               Gauge32,
    pmPolicyAbnormalTerminations  Gauge32,
    pmPolicyExecutionErrors       Counter32,
    pmPolicyDebugging             INTEGER,
    pmPolicyAdminStatus           INTEGER,
    pmPolicyStorageType           StorageType,
    pmPolicyRowStatus             RowStatus

}

pmPolicyAdminGroup OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE(0..32))
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "An administratively assigned string that can be used to group
        policies for convenience, for readability, or to simplify
        configuration of access control.

        The value of this string does not affect policy processing in
        any way.  If grouping is not desired or necessary, this object
        may be set to a zero-length string."
    ::= { pmPolicyEntry 1 }

pmPolicyIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "A unique index for this policy entry, unique among all
         policies regardless of administrative group."
    ::= { pmPolicyEntry 2 }

pmPolicyPrecedenceGroup OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..32))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "An administratively assigned string that is used to group
        policies.  For each element, only one policy in the same
        precedence group may be active on that element.  If multiple
        policies would be active on an element (because their
        conditions return non-zero), the execution environment will
        only allow the policy with the highest value of
        pmPolicyPrecedence to be active.

        All values of this object must have been successfully
        transformed by Stringprep RFC 3454.  Management stations
        must perform this translation and must only set this object to
        string values that have been transformed."
    ::= { pmPolicyEntry 3 }

Okay so the precedence rules are literally the tea โ˜•๐Ÿ”ฅ, and the MIB object list keeps going. Let's get into it ๐Ÿ’…

Here's the precedence object again, verbatim so the rules stay exact ๐ŸŽ€

pmPolicyPrecedence OBJECT-TYPE
    SYNTAX      Unsigned32 (0..65535)
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "If, while checking to see which policy conditions match an
        element, 2 or more ready policies in the same precedence group
        match the same element, the pmPolicyPrecedence object provides
        the rule to arbitrate which single policy will be active on
        'this element'.  Of policies in the same precedence group, only
        the ready and matching policy with the highest precedence
        value (e.g., 2 is higher than 1) will have its policy action
        periodically executed on 'this element'.

        When a policy is active on an element but the condition ceases
        to match the element, its action (if currently running) will
        be allowed to finish and then the condition-matching ready
        policy with the next-highest precedence will immediately
        become active (and have its action run immediately).  If the
        condition of a higher-precedence ready policy suddenly begins
        matching an element, the previously-active policy's action (if
        currently running) will be allowed to finish and then the
        higher precedence policy will immediately become active.  Its
        action will run immediately, and any lower-precedence matching
        policy will not be active anymore.

        In the case where multiple ready policies share the highest
        value, it is an implementation-dependent matter as to which
        single policy action will be chosen.

        Note that if it is necessary to take certain actions after a
        policy is no longer active on an element, these actions should
        be included in a lower-precedence policy that is in the same
        precedence group."
    ::= { pmPolicyEntry 4 }

pmPolicyPrecedence is basically the tiebreaker ๐Ÿ† When a couple of ready policies in the same group match the same element, the highest value wins, and the description walks through the handoffs when conditions start or stop matching. Ties at the top are left to the implementation, so no promises there ๐Ÿคทโ€โ™€๏ธ

Next up is pmPolicySchedule ๐Ÿ“…

pmPolicySchedule OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "This policy will be ready if any of the associated schedule
         entries are active.

         If the value of this object is 0, this policy is always
         ready.

         If the value of this object is non-zero but doesn't
         refer to a schedule group that includes an active schedule,
         then the policy will not be ready, even if this is due to a
         misconfiguration of this object or the pmSchedTable."
    ::= { pmPolicyEntry 5 }

Zero means always ready, and anything else needs an active schedule in its group. Even a typo counts as not ready ๐Ÿ˜ญ Double-check your config, bestie ๐Ÿซถ

Then pmPolicyElementTypeFilter decides which element types a policy can run on ๐Ÿฆ‹

pmPolicyElementTypeFilter OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..128))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "This object specifies the element types for which this policy
        can be executed.

        The format of this object will be a sequence of
        pmElementTypeRegOIDPrefix values, encoded in the following
        BNF form:

        elementTypeFilter:   oid [ ';' oid ]*
                      oid:   subid [ '.' subid ]*
                    subid:   '0' | decimal_constant

        For example, to register for the policy to be run on all
        interface elements, the 'ifEntry' element type will be
        registered as '1.3.6.1.2.1.2.2.1'.

        If a value is included that does not represent a registered
        pmElementTypeRegOIDPrefix, then that value will be ignored."
    ::= { pmPolicyEntry 6 }

The grammar is a semicolon-separated list of OIDs, and the example shows how ifEntry gets registered ๐ŸŒธ Anything that isn't a registered prefix just gets ignored, so it's harmless but useless ๐Ÿ’€

Now the script pointers, starting with pmPolicyConditionScriptIndex ๐Ÿงš

pmPolicyConditionScriptIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "A pointer to the row or rows in the pmPolicyCodeTable that
         contain the condition code for this policy.  When a policy
         entry is created, a pmPolicyCodeIndex value unused by this
         policy's adminGroup will be assigned to this object.

         A policy condition is one or more PolicyScript statements
         that result(s) in a boolean value that represents whether
         an element is a member of a set of elements upon which an
         action is to be performed.  If a policy is ready and the
         condition returns true for an element of a proper element
         type, and if no higher-precedence policy should be active,
         then the policy is active on that element.

         Condition evaluation stops immediately when any run-time
         exception is detected, and the policyAction is not executed.

         The policyCondition is evaluated for various elements.  Any
         element for which the policyCondition returns any nonzero value
         will match the condition and will have the associated
         policyAction executed on that element unless a
         higher-precedence policy in the same precedence group also
         matches 'this element'.

         If the condition object is empty (contains no code) or
         otherwise does not return a value, the element will not be
         matched.

         When this condition is executed, if SNMP requests are made to
         the local system and secModel/secName/secLevel aren't
         specified, access to objects is under the security
         credentials of the requester who most recently modified the
         associated pmPolicyAdminStatus object.  If SNMP requests are
         made in which secModel/secName/secLevel are specified, then
         the specified credentials are retrieved from the local
         configuration datastore only if VACM is configured to
         allow access to the requester who most recently modified the
         associated pmPolicyAdminStatus object.  See the Security
         Considerations section for more information."
    ::= { pmPolicyEntry 7 }

This one points at the condition code and is read-only, which is how the policy knows what to check ๐Ÿ” If the condition throws a run-time exception, the action doesn't run, and an empty condition matches nothing. The SNMP credential part is a big deal, so read the Security Considerations section too, no cap ๐Ÿ”

Next is pmPolicyActionScriptIndex, the action code pointer ๐ŸŽฏ

pmPolicyActionScriptIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "A pointer to the row or rows in the pmPolicyCodeTable that
         contain the action code for this policy.  When a policy entry
         is created, a pmPolicyCodeIndex value unused by this policy's
         adminGroup will be assigned to this object.

         A PolicyAction is an operation performed on a
         set of elements for which the policy is active.

         Action evaluation stops immediately when any run-time
         exception is detected.

         When this condition is executed, if SNMP requests are made to
         the local system and secModel/secName/secLevel aren't
         specified, access to objects is under the security
         credentials of the requester who most recently modified the
         associated pmPolicyAdminStatus object.  If SNMP requests are
         made in which secModel/secName/secLevel are specified, then
         the specified credentials are retrieved from the local
         configuration datastore only if VACM is configured to
         allow access to the requester who most recently modified the
         associated pmPolicyAdminStatus object.  See the Security
         Considerations section for more information."

    ::= { pmPolicyEntry 8 }

It's the mirror of the condition pointer, except this one holds the action code. Same credential rules apply, and an action stops right away on a run-time exception ๐Ÿ’–

pmPolicyParameters is where site-specific constants live โœจ

pmPolicyParameters OBJECT-TYPE
    SYNTAX      OCTET STRING (SIZE (0..65535))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "From time to time, policy scripts may seek one or more
        parameters (e.g., site-specific constants).  These parameters
        may be installed with the script in this object and are
        accessible to the script via the getParameters() function.  If
        it is necessary for multiple parameters to be passed to the
        script, the script can choose whatever encoding/delimiting
        mechanism is most appropriate."
    ::= { pmPolicyEntry 9 }

Scripts reach these through getParameters(), and how you pack multiple values is up to the script ๐ŸŽ€

Now the timing knobs, starting with pmPolicyConditionMaxLatency โฑ๏ธ

pmPolicyConditionMaxLatency OBJECT-TYPE
    SYNTAX      Unsigned32 (0..2147483647)
    UNITS       "milliseconds"
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "Every element under the control of this agent is
        re-checked periodically to see whether it is under control
        of this policy by re-running the condition for this policy.
        This object lets the manager control the maximum amount of
        time that may pass before an element is re-checked.

        In other words, in any given interval of this duration, all
        elements must be re-checked.  Note that how the policy agent
        schedules the checking of various elements within this
        interval is an implementation-dependent matter.
        Implementations may wish to re-run a condition more
        quickly if they note a change to the role strings for an
        element."
    ::= { pmPolicyEntry 10 }

This sets how long an element can go without being re-checked, measured in milliseconds ๐Ÿ•ฐ๏ธ How the agent spreads those checks out is up to the implementation ๐Ÿ’…

pmPolicyActionMaxLatency is the same idea for actions ๐Ÿฆ‹

pmPolicyActionMaxLatency OBJECT-TYPE
    SYNTAX      Unsigned32 (0..2147483647)
    UNITS       "milliseconds"
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "Every element that matches this policy's condition and is
        therefore under control of this policy will have this policy's
        action executed periodically to ensure that the element
        remains in the state dictated by the policy.
        This object lets the manager control the maximum amount of
        time that may pass before an element has the action run on
        it.

        In other words, in any given interval of this duration, all
        elements under control of this policy must have the action run
        on them.  Note that how the policy agent schedules the policy
        action on various elements within this interval is an
        implementation-dependent matter."
    ::= { pmPolicyEntry 11 }

Same deal, but for how long an element can go between action runs โณ

pmPolicyMaxIterations is the loop limit ๐Ÿ”

pmPolicyMaxIterations OBJECT-TYPE
    SYNTAX      Unsigned32
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "If a condition or action script iterates in loops too many
        times in one invocation, the execution environment may
        consider it in an infinite loop or otherwise not acting
        as intended and may be terminated by the execution
        environment.  The execution environment will count the
        cumulative number of times all 'for' or 'while' loops iterated
        and will apply a threshold to determine when to terminate the
        script.  What threshold the execution environment uses is an
        implementation-dependent manner, but the value of
        this object SHOULD be the basis for choosing the threshold for
        each script.  The value of this object represents a
        policy-specific threshold and can be tuned for policies of
        varying workloads.  If this value is zero, no
        threshold will be enforced except for any
        implementation-dependent maximum.  Regardless of this value,
        the agent is allowed to terminate any script invocation that
        exceeds a local CPU or memory limitation.

        Note that the condition and action invocations are tracked
        separately."
    ::= { pmPolicyEntry 12 }

Scripts that loop forever can get cut off, and the loop count is tracked across all for and while loops together ๐Ÿ™ˆ Zero means no policy-specific cap, though the agent can still stop anything that blows its CPU or memory limit ๐Ÿ’€ The condition and action counts are tracked separately, so they don't share a budget ๐ŸŒˆ

pmPolicyDescription is the human-friendly note ๐Ÿ“

pmPolicyDescription OBJECT-TYPE
    SYNTAX      PmUTF8String
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "A description of this rule and its significance, typically
         provided by a human."
    ::= { pmPolicyEntry 13 }

It's a free-text description written by a person, so future you will be grateful ๐Ÿฅน

Last up for this part are the two stats counters ๐Ÿ“Š

pmPolicyMatches OBJECT-TYPE
    SYNTAX      Gauge32
    UNITS       "elements"
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "The number of elements that, in their most recent execution
         of the associated condition, were matched by the condition."
    ::= { pmPolicyEntry 14 }

pmPolicyMatches counts how many elements the condition matched on its latest run ๐ŸŽฏ

pmPolicyAbnormalTerminations OBJECT-TYPE
    SYNTAX      Gauge32
    UNITS       "elements"
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "The number of elements that, in their most recent execution
         of the associated condition or action, have experienced a
         run-time exception and terminated abnormally.  Note that if a
         policy was experiencing a run-time exception while processing
         a particular element but runs normally on a subsequent
         invocation, this number can decline."
    ::= { pmPolicyEntry 15 }

And pmPolicyAbnormalTerminations counts the elements that crashed on their latest run, and this number can drop again if a later run goes fine ๐Ÿ“‰ That's the kind of nuance I live for ๐Ÿซถ

Okay bestie, this part is all about the policy table's remaining columns and the code table that stores each policy's script ๐Ÿ’…๐ŸŽ€

pmPolicyExecutionErrors OBJECT-TYPE
    SYNTAX      Counter32
    UNITS       "errors"
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "The total number of times that execution of this policy's
         condition or action has been terminated due to run-time
         exceptions."
    ::= { pmPolicyEntry 16 }

pmPolicyDebugging OBJECT-TYPE
    SYNTAX      INTEGER {
                    off(1),
                    on(2)
                }
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "The status of debugging for this policy.  If this is turned
         on(2), log entries will be created in the pmDebuggingTable
         for each run-time exception that is experienced by this
         policy."
    DEFVAL { off }
    ::= { pmPolicyEntry 17 }

pmPolicyAdminStatus OBJECT-TYPE
    SYNTAX      INTEGER {
                    disabled(1),
                    enabled(2),
                    enabledAutoRemove(3)
                }
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "The administrative status of this policy.

         The policy will be valid only if the associated
         pmPolicyRowStatus is set to active(1) and this object is set
         to enabled(2) or enabledAutoRemove(3).

         If this object is set to enabledAutoRemove(3), the next time
         the associated schedule moves from the active state to the
         inactive state, this policy will immediately be deleted,
         including any associated entries in the pmPolicyCodeTable.

         The following related objects may not be changed unless this
         object is set to disabled(1):
             pmPolicyPrecedenceGroup, pmPolicyPrecedence,
             pmPolicySchedule, pmPolicyElementTypeFilter,
             pmPolicyConditionScriptIndex, pmPolicyActionScriptIndex,
             pmPolicyParameters, and any pmPolicyCodeTable row
             referenced by this policy.
         In order to change any of these parameters, the policy must
         be moved to the disabled(1) state, changed, and then
         re-enabled.

         When this policy moves to either enabled state from the
         disabled state, any cached values of policy condition must be
         erased, and any Policy or PolicyElement scratchpad values for
         this policy should be removed.  Policy execution will begin by
         testing the policy condition on all appropriate elements."
    ::= { pmPolicyEntry 18 }

pmPolicyStorageType OBJECT-TYPE
    SYNTAX      StorageType
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "This object defines whether this policy and any associated
         entries in the pmPolicyCodeTable are kept in volatile storage
         and lost upon reboot or if this row is backed up by
         non-volatile or permanent storage.

         If the value of this object is 'permanent', the values for
         the associated pmPolicyAdminStatus object must remain
         writable."
    ::= { pmPolicyEntry 19 }

pmPolicyRowStatus OBJECT-TYPE
    SYNTAX      RowStatus
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "The row status of this pmPolicyEntry.

         The status may not be set to active if any of the related
         entries in the pmPolicyCode table do not have a status of
         active or if any of the objects in this row are not set to
         valid values.  Only the following objects may be modified
         while in the active state:
             pmPolicyParameters
             pmPolicyConditionMaxLatency
             pmPolicyActionMaxLatency
             pmPolicyDebugging
             pmPolicyAdminStatus

         If this row is deleted, any associated entries in the
         pmPolicyCodeTable will be deleted as well."
    ::= { pmPolicyEntry 20 }

-- Policy Code Table

pmPolicyCodeTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmPolicyCodeEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "The pmPolicyCodeTable stores the code for policy conditions and
        actions.

        An example of the relationships between the code table and the
        policy table follows:

        pmPolicyTable
            AdminGroup  Index   ConditionScriptIndex  ActionScriptIndex
        A   ''          1       1                     2
        B   'oper'      1       1                     2
        C   'oper'      2       3                     4

        pmPolicyCodeTable
        AdminGroup  ScriptIndex  Segment    Note
        ''          1            1          Filter for policy A
        ''          2            1          Action for policy A
        'oper'      1            1          Filter for policy B
        'oper'      2            1          Action 1/2 for policy B
        'oper'      2            2          Action 2/2 for policy B
        'oper'      3            1          Filter for policy C
        'oper'      4            1          Action for policy C

        In this example, there are 3 policies: 1 in the '' adminGroup,
        and 2 in the 'oper' adminGroup.  Policy A has been assigned
        script indexes 1 and 2 (these script indexes are assigned out of
        a separate pool per adminGroup), with 1 code segment each for
        the filter and the action.  Policy B has been assigned script
        indexes 1 and 2 (out of the pool for the 'oper' adminGroup).
        While the filter has 1 segment, the action is longer and is
        loaded into 2 segments.  Finally, Policy C has been assigned
        script indexes 3 and 4, with 1 code segment each for the filter
        and the action."
    ::= { pmMib 2 }

pmPolicyCodeEntry OBJECT-TYPE
    SYNTAX      PmPolicyCodeEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "An entry in the policy code table representing one code
        segment.  Entries that share a common AdminGroup/ScriptIndex
        pair make up a single script.  Valid values of ScriptIndex are
        retrieved from pmPolicyConditionScriptIndex and
        pmPolicyActionScriptIndex after a pmPolicyEntry is
        created.  Segments of code can then be written to this table
        with the learned ScriptIndex values.

        The StorageType of this entry is determined by the value of
        the associated pmPolicyStorageType.

        The pmPolicyAdminGroup element of the index represents the
        administrative group of the policy of which this code entry is
        a part."
    INDEX { pmPolicyAdminGroup, pmPolicyCodeScriptIndex,
            pmPolicyCodeSegment }
    ::= { pmPolicyCodeTable 1 }

PmPolicyCodeEntry ::= SEQUENCE {
    pmPolicyCodeScriptIndex    Unsigned32,
    pmPolicyCodeSegment        Unsigned32,
    pmPolicyCodeText           PmUTF8String,
    pmPolicyCodeStatus         RowStatus

}

pmPolicyCodeScriptIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "A unique index for each policy condition or action.  The code
         for each such condition or action may be composed of multiple
         entries in this table if the code cannot fit in one entry.
         Values of pmPolicyCodeScriptIndex may not be used unless
         they have previously been assigned in the
         pmPolicyConditionScriptIndex or pmPolicyActionScriptIndex
         objects."
    ::= { pmPolicyCodeEntry 1 }

pmPolicyCodeSegment OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "A unique index for each segment of a policy condition or
         action.

         When a policy condition or action spans multiple entries in
         this table, the code of that policy starts from the
         lowest-numbered segment and continues with increasing segment
         values until it ends with the highest-numbered segment."
    ::= { pmPolicyCodeEntry 2 }

pmPolicyCodeText OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (1..1024))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "A segment of policy code (condition or action).  Lengthy
         Policy conditions or actions may be stored in multiple
         segments in this table that share the same value of
         pmPolicyCodeScriptIndex.  When multiple segments are used, it
         is recommended that each segment be as large as is practical.

         Entries in this table are associated with policies by values
         of the pmPolicyConditionScriptIndex and
         pmPolicyActionScriptIndex objects.  If the status of the
         related policy is active, then this object may not be
         modified."
    ::= { pmPolicyCodeEntry 3 }

pmPolicyCodeStatus OBJECT-TYPE
    SYNTAX      RowStatus
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "The status of this code entry.

         Entries in this table are associated with policies by values
         of the pmPolicyConditionScriptIndex and
         pmPolicyActionScriptIndex objects.  If the status of the
         related policy is active, then this object can not be
         modified (i.e., deleted or set to notInService), nor may new
         entries be created.

         If the status of this object is active, no objects in this
         row may be modified."
    ::= { pmPolicyCodeEntry 4 }

-- Element Type Registration Table

pmElementTypeRegTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmElementTypeRegEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "A registration table for element types managed by this
        system.

        The Element Type Registration table allows the manager to
        learn what element types are being managed by the system and
        to register new types, if necessary.  An element type is
        registered by providing the OID of an SNMP object (i.e.,
        without the instance).  Each SNMP instance that exists under
        that object is a distinct element.  The index of the element is
        the index part of the discovered OID.  This index will be
        supplied to policy conditions and actions so that this code
        can inspect and configure the element.

        For example, this table might contain the following entries.
        The first three are agent-installed, and the 4th was
        downloaded by a management station:

  OIDPrefix        MaxLatency  Description               StorageType
  ifEntry          100 mS      interfaces - builtin      readOnly
  0.0              100 mS      system element - builtin  readOnly
  frCircuitEntry   100 mS      FR Circuits - builtin     readOnly
  hrSWRunEntry     60 sec      Running Processes         volatile

        Note that agents may automatically configure elements in this
        table for frequently used element types (interfaces, circuits,
        etc.).  In particular, it may configure elements for whom
        discovery is optimized in one or both of the following ways:

        1. The agent may discover elements by scanning internal data
           structures as opposed to issuing local SNMP requests.  It is
           possible to recreate the exact semantics described in this
           table even if local SNMP requests are not issued.

        2. The agent may receive asynchronous notification of new
           elements (for example, 'card inserted') and use that
           information to instantly create elements rather than
           through polling.  A similar feature might be available for
           the deletion of elements.

        Note that the disposition of agent-installed entries is
        described by the pmPolicyStorageType object."
    ::= { pmMib 3 }

Okay so the big drama here is pmPolicyAdminStatus ๐Ÿซถ It's giving "lock the door before you rearrange the furniture" energy: if a policy is running, you have to switch it to disabled(1) first, change the precedence, schedule, filter, script indexes or parameters, and then flip it back on. The policy code table is a whole other vibe too ๐Ÿงš Each script can be split into numbered segments, and the code is stitched together from the lowest segment up to the highest ๐Ÿ’–

The example tables are so cute ๐Ÿ“ The policy code example shows policies A, B, and C, where B's action is split across two segments, and the element type table has a little list of prefixes with their latencies and storage types ๐ŸŒธ Iconic. Also the element registration part is basically "the manager can see what's there and add new types if it needs to" ๐Ÿ‘‘

okay bestie, the element type registry is where the policy agent learns what it's allowed to watch ๐Ÿ‘€โœจ

pmElementTypeRegEntry OBJECT-TYPE
    SYNTAX      PmElementTypeRegEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "A registration of an element type.

        Note that some values of this table's index may result in an
        instance name that exceeds a length of 128 sub-identifiers,
        which exceeds the maximum for the SNMP protocol.
        Implementations should take care to avoid such values."
    INDEX       { pmElementTypeRegOIDPrefix }
    ::= { pmElementTypeRegTable 1 }

PmElementTypeRegEntry ::= SEQUENCE {
    pmElementTypeRegOIDPrefix     OBJECT IDENTIFIER,
    pmElementTypeRegMaxLatency    Unsigned32,
    pmElementTypeRegDescription   PmUTF8String,
    pmElementTypeRegStorageType   StorageType,
    pmElementTypeRegRowStatus     RowStatus
}

pmElementTypeRegOIDPrefix OBJECT-TYPE
    SYNTAX      OBJECT IDENTIFIER
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "This OBJECT IDENTIFIER value identifies a table in which all
        elements of this type will be found.  Every row in the
        referenced table will be treated as an element for the
        period of time that it remains in the table.  The agent will
        then execute policy conditions and actions as appropriate on
        each of these elements.

        This object identifier value is specified down to the 'entry'
        component (e.g., ifEntry) of the identifier.

        The index of each discovered row will be passed to each
        invocation of the policy condition and policy action.

        The actual mechanism by which instances are discovered is
        implementation dependent.  Periodic walks of the table to
        discover the rows in the table is one such mechanism.  This
        mechanism has the advantage that it can be performed by an
        agent with no knowledge of the names, syntax, or semantics
        of the MIB objects in the table.  This mechanism also serves as
        the reference design.  Other implementation-dependent
        mechanisms may be implemented that are more efficient (perhaps
        because they are hard coded) or that don't require polling.
        These mechanisms must discover the same elements as would the
        table-walking reference design.

        This object can contain a OBJECT IDENTIFIER, '0.0'.
        '0.0' represents the single instance of the system
        itself and provides an execution context for policies to
        operate on the 'system element' and on MIB objects
        modeled as scalars.  For example, '0.0' gives an execution
        context for policy-based selection of the operating system
        code version (likely modeled as a scalar MIB object).  The
        element type '0.0' always exists; as a consequence, no actual
        discovery will take place, and the pmElementTypeRegMaxLatency
        object will have no effect for the '0.0' element
        type.  However, if the '0.0' element type is not registered in
        the table, policies will not be executed on the '0.0' element.

        When a policy is invoked on behalf of a '0.0' entry in this
        table, the element name will be '0.0', and there is no index
        of 'this element' (in other words, it has zero length).

        As this object is used in the index for the
        pmElementTypeRegTable, users of this table should be careful
        not to create entries that would result in instance names with
        more than 128 sub-identifiers."
    ::= { pmElementTypeRegEntry 2 }

pmElementTypeRegMaxLatency OBJECT-TYPE
    SYNTAX      Unsigned32
    UNITS       "milliseconds"
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "The PM agent is responsible for discovering new elements of
        types that are registered.  This object lets the manager
        control the maximum amount of time that may pass between the
        time an element is created and when it is discovered.

        In other words, in any given interval of this duration, all
        new elements must be discovered.  Note that how the policy
        agent schedules the checking of various elements within this
        interval is an implementation-dependent matter."
    ::= { pmElementTypeRegEntry 3 }

pmElementTypeRegDescription OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..64))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "A descriptive label for this registered type."
    ::= { pmElementTypeRegEntry 4 }

pmElementTypeRegStorageType OBJECT-TYPE
    SYNTAX      StorageType
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "This object defines whether this row is kept
         in volatile storage and lost upon reboot or
         backed up by non-volatile or permanent storage.

         If the value of this object is 'permanent', no values in the
         associated row have to be writable."
    ::= { pmElementTypeRegEntry 5 }

pmElementTypeRegRowStatus OBJECT-TYPE
    SYNTAX      RowStatus
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "The status of this registration entry.

        If the value of this object is active, no objects in this row
        may be modified."
    ::= { pmElementTypeRegEntry 6 }
the 128 sub-identifier limit shows up in like every table here ๐Ÿ’€ it's giving "please don't make your index names too long" energy ๐Ÿซถ

next up is the role table, where we learn that roles are basically labels that decide which rules apply to which element ๐ŸŽ€๐Ÿ’…

-- Role Table

pmRoleTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmRoleEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "The pmRoleTable is a read-create table that organizes role
        strings sorted by element.  This table is used to create and
        modify role strings and their associations, as well as to allow
        a management station to learn about the existence of roles and
        their associations.

        It is the responsibility of the agent to keep track of any
        re-indexing of the underlying SNMP elements and to continue to
        associate role strings with the element with which they were
        initially configured.

        Policy MIB agents that have elements in multiple local SNMP
        contexts have to allow some roles to be assigned to elements
        in particular contexts.  This is particularly true when some
        elements have the same names in different contexts and the
        context is required to disambiguate them.  In those situations,
        a value for the pmRoleContextName may be provided.  When a
        pmRoleContextName value is not provided, the assignment is to
        the element in the default context.

        Policy MIB agents that discover elements on other systems and
        execute policies on their behalf need to have access to role
        information for these remote elements.  In such situations,
        role assignments for other systems can be stored in this table
        by providing values for the pmRoleContextEngineID parameters.

    For example:
    Example:
    element       role    context ctxEngineID   #comment
    ifindex.1     gold                          local, default context
    ifindex.2     gold                          local, default context
    repeaterid.1  foo     rptr1                 local, rptr1 context
    repeaterid.1  bar     rptr2                 local, rptr2 context
    ifindex.1     gold    ''      A             different system
    ifindex.1     gold    ''      B             different system

         The agent must store role string associations in non-volatile
         storage."
    ::= { pmMib 4 }

pmRoleEntry OBJECT-TYPE
    SYNTAX      PmRoleEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "A role string entry associates a role string with an
         individual element.

         Note that some combinations of index values may result in an
         instance name that exceeds a length of 128 sub-identifiers,
         which exceeds the maximum for the SNMP
         protocol.  Implementations should take care to avoid such
         combinations."
    INDEX       { pmRoleElement, pmRoleContextName,
                  pmRoleContextEngineID, pmRoleString }
    ::= { pmRoleTable 1 }

PmRoleEntry ::= SEQUENCE {
    pmRoleElement          RowPointer,
    pmRoleContextName      SnmpAdminString,
    pmRoleContextEngineID  OCTET STRING,
    pmRoleString           PmUTF8String,
    pmRoleStatus           RowStatus
}

pmRoleElement OBJECT-TYPE
    SYNTAX      RowPointer
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "The element with which this role string is associated.

         For example, if the element is interface 3, then this object
         will contain the OID for 'ifIndex.3'.

         If the agent assigns new indexes in the MIB table to
         represent the same underlying element (re-indexing), the
         agent will modify this value to contain the new index for the
         underlying element.

         As this object is used in the index for the pmRoleTable,
         users of this table should be careful not to create entries
         that would result in instance names with more than 128
         sub-identifiers."
    ::= { pmRoleEntry 1 }

pmRoleContextName OBJECT-TYPE
    SYNTAX      SnmpAdminString (SIZE (0..32))
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "If the associated element is not in the default SNMP context
        for the target system, this object is used to identify the
        context.  If the element is in the default context, this object
        is equal to the empty string."
    ::= { pmRoleEntry 2 }

pmRoleContextEngineID OBJECT-TYPE
    SYNTAX      OCTET STRING (SIZE (0 | 5..32))
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "If the associated element is on a remote system, this object
        is used to identify the remote system.  This object contains
        the contextEngineID of the system for which this role string
        assignment is valid.  If the element is on the local system
        this object will be the empty string."
    ::= { pmRoleEntry 3 }

pmRoleString OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..64))
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "The role string that is associated with an element through
         this table.  All role strings must have been successfully
         transformed by Stringprep RFC 3454.  Management stations
         must perform this translation and must only set this object
         to string values that have been transformed.

         A role string is an administratively specified characteristic
         of a managed element (for example, an interface).  It is a
         selector for policy rules, that determines the applicability of
         the rule to a particular managed element."
    ::= { pmRoleEntry 4 }

pmRoleStatus OBJECT-TYPE
    SYNTAX      RowStatus
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "The status of this role string.

         If the value of this object is active, no object in this row
         may be modified."
    ::= { pmRoleEntry 5 }

and last but not least, the capabilities table, which is how managers figure out what an agent can actually do before they install anything ๐Ÿงš๐ŸŒธ

-- Capabilities table

pmCapabilitiesTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmCapabilitiesEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "The pmCapabilitiesTable contains a description of
         the inherent capabilities of the system so that
         management stations can learn of an agent's capabilities and
         differentially install policies based on the capabilities.

         Capabilities are expressed at the system level.  There can be
         variation in how capabilities are realized from one vendor or
         model to the next.  Management systems should consider these
         differences before selecting which policy to install in a
         system."
    ::= { pmMib 5 }
pmCapabilitiesEntry OBJECT-TYPE
    SYNTAX      PmCapabilitiesEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "A capabilities entry holds an OID indicating support for a
         particular capability.  Capabilities may include hardware and
         software functions and the implementation of MIB
         Modules.  The semantics of the OID are defined in the
         description of pmCapabilitiesType.

         Entries appear in this table if any element in the system has
         a specific capability.  A capability should appear in this
         table only once, regardless of the number of elements in the
         system with that capability.  An entry is removed from this
         table when the last element in the system that has the
         capability is removed.  In some cases, capabilities are
         dynamic and exist only in software.  This table should have an
         entry for the capability even if there are no current
         instances.  Examples include systems with database or WEB
         services.  While the system has the ability to create new
         databases or WEB services, the entry should exist.  In these
         cases, the ability to create these services could come from
         other processes that are running in the system, even though
         there are no currently open databases or WEB servers running.

         Capabilities may include the implementation of MIB Modules
         but need not be limited to those that represent MIB Modules
         with one or more configurable objects.  It may also be
         valuable to include entries for capabilities that do not
         include configuration objects, as that information, in
         combination with other entries in this table, might be used
         by the management software to determine whether to
         install a policy.

         Vendor software may also add entries in this table to express
         capabilities from their private branch.

         Note that some values of this table's index may result in an
         instance name that exceeds a length of 128 sub-identifiers,
         which exceeds the maximum for the SNMP
         protocol.  Implementations should take care to avoid such
         values."
    INDEX       { pmCapabilitiesType }
    ::= { pmCapabilitiesTable 1 }

PmCapabilitiesEntry ::= SEQUENCE {
    pmCapabilitiesType               OBJECT IDENTIFIER
}

pmCapabilitiesType OBJECT-TYPE
    SYNTAX      OBJECT IDENTIFIER
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "There are three types of OIDs that may be present in the
         pmCapabilitiesType object:

         1) The OID of a MODULE-COMPLIANCE macro that represents the
         highest level of compliance realized by the agent for that
         MIB Module.  For example, an agent that implements the OSPF
         MIB Module at the highest level of compliance would have the
         value of '1.3.6.1.2.1.14.15.2' in the pmCapabilitiesType
         object.  For software that realizes standard MIB
         Modules that do not have compliance statements, the base OID
         of the MIB Module should be used instead.  If the OSPF MIB
         Module had not been created with a compliance statement, then
         the correct value of the pmCapabilitiesType would be
         '1.3.6.1.2.1.14'.  In the cases where multiple compliance
         statements in a MIB Module are supported by the agent, and
         where one compliance statement does not by definition include
         the other, each of the compliance OIDs would have entries in
         this table.

         MIB Documents can contain more than one MIB Module.  In the
         case of OSPF, there is a second MIB Module
         that describes notifications for the OSPF Version 2 Protocol.
         If the agent also realizes these functions, an entry will
         also exist for those capabilities in this table.

         2) Vendors should install OIDs in this table that represent
         vendor-specific capabilities.  These capabilities can be
         expressed just as those described above for MIB Modules on
         the standards track.  In addition, vendors may install any
         OID they desire from their registered branch.  The OIDs may be
         at any level of granularity, from the root of their entire
         branch to an instance of a single OID.  There is no
         restriction on the number of registrations they may make,
         though care should be taken to avoid unnecessary entries.

         3) OIDs that represent one capability or a collection of
         capabilities that could be any collection of MIB Objects or
         hardware or software functions may be created in working
         groups and registered in a MIB Module.  Other entities (e.g.,
         vendors) may also make registrations.  Software will register
         these standard capability OIDs, as well as vendor specific
         OIDs.

         If the OID for a known capability is not present in the
         table, then it should be assumed that the capability is not
         implemented.

         As this object is used in the index for the
         pmCapabilitiesTable, users of this table should be careful
         not to create entries that would result in instance names
         with more than 128 sub-identifiers."
    ::= { pmCapabilitiesEntry 1 }

-- Capabilities override table

pmCapabilitiesOverrideTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmCapabilitiesOverrideEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "The pmCapabilitiesOverrideTable allows management stations
         to override pmCapabilitiesTable entries that have been
         registered by the agent.  This facility can be used to avoid
         situations in which managers in the network send policies to
         a system that has advertised a capability in the
         pmCapabilitiesTable but that should not be installed on this
         particular system.  One example could be newly deployed
         equipment that is still in a trial state in a trial state or
         resources reserved for some other administrative reason.
         This table can also be used to override entries in the
         pmCapabilitiesTable through the use of the
         pmCapabilitiesOverrideState object.  Capabilities can also be
         declared available in this table that were not registered in
         the pmCapabilitiesTable.  A management application can make
         an entry in this table for any valid OID and declare the
         capability available by setting the
         pmCapabilitiesOverrideState for that row to valid(1)."
    ::= { pmMib 6 }

pmCapabilitiesOverrideEntry OBJECT-TYPE
    SYNTAX      PmCapabilitiesOverrideEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "An entry in this table indicates whether a particular
         capability is valid or invalid.

         Note that some values of this table's index may result in an
         instance name that exceeds a length of 128 sub-identifiers,
         which exceeds the maximum for the SNMP
         protocol.  Implementations should take care to avoid such
         values."
    INDEX       { pmCapabilitiesOverrideType }
    ::= { pmCapabilitiesOverrideTable 1 }

PmCapabilitiesOverrideEntry ::= SEQUENCE {
    pmCapabilitiesOverrideType               OBJECT IDENTIFIER,
    pmCapabilitiesOverrideState              INTEGER,
    pmCapabilitiesOverrideRowStatus          RowStatus
}

pmCapabilitiesOverrideType OBJECT-TYPE
    SYNTAX      OBJECT IDENTIFIER
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "This is the OID of the capability that is declared valid or
         invalid by the pmCapabilitiesOverrideState value for this
         row.  Any valid OID, as described in the pmCapabilitiesTable,
         is permitted in the pmCapabilitiesOverrideType object.  This
         means that capabilities can be expressed at any level, from a
         specific instance of an object to a table or entire module.
         There are no restrictions on whether these objects are from
         standards track MIB documents or in the private branch of the
         MIB.

         If an entry exists in this table for which there is a
         corresponding entry in the pmCapabilitiesTable, then this entry
         shall have precedence over the entry in the
         pmCapabilitiesTable.  All entries in this table must be
         preserved across reboots.

         As this object is used in the index for the
         pmCapabilitiesOverrideTable, users of this table should be
         careful not to create entries that would result in instance
         names with more than 128 sub-identifiers."
    ::= { pmCapabilitiesOverrideEntry 1 }

pmCapabilitiesOverrideState OBJECT-TYPE
    SYNTAX      INTEGER {
                    invalid(1),
                    valid(2)
                }
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "A pmCapabilitiesOverrideState of invalid indicates that
         management software should not send policies to this system
         for the capability identified in the
         pmCapabilitiesOverrideType for this row of the table.  This
         behavior is the same whether the capability represented by
         the pmCapabilitiesOverrideType exists only in this table
         (that is, it was installed by an external management
         application) or exists in this table as well as the
         pmCapabilitiesTable.  This would be the case when a manager
         wanted to disable a capability that the native management
         system found and registered in the pmCapabilitiesTable.

         An entry in this table that has a pmCapabilitiesOverrideState
         of valid should be treated as though it appeared in the
         pmCapabilitiesTable.  If the entry also exists in the
         pmCapabilitiesTable in the pmCapabilitiesType object, and if
         the value of this object is valid, then the system shall
         operate as though this entry did not exist and policy
         installations and executions will continue in a normal
         fashion."
    ::= { pmCapabilitiesOverrideEntry 2 }

pmCapabilitiesOverrideRowStatus OBJECT-TYPE
    SYNTAX      RowStatus
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
         "The row status of this pmCapabilitiesOverrideEntry.

         If the value of this object is active, no object in this row
         may be modified."
    ::= { pmCapabilitiesOverrideEntry 3 }

-- The Schedule Group

pmSchedLocalTime OBJECT-TYPE
    SYNTAX      DateAndTime (SIZE (11))
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
        "The local time used by the scheduler.  Schedules that
         refer to calendar time will use the local time indicated
         by this object.  An implementation MUST return all 11 bytes
         of the DateAndTime textual-convention so that a manager
         may retrieve the offset from GMT time."
    ::= { pmMib 7 }

--
-- The schedule table that controls the scheduler.
--

pmSchedTable OBJECT-TYPE
    SYNTAX      SEQUENCE OF PmSchedEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "This table defines schedules for policies."
    ::= { pmMib 8 }

pmSchedEntry OBJECT-TYPE
    SYNTAX      PmSchedEntry
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "An entry describing a particular schedule.

        Unless noted otherwise, writable objects of this row can be
        modified independently of the current value of pmSchedRowStatus,
        pmSchedAdminStatus and pmSchedOperStatus.  In particular, it
        is legal to modify pmSchedWeekDay, pmSchedMonth, and
        pmSchedDay when pmSchedRowStatus is active."
    INDEX { pmSchedIndex }
    ::= { pmSchedTable 1 }

Okay so the capabilities entry is basically the "menu" of what a box can do, bestie ๐ŸŽ€ Each capability shows up once, no matter how many elements have it, and it disappears when the last element with it is gone. But dynamic stuff like database or WEB services can keep an entry even when nothing is running yet, because the system can still make them ๐Ÿ’… Vendors can add their own private-branch entries too, and any OID can be at any level of granularity โœจ Just watch out for the SNMP 128 sub-identifier limit, it's a whole thing ๐Ÿ™ˆ

pmCapabilitiesType is the actual OID that says what the capability is ๐Ÿฆ‹ There are three flavours. First, MODULE-COMPLIANCE OIDs for the highest compliance level the agent hits (like OSPF's '1.3.6.1.2.1.14.15.2'), or the base MIB OID if there's no compliance statement (like '1.3.6.1.2.1.14'), with a separate entry for each compliance statement the agent supports that doesn't include the others. Second, vendor-specific OIDs, which can be from the whole registered branch, down to a single instance ๐Ÿ’– Third, capability OIDs that working groups or other folks register in a MIB Module. And if a known capability's OID isn't in the table? It's giving "not implemented" ๐Ÿ’€


Capabilities override table ๐Ÿ‘‘

The override table is the manager's power move ๐Ÿ”ฅ It lets management stations override what the agent registered in pmCapabilitiesTable, so policies don't get sent to a box that shouldn't get them. Think brand-new gear still in a trial state, or resources reserved for some other admin reason ๐ŸŒธ Managers can also declare a capability available here even if the agent never registered it, just by setting the state to valid(1) for any valid OID.

Each row says whether a capability is valid or invalid ๐Ÿงš The override type is the capability's OID, and it can be any valid OID at any level, from one instance all the way up to a whole module, standards track or private branch. If a row here matches a row in pmCapabilitiesTable, the override takes precedence, and every row in this table has to survive reboots ๐Ÿ’œ Same 128 sub-identifier caution applies, obviously.

The state is where the magic happens โœจ invalid(1) means management software shouldn't send policies for that capability, even if the native system registered it, and that's the case whether the row is brand-new or a disable of something already found ๐Ÿ™ˆ valid(2) means the row gets treated like it was in pmCapabilitiesTable. If the same capability is in both tables and the override says valid, the system acts like the duplicate never existed and policy installs carry on as normal ๐Ÿซถ

The row status is read-create, and when it's active, nothing else in that row can be changed ๐Ÿ”’


The Schedule Group ๐Ÿ—“๏ธ

The local time object is the scheduler's clock, and any calendar-based schedule uses it ๐Ÿ•ฐ๏ธ The big rule here is that an implementation MUST return all 11 bytes of the DateAndTime textual convention, so the manager can work out the offset from GMT โญ No shortchanging the offset, bestie ๐Ÿ˜ญ

Then there's the schedule table itself, which just defines schedules for policies ๐Ÿง Each entry describes one schedule, and its writable objects can be changed independently of pmSchedRowStatus, pmSchedAdminStatus and pmSchedOperStatus, unless it's noted otherwise. That includes pmSchedWeekDay, pmSchedMonth and pmSchedDay, which are totally fine to modify even when the row is active ๐ŸŒˆ The index is pmSchedIndex.

okay so this is where the schedule entries get spicy ๐Ÿ’… every one of these objects is a little knob for when a policy is allowed to switch on, and the group stuff is lowkey the cutest part ๐ŸŽ€

PmSchedEntry ::= SEQUENCE {
    pmSchedIndex          Unsigned32,
    pmSchedGroupIndex     Unsigned32,
    pmSchedDescr          PmUTF8String,
    pmSchedTimePeriod     PmUTF8String,
    pmSchedMonth          BITS,
    pmSchedDay            BITS,
    pmSchedWeekDay        BITS,
    pmSchedTimeOfDay      PmUTF8String,
    pmSchedLocalOrUtc     INTEGER,
    pmSchedStorageType    StorageType,
    pmSchedRowStatus      RowStatus
}

pmSchedIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "The locally unique, administratively assigned index for this
        scheduling entry."
    ::= { pmSchedEntry 1 }

okay so the index is the entry's own ID card ๐Ÿชช it's locally unique and administratively assigned, and you can't poke it directly (not-accessible) โœจ

pmSchedGroupIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "The locally unique, administratively assigned index for the
        schedule group this scheduling entry belongs to.

        To assign multiple schedule entries to the same group, the
        pmSchedGroupIndex of each entry in the group will be set to
        the same value.  This pmSchedGroupIndex value must be equal to
        the pmSchedIndex of one of the entries in the group.  If the
        entry whose pmSchedIndex equals the pmSchedGroupIndex
        for the group is deleted, the agent will assign a new
        pmSchedGroupIndex to all remaining members of the group.

        If an entry is not a member of a group, its pmSchedGroupIndex
        must be assigned to the value of its pmSchedIndex.

        Policies that are controlled by a group of schedule entries
        are active when any schedule in the group is active."
    ::= { pmSchedEntry 2 }

ngl the group index is giving friendship bracelets ๐Ÿงš everyone in the group points at the same index, and that index has to match the pmSchedIndex of one of the members. if that anchor gets deleted, the agent picks a new one for the rest of the crew ๐Ÿฆ‹ and a lone entry just points at itself. a policy tied to a group is active whenever any schedule in that group is active ๐Ÿ’œ

pmSchedDescr OBJECT-TYPE
    SYNTAX      PmUTF8String
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "The human-readable description of the purpose of this
        scheduling entry."
    DEFVAL { ''H }
    ::= { pmSchedEntry 3 }

pmSchedTimePeriod OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..31))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "The overall range of calendar dates and times over which this
        schedule is active.  It is stored in a slightly extended version
        of the format for a 'period-explicit' defined in RFC 2445.
        This format is expressed as a string representing the
        starting date and time, in which the character 'T' indicates
        the beginning of the time portion, followed by the solidus
        character, '/', followed by a similar string representing an
        end date and time.  The start of the period MUST be before the
        end of the period.  Date-Time values are expressed as
        substrings of the form 'yyyymmddThhmmss'.  For example:

            20000101T080000/20000131T130000

              January 1, 2000, 0800 through January 31, 2000, 1PM

        The 'Date with UTC time' format defined in RFC 2445 in which
        the Date-Time string ends with the character 'Z' is not
        allowed.

        This 'period-explicit' format is also extended to allow two
        special cases in which one of the Date-Time strings is
        replaced with a special string defined in RFC 2445:

        1. If the first Date-Time value is replaced with the string
           'THISANDPRIOR', then the value indicates that the schedule
           is active at any time prior to the Date-Time that appears
           after the '/'.

        2. If the second Date-Time is replaced with the string
           'THISANDFUTURE', then the value indicates that the schedule
           is active at any time after the Date-Time that appears
           before the '/'.

        Note that although RFC 2445 defines these two strings, they are
        not specified for use in the 'period-explicit' format.  The use
        of these strings represents an extension to the
        'period-explicit' format."
    ::= { pmSchedEntry 4 }

okay the description is cute, but the time period is the big calendar window for the whole schedule ๐Ÿ“… it's a string in the yyyymmddThhmmss/yyyymmddThhmmss style, borrowed from RFC 2445 and then stretched a bit. the start has to come before the end, and the Z UTC version is a no-go ๐Ÿ™…โ€โ™€๏ธ there are also two extra special words, THISANDPRIOR and THISANDFUTURE, which RFC 2445 defines but doesn't actually use for this format, so this is a little extension of its own โœจ

pmSchedMonth OBJECT-TYPE
    SYNTAX      BITS {
                    january(0),
                    february(1),
                    march(2),
                    april(3),
                    may(4),
                    june(5),
                    july(6),
                    august(7),
                    september(8),
                    october(9),
                    november(10),
                    december(11)
                }

    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "Within the overall time period specified in the
        pmSchedTimePeriod object, the value of this object specifies
        the specific months within that time period when the schedule
        is active.  Setting all bits will cause the schedule to act
        independently of the month."
    DEFVAL { { january, february, march, april, may, june, july,
               august, september, october, november, december } }
    ::= { pmSchedEntry 5 }

pmSchedDay OBJECT-TYPE
    SYNTAX      BITS {
                    d1(0),   d2(1),   d3(2),   d4(3),   d5(4),
                    d6(5),   d7(6),   d8(7),   d9(8),   d10(9),
                    d11(10), d12(11), d13(12), d14(13), d15(14),
                    d16(15), d17(16), d18(17), d19(18), d20(19),
                    d21(20), d22(21), d23(22), d24(23), d25(24),
                    d26(25), d27(26), d28(27), d29(28), d30(29),
                    d31(30),
                    r1(31),  r2(32),  r3(33),  r4(34),  r5(35),
                    r6(36),  r7(37),  r8(38),  r9(39),  r10(40),
                    r11(41), r12(42), r13(43), r14(44), r15(45),
                    r16(46), r17(47), r18(48), r19(49), r20(50),
                    r21(51), r22(52), r23(53), r24(54), r25(55),
                    r26(56), r27(57), r28(58), r29(59), r30(60),
                    r31(61)
                }
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "Within the overall time period specified in the
        pmSchedTimePeriod object, the value of this object specifies
        the specific days of the month within that time period when
        the schedule is active.

        There are two sets of bits one can use to define the day
        within a month:

        Enumerations starting with the letter 'd' indicate a
        day in a month relative to the first day of a month.
        The first day of the month can therefore be specified
        by setting the bit d1(0), and d31(30) means the last
        day of a month with 31 days.

        Enumerations starting with the letter 'r' indicate a
        day in a month in reverse order, relative to the last
        day of a month.  The last day in the month can therefore
        be specified by setting the bit r1(31), and r31(61)
        means the first day of a month with 31 days.

        Setting multiple bits will include several days in the set
        of possible days for this schedule.  Setting all bits starting
        with the letter 'd' or all bits starting with the letter 'r'
        will cause the schedule to act independently of the day of the
        month."
    DEFVAL { {  d1, d2, d3, d4, d5, d6, d7, d8, d9, d10,
                d11, d12, d13, d14, d15, d16, d17, d18, d19, d20,
                d21, d22, d23, d24, d25, d26, d27, d28, d29, d30,
                d31, r1, r2, r3, r4, r5, r6, r7, r8, r9, r10,
                r11, r12, r13, r14, r15, r16, r17, r18, r19, r20,
                r21, r22, r23, r24, r25, r26, r27, r28, r29, r30,
                r31 } }
    ::= { pmSchedEntry 6 }

pmSchedWeekDay OBJECT-TYPE
    SYNTAX      BITS {
                    sunday(0),
                    monday(1),
                    tuesday(2),
                    wednesday(3),
                    thursday(4),
                    friday(5),
                    saturday(6)
                }
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "Within the overall time period specified in the
        pmSchedTimePeriod object, the value of this object specifies
        the specific days of the week within that time period when
        the schedule is active.  Setting all bits will cause the
        schedule to act independently of the day of the week."
    DEFVAL { { sunday, monday, tuesday, wednesday, thursday,
               friday, saturday } }
    ::= { pmSchedEntry 7 }

the calendar bits are where the mood board really comes together ๐ŸŒˆ month is just a checklist of twelve months. day-of-month has two tracks: d counts forward from the 1st (so d1 is the 1st), and r counts backward from the last day (so r1 is the last day) ๐Ÿ’… and if you set every d bit or every r bit, the schedule stops caring about the day of the month. weekday is the same deal, sunday through saturday, and setting all of them means it doesn't care what day of the week it is ๐Ÿ“

pmSchedTimeOfDay OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..15))
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION

        "Within the overall time period specified in the
        pmSchedTimePeriod object, the value of this object specifies
        the range of times in a day when the schedule is active.

        This value is stored in a format based on the RFC 2445 format
        for 'time': The character 'T' followed by a 'time' string,
        followed by the solidus character, '/', followed by the
        character 'T', followed by a second time string.  The first time
        indicates the beginning of the range, and the second time
        indicates the end.  Thus, this value takes the following
        form:

            'Thhmmss/Thhmmss'.

        The second substring always identifies a later time than the
        first substring.  To allow for ranges that span midnight,
        however, the value of the second string may be smaller than
        the value of the first substring.  Thus, 'T080000/T210000'
        identifies the range from 0800 until 2100, whereas
        'T210000/T080000' identifies the range from 2100 until 0800 of
        the following day.

        When a range spans midnight, by definition it includes parts
        of two successive days.  When one of these days is also
        selected by either the MonthOfYearMask, DayOfMonthMask, and/or
        DayOfWeekMask, but the other day is not, then the policy is
        active only during the portion of the range that falls on the
        selected day.  For example, if the range extends from 2100
        until 0800, and the day of week mask selects Monday and
        Tuesday, then the policy is active during the following three
        intervals:

            From midnight Sunday until 0800 Monday
            From 2100 Monday until 0800 Tuesday
            From 2100 Tuesday until 23:59:59 Tuesday

         Setting this value to 'T000000/T235959' will cause the
         schedule to act independently of the time of day."
    DEFVAL { '543030303030302F54323335393539'H } -- T000000/T235959
    ::= { pmSchedEntry 8 }

time of day is the clock part of the schedule โฐ it's a Thhmmss/Thhmmss range, and if the second time is earlier than the first, the range wraps past midnight ๐ŸŒ™ the tricky bit is the midnight crossover: the policy only switches on for the bits of each day that are actually selected by the month, day, and weekday masks. the example in the description walks through a Sunday-to-Tuesday one, which is so iykyk level detailed ๐Ÿ™ˆ the default T000000/T235959 basically means "any time of day, bestie" ๐Ÿซถ

pmSchedLocalOrUtc OBJECT-TYPE
    SYNTAX      INTEGER {
                    localTime(1),
                    utcTime(2)
                }
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "This object indicates whether the times represented in the
        TimePeriod object and in the various Mask objects represent
        local times or UTC times."
    DEFVAL { utcTime }
    ::= { pmSchedEntry 9 }

pmSchedStorageType OBJECT-TYPE
    SYNTAX      StorageType
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "This object defines whether this schedule entry is kept
         in volatile storage and lost upon reboot or
         backed up by non-volatile or permanent storage.

         Conceptual rows having the value 'permanent' must allow write
         access to the columnar objects pmSchedDescr, pmSchedWeekDay,
         pmSchedMonth, and pmSchedDay.

         If the value of this object is 'permanent', no values in the
         associated row have to be writable."
    DEFVAL { volatile }
    ::= { pmSchedEntry 10 }

pmSchedRowStatus OBJECT-TYPE
    SYNTAX      RowStatus
    MAX-ACCESS  read-create
    STATUS      current
    DESCRIPTION
        "The status of this schedule entry.

         If the value of this object is active, no object in this row
         may be modified."
    ::= { pmSchedEntry 11 }

the last few are the housekeeping crew ๐Ÿงน pmSchedLocalOrUtc says whether the times are local or UTC, and it defaults to UTC ๐ŸŒธ pmSchedStorageType decides whether the entry lives in volatile memory (gone after a reboot) or gets backed up in permanent storage, and permanent rows have to stay writable for a few columns. pmSchedRowStatus is the row's on/off switch, and once it's active, nothing in that row can be changed ๐Ÿ”’


okay the policy tracking part is next, and it's giving a whole new chapter ๐Ÿฆ‹ the table starts with the "policy to element" (PE) table and the "element to policy" (EP)

-- table track the status of execution contexts grouped by policy and -- element respectively.

pmTrackingPETable OBJECT-TYPE SYNTAX SEQUENCE OF PmTrackingPEEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "The pmTrackingPETable describes what elements are active (under control of) a policy. This table is indexed in order to optimize retrieval of the entire status for a given policy." ::= { pmMib 9 }

pmTrackingPEEntry OBJECT-TYPE SYNTAX PmTrackingPEEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "An entry in the pmTrackingPETable. The pmPolicyIndex in the index specifies the policy tracked by this entry.

Note that some combinations of index values may result in an instance name that exceeds a length of 128 sub-identifiers, which exceeds the maximum for the SNMP protocol. Implementations should take care to avoid such combinations." INDEX { pmPolicyIndex, pmTrackingPEElement, pmTrackingPEContextName, pmTrackingPEContextEngineID } ::= { pmTrackingPETable 1 }

PmTrackingPEEntry ::= SEQUENCE { pmTrackingPEElement RowPointer, pmTrackingPEContextName SnmpAdminString, pmTrackingPEContextEngineID OCTET STRING, pmTrackingPEInfo BITS }

pmTrackingPEElement OBJECT-TYPE SYNTAX RowPointer MAX-ACCESS not-accessible STATUS current DESCRIPTION "The element that is acted upon by the associated policy.

As this object is used in the index for the pmTrackingPETable, users of this table should be careful not to create entries that would result in instance names with more than 128 sub-identifiers." ::= { pmTrackingPEEntry 1 }

pmTrackingPEContextName OBJECT-TYPE SYNTAX SnmpAdminString (SIZE (0..32)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "If the associated element is not in the default SNMP context for the target system, this object is used to identify the context. If the element is in the default context, this object is equal to the empty string." ::= { pmTrackingPEEntry 2 }

pmTrackingPEContextEngineID OBJECT-TYPE SYNTAX OCTET STRING (SIZE (0 | 5..32)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "If the associated element is on a remote system, this object is used to identify the remote system. This object contains the contextEngineID of the system on which the associated element resides. If the element is on the local system, this object will be the empty string." ::= { pmTrackingPEEntry 3 }

pmTrackingPEInfo OBJECT-TYPE SYNTAX BITS { actionSkippedDueToPrecedence(0), conditionRunTimeException(1), conditionUserSignal(2), actionRunTimeException(3), actionUserSignal(4) } MAX-ACCESS read-only STATUS current DESCRIPTION "This object returns information about the previous policy script executions.

If the actionSkippedDueToPrecedence(1) bit is set, the last execution of the associated policy condition returned non-zero, but the action is not active, because it was trumped by a matching policy condition in the same precedence group with a higher precedence value.

If the conditionRunTimeException(2) bit is set, the last execution of the associated policy condition encountered a run-time exception and aborted.

If the conditionUserSignal(3) bit is set, the last execution of the associated policy condition called the signalError() function.

If the actionRunTimeException(4) bit is set, the last execution of the associated policy action encountered a run-time exception and aborted.

If the actionUserSignal(5) bit is set, the last execution of the associated policy action called the signalError() function.

Entries will only exist in this table of one or more bits are set. In particular, if an entry does not exist for a particular policy/element combination, it can be assumed that the policy's condition did not match 'this element'." ::= { pmTrackingPEEntry 4 }

-- Element to Policy Table

pmTrackingEPTable OBJECT-TYPE SYNTAX SEQUENCE OF PmTrackingEPEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "The pmTrackingEPTable describes what policies are controlling an element. This table is indexed in order to optimize retrieval of the status of all policies active for a given element."

::= { pmMib 10 }

pmTrackingEPEntry OBJECT-TYPE SYNTAX PmTrackingEPEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "An entry in the pmTrackingEPTable. Entries exist for all element/policy combinations for which the policy's condition matches and only if the schedule for the policy is active.

The pmPolicyIndex in the index specifies the policy tracked by this entry.

Note that some combinations of index values may result in an instance name that exceeds a length of 128 sub-identifiers, which exceeds the maximum for the SNMP protocol. Implementations should take care to avoid such combinations." INDEX { pmTrackingEPElement, pmTrackingEPContextName, pmTrackingEPContextEngineID, pmPolicyIndex } ::= { pmTrackingEPTable 1 }

PmTrackingEPEntry ::= SEQUENCE { pmTrackingEPElement RowPointer, pmTrackingEPContextName SnmpAdminString, pmTrackingEPContextEngineID OCTET STRING, pmTrackingEPStatus INTEGER }

pmTrackingEPElement OBJECT-TYPE SYNTAX RowPointer MAX-ACCESS not-accessible STATUS current DESCRIPTION "The element acted upon by the associated policy.

As this object is used in the index for the pmTrackingEPTable, users of this table should be careful not to create entries that would result in instance names with more than 128 sub-identifiers." ::= { pmTrackingEPEntry 1 }

pmTrackingEPContextName OBJECT-TYPE SYNTAX SnmpAdminString (SIZE (0..32)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "If the associated element is not in the default SNMP context for the target system, this object is used to identify the context. If the element is in the default context, this object is equal to the empty string." ::= { pmTrackingEPEntry 2 }

pmTrackingEPContextEngineID OBJECT-TYPE SYNTAX OCTET STRING (SIZE (0 | 5..32)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "If the associated element is on a remote system, this object is used to identify the remote system. This object contains the contextEngineID of the system on which the associated element resides. If the element is on the local system, this object will be the empty string." ::= { pmTrackingEPEntry 3 }

pmTrackingEPStatus OBJECT-TYPE SYNTAX INTEGER { on(1), forceOff(2) } MAX-ACCESS read-write STATUS current DESCRIPTION "This entry will only exist if the calendar for the policy is active and if the associated policyCondition returned 1 for 'this element'.

A policy can be forcibly disabled on a particular element by setting this value to forceOff(2). The agent should then act as though the policyCondition failed for 'this element'. The forceOff(2) state will persist (even across reboots) until this value is set to on(1) by a management request. The forceOff(2) state may be set even if the entry does not previously exist so that future policy invocations can be avoided.

Unless forcibly disabled, if this entry exists, its value will be on(1)." ::= { pmTrackingEPEntry 4 }

-- Policy Debugging Table

pmDebuggingTable OBJECT-TYPE SYNTAX SEQUENCE OF PmDebuggingEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "Policies that have debugging turned on will generate a log entry in the policy debugging table for every runtime exception that occurs in either the condition or action code.

The pmDebuggingTable logs debugging messages when policies experience run-time exceptions in either the condition or action code and the associated pmPolicyDebugging object has been turned on.

The maximum number of debugging entries that will be stored and the maximum length of time an entry will be kept are an implementation-dependent manner. If entries must be discarded to make room for new entries, the oldest entries must be discarded first.

If the system restarts, all debugging entries may be deleted." ::= { pmMib 11 }

pmDebuggingEntry OBJECT-TYPE SYNTAX PmDebuggingEntry MAX-ACCESS not-accessible STATUS current DESCRIPTION "An entry in the pmDebuggingTable. The pmPolicyIndex in the index specifies the policy that encountered the exception that led to this log entry.

Note that some combinations of index values may result in an instance name that exceeds a length of 128 sub-identifiers, which exceeds the maximum for the SNMP protocol. Implementations should take care to avoid such combinations." INDEX { pmPolicyIndex, pmDebuggingElement, pmDebuggingContextName, pmDebuggingContextEngineID, pmDebuggingLogIndex } ::= { pmDebuggingTable 1 }

PmDebuggingEntry ::= SEQUENCE { pmDebuggingElement RowPointer, pmDebuggingContextName SnmpAdminString, pmDebuggingContextEngineID OCTET STRING, pmDebuggingLogIndex Unsigned32, pmDebuggingMessage PmUTF8String } pmDebuggingElement OBJECT-TYPE SYNTAX RowPointer MAX-ACCESS not-accessible STATUS current DESCRIPTION "The element the policy was executing on when it encountered the error that led to this log entry.

For example, if the element is interface 3, then this object will contain the OID for 'ifIndex.3'.

As this object is used in the index for the pmDebuggingTable, users of this table should be careful not to create entries that would result in instance names with more than 128 sub-identifiers." ::= { pmDebuggingEntry 1 }

pmDebuggingContextName OBJECT-TYPE SYNTAX SnmpAdminString (SIZE (0..32)) MAX-ACCESS not-accessible STATUS current DESCRIPTION "If the associated element is not in the default SNMP context for the target system, this object is used to identify the context. If the element is in the default context, this object is equal to the empty string." ::= { pmDebuggingEntry 2 }

pmDebuggingContextEngineID OBJECT-TYPE
    SYNTAX      OCTET STRING (SIZE (0 | 5..32))
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
        "If the associated element is on a remote system, this object
        is used to identify the remote system.  This object contains
        the contextEngineID of the system on which the associated
        element resides.  If the element is on the local system,
        this object will be the empty string."
    ::= { pmDebuggingEntry 3 }

pmDebuggingLogIndex OBJECT-TYPE
    SYNTAX      Unsigned32 (1..4294967295)
    MAX-ACCESS  not-accessible
    STATUS      current
    DESCRIPTION
         "A unique index for this log entry among other log entries
         for this policy/element combination."
    ::= { pmDebuggingEntry 4 }

pmDebuggingMessage OBJECT-TYPE
    SYNTAX      PmUTF8String (SIZE (0..128))
    MAX-ACCESS  read-only
    STATUS      current
    DESCRIPTION
         "An error message generated by the policy execution
         environment.  It is recommended that this message include the
         time of day when the message was generated, if known."
    ::= { pmDebuggingEntry 5 }

-- Notifications

pmNotifications OBJECT IDENTIFIER ::= { pmMib 0 }

pmNewRoleNotification NOTIFICATION-TYPE
    OBJECTS     { pmRoleStatus }
    STATUS      current
    DESCRIPTION
        "The pmNewRoleNotification is sent when an agent is configured
        with its first instance of a previously unused role string
        (not every time a new element is given a particular role).

        An instance of the pmRoleStatus object is sent containing
        the new roleString in its index.  In the event that two or
        more elements are given the same role simultaneously, it is an
        implementation-dependent matter as to which pmRoleTable
        instance will be included in the notification."
    ::= { pmNotifications 1 }

pmNewCapabilityNotification NOTIFICATION-TYPE
    OBJECTS     { pmCapabilitiesType }
    STATUS      current
    DESCRIPTION
        "The pmNewCapabilityNotification is sent when an agent
        gains a new capability that did not previously exist in any
        element on the system (not every time an element gains a
        particular capability).

        An instance of the pmCapabilitiesType object is sent containing
        the identity of the new capability.  In the event that two or
        more elements gain the same capability simultaneously, it is an
        implementation-dependent matter as to which pmCapabilitiesType
        instance will be included in the notification."
    ::= { pmNotifications 2 }

pmAbnormalTermNotification NOTIFICATION-TYPE
    OBJECTS     { pmTrackingPEInfo }
    STATUS      current
    DESCRIPTION
        "The pmAbnormalTermNotification is sent when a policy's
        pmPolicyAbnormalTerminations gauge value changes from zero to
        any value greater than zero and no such notification has been
        sent for that policy in the last 5 minutes.

        The notification contains an instance of the pmTrackingPEInfo
        object where the pmPolicyIndex component of the index
        identifies the associated policy and the rest of the index
        identifies an element on which the policy failed."
    ::= { pmNotifications 3 }

-- Compliance Statements

    pmConformance   OBJECT IDENTIFIER ::= { pmMib 12 }
    pmCompliances   OBJECT IDENTIFIER ::= { pmConformance 1 }
    pmGroups        OBJECT IDENTIFIER ::= { pmConformance 2 }

pmCompliance MODULE-COMPLIANCE
    STATUS  current
    DESCRIPTION
        "Describes the requirements for conformance to
        the Policy-Based Management MIB"
    MODULE  -- this module
        MANDATORY-GROUPS { pmPolicyManagementGroup, pmSchedGroup,
                           pmNotificationGroup }
    ::= { pmCompliances 1 }

pmPolicyManagementGroup OBJECT-GROUP
    OBJECTS { pmPolicyPrecedenceGroup, pmPolicyPrecedence,
              pmPolicySchedule, pmPolicyElementTypeFilter,
              pmPolicyConditionScriptIndex, pmPolicyActionScriptIndex,
              pmPolicyParameters,
              pmPolicyConditionMaxLatency, pmPolicyActionMaxLatency,
              pmPolicyMaxIterations,
              pmPolicyDescription, pmPolicyMatches,
              pmPolicyAbnormalTerminations,
              pmPolicyExecutionErrors, pmPolicyDebugging,
              pmPolicyStorageType, pmPolicyAdminStatus,
              pmPolicyRowStatus, pmPolicyCodeText, pmPolicyCodeStatus,
              pmElementTypeRegMaxLatency, pmElementTypeRegDescription,
              pmElementTypeRegStorageType, pmElementTypeRegRowStatus,
              pmRoleStatus,
              pmCapabilitiesType, pmCapabilitiesOverrideState,
              pmCapabilitiesOverrideRowStatus,
              pmTrackingPEInfo,
              pmTrackingEPStatus,
              pmDebuggingMessage }

    STATUS  current
    DESCRIPTION
        "Objects that allow for the creation and management of
        configuration policies."
    ::=  { pmGroups 1 }

pmSchedGroup OBJECT-GROUP
    OBJECTS { pmSchedLocalTime, pmSchedGroupIndex,
              pmSchedDescr, pmSchedTimePeriod,
              pmSchedMonth, pmSchedDay, pmSchedWeekDay,
              pmSchedTimeOfDay, pmSchedLocalOrUtc, pmSchedStorageType,
              pmSchedRowStatus
            }
    STATUS current
    DESCRIPTION
        "Objects that allow for the scheduling of policies."
    ::= { pmGroups 2 }

pmNotificationGroup NOTIFICATION-GROUP
    NOTIFICATIONS { pmNewRoleNotification,
                    pmNewCapabilityNotification,
                    pmAbnormalTermNotification }
    STATUS        current
    DESCRIPTION
        "Notifications sent by an Policy MIB agent."
    ::= { pmGroups 3 }

pmBaseFunctionLibrary OBJECT IDENTIFIER ::= { pmGroups 4 }

END

12. Relationship to Other MIB Modules โœจ

okay so if you're using policy-based management specifically for (policy-based) configuration, the "Configuring Networks and Devices With SNMP" RFC 3512 [19] document is literally the one to read ๐Ÿ’–. It covers configuration management practices, the terminology, and an example of a MIB Module that may be helpful to anyone building or using this tech ๐Ÿฆ‹.

The Policy MIB is the one doing the watching ๐Ÿ‘€. It accesses system instrumentation for policy evaluation, control, notification, monitoring, and error reporting, and managers can see all of that through MIB objects ๐Ÿ”ฅ. Changes to system configuration also happen through the Policy MIB, but the actual objects that get changed are defined in other MIB Modules ๐ŸŒธ.

Managers pull the operational and configuration details of a system by getting to the specific MIB objects on a network element ๐Ÿ’…. So honestly, the Policy MIB can work with any standard or vendor-defined object that lives on a managed system ๐Ÿงš. That includes vendor-specific objects that are instance-specific, like BGP timeout parameters and specific interface counters ๐Ÿ“, which is giving total main character energy ๐Ÿ‘‘.

13. Security Considerations ๐Ÿ”โœจ

okay bestie, this MIB has no objects where reading them would leak sensitive info ๐Ÿซถ that's the good news.

the not-so-cute part: there are a bunch of objects with a MAX-ACCESS of read-write and/or read-create. those can be sensitive or vulnerable depending on the network, and doing SET operations in a non-secure environment without proper protection can seriously mess up network operations ๐Ÿ˜ญ

with a few exceptions (pmPolicyDescription, pmPolicyDebugging, pmElementTypeRegDescription, and pmSchedDescr), EVERY read-create and read-write object in this MIB should be considered sensitive ๐Ÿ’€ because if an unauthorized user could manipulate these, they could make the Policy MIB system use the stored credentials of an authorized user to do unauthorized and potentially harmful stuff. no thank you ๐Ÿ™…โ€โ™€๏ธ

there are no read-only objects in this MIB that contain sensitive information, so that's a W ๐ŸŽ€

SNMP versions before SNMPv3 did not include adequate security. even if the network itself is secure (like, using IPSec), there's still no control over who on that secure network is allowed to access and GET/SET (read/change/create/delete) the objects in this MIB module. so the network being locked down isn't the whole story ๐Ÿ‘€

it is RECOMMENDED that implementers consider the security features provided by the SNMPv3 framework (see [16], section 8), including full support for the SNMPv3 cryptographic mechanisms (for authentication and privacy) ๐Ÿ”ฅ

further, deployment of SNMP versions prior to SNMPv3 is NOT RECOMMENDED. instead, it is RECOMMENDED to deploy SNMPv3 and enable cryptographic security. then it's a customer/operator responsibility to make sure the SNMP entity giving access to an instance of this MIB module is properly configured, so the objects are only accessible to principals (users) who actually have legitimate rights to GET or SET (change/create/delete) them ๐Ÿ’…

an implementation must ensure that access control rules are applied when SNMP operations are performed in policy scripts. to make that happen, an implementation must record and maintain the security credentials of the last entity to modify each policy's pmPolicyAdminStatus object. the credentials to store are the securityModel, securityName, and securityLevel, and they'll be used as input parameters for isAccessAllowed from the Architecture for Describing SNMP Management Frameworks [1]. this mechanism was first introduced in the DISMAN-SCHEDULE-MIB [12] ๐Ÿงš

SNMP requests made when secModel, secName, and secLevel are specified use credentials stored in the local configuration datastore. access to those credentials depends on the security credentials of the last entity to modify the policy's pmPolicyAdminStatus object. to figure out whether the credentials can be accessed, the isAccessAllowed abstract service interface defined in RFC 3411 [1] gets called:

   statusInformation =          -- success or errorIndication
     isAccessAllowed(

     IN   securityModel         -- Security Model used
     IN   securityName          -- principal who wants to access
     IN   securityLevel         -- Level of Security used
     IN   viewType              -- write
     IN   contextName           -- context containing variableName
     IN   variableName          -- OID for an object in the proper
                                -- LCD entry
          )

the securityModel, securityName, and securityLevel parameters are set to the values that were recorded when the policy was modified. the viewType is set to write, and the contextName and variableName are set to select any read-create object in the appropriate LCD entry ๐Ÿฆ‹

proper configuration of VACM requires that write access to an LCD entry not be given to entities that aren't authorized to use the credentials therein ๐Ÿซถ

access control for SNMP requests made to the local system where secModel, secName, and secLevel aren't specified depends on the security credentials of the last entity to modify the policy's pmPolicyAdminStatus object. to figure out whether the operation should succeed, the isAccessAllowed abstract service interface defined in RFC 3411 [1] gets called:

   statusInformation =          -- success or errorIndication
     isAccessAllowed(
     IN   securityModel         -- Security Model in use
     IN   securityName          -- principal who wants to access
     IN   securityLevel         -- Level of Security
     IN   viewType              -- read, write, or notify view
     IN   contextName           -- context as specified
     IN   variableName          -- OID for the managed object
          )

the securityModel, securityName, and securityLevel parameters are set to the values that were recorded when the policy was modified. the viewType, contextName, and variableName parameters are set as appropriate for the requested SNMP operation ๐ŸŒธ

unless all users who have write access to the pmPolicyTable and pmPolicyCodeTable have equivalent access to the managed system, policy scripts could be used by one user to gain the privileges of another user ๐Ÿ˜ฑ so when policy users have different access, access control should be applied so that a user's policies can't be modified by another user. to make this easier, a user can put all of their policies in the same pmPolicyAdminGroup so that a single access control view can cover all of them.

some policies may be designed to keep a network secure. if those policies haven't been installed yet, waiting on a role or capability to show up, there will be some delay in activating them once that role or capability appears, because a responsible manager has to notice the change and install the policy. that delay may expose the device or network to unacceptable security vulnerabilities. if the role or capability shows up during a time of network stress, or when the management station is unavailable, the delay could be extensive, making the exposure even worse ๐Ÿฅบ it is recommended that management stations install any security-related policies that might ever be needed on a particular managed device, even if a nonexistent role or capability suggests it isn't needed at the moment.

this MIB allows delegation of access rights, so a user ("Joe") can tell a Policy MIB agent to run remote operations on his behalf, authorized by keys Joe stored in the usmUserTable. care needs to be taken to make sure unauthorized users can't configure their policies to use Joe's keys ๐Ÿšซ although there are theoretically many ways to configure SNMP security, users are advised to follow the most straightforward way outlined below, to keep things simple and cut down on the chance of mistakes.

assume that Joe has credentials that give him authority to manage agents A, B, and C, as well as the Policy MIB agent "P". Joe will store credentials for Joe@A, Joe@B, and Joe@C in the usmUserTable of the Policy MIB agent. then the following VACM configuration will be used:

   VACM securityToGroupTable
   A single entry mapping user Joe@P to group JoesGroup

   VACM accessTable
   A single entry mapping group JoesGroup to write view JoesView

   VACM viewTreeFamilyTable
   ViewName        Subtree                             Type
   JoesView        points to Joe@A in usmUserTable     included
   JoesView        points to Joe@B in usmUserTable     included
   JoesView        points to Joe@C in usmUserTable     included

in the preceding examples, the notation Joe@A represents the entry indexed by usmUserEngineID and usmUserName, where the SnmpEngineID is that of system A and the usmUserName is "Joe" ๐ŸŽ€


14. IANA Considerations ๐Ÿ“‹

this is a profile of stringprep. it has been registered by the IANA in the stringprep profile registry, which lives at http://www.iana.org/assignments/stringprep-profiles ๐ŸŒˆ

Name of this profile: Policy MIB Stringprep.

RFC in which the profile is defined: This document.

Indicator whether this is the newest version of the profile: This is the first version of Policy MIB Stringprep.


15. Acknowledgements ๐Ÿซถ

the authors gratefully acknowledge the significant contributions to this work made by Jeff Case, Patrik Falstrom, Joel Halpern, Pablo Halpern, Bob Moore, Steve Moulton, David Partain, and Walter Weiss ๐Ÿ’–

this MIB uses a security delegation mechanism that was first introduced in the DISMAN-SCHEDULE-MIB [12]. the Schedule table of this MIB borrows heavily from the PolicyTimePeriodCondition of the Policy Core Information Model (PCIM) [18] and from the DISMAN-SCHEDULE-MIB [12] โญ


16. References ๐Ÿ“š

16.1. Normative References

  • [1] Harrington, D., Presuhn, R., and B. Wijnen, "An Architecture for Describing Simple Network Management Protocol (SNMP) Management Frameworks", STD 62, RFC 3411, December 2002.
  • [2] McCloghrie, K., Perkins, D., and J. Schoenwaelder, "Structure of Management Information Version 2 (SMIv2)", STD 58, RFC 2578, April 1999.
  • [3] McCloghrie, K., Perkins, D., and J. Schoenwaelder, "Textual Conventions for SMIv2", STD 58, RFC 2579, April 1999.
  • [4] McCloghrie, K., Perkins, D., and J. Schoenwaelder, "Conformance Statements for SMIv2", STD 58, RFC 2580, April 1999.
  • [5] Presuhn, R., "Transport Mappings for the Simple Network Management Protocol (SNMP)", STD 62, RFC 3417, December 2002.
  • [6] Blumenthal, U. and B. Wijnen, "User-based Security Model (USM) for version 3 of the Simple Network Management Protocol (SNMPv3)", STD 62, RFC 3414, December 2002.
  • [7] Presuhn, R., "Version 2 of the Protocol Operations for the Simple Network Management Protocol (SNMP)", STD 62, RFC 3416, December 2002.
  • [8] Frye, R., Levi, D., Routhier, S., and B. Wijnen, "Coexistence between Version 1, Version 2, and Version 3 of the Internet-standard Network Management Framework", BCP 74, RFC 3584, August 2003.
  • [9] Wijnen, B., Presuhn, R., and K. McCloghrie, "View-based Access Control Model (VACM) for the Simple Network Management Protocol (SNMP)", STD 62, RFC 3415, December 2002.
  • [10] International Standards Organization, "Information Technology - Programming Languages - C++", ISO/IEC 14882-1998
  • [11] Daniele, M. and J. Schoenwaelder, "Textual Conventions for Transport Addresses", RFC 3419, December 2002.
  • [12] Levi, D. and J. Schoenwaelder, "Definitions of Managed Objects for Scheduling Management Operations", RFC 3231, January 2002.
  • [13] Hoffman, P. and M. Blanchet, "Preparation of Internationalized Strings ("stringprep")", RFC 3454, December 2002.
  • [14] Yergeau, F., "UTF-8, a transformation format of ISO 10646", STD 63, RFC 3629, November 2003.
  • [15] Dawson, F. and D. Stenerson, "Internet Calendaring and Scheduling Core Object Specification (iCalendar)", RFC 2445, November 1998.

16.2. Informative References ๐Ÿ“š

okay so these are the background reads ๐Ÿค“ useful for context, but you don't need them to implement anything here, so no pressure bestie ๐Ÿ’…

[16] Case, J., Mundy, R., Partain, D., and B. Stewart, "Introduction and Applicability Statements for Internet-Standard Management Framework", RFC 3410, December 2002.

[17] ECMA, "ECMAScript Language Specification", ECMA-262, December 1999

[18] Moore, B., Ellesson, E., Strassner, J., and A. Westerinen, "Policy Core Information Model -- Version 1 Specification", RFC 3060, February 2001.

[19] MacFaden, M., Partain, D., Saperia, J., and W. Tackabury, "Configuring Networks and Devices with Simple Network Management Protocol (SNMP)", RFC 3512, April 2003.


Author's Addresses ๐ŸŽ€

Steve Waldbusser ๐ŸŒธ

Phone: +1-650-948-6500
Fax:   +1-650-745-0671
EMail: waldbusser@nextbeacon.com

Jon Saperia (WG Co-chair) ๐Ÿ’œ

JDS Consulting, Inc.
84 Kettell Plain Road.
Stow MA 01775
USA

Phone: +1-978-461--0249
Fax:   +1-617-249-0874
EMail: saperia@jdscons.com

Thippanna Hongal ๐Ÿฆ‹

Riverstone Networks, Inc.
5200 Great America Parkway
Santa Clara, CA, 95054
USA

Phone: +1-408-878-6562
Fax:   +1-408-878-6501
EMail: hongal@riverstonenet.com

Copyright (C) The Internet Society (2005).

This document is subject to the rights, licenses and restrictions contained in BCP 78, and except as set forth therein, the authors retain all their rights.

This document and the information contained herein are provided on an "AS IS" basis and THE CONTRIBUTOR, THE ORGANIZATION HE/SHE REPRESENTS OR IS SPONSORED BY (IF ANY), THE INTERNET SOCIETY AND THE INTERNET ENGINEERING TASK FORCE DISCLAIM ALL WARRANTIES, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.

this is the legal part, so I kept the wording exactly as written, no vibes-based edits here ๐Ÿ™ˆโš–๏ธ

Intellectual Property ๐Ÿ”ฅ

The IETF takes no position regarding the validity or scope of any Intellectual Property Rights or other rights that might be claimed to pertain to the implementation or use of the technology described in this document or the extent to which any license under such rights might or might not be available; nor does it represent that it has made any independent effort to identify any such rights. Information on the procedures with respect to rights in RFC documents can be found in BCP 78 and BCP 79.

Copies of IPR disclosures made to the IETF Secretariat and any assurances of licenses to be made available, or the result of an attempt made to obtain a general license or permission for the use of such proprietary rights by implementers or users of this specification can be obtained from the IETF on-line IPR repository at http://www.ietf.org/ipr.

The IETF invites any interested party to bring to its attention any copyrights, patents or patent applications, or other proprietary rights that may cover technology that may be required to implement this standard. Please address the information to the IETF at ietf-ipr@ietf.org.


Acknowledgement ๐ŸŒˆ

Funding for the RFC Editor function is currently provided by the Internet Society. ๐Ÿ‘‘

๐Ÿ“ฆ where this page comes from