# SNMP

> 响应 SNMP v1、v2c 或 v3 的网络设备（UPS、PDU、交换机、路由器、精密空调和环境传感器），在每个点位的轮询间隔通过其代理读取：“SNMP 代理”卡片、用数字或标准名称书写的点位 OID、每个对象读取成的数据类型、读取与写入、发现代理对象的遍历，以及两个完整示例。

**SNMP** 驱动通过机房设备的 SNMP 代理读写这些设备：UPS、PDU、交换机、路由器、精密空调，以及温度或湿度传感器。它补全了读取服务器管理控制器的 [Redfish](driver-redfish) 驱动：两者合起来覆盖整个机房。每个点位按 OID 读取代理的一个对象，例如 `upsBatteryStatus.0` 或 `ifHCInOctets.2`，它的值和其他任何点位的值一样送达各处：Logic、仪表画面，以及 [Process](process) 测试台的运行记录。

由工作站发问：每个点位按其轮询间隔，用 SNMPv1、SNMPv2c 或 SNMPv3 读取；“设备”卡片的**轮询间隔（ms）**与任何驱动一样起作用，每个点位都可以覆盖它。设备主动发送的内容，即它的 trap，不会被接收；见[值得了解的限制](#limits-worth-knowing)。

本驱动的一台设备就是一个代理，通过其主机的 UDP 端口 161 访问。设备位于驱动之下，可以归入文件夹，与任何没有串行线路也没有 MQTT 代理的驱动一样，形成 **驱动 → 文件夹 → 设备 → 点位**。

## 驱动页面 {#the-drivers-page}

对有“配置 Connector”权限的角色，页面上有本驱动自己的扫描范围卡片，里面是设备发现要遍历的代理和子树（见[设备发现](#discovery)）。它下面是任何驱动都有的**设备**表（见[设备表](connector-drivers#the-device-table)），用标题右侧的**添加设备**增加；为空时显示“还没有设备。”驱动的命令栏上有**发现设备**，在扫描范围卡片填写完整之前呈灰色。

## 设备 {#device}

### 连接 {#connection}

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **主机** | 代理的主机名或 IP 地址。 | 空 |
| **端口** | 代理应答的 UDP 端口，1 到 65535。框留空时以灰色显示 161，并按该端口询问代理。 | 161 |

新设备没有主机：显示“尚未填写主机”，在有主机之前不发送任何内容。标题栏显示的 Base URI 就是运行时地址，含版本、当前的 SNMPv3 安全设置、响应超时和重试次数：`snmp://192.168.10.40:161?version=2c&timeoutMs=2000&retries=1`，或 `snmp://192.168.10.2:161?version=3&security=authPriv&auth=sha256&priv=aes128&timeoutMs=2000&retries=1`。它从不包含 community、用户或任何密码。

### “SNMP 代理”卡片 {#the-snmp-agent-card}

在“连接”卡片下面，**SNMP 代理**卡片说明如何询问代理。它绘制的字段取决于版本，在 SNMPv3 下还取决于安全级别。

| 字段 | 何时显示 | 含义 | 默认值 |
| --- | --- | --- | --- |
| **SNMP 版本** | 始终 | SNMPv1、SNMPv2c 或 SNMPv3，下面各有一行说明。SNMPv1：“SNMPv1 没有 64 位计数器，代理缺少的对象会使整个请求被拒绝，驱动随后去掉该对象重新请求。”SNMPv2c：“SNMPv2c 可读取 64 位计数器，代理缺少的对象只会让该点位失败。大多数设备都支持。”SNMPv3：“SNMPv3 以用户身份登录，并按安全级别对消息签名和加密。” | SNMPv2c |
| **Community** | SNMPv1、SNMPv2c | 代理响应的 community，最多 255 字节，放在隐藏输入内容的框里；为空时以 `public` 作为提示显示，但从不作为值。下面写着：“代理会忽略带有其不接受的 community 的请求，因此 community 错误看起来就像代理没有应答。” | 空 |
| **SNMPv3 用户** | SNMPv3 | 代理保存的用户，最多 32 个字符。 | 空 |
| **安全级别** | SNMPv3 | **签名并加密（authPriv）**：“每条消息都用认证密码签名，并用加密密码加密。”**仅签名（authNoPriv）**：“每条消息都用认证密码签名，以明文传输。”**不签名也不加密（noAuthNoPriv）**：“只发送用户名：不签名，也不加密。” | **签名并加密（authPriv）** |
| **认证协议** | SNMPv3，签名时 | MD5、SHA-1、SHA-256、SHA-384 或 SHA-512。 | SHA-1 |
| **认证密码** | SNMPv3，签名时 | 给消息签名的密码。 | 空 |
| **加密协议** | SNMPv3，authPriv | DES、AES-128、AES-192 或 AES-256。 | AES-128 |
| **加密密码** | SNMPv3，authPriv | 给消息加密的密码。 | 空 |
| **响应超时（ms）** | 始终 | 100 到 60000：“请求在重新发送之前等待代理应答的时间。” | 2000 |
| **重试次数** | 始终 | 0 到 5：“未获应答的请求重新发送的次数，超过后设备视为离线。” | 1 |

两个密码下面写着：“每个密码都是代理为该用户保存的密码，至少 8 个字符。”用户、安全级别和两个协议，都是代理在设备自身的设置页面上为该用户配置的内容：工作站无法向代理询问它们。之所以提供 MD5、SHA-1 和 DES，是因为许多已安装的设备不接受更新的算法。

尚未填写的凭据不算错误：设备照常保存，在凭据填写之前驱动不向代理发送任何内容，卡片下面平静的一行说明还缺什么，取以下第一条：

| 缺少 | 卡片显示 |
| --- | --- |
| community（SNMPv1、SNMPv2c） | “请输入代理响应的 community。在此之前设备不发送任何请求。” |
| 用户（SNMPv3） | “请输入 SNMPv3 用户。在此之前设备不发送任何请求。” |
| 认证密码（authNoPriv、authPriv） | “请输入认证密码。在此之前设备不发送任何请求。” |
| 加密密码（authPriv） | “请输入加密密码。在此之前设备不发送任何请求。” |

有主机但仍缺凭据的设备不会连接，它的最近错误会说出代理在等哪一项凭据。卡片不能接受的值不会保存，卡片用红色说明原因：

| 您输入的 | 卡片显示 |
| --- | --- |
| 少于 8 个字符的认证密码 | “认证密码至少需要 8 个字符。” |
| 少于 8 个字符的加密密码 | “加密密码至少需要 8 个字符。” |
| 超过 32 个字符的用户，或超过 255 字节的 community | “用户最多 32 个字符，community 最多 255 字节。” |
| 超出 100 到 60000 的响应超时，或超出 0 到 5 的重试次数 | 框恢复为当前值，并显示“请输入 100 到 60000 之间的数字。”或“请输入 0 到 5 之间的数字。” |

保存时遵守和卡片相同的规则，拒绝时操作记录条会这样说：“设备‘UPS room 1’有驱动无法使用的 SNMP 设置。请打开它并修正面板标出的字段。”community 和两个密码与任何设备凭据一样保管（见[凭据与证书](connector-devices#credentials-and-certificates)）：为输入它们的 Windows 账户加密存储，在配置导出中密封，从不显示在 Base URI、诊断日志或代理的回答中。在另一个 Windows 账户下输入的凭据读出来为空，设备会像从未填写过一样等待它。

## 点位寻址 {#tag-addressing}

### OID {#the-oid}

本驱动的点位读取其代理的一个对象。点位的“信号源”卡片上有 **OID** 框，下面的提示是“用数字写的 OID（例如 1.3.6.1.2.1.1.3.0），或带实例的标准名称（例如 sysUpTime.0、ifInOctets.2、upsBatteryStatus.0）”。无论点位用哪种形式书写，请求携带的都是数字：

| 写法 | 读作 |
| --- | --- |
| `1.3.6.1.2.1.1.3.0`、`.1.3.6.1.2.1.1.3.0`、`iso.3.6.1.2.1.1.3.0` | 该数字，面板称之为 `sysUpTime.0`。 |
| `sysUpTime.0`、`sysUpTime`、`SNMPv2-MIB::sysUpTime.0`（不区分大小写） | 单个值，它唯一的实例 `.0` 可以省略。 |
| `ifInOctets.2` | 表格的一列加上其行的索引，这里是接口 2，索引总要写出。 |
| `enterprises.318.1.1.1.2.2.1.0` | 一个分支加上其下的数字：设备自有 MIB 中的对象，按其数字显示。 |

框下面以只读方式显示两种形式的 OID：

| 显示 | 内容 |
| --- | --- |
| **MIB 名称** | 对象的标准名称加实例（`ifInOctets.2`），下面是 MIB 对该对象的说明：所属 MIB、所含值的类型以及是否可写，例如 `IF-MIB · Counter32 · 只读` 或 `UPS-MIB · Integer32 · 可写`。驱动不认识其名称的对象显示“无标准名称”。 |
| **数字 OID** | 用数字写的 OID，也就是每个请求携带的内容。 |

没有标准名称的对象会多出一行：“设备自有 MIB 中的对象：由于不读取 MIB 文件，按其数字显示。”驱动无法读取其 OID 的点位不会保存，点位页面会说明原因：

| 您输入的 | 点位页面显示 |
| --- | --- |
| 没有 OID | “这个驱动必须填写OID。” |
| 驱动不认识的名称 | “此名称不是驱动所知的标准 MIB 对象。请用数字书写 OID。” |
| 没有行的列，例如 `ifInOctets` | “表格列需要其行的索引，例如 ifInOctets.1。” |
| 实例不是 0 的单个值，例如 `sysUpTime.1` | “单个值只有一个实例 .0，可以省略。” |
| 单独的分支，例如 `enterprises` 或 `ifTable` | “树的分支不指代任何值。请写出其下对象的数字。” |
| 第一个数大于 2、第二个数为 40 或以上、超过 128 个数，或某个数大于 4294967295 | “OID 以 0、1 或 2 和一个小于 40 的数开头，最多含 128 个不超过 4294967295 的数。” |
| 其他不是 OID 的内容 | “请用数字（例如 1.3.6.1.2.1.1.3.0）或带实例的标准名称（例如 sysUpTime.0）书写 OID。” |

保存时遵守同样的规则：“点位‘Battery status’未指向 SNMP 驱动能读取的 OID。请打开它并修正点位面板标出的地址。”只读的**源地址（总线）**一行显示驱动收到的数字：写作 `upsBatteryStatus.0` 的点位为 `1.3.6.1.2.1.33.1.2.1.0`。

### 驱动认识的名称 {#the-names-the-driver-knows}

驱动带有一张值得命名的标准 MIB 对象表，依据 RFC 编写，因此一个名称在每个工作站上都表示同一个东西。不读取任何 MIB 文件。

| MIB | 命名的内容 |
| --- | --- |
| SNMPv2-MIB（RFC 3418） | system 组：`sysDescr`、`sysObjectID`、`sysUpTime`、`sysContact`、`sysName`、`sysLocation`、`sysServices`。 |
| IF-MIB（RFC 2863） | `ifNumber`、接口表（`ifDescr`、`ifOperStatus`、`ifInOctets`、`ifOutErrors` 等）及其带 64 位计数器的扩展（`ifName`、`ifHCInOctets`、`ifHCOutOctets`、`ifHighSpeed`、`ifAlias`）。 |
| UPS-MIB（RFC 1628） | UPS 的标识、电池、输入、输出、旁路、报警、测试、控制和配置：`upsBatteryStatus`、`upsEstimatedChargeRemaining`、`upsOutputSource`、`upsOutputPercentLoad` 等。 |
| ENTITY-SENSOR-MIB（RFC 3433） | 传感器表：`entPhySensorType`、`entPhySensorScale`、`entPhySensorValue`、`entPhySensorOperStatus` 等。 |
| ENTITY-MIB（RFC 6933） | 物理实体的名称、描述和类别，用来标记传感器：`entPhysicalName`、`entPhysicalDescr`、`entPhysicalClass`。 |
| HOST-RESOURCES-MIB（RFC 2790） | 系统（`hrSystemUptime`、`hrSystemProcesses`）、存储表（`hrStorageUsed`）和处理器表（`hrProcessorLoad`）。 |

`driver_capabilities` 列出每个名称及其 OID；见[智能体工具](#agent-tools)。

### 数据类型与访问 {#data-types-and-access}

**数据类型**列表只提供对象能无损读取的类型：

| 对象所含 | 最自然的读取类型 | 也可选择 |
| --- | --- | --- |
| Integer32，含枚举和 TruthValue | Int32 | Int64、Float、Double、String |
| Counter32、Gauge32、TimeTicks（百分之一秒） | UInt32 | Int64、Double、String |
| Counter64 | UInt64 | Double、String |
| DisplayString | String | |
| 字节、IP 地址或 OID | String：字节用十六进制（`00:1A:2B`），地址用点分形式，OID 用点分形式 | |
| 没有标准名称的对象 | Float | Int32、UInt32、Int64、UInt64、Double、String：读取时由代理的应答决定 |

计数器不提供 Float，因为 Float 超过 16,777,216 会舍入；Counter64 不提供 Int64，因为 Int64 会拒绝超过其一半的计数。从不提供 Boolean：SNMP 没有布尔值，它的 TruthValue（1 为真，2 为假）与设备自定的 0 和 1 会把同一类型读成相反的两种意思，所以状态按 Int32 读取。OID 改变时，新对象无法读取的类型会变成它最自然的读取类型；而它无法读取的类型会被拒绝，并列出它可以读取的类型：“ifHCOutOctets.2 可按以下类型之一读取：UInt64, Double, String。”

MIB 声明为只读的标准对象（计数器、`sysUpTime`、各种状态）只提供**只读**，“访问”字段下写着“只读：这个值由设备报告，外部不能设定它。”MIB 声明为可写的标准对象（`sysContact`、`sysName`、`sysLocation`、`ifAdminStatus`、`ifAlias`、UPS-MIB 的测试、控制和配置对象）以及设备自有 MIB 中的对象提供全部访问方式：代理是否接受写入，由它自己回答。

## 读取 {#reading}

在每个点位的轮询间隔，工作站发送一个 GET。代理的应答决定点位显示什么（见[数值转换链](connector-value-pipeline)）：

| 代理应答 | 读取的结果 |
| --- | --- |
| 值 | 点位类型的值，在点位自己的转换环节之前。含数字的文本（有些设备写的 `23.5`）可在数值点位中读取。 |
| 点位类型容纳不了的值，例如 UInt32 中的负数 | 该点位读取失败并附原因；设备继续读取其他点位。 |
| noSuchObject 或 noSuchInstance，或仅该对象出错 | 该点位读取失败，并以代理的应答命名；设备继续读取其他点位。在 SNMPv1 下，缺一个对象就会使整个请求被拒绝，驱动会去掉它重新询问。 |
| 所有重试之后，响应超时内仍无应答 | 静默：设备转为离线并重新连接，点位针对[设备没有应答](connector-value-pipeline#the-device-did-not-answer)的策略决定它显示什么。原因中写明代理、等待时间和尝试次数，并提醒：对于不接受的 community 或 SNMPv3 用户，代理同样保持沉默。 |
| SNMPv3 报告（未知用户、错误的密码或协议、代理不接受的安全级别），或主机回答该端口上没有代理在监听 | 带有该原因的连接失败，记入诊断日志和设备的最近错误；设备重新连接。 |

一台设备一次只发一个请求。一次读取询问该点位的对象，并在同一个请求中询问设备上其他没有新鲜应答的点位，最多 32 个 OID；代理回答请求太大（tooBig）时，此后只发一半。这样带回的应答，在比另一个点位轮询间隔的一半更新时，用于该点位自己的读取，所以四十个点位每一轮只需一两个请求。SNMPv3 代理的引擎每次连接学习一次；代理报告超出其时间窗口的请求，会按代理自己的数值再发送一次。

## 写入 {#writing}

访问方式为写的点位通过设备的队列发送 SET。写入与其他任何驱动走同一条通道：写入框接收工程值，在发送原始值之前把转换环节反向执行（见 [Connector](connector)）。SET 携带的值类型依次取：代理上次应答该对象时所用的类型；没有时取 MIB 声明的类型；再没有时取此刻 GET 应答的类型；最后取点位自己的类型（Int32 用 Integer32，UInt32 用 Gauge32，String 用 OCTET STRING）。该类型容纳不了的值，在发送任何内容之前就被拒绝。

| 代理应答 | 写入的报告 |
| --- | --- |
| 无错误 | 完成。设备保存的应答全部作废，下一次读取会重新询问。 |
| 错误状态 | “现场设备拒绝了写入：`<reason>`。”原因按 RFC 3416 的写法说明状态，例如 `notWritable (17)`、`wrongType (7)` 或 `wrongValue (10)`，SNMPv1 下为 `readOnly (4)`。 |
| 所有重试之后，响应超时内仍无应答 | “写入未得到确认：现场设备没有及时应答。” |

## 设备发现 {#discovery}

### “扫描范围”卡片 {#the-scan-scope-card}

对有“配置 Connector”权限的角色，驱动页面上有它的**扫描范围**卡片，标题下一行写着“遍历一个代理的标准子树并列出找到的内容。SNMPv3 代理需通过其设备扫描：请将该设备选为要扫描的代理。”

| 字段 | 含义 | 默认值 |
| --- | --- | --- |
| **要扫描的代理** | **在此输入的地址**，或按名称选本驱动的一台设备。设备的代理按保存的主机、端口、版本、设置和凭据读取，设备还在等待保存的编辑会先保存。这是扫描 SNMPv3 代理的唯一方式。 | **在此输入的地址** |
| **代理地址** | 输入的代理：主机名或 IP 地址，端口与下面不同时在冒号后写出端口。 | 空 |
| **端口** | 输入的代理：1 到 65535。 | 161 |
| **SNMP 版本** | 输入的代理：SNMPv1 或 SNMPv2c。 | SNMPv2c |
| **Community** | 输入的代理：它响应的 community，放在隐藏输入内容的框里，以 `public` 作为提示。下面写着：“仅用于本次扫描。从结果中添加的设备会将其保留为自己的 community。” | 空 |
| **子树** | 各一个勾选框：**系统（SNMPv2-MIB）**、**接口（IF-MIB）**、**不间断电源（UPS-MIB）**、**传感器（ENTITY-SENSOR-MIB）**和**主机资源（HOST-RESOURCES-MIB）**。 | 除主机资源外全部 |
| **另外遍历** | “再加一个子树，用数字或在标准名称下书写，例如 enterprises.318。设备自有的对象以数字返回。” | 空 |

范围还缺什么，会以红色写在卡片下面，在填写完整之前发现设备保持灰色：

| 范围 | 卡片显示 |
| --- | --- |
| 没有地址的输入代理 | “请输入要扫描的 SNMP 代理的地址。” |
| 地址不是主机的输入代理 | “‘http://192.168.0.30’不是 SNMP 代理的地址。请填写它的主机名或 IP 地址，需要时带上端口，例如 192.168.0.30 或 192.168.0.30:161。” |
| 没有 community 或 community 超过 255 字节的输入代理 | “请输入代理响应的 community，最多 255 字节。” |
| 已不存在的设备 | “选定扫描的 SNMP 设备已不存在。请选择另一个要扫描的代理。” |
| 缺少主机或代理所需凭据的设备 | “设备‘UPS room 1’仍缺少主机或其代理所需的凭据。请先填写其 SNMP 卡片。” |
| 不是 OID 的待遍历子树 | “‘vendor’不是可遍历的 OID。请用数字或在标准名称下书写，例如 enterprises.318。” |
| 什么都没勾选，也没指定 | “请至少勾选一个子树，或指定一个要遍历的子树。” |

要扫描的设备有无法保存的编辑时，扫描不会开始：“设备发现没有开始，因为要扫描的设备的修改无法保存。”

### 扫描提供的内容 {#what-the-scan-offers}

发现设备先询问代理的名称（`sysName.0`），这也证明代理会响应这些凭据；然后遍历每个勾选的子树和指定的子树，SNMPv2c 和 SNMPv3 用 GETBULK，SNMPv1 用 GETNEXT，直到代理应答到子树之外。进度行计数已读取的内容：“正在遍历 192.168.10.40:161 处的 SNMP 代理：找到 120 个对象。”一次扫描最多列出 2000 个对象、最多遍历 60 秒；在此被截断，或代理中途沉默时，它保留已找到的内容并说明：“对 192.168.10.40:161 的遍历提前停止，找到 2000 个对象。一次扫描最多列出 2000 个对象、最多遍历 60 秒，并在代理不再应答时停止。”

扫描为每个代理提供一个候选项，以其 sysName 命名（代理不给名称时用设备名称或主机），带有读取时使用的版本、超时和重试次数，并把找到的对象作为候选点位：

| 找到的对象 | 点位名称 | 数据类型 | 访问 |
| --- | --- | --- | --- |
| 驱动认识其名称的单个值 | 去掉 `.0` 的名称：`sysUpTime` | 应答的值最自然的读取类型 | MIB 声明可写时为读写，否则为只读 |
| 驱动认识其名称的表格的一列 | 名称加行：`ifInOctets.2` | 同上 | 同上 |
| 设备自有 MIB 中的对象 | 数字：`1.3.6.1.4.1.318.1.1.1.2.2.1.0` | 同上 | 只读 |

任何点位类型都容纳不了的值会被略去。从输入地址的扫描中添加的设备，保留扫描所用的 community，像任何设备凭据一样加密保存；从设备扫描中添加的设备，带上该设备的凭据。本驱动已有设备在读取其代理的候选项（被扫描的设备，或同一主机和端口上的另一台设备）标记为**已经配置过**：它的卡片只列出该设备还没有点位读取的对象（按名称写和按数字写的 OID 算作同一个），**打开**之前的**添加所选点位**把勾选的对象添加到该设备。扫描的其他行为见[设备发现](connector-discovery)。

设备自己的页面不经过驱动页面也能做同样的事：“点位”卡片标题栏里的**发现点位**用这台设备的设置和凭据遍历它的代理，范围是标准子树和它的点位已经在读的厂商分支（有点位在 `enterprises.318` 之下时就是它），只列出它还没有点位的对象（见[发现一台设备的点位](connector-discovery#discovering-a-devices-tags)）。

## 示例：通过 SNMPv2c 读取 UPS {#example-a-ups-over-snmpv2c}

一台带网卡的 UPS 在 192.168.10.40 上以 SNMPv2c 应答，网卡上设置了只读 community。读取它的电池：

1. 在驱动页面点**添加设备**；命名为 `UPS room 1`，在**主机**填写 192.168.10.40，**端口**保持 161。
2. 在 **SNMP 代理**卡片上保持 SNMPv2c，在 **Community** 填写网卡的只读 community，**响应超时（ms）**保持 2000，**重试次数**保持 1。要求填写 community 的那一行随即消失。
3. **添加点位** `Battery status`，**OID** `upsBatteryStatus.0`，**数据类型** Int32。框下面的 MIB 名称显示 upsBatteryStatus.0 和 `UPS-MIB · Integer32 · 只读`，数字 OID 为 1.3.6.1.2.1.33.1.2.1.0，访问保持只读。值为 1 unknown、2 batteryNormal、3 batteryLow 或 4 batteryDepleted。
4. **添加点位** `Battery charge`，**OID** `upsEstimatedChargeRemaining.0`，**数据类型** Int32，**单位** %。
5. 如果 UPS 提供，**添加点位** `Minutes remaining`，**OID** `upsEstimatedMinutesRemaining.0`，**数据类型** Int32，**单位** min。

Base URI 为 `snmp://192.168.10.40:161?version=2c&timeoutMs=2000&retries=1`。智能体用工具做同样的事：

```
device_upsert name="UPS room 1" driver=Snmp host=192.168.10.40 snmpVersion=V2c snmpCommunity="<the UPS card's read community>"
tag_upsert device="UPS room 1" name="Battery status" address=upsBatteryStatus.0 opcUaType=Int32
tag_upsert device="UPS room 1" name="Battery charge" address=upsEstimatedChargeRemaining.0 opcUaType=Int32 unit=%
tag_upsert device="UPS room 1" name="Minutes remaining" address=upsEstimatedMinutesRemaining.0 opcUaType=Int32 unit=min
```

## 示例：通过 SNMPv3 读取交换机的接口计数器 {#example-a-switchs-interface-counters-over-snmpv3}

192.168.10.2 上的一台网管交换机为用户 `scada` 以 SNMPv3 应答，交换机上为它配置了 SHA-256 认证和 AES-128 加密。读取其端口 2 的流量和状态：

1. **添加设备**；命名为 `Core switch`，**主机** 192.168.10.2，**端口** 161。
2. 在 **SNMP 代理**卡片上选择 SNMPv3，在 **SNMPv3 用户**填写 `scada`，保持**签名并加密（authPriv）**，在**认证协议**选择 SHA-256 并填写其密码，**加密协议**保持 AES-128 并填写其密码：都是交换机为 `scada` 保存的密码，至少 8 个字符。
3. **添加点位** `Port 2 in`，**OID** `ifHCInOctets.2`。数据类型变为 UInt64，即 64 位计数器的读取类型，列表只提供 UInt64、Double 和 String；**单位** B。
4. **添加点位** `Port 2 out`，**OID** `ifHCOutOctets.2`，UInt64，**单位** B。
5. **添加点位** `Port 2 state`，**OID** `ifOperStatus.2`，**数据类型** Int32：1 up、2 down、3 testing、7 lowerLayerDown。

`.2` 是交换机上的接口索引，即 `ifName.2` 所命名的那个接口；扫描接口子树会列出每个接口及其索引。Base URI 为 `snmp://192.168.10.2:161?version=3&security=authPriv&auth=sha256&priv=aes128&timeoutMs=2000&retries=1`。使用工具时，对象无法读取的 `opcUaType`（例如 `ifHCOutOctets.2` 上的 Int32）会被拒绝，并给出它可以读取的类型：

```
device_upsert name="Core switch" driver=Snmp host=192.168.10.2 snmpVersion=V3 username=scada password="<authentication password>" snmpSecurityLevel=AuthPriv snmpAuthProtocol=Sha256 snmpPrivacyProtocol=Aes128 snmpPrivacyPassword="<privacy password>"
tag_upsert device="Core switch" name="Port 2 in" address=ifHCInOctets.2 opcUaType=UInt64 unit=B
tag_upsert device="Core switch" name="Port 2 out" address=ifHCOutOctets.2 opcUaType=UInt64 unit=B
tag_upsert device="Core switch" name="Port 2 state" address=ifOperStatus.2 opcUaType=Int32
```

## 智能体工具 {#agent-tools}

`driver_capabilities` 在 `snmp` 下描述本驱动：各版本及其接受的凭据、安全级别和协议、响应超时和重试次数、OID 的写法、按值类型划分的数据类型、驱动认识的每个名称（含 MIB、OID 以及是否可写）、读取与写入的行为、设备发现，以及上面的两个示例。`device_upsert driver=Snmp` 接受 `host`、`port`（默认 161）、`snmpVersion`（`V1`、默认的 `V2c` 或 `V3`）、SNMPv1 和 SNMPv2c 下的 `snmpCommunity`（`''` 清除它），以及 SNMPv3 下的 `username`（用户）、`password`（其认证密码，至少 8 个字符）、`snmpSecurityLevel`（默认的 `AuthPriv`、`AuthNoPriv` 或 `NoAuthNoPriv`）、`snmpAuthProtocol`（`Md5`、默认的 `Sha1`、`Sha256`、`Sha384` 或 `Sha512`）、`snmpPrivacyProtocol`（`Des`、默认的 `Aes128`、`Aes192` 或 `Aes256`）和 `snmpPrivacyPassword`；另有 `responseTimeoutMs`（100 到 60000，默认 2000）和 `snmpRetries`（0 到 5，默认 1）。SNMPv3 设备上的 community，或 community 设备上的 SNMPv3 字段，会被拒绝。工具返回的设备带有 `snmp`：版本、`hasCommunity`、当前的安全级别和协议、`hasPrivacyPassword`、超时、重试次数，以及 `missingCredential`，即它仍在等待的凭据；从不返回任何秘密。`tag_upsert` 以 `address` 接收 OID，`opcUaType` 默认为对象最自然的读取类型；`wireQuantity` 和 `eipPath` 会被拒绝。`device_discover driver=Snmp` 或者遍历 `target`（一个主机，不是 161 时加 `port`），配合 `snmpVersion`（`V1` 或 `V2c`）和 `snmpCommunity`；或者遍历 `snmpDevice`，即本驱动一台设备的名称，这是扫描 SNMPv3 代理的唯一方式；`snmpSubtrees` 选择标准子树（`System`、`Interfaces`、`Ups`、`Sensors`、`HostResources`，用逗号分隔），`snmpSubtree` 再加一个。工具不保留扫描用的 community：根据扫描结果创建的设备在 `device_upsert` 中重新接收它，已配置设备缺少的点位用 `tag_upsert` 添加。其他工具见[智能体工具](agent-tools)。

## 值得了解的限制 {#limits-worth-knowing}

- 发生情况时设备主动发送的 trap 和 inform 不会被接收：每个值都按其轮询间隔读取，所以电量变低的电池要到下一次读取其状态时才会看到。
- 不导入任何 MIB 文件。驱动认识的名称来自它内置的标准 MIB；`enterprises` 下设备自有 MIB 中的对象按数字书写和显示，其含义请查阅厂商文档。
- 工作站本身不是 SNMP 代理：没有任何东西能通过 SNMP 读取工作站本身。
- 扫描只能通过保存了用户和密码的设备访问 SNMPv3 代理，它不会自行尝试任何 community 或用户。
- 一次扫描最多列出 2000 个对象、最多遍历 60 秒：在大型交换机上，请取消勾选不需要的子树，或指定需要的那个。
- 没有 Boolean 点位：TruthValue 或设备自定的开和关都按 Int32 读取。
