全部例程

SNMP

W5500 其他标签

2025/02/12 更新

本篇文章,我们将详细介绍如何在W5500芯片上面实现SNMP功能。并通过实战例程,为大家讲解如何使用MIB Browser管理W5500。

该例程用到的其他网络协议,例如DHCP, 请参考相关章节。有关 W5500 的初始化过程,请参考Network install,这里将不再赘述。

SNMP协议简介

SNMP(Simple Network Management Protocol,简单网络管理协议)是一种用于管理和监控网络设备的协议。它是应用层协议,广泛应用于网络设备(如路由器、交换机、服务器、打印机等)的管理和监控。 SNMP提供了机制以便网络管理员可以监控网络性能、发现网络问题,并对设备进行管理。

SNMP协议特点

  • 简单性::设计轻量,便于实现和部署。
  • 互操作性:提供标准化的设备管理方式,不同厂商的设备可以通过SNMP实现互通
  • 跨平台支持:SNMP是一种开放的标准协议,被广泛应用于各种网络设备和操作系统。。
  • 实时监控:支持快速的数据采集和报警机制。
  • 扩展性:通过MIB支持不同设备的特定功能。
  • 资源效率:协议设计轻量,适合低带宽和高延迟的网络环境。

应用场景

接下来,我们了解下在W5500上,可以使用SNMP协议完成哪些操作及应用呢?

  • 故障报警和日志管理:设备在检测到异常或故障时,可以通过 SNMP TRAP 将报警信息发送到网络管理系统。
  • 工业自动化和环境监控:在工业自动化和环境监控中,W5500 可使用 SNMP 汇报传感器数据。
  • 数据中心和机房管理:用于数据中心和机房中监控服务器、交换机及其他网络设备的状态。

使用MIB Browser管理W5500流程

  1. 下载并安装MIB Browser链接:https://www.ireasoning.com/download/mibfree/setup.exe
  2. 创建分支
  3. 打开安装目录下 mibs 文件夹,找到 RFC1213-MIB 文件,右键使用记事本打开,上方路径栏可以寻找路径。

    在snmp后添加一个新分支,命名为User

  4. 添加叶子节点(功能)
  5. 在分支后方继续添加功能代码

    叶子节点格式如下:

    setLED OBJECT-TYPE                        //添加一个新叶子节点,命名为 setLED
    SYNTAX INTEGER { enabled(1),disabled(0) } //设置数据类型
    ACCESS read-write                         //设置读写权限
    DESCRIPTION                               //注释
    ::= { User 1 }                            //叶子所在分支,与叶子编号

    添加完毕后保存并退出

    添加完成后,MIB Browser软件如下所示:

  6. 代码适配功能
  7. 测试

SNMP架构组成

SNMP架构包括以下几个主要部分:

  • 管理站(Manager):运行SNMP管理软件,用于发送请求和接收设备信息。通过命令操作网络设备,执行配置或监控任务。
  • 代理(Agent): 安装在受管理设备上的软件,负责将设备的状态信息存储在MIB中,并响应来自管理站的查询。能动地发送陷阱(Trap)以报告事件。
  • 管理信息库(MIB):定义设备的可管理参数及其数据结构MIB通常以树状结构组织,每个节点代表一个管理对象,具有唯一的对象标识符(OID)。
  • 协议(SNMP协议本身):负责管理站和代理之间的通信,支持基本操作如获取、设置和通知。

OID详解

简单网络管理协议(SNMP)的OID(对象标识符)是一个用于唯一标识网络设备上管理信息库(MIB)中对象的标识符。 OID是一种分层的命名方案,允许管理员查询和设置网络设备上的各种参数。

下面是OID的详解:

OID结构

OID由一系列整数组成,这些整数通过点号(.)分隔,形成了一个层次结构。例如:

1.3.6.1.2.1.1.1.0

在这个结构中,每个数字都代表一个组织或节点在层次结构中的位置。

OID层次解释

1:表示ISO(国际标准化组织)

3:表示org(组织)

6:指定IETF(互联网工程任务组)作为组织

1:表示IETF管理的MIB(管理信息库)

后续的数字则进一步定义了特定的MIB模块、对象类型、实例等信息。

常见的OID前缀

1.3.6.1.2.1:这是最常用的OID前缀,通常简写为.iso.org.dod.internet.mgmt.mib-2,它指的是IETF定义的MIB-2。

