Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feature] support snmp v3 monitoring protocol #1469

Merged
merged 8 commits into from
Jan 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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^_^
Comment on lines +398 to +402
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

field contextName seem not used in snmp protocol config

contextName: ^_^contextName^_^
timeout: ^_^timeout^_^
community: ^_^community^_^
version: ^_^version^_^
Expand Down
Loading
Loading