Skip to content

Commit

Permalink
[feature] support snmp v3 monitoring protocol (#1469)
Browse files Browse the repository at this point in the history
  • Loading branch information
TJxiaobao authored and tomsun28 committed Jan 16, 2024
1 parent 20300f7 commit 6d766a4
Show file tree
Hide file tree
Showing 7 changed files with 776 additions and 15 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,19 @@
import lombok.extern.slf4j.Slf4j;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.mp.MPv3;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.security.*;
import org.snmp4j.smi.*;
import org.snmp4j.util.*;
import org.springframework.util.Assert;
import org.snmp4j.*;
import org.snmp4j.fluent.SnmpBuilder;
import org.snmp4j.fluent.SnmpCompletableFuture;
import org.snmp4j.fluent.TargetBuilder;
import org.snmp4j.security.SecurityModel;
import org.springframework.util.StringUtils;

import java.io.IOException;
import java.nio.Buffer;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
Expand All @@ -54,6 +53,9 @@
@Slf4j
public class SnmpCollectImpl extends AbstractCollect {

private static final String AES128 = "1";

private static final String SHA1 = "1";
private static final String DEFAULT_PROTOCOL = "udp";
private static final String OPERATION_GET = "get";
private static final String OPERATION_WALK = "walk";
Expand All @@ -80,21 +82,31 @@ public void collect(CollectRep.MetricsData.Builder builder, long monitorId, Stri
SnmpProtocol snmpProtocol = metrics.getSnmp();
int timeout = CollectUtil.getTimeout(snmpProtocol.getTimeout());
int snmpVersion = getSnmpVersion(snmpProtocol.getVersion());
Snmp snmpService = null;
try {
SnmpBuilder snmpBuilder = new SnmpBuilder();
Snmp snmpService = getSnmpService(snmpVersion);
snmpService = getSnmpService(snmpVersion, snmpBuilder);
snmpService.listen();
Target<?> target;
Address targetAddress = GenericAddress.parse(DEFAULT_PROTOCOL + ":" + snmpProtocol.getHost()
+ "/" + snmpProtocol.getPort());
TargetBuilder<?> targetBuilder = snmpBuilder.target(targetAddress);
if (snmpVersion == SnmpConstants.version3) {
TargetBuilder.PrivProtocol PrivPasswordEncryption = getPrivPasswordEncryption(snmpProtocol.getPrivPasswordEncryption());
TargetBuilder.AuthProtocol authPasswordEncryption = getAuthPasswordEncryption(snmpProtocol.getAuthPasswordEncryption());
target = targetBuilder
.user(snmpProtocol.getUsername())
.auth(TargetBuilder.AuthProtocol.hmac192sha256).authPassphrase(snmpProtocol.getAuthPassphrase())
.priv(TargetBuilder.PrivProtocol.aes128).privPassphrase(snmpProtocol.getPrivPassphrase())
.auth(authPasswordEncryption).authPassphrase(snmpProtocol.getAuthPassphrase())
.priv(PrivPasswordEncryption).privPassphrase(snmpProtocol.getPrivPassphrase())
.done()
.timeout(timeout).retries(1)
.build();
USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0);
SecurityModels.getInstance().addSecurityModel(usm);
snmpService.getUSM().addUser(
new OctetString(snmpProtocol.getUsername()),
new UsmUser(new OctetString(snmpProtocol.getUsername()), AuthMD5.ID, new OctetString(snmpProtocol.getAuthPassphrase()), PrivDES.ID, new OctetString(snmpProtocol.getPrivPassphrase()))
);
} else if (snmpVersion == SnmpConstants.version1) {
target = targetBuilder
.v1()
Expand All @@ -113,7 +125,7 @@ public void collect(CollectRep.MetricsData.Builder builder, long monitorId, Stri
String operation = snmpProtocol.getOperation();
operation = StringUtils.hasText(operation) ? operation : OPERATION_GET;
if (OPERATION_GET.equalsIgnoreCase(operation)) {
PDU pdu = targetBuilder.pdu().type(PDU.GET).oids(snmpProtocol.getOids().values().toArray(new String[0])).build();
PDU pdu = targetBuilder.pdu().type(PDU.GET).oids(snmpProtocol.getOids().values().toArray(new String[0])).contextName(snmpProtocol.getContextName()).build();
SnmpCompletableFuture snmpRequestFuture = SnmpCompletableFuture.send(snmpService, target, pdu);
List<VariableBinding> vbs = snmpRequestFuture.get().getAll();
long responseTime = System.currentTimeMillis() - startTime;
Expand Down Expand Up @@ -212,6 +224,16 @@ public void collect(CollectRep.MetricsData.Builder builder, long monitorId, Stri
log.warn("[snmp collect] error: {}", errorMsg, e);
builder.setCode(CollectRep.Code.FAIL);
builder.setMsg(errorMsg);
} finally {
if (snmpService != null) {
if (snmpVersion == SnmpConstants.version3) {
try {
snmpClose(snmpService, SnmpConstants.version3);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
}
}

Expand All @@ -231,14 +253,13 @@ private void validateParams(Metrics metrics) {
Assert.notNull(snmpProtocol.getVersion(), "snmp version is required.");
}

private synchronized Snmp getSnmpService(int snmpVersion) throws IOException {
private synchronized Snmp getSnmpService(int snmpVersion, SnmpBuilder snmpBuilder) throws IOException {
Snmp snmpService = versionSnmpService.get(snmpVersion);
if (snmpService != null) {
return snmpService;
}
SnmpBuilder snmpBuilder = new SnmpBuilder();
if (snmpVersion == SnmpConstants.version3) {
snmpService = snmpBuilder.udp().v3().usm().threads(4).build();
snmpService = snmpBuilder.udp().v3().securityProtocols(SecurityProtocols.SecurityProtocolSet.maxCompatibility).usm().threads(4).build();
} else if (snmpVersion == SnmpConstants.version1) {
snmpService = snmpBuilder.udp().v1().threads(4).build();
} else {
Expand Down Expand Up @@ -282,4 +303,22 @@ private String bingdingHexValueToString(VariableBinding binding) {
return hexString;
}
}

private void snmpClose(Snmp snmp, int version) throws IOException {
snmp.close();
versionSnmpService.remove(version);
}

private TargetBuilder.PrivProtocol getPrivPasswordEncryption(String privPasswordEncryption) {
if (privPasswordEncryption == null) return TargetBuilder.PrivProtocol.des;
else if (AES128.equals(privPasswordEncryption)) {
return TargetBuilder.PrivProtocol.aes128;
} else return TargetBuilder.PrivProtocol.des;
}

private TargetBuilder.AuthProtocol getAuthPasswordEncryption(String authPasswordEncryption) {
if (authPasswordEncryption == null) return TargetBuilder.AuthProtocol.md5;
else if (SHA1.equals(authPasswordEncryption)) return TargetBuilder.AuthProtocol.sha1;
else return TargetBuilder.AuthProtocol.md5;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,4 +77,21 @@ public class SnmpProtocol {
* oid map
*/
private Map<String, String> oids;

/**
* contextName
*/
private String contextName = "";

/**
* authPasswordEncryption
* v3 需要
*/
private String authPasswordEncryption;

/**
* privPasswordEncryption
* v3 需要
*/
private String privPasswordEncryption;
}
144 changes: 143 additions & 1 deletion manager/src/main/resources/define/app-cisco_switch.yml
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ params:
value: 0
- label: SNMPv2c
value: 1
- label: SNMPv3
value: 3
# field-param field key
# field-变量字段标识符
- field: community
Expand All @@ -107,12 +109,134 @@ params:
limit: 100
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: true
required: false
# 参数输入框提示信息
# param field input placeholder
placeholder: 'Snmp community for v1 v2c'
# field-param field key
# field-变量字段标识符
- field: username
# name-param field display i18n name
# name-参数字段显示名称
name:
zh-CN: SNMP username
en-US: SNMP username
# type-param field type(most mapping the html input type)
# type-字段类型,样式(大部分映射input标签type属性)
type: text
# when type is text, use limit to limit string length
# 当type为text时,用limit表示字符串限制大小
limit: 20
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: false
# 参数输入框提示信息
# param field input placeholder
placeholder: 'Snmp username for v3'
# field-param field key
# field-变量字段标识符
- field: contextName
# name-param field display i18n name
# name-参数字段显示名称
name:
zh-CN: SNMP contextName
en-US: SNMP contextName
# type-param field type(most mapping the html input type)
# type-字段类型,样式(大部分映射input标签type属性)
type: text
# when type is text, use limit to limit string length
# 当type为text时,用limit表示字符串限制大小
limit: 100
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: false
# 参数输入框提示信息
# param field input placeholder
placeholder: 'Snmp contextName for v3'
# field-param field key
# field-变量字段标识符
- field: authPassphrase
# name-param field display i18n name
# name-参数字段显示名称
name:
zh-CN: SNMP authPassword
en-US: SNMP authPassword
# type-param field type(most mapping the html input type)
# type-字段类型,样式(大部分映射input标签type属性)
type: text
# when type is text, use limit to limit string length
# 当type为text时,用limit表示字符串限制大小
limit: 100
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: false
# 参数输入框提示信息
# param field input placeholder
placeholder: 'Snmp authPassword for v3'
# field-param field key
# field-变量字段标识符
- field: authPasswordEncryption
# name-param field display i18n name
# name-参数字段显示名称
name:
zh-CN: authPassword 加密方式
en-US: authPassword Encryption
# type-param field type(radio mapping the html radio tag)
# type-当type为radio时,前端用radio展示开关
type: radio
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: false
# when type is radio checkbox, use option to show optional values {name1:value1,name2:value2}
# 当type为radio单选框, checkbox复选框时, option表示可选项值列表 {name1:value1,name2:value2}
options:
- label: MD5
value: 0
- label: SHA1
value: 1
# field-param field key
# field-变量字段标识符
- field: privPassphrase
# name-param field display i18n name
# name-参数字段显示名称
name:
zh-CN: SNMP privPassphrase
en-US: SNMP privPassphrase
# type-param field type(most mapping the html input type)
# type-字段类型,样式(大部分映射input标签type属性)
type: text
# when type is text, use limit to limit string length
# 当type为text时,用limit表示字符串限制大小
limit: 100
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: false
# 参数输入框提示信息
# param field input placeholder
placeholder: 'Snmp authPassword for v3'
# field-param field key
# field-变量字段标识符
- field: privPasswordEncryption
# name-param field display i18n name
# name-参数字段显示名称
name:
zh-CN: privPassword 加密方式
en-US: privPassword Encryption
# type-param field type(radio mapping the html radio tag)
# type-当type为radio时,前端用radio展示开关
type: radio
# required-true or false
# required-是否是必输项 true-必填 false-可选
required: false
# when type is radio checkbox, use option to show optional values {name1:value1,name2:value2}
# 当type为radio单选框, checkbox复选框时, option表示可选项值列表 {name1:value1,name2:value2}
options:
- label: DES
value: 0
- label: AES128
value: 1
# field-param field key
# field-变量字段标识符
- field: timeout
# name-param field display i18n name
# name-参数字段显示名称
Expand Down Expand Up @@ -171,6 +295,18 @@ metrics:
host: ^_^host^_^
# server port
port: ^_^port^_^
# snmp username
username: ^_^username^_^
# snmp authPassphrase
authPassphrase: ^_^authPassphrase^_^
# snmp privPassphrase
privPassphrase: ^_^privPassphrase^_^
# snmp privPasswordEncryption
privPasswordEncryption: ^_^privPasswordEncryption^_^
# snmp authPasswordEncryption
authPasswordEncryption: ^_^authPasswordEncryption^_^
# contextName
contextName: ^_^contextName^_^
# snmp connect timeout
timeout: ^_^timeout^_^
# snmp community
Expand Down Expand Up @@ -259,6 +395,12 @@ metrics:
snmp:
host: ^_^host^_^
port: ^_^port^_^
username: ^_^username^_^
authPassphrase: ^_^authPassphrase^_^
privPassphrase: ^_^privPassphrase^_^
privPasswordEncryption: ^_^privPasswordEncryption^_^
authPasswordEncryption: ^_^authPasswordEncryption^_^
contextName: ^_^contextName^_^
timeout: ^_^timeout^_^
community: ^_^community^_^
version: ^_^version^_^
Expand Down
Loading

0 comments on commit 6d766a4

Please sign in to comment.