具体OID示例

1.3.6.1.2.1.1.1.0:这是sysDescr的OID,用于获取系统描述。

1.3.6.1.2.1.1.2.0:这是sysObjectID的OID,用于获取系统对象标识符。

1.3.6.1.2.1.1.3.0:这是sysUpTime的OID,用于获取系统正常运行时间。

如何使用OID

查询:使用SNMP GET请求,可以查询特定OID的值。

设置:使用SNMP SET请求,可以修改特定OID的值(需要设备支持)。

遍历:使用SNMP WALK请求,可以遍历一个OID下的所有子节点。

请注意:

OID的具体值和结构可能会随着网络设备的不同而有所不同。在使用OID之前,最好查阅相应设备的MIB文档,以了解其支持的具体OID及其功能。OID是SNMP管理中不可或缺的部分,通过OID, 网络管理员可以进行监控网络状态、配置网络设备、接收警报通知等操作。理解和掌握OID对于网络管理和故障排除非常重要。

SNMP报文格式

SNMP 报文基于ASN.1(Abstract Syntax Notation One)编码规则,通常使用BER(Basic Encoding Rules)进行传输。以下是SNMP报文的基本格式和关键字段,本例程中使用的是SNMPv1版本,以下对SNMPv1进行讲解, 而对于其他版本的报文格式,由于篇幅有限,这里将不再进行讲解,感兴趣的朋友可自行查阅资料进行学习。

SNMP报文结构

报文字段详解

Version(版本号)

此字段定义 SNMP 协议的版本号,用一个整数表示:

  • 0 表示 SNMPv1。
  • 1 表示 SNMPv2c。
  • 3 表示 SNMPv3。

该字段是解析报文的基础,不同版本的报文格式存在差异。

Community(社区字符串)

社区字符串是 SNMPv1 和 SNMPv2c 的一种简单身份验证机制,用于限制对管理对象的访问权限。 它是一个字符串(Octet String),常见的值包括:

  • public:表示只读访问权限。
  • private:表示读写访问权限。

PDU(协议数据单元)类型

PDU 定义了操作类型,通过一个标记值(Tag, Context-Specific)来区分:

  • 0xA0:GET 请求,用于获取管理对象的值。
  • 0xA1:GET-NEXT 请求,用于获取下一个对象的值。
  • 0xA3:SET 请求,用于设置管理对象的值。
  • 0xA4:TRAP,用于代理向管理站报告事件。

Request ID(请求 ID)

请求 ID 是一个整数,用于唯一标识一个请求。它由发起方生成,并在响应中携带相同的 ID。 通过此字段,接收方可以将响应与对应的请求进行匹配。如果响应中的请求 ID 不一致,则表明响应与请求无关。

Error Status(错误状态)

此字段用整数表示请求的执行状态:

  • 0:noError,表示请求执行成功,没有错误。
  • 1:tooBig,请求的响应数据超出接收方的最大允许大小。
  • 2:noSuchName,请求中指定的 OID 不存在。
  • 3:badValue,请求中的值非法或不支持。
  • 4:readOnly,请求试图修改只读对象。
  • 5:genErr,发生了通用错误,具体原因未明确。

Error Index(错误索引)

如果 Error Status 的值不为 0,此字段指示变量绑定列表中出错的变量位置(从 1 开始计数)。如果没有错误,此字段的值为 0。

5Variable Bindings(变量绑定列表)

变量绑定列表是 SNMP 报文的核心部分,包含一个或多个变量绑定(VarBind)。每个变量绑定由以下两部分组成:

OID(对象标识符)

OID 是管理对象的唯一标识符,用点分十进制表示。例如:

  • 1.3.6.1.2.1.1.1.0:表示 sysDescr,系统描述。
  • 1.3.6.1.2.1.1.5.0:表示 sysName,系统名称。

Value(值)

OID 的值根据请求类型不同可能为以下几种:

  • 在 GET 请求中,值通常为空(Null)。
  • 在 GET-RESPONSE 报文中,值为具体数据,例如整型(Integer32)、字符串(Octet String)等。
  • 在 SET 请求中,值是要设置的新值。

SNMP报文解析

MIB Browser向W5500发送LED设置报文:

