SNMP代理案例

本文最后更新于 2025年6月11日 下午

SNMP代理案例

背景

在某些网络环境中,直接访问目标设备的 SNMP 服务可能受到限制,例如由于网络隔离、防火墙限制或设备配置问题。为了绕过这些限制,可以使用 SNMP 代理服务器。代理服务器接收来自客户端的 SNMP 请求,然后将请求转发到目标设备,并将响应返回给客户端。这种方式可以实现对目标设备的间接访问和管理。

依赖

以下是运行此 SNMP 代理程序所需的依赖项:

  1. Python:程序基于 Python 编写,需要 Python 环境(建议使用 Python 3.7 及以上版本)。
  2. asyncudp:用于异步 UDP 通信,可通过 pip install asyncudp 安装。
  3. pysnmp:用于处理 SNMP 请求和响应,可通过 pip install pysnmp 安装。
  4. logging:Python 内置的日志模块,用于记录程序运行日志。

原理

客户端

客户端程序(snmp_proxy_client.py)通过 UDP 协议向代理服务器发送 SNMP 请求。请求内容包括目标设备的 IP 地址、端口号、社区字符串和 OID。代理服务器接收到请求后,会根据这些信息向目标设备发起 SNMP 查询,并将查询结果返回给客户端。

服务端

服务端程序(snmp_proxy_server.py)运行在代理服务器上,监听指定的 UDP 端口(默认为 161)。当接收到客户端的请求时,服务端会解析请求内容,使用 pysnmp 库向目标设备发起 SNMP 查询,并将查询结果返回给客户端。服务端还配置了日志记录功能,用于记录请求和响应信息,便于调试和监控。

代码

客户端代码:snmp_proxy_client.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import socket
import sys

PROXY_SERVER = ("192.168.1.2", 161)

def query_snmp(ip, port, community, oid):
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(10)
msg = f"{ip}|{port}|{community}|{oid}".encode('utf-8')
s.sendto(msg, PROXY_SERVER)
response, _ = s.recvfrom(65536)
return response.decode('utf-8')

if __name__ == "__main__":
ip = "192.168.1.3"
port = 161
community = "public"
oid = "1.3.6.1.2.1.1.5.0"

result = query_snmp(ip, port, community, oid)
print("Response:", result)

服务端代码:snmp_proxy_server.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
import asyncudp
import asyncio
import logging
from logging.handlers import RotatingFileHandler
from pysnmp.hlapi.asyncio import *

# 配置日志
def setup_logger():
logger = logging.getLogger('snmp_proxy')
logger.setLevel(logging.INFO)

# 创建文件处理器,最大5MB,保留2个备份
file_handler = RotatingFileHandler(
'snmp_proxy.log',
maxBytes=5*1024*1024, # 5MB
backupCount=2
)
file_handler.setLevel(logging.INFO)

# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)

# 定义日志格式
formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
file_handler.setFormatter(formatter)
console_handler.setFormatter(formatter)

# 添加处理器
logger.addHandler(file_handler)
logger.addHandler(console_handler)

return logger

logger = setup_logger()
LOCAL_ADDR = ('0.0.0.0', 161)

async def snmp_get(target_ip, port, community, oid):
try:
logger.info(f"向目标设备 {target_ip}:{port} 发送SNMP请求, OID: {oid}")
snmp_engine = SnmpEngine()

transport_target = await UdpTransportTarget.create(
(target_ip, port),
timeout=5.0,
retries=3
)

iterator = get_cmd(
snmp_engine,
CommunityData(community, mpModel=1), # SNMP v2c
transport_target,
ContextData(),
ObjectType(ObjectIdentity(oid))
)

error_indication, error_status, error_index, var_binds = await iterator

