snmp v1 get请求响应c++实现

1背景

第一篇文章查了rfc的文档,第二篇用wireshark来抓包,这一篇轮到具体实现了。

2最终效果

2.1 IReasoning MIB Browser 测试

js 复制代码
模拟 localhost,v1,OID 为 .1.3.6.1.2.1.1.5.0 的get请求 (system 下的 sysName)

返回 Name/OID: sysName.0; Value (OctetString): Simple SNMP Agent v1.0

2.2 snmpget 命令行测试

www.net-snmp.org/docs/man/sn...

js 复制代码
$ snmpget -v1 -c public localhost system.sysName
SNMPv2-MIB::sysName.0 = STRING: Simple SNMP Agent v1.0

3 github 代码库

github.com/xcyxiner/sn...

PS:使用了vscode的 c++插件(需要借助vs2019),具体可以看欢迎页的 c++演练

运行后的效果

4 详细步骤

4.1 winsock 教程还是直接抄微软官方文档就行,这个基本都一样。

learn.microsoft.com/zh-cn/windo...

主要步骤

markdown 复制代码
-   [为服务器](https://learn.microsoft.com/zh-cn/windows/win32/winsock/creating-a-socket-for-the-server) 创建套接字
-   [绑定套接字](https://learn.microsoft.com/zh-cn/windows/win32/winsock/binding-a-socket)
-   [侦听套接字](https://learn.microsoft.com/zh-cn/windows/win32/winsock/listening-on-a-socket)
-   [接受连接](https://learn.microsoft.com/zh-cn/windows/win32/winsock/accepting-a-connection)
-   [在服务器上接收和发送数据](https://learn.microsoft.com/zh-cn/windows/win32/winsock/receiving-and-sending-data-on-the-server)
-   [断开服务器](https://learn.microsoft.com/zh-cn/windows/win32/winsock/disconnecting-the-server) 的连接
c++ 复制代码
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <WinSock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
#include <string.h>

#pragma comment(lib, "ws2_32.lib")

#define DEFAULT_PORT "161"
#define DEFAULT_IP "127.0.0.1"
#define DEFAULT_COMMUNITY "public"

#define RECV_BUFFER_SIZE 1024
#define SEND_BUFFER_SIZE 1024

int main(void)
{
    WSADATA wsaData;
    SOCKET sockfd;
    struct addrinfo hints, *servinfo;
    int rv;

    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
        std::cout << "Error in WSAStartup" << WSAGetLastError() << std::endl;
        return 1;
    }

    memset(&hints, 0, sizeof(hints));
    hints.ai_family = AF_INET;
    hints.ai_socktype = SOCK_DGRAM;
    hints.ai_flags = AI_PASSIVE;

    if ((rv = getaddrinfo(NULL, DEFAULT_PORT, &hints, &servinfo)) != 0)
    {
        fprintf(stderr, "getaddrinfo:%d\n", rv);
        WSACleanup();
        return 1;
    }

    if ((sockfd = socket(servinfo->ai_family, servinfo->ai_socktype, servinfo->ai_protocol)) == INVALID_SOCKET)
    {
        std::cout << "Error in socket" << WSAGetLastError() << std::endl;
        freeaddrinfo(servinfo);
        WSACleanup();
        return 1;
    }

    if (bind(sockfd, servinfo->ai_addr, (int)servinfo->ai_addrlen) == SOCKET_ERROR)
    {

        std::cout << "Error in bind" << WSAGetLastError() << std::endl;
        closesocket(sockfd);
        freeaddrinfo(servinfo);
        WSACleanup();
        return 1;
    }

    freeaddrinfo(servinfo);

    std::cout << "Waiting for a client..." << std::endl;

    struct sockaddr_storage client_addr;
    int addr_len;
    unsigned char recv_buffer[RECV_BUFFER_SIZE];
    unsigned char send_buffer[SEND_BUFFER_SIZE];

    while (1)
    {
        addr_len = sizeof(client_addr);
        int numbytes = recvfrom(sockfd, (char *)recv_buffer, RECV_BUFFER_SIZE, 0, (struct sockaddr *)&client_addr, &addr_len);

        if (numbytes == SOCKET_ERROR)
        {
            std::cout << "Error in recvfrom" << WSAGetLastError() << std::endl;
            continue;
        }

        int resp_len = build_snpm_response(recv_buffer, numbytes, send_buffer);
        if (resp_len > 0)
        {
            if (sendto(sockfd, (char *)send_buffer, resp_len, 0, (struct sockaddr *)&client_addr, addr_len) == SOCKET_ERROR)
            {
                std::cout << "Error in sendTo" << WSAGetLastError() << std::endl;
            }
        }
    }

    closesocket(sockfd);
    WSACleanup();

    return 0;
}

4.2 重点解析 build_snpm_response

4.2.1 从wireshark上看完整的接收数据如下所示

4.2.2 按照字节填写对应的response

这里第一个是十六进制的30,这个翻了下rfc没找到,然后找到一点ASN1的资料。

learn.microsoft.com/en-us/windo...

从下面的图片能看到这里0x30是一个固定标签编码

所以在代码里我记录了下用到的几个基础类型

arduino 复制代码
// 跨协议标准
#define ASN1_SEQUENCE 0x30
#define ASN1_INTEGER 0x02
#define ASN1_OCTET_STRING 0x04
#define ASN1_OBJECT_ID 0x06

按这个套路,可以走到下图的data前

30 29 02 01 00 04 06 70 75 62 6c 69 63 分别标记 TLV

scss 复制代码
30(SEQUENCE) 29(字节长度)
                      02(INTEGER) 01(字节长度) 00(版本号-1)
                      
                                                       04(OCTET_STRING) 06(字节长度)  70 75 62 6c 69 63 (public)                                 

对应的代码如下

c++ 复制代码
    unsigned char *ptr = response;
    int total_len = 0;

    *ptr++ = ASN1_SEQUENCE;
    unsigned char *len_ptr = ptr++;

    // version        -- version-1 for this RFC
    //                          INTEGER {
    //                              version-1(0)
    //                          },
    *ptr++ = ASN1_INTEGER;
    *ptr++ = 0x01;
    *ptr++ = SNMP_VERSION_1;

    // community      -- community name
    // OCTET STRING,
    *ptr++ = ASN1_OCTET_STRING;
    *ptr++ = (unsigned char)strlen(DEFAULT_COMMUNITY);
    memcpy(ptr, DEFAULT_COMMUNITY, strlen(DEFAULT_COMMUNITY));
    ptr += strlen(DEFAULT_COMMUNITY);

    // data skip

4.2.3 处理pdu

从前面的wireshark上能看到pdu开头的字节是A0,对应的get请求,但是怎么来的,从4.2.2中的微软的文档没找到来源,又找到另外两篇资料

letsencrypt.org/docs/a-warm...

luca.ntop.org/Teaching/Ap...

第一篇链接里有如下的资料,前面4.2.2用的其实是Universal(所以前面定义的时候 我补了一个 跨协议标准,pdu实际上用了一个 上下文的标签)

第一篇用谷歌翻译后的截图如下,再差下之前的rfc协议,能看到没有APPLICATION限定词,那就是上下文了

上面用了8和7位,按二进制10来计算实际的值如下,最终值为80。

然后是第6位,PDU查看RFC,是 SEQUENCE,并且有IMPLICIT,构造位这里也是1

综合上面8和7位,以及6位,最终的值如下所示,最终值A0,这个也是wireshark中看到的标签。

按上面的规则,我在代码声明的时候补充了如下,剩下的5位是索引,这个没啥好解释的,直接抄就行

c++ 复制代码
// 临时字段标识 7-8
#define ASN1_CONTEXT_SPECIFIC (0B10 << 6)
// 结构化 6
#define ASN1_CONSTRUCTED (0B01 << 5)

#define SNMP_VERSION_1 0
// GetRequest-PDU ::=
// [0]
//     IMPLICIT PDU
#define SNMP_GET_REQUEST ASN1_CONTEXT_SPECIFIC + ASN1_CONSTRUCTED + 0

// GetResponse-PDU ::=
//               [2]
//                   IMPLICIT PDU
#define SNMP_GET_RESPONSE ASN1_CONTEXT_SPECIFIC + ASN1_CONSTRUCTED + 2

下面的 SNMP_GET_RESPONSE 就是 0xA2,(wireshark索引是get的,之后还有next,然后才是response)

ini 复制代码
    // PDU
    *ptr++ = SNMP_GET_RESPONSE;
    unsigned char *pdu_len_ptr = ptr++;

上面的处理完后,就是之前默认的ASAN1的标签了

4.2.4 PDU下的request_id

为啥提这个,因为 IReasoning MIB Browser 是4个字节长度,而 snmpget 是两个字节的长度,因为时间来不及处理,就直接读取了原有的get请求下的长度,然后再拷贝. 正常应该有个解析专门处理request的,但是没来得及处理

c++ 复制代码
    // request id
    *ptr++ = ASN1_INTEGER;
    if(request[16] == 0x02){
        *ptr++ = 0x02;
        *ptr++ = request[17];
        *ptr++ = request[18];
    }
    if(request[16] == 0x04){
        *ptr++ = 0x04;
        *ptr++ = request[17];
        *ptr++ = request[18];
        *ptr++ = request[19];
        *ptr++ = request[20];
    }

4.2.5 PDU下的 OID

OID有一套字节的规则,RFC里提到的MIB就有。转换和查询的还没来及检查,先随机绑定一个字符串回去。

c++ 复制代码
 //  variable-bindings -- values are sometimes ignored
    //      VarBindList

    //     -- variable bindings

    //     VarBind ::=
    //             SEQUENCE {
    //                 name
    //                     ObjectName,

    //                 value
    //                     ObjectSyntax
    //             }

    //    VarBindList ::=
    //             SEQUENCE OF
    //                VarBind

    *ptr++ = ASN1_SEQUENCE;
    unsigned char *var_len_ptr = ptr++;

    // 单个变量绑定
    *ptr++ = ASN1_SEQUENCE; // SEQUENCE
    unsigned char *item_len_ptr = ptr++; // 项长度

    // OID
    *ptr++ = ASN1_OBJECT_ID;
    *ptr++ = sizeof(sysDescrOID);
    memcpy(ptr, sysDescrOID, sizeof(sysDescrOID));
    ptr += sizeof(sysDescrOID);

    // 值
    *ptr++ = 0x04; // OCTET STRING
    *ptr++ = (unsigned char)strlen(sysDescr);
    memcpy(ptr, sysDescr, strlen(sysDescr));
    ptr += strlen(sysDescr);

4.2.6 测试结果见前文,这里不补充了

相关推荐
jyan_敬言4 分钟前
【C++】string类(二)相关接口介绍及其使用
android·开发语言·c++·青少年编程·visual studio
liulilittle28 分钟前
SNIProxy 轻量级匿名CDN代理架构与实现
开发语言·网络·c++·网关·架构·cdn·通信
tan77º1 小时前
【Linux网络编程】Socket - UDP
linux·服务器·网络·c++·udp
GiraKoo2 小时前
【GiraKoo】C++14的新特性
c++
悠悠小茉莉2 小时前
Win11 安装 Visual Studio(保姆教程 - 更新至2025.07)
c++·ide·vscode·python·visualstudio·visual studio
坏柠2 小时前
C++ Qt 基础教程:信号与槽机制详解及 QPushButton 实战
c++·qt
泽02022 小时前
C++之红黑树认识与实现
java·c++·rpc
岁忧3 小时前
(LeetCode 每日一题) 1865. 找出和为指定值的下标对 (哈希表)
java·c++·算法·leetcode·go·散列表
whoarethenext3 小时前
使用 C++ 实现 MFCC 特征提取与说话人识别系统
开发语言·c++·语音识别·mfcc
R-G-B3 小时前
【MFC】Combobox下拉框中4个选项,运行后点击下拉框选项不能全部展示出来,只能显示2个选项,需要垂直滚动条滚动显示其余选项
c++·mfc