| 报文原文 |
30 2a 02 01 00 04 06 70 75 62 6c 69 63 a3 1d 02 04 58 2d f9 6c 02 01 00 02 01 00 30 0f 30 0d 06 08 2b 06 01 02 01 0c 01 00 02 01 01

| 报文解析 |
Simple Network Management Protocol	(简单网络管理协议(SNMP 报文))
    version: version-1 (0)	        (使用的 SNMP 协议版本,值为 0 表示 SNMPv1)
    community: public	            (社区字符串为 "public",通常表示只读权限)
    data: set-request (3)	        (PDU 类型为 SET-REQUEST,表示设置管理对象的值)
        set-request
            request-id: 1479407980	 (请求的唯一标识符,用于匹配请求和响应)
            error-status: noError (0)(错误状态为 noError (0),表示没有错误发生)
            error-index: 0	         (错误索引为 0,表示变量绑定列表中没有错误对象)
            variable-bindings: 1 item	(变量绑定列表中包含 1 个变量)
                1.3.6.1.2.1.12.1.0: 1	    (OID 为 1.3.6.1.2.1.12.1.0,设置的值为 1)
                    Object Name: 1.3.6.1.2.1.12.1.0 (iso.3.6.1.2.1.12.1.0)		(OID 对应的完整名称)
                    Value (Integer32): 1	(设置的值为整数类型,值为 1)
                    [Response In: 237]	    (表示该请求的响应报文序号为 237)

W5500响应报文:

| 报文原文 |
30 2a 02 01 00 04 06 70 75 62 6c 69 63 a2 1d 02 04 58 2d f9 6c 02 01 00 02 01 00 30 0f 30 0d 06 08 2b 06 01 02 01 0c 01 00 02 01 01

| 报文解析 |
Simple Network Management Protocol
    version: version-1 (0)	                            (版本号:SNMPv1(值为 0))
    community: public	                                (社区字符串:"public",用于身份验证)
    data: get-response (2)	                                                
        get-response	(GET-RESPONSE 数据部分)          (数据类型:GET-RESPONSE(值为 2),表示这是一个响应报文)
            request-id: 1479407980	                    (请求 ID:1479407980,用于匹配请求和响应)                                           
            error-status: noError (0)	                (错误状态:noError(值为 0),表示没有错误)       
            error-index: 0	                            (错误索引:0,表示没有发生错误的变量索引)                                     
            variable-bindings: 1 item	                (变量绑定:包含 1 个变量)             
                1.3.6.1.2.1.12.1.0: 1                  	(变量绑定内容)
                    Object Name: 1.3.6.1.2.1.12.1.0 (iso.3.6.1.2.1.12.1.0)		(对象标识符(OID))
                    Value (Integer32): 1	(OID 对应的值,类型为 Integer32,值为 1)
    [Response To: 236]	(对请求 ID 236 的响应)
    [Time: 0.023844000 seconds]	(响应耗时:0.023844 秒)


实现过程

请注意:

测试实例需要PC端和W5500处于同一网段。

接下来,我们看看如何在代码上适配MIB Browser上添加的功能。

步骤1:初始化LED并注册到SNMP中


user_led_init();
user_led_control_init(get_user_led_status, set_user_led_status);

步骤2:注册snmp定时器