if error_indication:
error_msg = f"SNMP请求错误: {error_indication}"
logger.error(error_msg)
return f"ERROR: {error_indication}"
elif error_status:
error_msg = f"SNMP状态错误: {error_status.prettyPrint()}"
logger.error(error_msg)
return f"ERROR: {error_status.prettyPrint()}"
else:
for var_bind in var_binds:
result = var_bind[1].prettyPrint()
logger.info(f"从目标设备 {target_ip}:{port} 收到响应: {result}")
return result
except Exception as e:
error_msg = f"SNMP请求异常: {str(e)}"
logger.error(error_msg)
return f"EXCEPTION: {str(e)}"
finally:
snmp_engine.close_dispatcher()

async def proxy_handler(sock, addr, data):
client_ip, client_port = addr
logger.info(f"接收到来自客户端 {client_ip}:{client_port} 的请求")

try:
decoded = data.decode('utf-8').strip()
parts = decoded.split('|', 3)
if len(parts) != 4:
error_msg = f"来自客户端 {client_ip}:{client_port} 的请求格式错误"
logger.error(error_msg)
sock.sendto(b"ERROR: INVALID FORMAT", addr)
return

target_ip, port_str, community, oid = parts
try:
port = int(port_str)
except ValueError:
error_msg = f"来自客户端 {client_ip}:{client_port} 的端口号无效"
logger.error(error_msg)
sock.sendto(b"ERROR: INVALID PORT", addr)
return

logger.info(f"转发请求到 {target_ip}:{port} | OID: {oid} | 社区: {community}")
result = await snmp_get(target_ip, port, community, oid)

logger.info(f"向客户端 {client_ip}:{client_port} 发送响应")
sock.sendto(result.encode('utf-8'), addr)
except Exception as e:
error_msg = f"处理客户端 {client_ip}:{client_port} 请求时发生内部错误: {str(e)}"
logger.error(error_msg)
sock.sendto(b"ERROR: INTERNAL SERVER ERROR", addr)

async def run_proxy():
sock = await asyncudp.create_socket(local_addr=LOCAL_ADDR)
logger.info(f"SNMP代理服务器已启动,监听地址: {LOCAL_ADDR}")

while True:
data, addr = await sock.recvfrom()
asyncio.create_task(proxy_handler(sock, addr, data))

if __name__ == "__main__":
asyncio.run(run_proxy())

优点

  1. 灵活性:客户端可以通过代理服务器访问任何目标设备,无需直接连接到目标设备。
  2. 安全性:代理服务器可以作为中间层,隐藏目标设备的网络拓扑结构,增强安全性。
  3. 可扩展性:代理服务器可以处理多个客户端的请求,支持大规模部署。
  4. 日志记录:服务端配置了详细的日志记录功能,便于监控和调试。

缺点

  1. 性能开销:代理服务器会增加请求和响应的延迟,尤其是在高负载情况下。
  2. 单点故障:如果代理服务器出现故障,所有客户端的请求都将受到影响。
  3. 配置复杂性:需要正确配置代理服务器和客户端的参数,否则可能导致请求失败。
  4. 安全性风险:如果代理服务器被攻击,可能会泄露目标设备的信息。

使用方法

  1. 启动代理服务器
    1
    python snmp_proxy_server.py
  2. 运行客户端程序
    1
    python snmp_proxy_client.py
  3. 查看日志
    • 代理服务器的日志文件为 snmp_proxy.log,可以查看请求和响应的详细信息
1
2
3
4
5
2025-06-10 17:29:49,083 - INFO - 接收到来自客户端 192.168.1.1:50014 的请求
2025-06-10 17:29:49,084 - INFO - 转发请求到 192.168.1.3:161 | OID: 1.3.6.1.2.1.1.5.0 | 社区: ccbnms
2025-06-10 17:29:49,084 - INFO - 向目标设备 192.168.1.3:161 发送SNMP请求, OID: 1.3.6.1.2.1.1.5.0
2025-06-10 17:29:49,386 - INFO - 从目标设备 192.168.1.3:161 收到响应: ZS_GeDai
2025-06-10 17:29:49,387 - INFO - 向客户端 192.168.1.1:50014 发送响应

SNMP代理案例
http://example.com/2025/06/11/SNMP代理/
作者
xinhaojin
发布于
2025年6月11日
许可协议