/**
* @brief   1ms timer IRQ Handler
* @param   none
* @return  none
*/
void TIM3_IRQHandler(void)
{
    static uint32_t tim3_1ms_count = 0;
    static uint8_t tim3_10ms_count = 0;
    if (TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET)
    {
        tim3_1ms_count++;
        tim3_10ms_count++;
        if (tim3_1ms_count >= 1000)
        {
            DHCP_time_handler();
            
            tim3_1ms_count = 0;
        }
        if(tim3_10ms_count>=10)
        {
            SNMP_time_handler();
            tim3_10ms_count=0;
        }
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

我们需要10ms调用一次SNMP_time_handler()函数,方便SNMP进行超时处理。

步骤3:添加功能函数

在snmp_custom.c文件中的snmpData结构体变量中,添加功能函数:


dataEntryType snmpData[] =
{
    // System MIB
    // SysDescr Entry
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 1, 0}, SNMPDTYPE_OCTET_STRING,   30,               
{"WIZnet Embedded SNMP Agent"},                  NULL,                  NULL},
    // SysObjectID Entry
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 2, 0},       SNMPDTYPE_OBJ_ID,    8,         
{"\x2b\x06\x01\x02\x01\x01\x02\x00"},                  NULL,                  NULL},
    // SysUptime Entry
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 3, 0},   SNMPDTYPE_TIME_TICKS,    0,                               
{""},         currentUptime,                  NULL},
    // sysContact Entry
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 4, 0}, SNMPDTYPE_OCTET_STRING,   30,             
{"http://www.wizwiki.net/forum"},                  NULL,                  NULL},
    // sysName Entry
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 5, 0}, SNMPDTYPE_OCTET_STRING,   30,                  
{"http://www.wiznet.co.kr"},                  NULL,                  NULL},
    // Location Entry
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 6, 0}, SNMPDTYPE_OCTET_STRING,   30,                         
{"4F Humax Village"},                  NULL,                  NULL},
    // SysServices
    {   8,                                  {0x2b, 6, 1, 2, 1, 1, 7, 0},      SNMPDTYPE_INTEGER,    4,                               
{""},                  NULL,                  NULL},
    {   8,                                  {0x2b, 6, 1, 2, 1, 12, 2, 0}, SNMPDTYPE_OCTET_STRING,   30,                              
{""}, get_LEDStatus_UserLED,                  NULL},
    {   8,                                  {0x2b, 6, 1, 2, 1, 12, 1, 0},      SNMPDTYPE_INTEGER,    4,                              
{""},                  NULL, set_LEDStatus_UserLED},
    // OID Test #1 (long-length OID example, 19865)
    {0x0a, {0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0x9b, 0x19, 0x01, 0x00}, SNMPDTYPE_OCTET_STRING,   30,                  
{"long-length OID Test #1"},                  NULL,                  NULL},
    // OID Test #2 (long-length OID example, 22210)
    {0x0a, {0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0xad, 0x42, 0x01, 0x00}, SNMPDTYPE_OCTET_STRING,   35,                  
{"long-length OID Test #2"},                  NULL,                  NULL},
    // OID Test #2: SysObjectID Entry
    {0x0a, {0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0xad, 0x42, 0x02, 0x00},       SNMPDTYPE_OBJ_ID, 0x0a, 
{"\x2b\x06\x01\x04\x01\x81\xad\x42\x02\x00"},                  NULL,                  NULL},
      };

结构体变量snmpData 的结构体dataEntryType定义如下所示:


typedef struct {
  uint8_t oidlen;
  uint8_t oid[MAX_OID];
  uint8_t dataType;
  uint8_t dataLen;
  union {
    uint8_t octetstring[MAX_STRING];
    uint32_t intval;
  } u;
  void (*getfunction)(void *, uint8_t *);
  void (*setfunction)(int32_t);
} dataEntryType;

步骤4:初始化snmp协议


void snmpd_init(uint8_t * managerIP, uint8_t * agentIP, uint8_t sn_agent, uint8_t sn_trap)
{
#ifdef _SNMP_DEBUG_
  printf("\r\n - SNMP : Start SNMP Agent Daemon\r\n");
#endif
  SOCK_SNMP_AGENT = sn_agent;
  SOCK_SNMP_TRAP = sn_trap;
  if((SOCK_SNMP_AGENT > _WIZCHIP_SOCK_NUM_) || (SOCK_SNMP_TRAP > _WIZCHIP_SOCK_NUM_)) return;
  startTime = getSNMPTimeTick(); // Start time (unit: 10ms)
  initTable(); // Settings for OID entry values
  
  initial_Trap(managerIP, agentIP);
/*
  // Example Codes for SNMP Trap
  {
  dataEntryType enterprise_oid = {0x0a, {0x2b, 0x06, 0x01, 0x04, 0x01, 0x81, 0x9b, 0x19, 0x01,  0x00},
                        
SNMPDTYPE_OBJ_ID, 0x0a, {"\x2b\x06\x01\x04\x01\x81\x9b\x19\x10\x00"},	NULL, NULL};
  dataEntryType trap_oid1 = {8, {0x2b, 6, 1, 4, 1, 0, 11, 0}, SNMPDTYPE_OCTET_STRING, 30, {""},      
NULL, NULL};
  dataEntryType trap_oid2 = {8, {0x2b, 6, 1, 4, 1, 0, 12, 0}, SNMPDTYPE_INTEGER, 4, {""}, NULL,  NULL};
  strcpy((char *)trap_oid1.u.octetstring, "Alert!!!"); 	// String added
  trap_oid2.u.intval = 123456;	// Integer value added
  // Generic Trap: warmStart
  snmp_sendTrap((void *)"192.168.0.214", (void *)"192.168.0.112", (void *)"public",        
enterprise_oid, SNMPTRAP_WARMSTART, 0, 0);
  // Enterprise-Specific Trap
  snmp_sendTrap((void *)"192.168.0.214", (void *)"192.168.0.112", (void *)"public",   enterprise_oid, 
6,  0, 2, &trap_oid1, &trap_oid2);
}
*/
}

这一步,主要是将使用的socket号,管理IP地址,请求IP地址等参数注册进去,并且记录开始时间。如果想使用Trap主动上报,可以参考注释中的示例代码。

步骤5:在主循环中运行snmpd_run()函数

snmpd run()函数代码如下:


int32_t snmpd_run(void)
{
    int32_t ret;
  int32_t len = 0;
    
  uint8_t svr_addr[6];
  uint16_t  svr_port;
  if(SOCK_SNMP_AGENT > _WIZCHIP_SOCK_NUM_) return -99;
    
  switch(getSn_SR(SOCK_SNMP_AGENT))
  {
    case SOCK_UDP :
      if ( (len = getSn_RX_RSR(SOCK_SNMP_AGENT)) > 0)
      {
        request_msg.len= recvfrom(SOCK_SNMP_AGENT, request_msg.buffer, len, svr_addr, 
&svr_port);
      }
      else
      {
        request_msg.len = 0;
      }
      if (request_msg.len > 0)
      {
#ifdef _SNMP_DEBUG_
        dumpCode((void *)"\r\n[Request]\r\n", (void *)"\r\n", request_msg.buffer, 
request_msg.len);
  #endif
        // Initialize
        request_msg.index = 0;
        response_msg.index = 0;
        errorStatus = errorIndex = 0;
        memset(response_msg.buffer, 0x00, MAX_SNMPMSG_LEN);
        // Received message parsing and send response process
        if (parseSNMPMessage() != -1)
        {
          sendto(SOCK_SNMP_AGENT, response_msg.buffer, response_msg.index,  svr_addr, 
svr_port);
        }
  #ifdef _SNMP_DEBUG_
        dumpCode((void *)"\r\n[Response]\r\n", (void *)"\r\n", response_msg.buffer, 
response_msg.index);
    #endif
      }
      break;
    case SOCK_CLOSED :
      if((ret = socket(SOCK_SNMP_AGENT, Sn_MR_UDP, PORT_SNMP_AGENT, 0x00)) != SOCK_SNMP_AGENT)
        return ret;
    #ifdef _SNMP_DEBUG_
      printf(" - [%d] UDP Socket for SNMP Agent, port [%d]\r\n", SOCK_SNMP_AGENT,   
PORT_SNMP_AGENT);
    #endif
      break;
    default :
      break;
  }
  return 1;
}

snmpd_run()函数会执行一个UDP状态机,当收到SNMP管理的消息后会执行解析以及回复操作。

运行结果

烧录例程运行后,首先可以看到打印了PHY链路检测和DHCP获取网络信息,然后是运行SNMP程序:

打开MIB Borwser,输入W5500的地址,然后依次点击system分支下的各个节点,获取到的结果与代码定义相同:

再找到User分支下的setLED 指令,右键指令,点击 set,类型选择 integer,Value 填 1,点击 OK:

getLED 指令,右键指令,点击 get,即可读出 LED 状态:

总结

本文讲解了如何在 W5500 芯片上实现 SNMP 功能,通过实战例程展示了使用 MIB Browser 管理 W5500 的具体过程,涵盖在 MIB Browser 中创建分支、添加叶子节点,以及在代码中适配功能等关键步骤。文章详细介绍了 SNMP 协议的概念、特点、应用场景、架构组成、OID 详解和报文格式,帮助读者理解其在网络设备管理和监控中的重要作用。

下一篇文章将讲解如何使用W5500的IPRAW模式实现PING操作,敬请期待!

下载本章例程

我们提供完整的工程文件以及配套开发板,方便你随时测试,快速完成产品开发:

开发环境: Keil MDK5 配套开发板