From e52ac96c624fa2f2790d5a288683e675311e8a8a Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 6 Apr 2021 17:05:34 +0300 Subject: [PATCH 1/4] Implement SNMP v3 security support, remove traps support --- .../SnmpDeviceTransportConfiguration.java | 23 +- .../snmp/AuthenticationProtocol.java | 45 ++ .../data/transport/snmp/PrivacyProtocol.java | 43 ++ .../transport/snmp/SnmpCommunicationSpec.java | 5 +- ...TrapsReceivingSnmpCommunicationConfig.java | 25 - .../snmp/configs/SnmpCommunicationConfig.java | 4 +- ...TrapsReceivingSnmpCommunicationConfig.java | 30 - .../transport/snmp/SnmpAuthService.java | 118 +++ .../transport/snmp/SnmpTransportContext.java | 12 +- .../snmp/service/SnmpTransportService.java | 142 ++-- .../snmp/session/DeviceSessionContext.java | 74 +- .../transport/snmp/SnmpDeviceSimulatorV2.java | 26 - .../transport/snmp/SnmpDeviceSimulatorV3.java | 723 +++++++++++++++++- .../snmp/{SnmpTest.java => SnmpTestV2.java} | 2 +- .../server/transport/snmp/SnmpTestV3.java | 46 ++ .../snmp-device-profile-transport-config.json | 10 - .../snmp-device-transport-config-v3.json | 13 + .../snmp-device-transport-config.json | 2 +- 18 files changed, 1057 insertions(+), 286 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java rename common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/{SnmpTest.java => SnmpTestV2.java} (98%) create mode 100644 common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java create mode 100644 common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index a6056b4188..24ab463129 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -19,6 +19,8 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol; +import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; @Data @@ -26,9 +28,23 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur private String address; private int port; private SnmpProtocolVersion protocolVersion; + + /* + * For SNMP v1 and v2c + * */ + private String community; + + /* + * For SNMP v3 with User Based Security Model + * */ + private String username; private String securityName; - private String authenticationPassphrase; // for SNMP v3 - private String privacyPassphrase; // for SNMP v3 + private String contextName; + private AuthenticationProtocol authenticationProtocol; + private String authenticationPassphrase; + private PrivacyProtocol privacyProtocol; + private String privacyPassphrase; + private String engineId; @Override public DeviceTransportType getType() { @@ -44,7 +60,6 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @JsonIgnore private boolean isValid() { - return StringUtils.isNotBlank(address) && port > 0 && - StringUtils.isNotBlank(securityName) && protocolVersion != null; + return true; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java new file mode 100644 index 0000000000..42d2e98798 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/AuthenticationProtocol.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.transport.snmp; + +import java.util.Arrays; +import java.util.Optional; + +public enum AuthenticationProtocol { + SHA_1("1.3.6.1.6.3.10.1.1.3"), + SHA_224("1.3.6.1.6.3.10.1.1.4"), + SHA_256("1.3.6.1.6.3.10.1.1.5"), + SHA_384("1.3.6.1.6.3.10.1.1.6"), + SHA_512("1.3.6.1.6.3.10.1.1.7"), + MD5("1.3.6.1.6.3.10.1.1.2"); + + // oids taken from org.snmp4j.security.SecurityProtocol implementations + private final String oid; + + AuthenticationProtocol(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + public static Optional forName(String name) { + return Arrays.stream(values()) + .filter(protocol -> protocol.name().equalsIgnoreCase(name)) + .findFirst(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java new file mode 100644 index 0000000000..1c040eb83e --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/PrivacyProtocol.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.transport.snmp; + +import java.util.Arrays; +import java.util.Optional; + +public enum PrivacyProtocol { + DES("1.3.6.1.6.3.10.1.2.2"), + AES_128("1.3.6.1.6.3.10.1.2.4"), + AES_192("1.3.6.1.4.1.4976.2.2.1.1.1"), + AES_256("1.3.6.1.4.1.4976.2.2.1.1.2"); + + // oids taken from org.snmp4j.security.SecurityProtocol implementations + private final String oid; + + PrivacyProtocol(String oid) { + this.oid = oid; + } + + public String getOid() { + return oid; + } + + public static Optional forName(String name) { + return Arrays.stream(values()) + .filter(protocol -> protocol.name().equalsIgnoreCase(name)) + .findFirst(); + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index f6ff7d3109..a2edf75968 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -19,10 +19,7 @@ public enum SnmpCommunicationSpec { TELEMETRY_QUERYING(true), CLIENT_ATTRIBUTES_QUERYING(true), - SHARED_ATTRIBUTES_SETTING, - - TELEMETRY_TRAPS_RECEIVING, - CLIENT_ATTRIBUTES_TRAPS_RECEIVING; + SHARED_ATTRIBUTES_SETTING; private final boolean isRepeatingQuerying; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java deleted file mode 100644 index 9a11b01f1b..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesTrapsReceivingSnmpCommunicationConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.transport.snmp.configs; - -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; - -public class ClientAttributesTrapsReceivingSnmpCommunicationConfig extends SnmpCommunicationConfig { - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_TRAPS_RECEIVING; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java index bfc194669b..7e1ee27237 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java @@ -31,9 +31,7 @@ import java.util.List; @JsonSubTypes({ @Type(value = TelemetryQueryingSnmpCommunicationConfig.class, name = "TELEMETRY_QUERYING"), @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), - @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING"), - @Type(value = TelemetryTrapsReceivingSnmpCommunicationConfig.class, name = "TELEMETRY_TRAPS_RECEIVING"), - @Type(value = ClientAttributesTrapsReceivingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_TRAPS_RECEIVING") + @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING") }) public abstract class SnmpCommunicationConfig { protected List mappings; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java deleted file mode 100644 index 66be4d0cf8..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryTrapsReceivingSnmpCommunicationConfig.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.transport.snmp.configs; - -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; - -public class TelemetryTrapsReceivingSnmpCommunicationConfig extends SnmpCommunicationConfig { - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.TELEMETRY_TRAPS_RECEIVING; - } - - @Override - public boolean isValid() { - return false; - } -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java new file mode 100644 index 0000000000..59b31d593c --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.snmp; + +import lombok.RequiredArgsConstructor; +import org.snmp4j.AbstractTarget; +import org.snmp4j.CommunityTarget; +import org.snmp4j.Target; +import org.snmp4j.UserTarget; +import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; +import org.snmp4j.security.SecurityProtocols; +import org.snmp4j.security.USM; +import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.service.SnmpTransportService; +import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; + +@Service +@TbSnmpTransportComponent +@RequiredArgsConstructor +public class SnmpAuthService { + private final SnmpTransportService snmpTransportService; + + @Value("${transport.snmp.underlying_protocol}") + private String snmpUnderlyingProtocol; + + public Target setUpSnmpTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + AbstractTarget target; + + SnmpProtocolVersion protocolVersion = deviceTransportConfig.getProtocolVersion(); + switch (protocolVersion) { + case V1: + CommunityTarget communityTargetV1 = new CommunityTarget(); + communityTargetV1.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1); + communityTargetV1.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + communityTargetV1.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); + target = communityTargetV1; + break; + case V2C: + CommunityTarget communityTargetV2 = new CommunityTarget(); + communityTargetV2.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c); + communityTargetV2.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); + communityTargetV2.setCommunity(new OctetString(deviceTransportConfig.getCommunity())); + target = communityTargetV2; + break; + case V3: + OctetString username = new OctetString(deviceTransportConfig.getUsername()); + OctetString securityName = new OctetString(deviceTransportConfig.getSecurityName()); + OctetString engineId = new OctetString(deviceTransportConfig.getEngineId()); + + OID authenticationProtocol = new OID(deviceTransportConfig.getAuthenticationProtocol().getOid()); + OID privacyProtocol = new OID(deviceTransportConfig.getPrivacyProtocol().getOid()); + OctetString authenticationPassphrase = new OctetString(deviceTransportConfig.getAuthenticationPassphrase()); + authenticationPassphrase = new OctetString(SecurityProtocols.getInstance().passwordToKey(authenticationProtocol, authenticationPassphrase, engineId.getValue())); + OctetString privacyPassphrase = new OctetString(deviceTransportConfig.getPrivacyPassphrase()); + privacyPassphrase = new OctetString(SecurityProtocols.getInstance().passwordToKey(privacyProtocol, authenticationProtocol, privacyPassphrase, engineId.getValue())); + + USM usm = snmpTransportService.getSnmp().getUSM(); + if (usm.hasUser(engineId, securityName)) { + usm.removeAllUsers(username, engineId); + } + usm.addLocalizedUser( + engineId.getValue(), username, + authenticationProtocol, authenticationPassphrase.getValue(), + privacyProtocol, privacyPassphrase.getValue() + ); + + UserTarget userTarget = new UserTarget(); + userTarget.setSecurityName(securityName); + userTarget.setAuthoritativeEngineID(engineId.getValue()); + userTarget.setSecurityModel(SecurityModel.SECURITY_MODEL_USM); + userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); + target = userTarget; + break; + default: + throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); + } + + target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + target.setTimeout(profileTransportConfig.getTimeoutMs()); + target.setRetries(profileTransportConfig.getRetries()); + target.setVersion(protocolVersion.getCode()); + + return target; + } + + public void cleanUpSnmpAuthInfo(DeviceSessionContext sessionContext) { + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + if (deviceTransportConfiguration.getProtocolVersion() == SnmpProtocolVersion.V3) { + OctetString username = new OctetString(deviceTransportConfiguration.getUsername()); + OctetString engineId = new OctetString(deviceTransportConfiguration.getEngineId()); + snmpTransportService.getSnmp().getUSM().removeAllUsers(username, engineId); + } + } + +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 877d4f1652..2b0ce984aa 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -15,9 +15,9 @@ */ package org.thingsboard.server.transport.snmp; +import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Device; @@ -61,18 +61,18 @@ import java.util.stream.Collectors; @Slf4j @RequiredArgsConstructor public class SnmpTransportContext extends TransportContext { + @Getter private final SnmpTransportService snmpTransportService; private final TransportDeviceProfileCache deviceProfileCache; private final TransportService transportService; private final ProtoTransportEntityService protoEntityService; private final SnmpTransportBalancingService balancingService; + @Getter + private final SnmpAuthService snmpAuthService; private final Map sessions = new ConcurrentHashMap<>(); private Collection allSnmpDevicesIds = new ConcurrentLinkedDeque<>(); - @Value("${transport.snmp.underlying_protocol}") - private String snmpUnderlyingProtocol; - @AfterStartUp(order = 2) public void initDevicesSessions() { log.info("Initializing SNMP devices sessions"); @@ -116,8 +116,7 @@ public class SnmpTransportContext extends TransportContext { DeviceSessionContext deviceSessionContext = new DeviceSessionContext( device, deviceProfile, credentials.getCredentialsId(), - profileTransportConfiguration, deviceTransportConfiguration, - this, snmpTransportService, snmpUnderlyingProtocol + profileTransportConfiguration, deviceTransportConfiguration, this ); registerSessionMsgListener(deviceSessionContext); sessions.put(device.getId(), deviceSessionContext); @@ -155,6 +154,7 @@ public class SnmpTransportContext extends TransportContext { if (sessionContext == null) return; log.info("Destroying SNMP device session for device {}", sessionContext.getDevice().getId()); sessionContext.close(); + snmpAuthService.cleanUpSnmpAuthInfo(sessionContext); transportService.deregisterSession(sessionContext.getSessionInfo()); sessions.remove(sessionContext.getDeviceId()); snmpTransportService.cancelQueryingTasks(sessionContext); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 963b5db382..2f26bfa98c 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -19,29 +19,31 @@ import com.google.gson.JsonObject; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; -import org.snmp4j.CommandResponder; -import org.snmp4j.CommandResponderEvent; import org.snmp4j.PDU; +import org.snmp4j.ScopedPDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; +import org.snmp4j.mp.MPv3; +import org.snmp4j.security.SecurityModels; +import org.snmp4j.security.SecurityProtocols; +import org.snmp4j.security.USM; import org.snmp4j.smi.Null; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.TcpAddress; -import org.snmp4j.smi.UdpAddress; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.configs.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; @@ -49,12 +51,12 @@ import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; -import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; +import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; import java.util.List; @@ -67,13 +69,11 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import java.util.stream.Collectors; -import java.util.stream.Stream; @TbSnmpTransportComponent @Service @Slf4j -public class SnmpTransportService implements TbTransportService, CommandResponder { - private final SnmpTransportContext snmpTransportContext; +public class SnmpTransportService implements TbTransportService { private final TransportService transportService; @Getter @@ -88,9 +88,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde @Value("${transport.snmp.underlying_protocol}") private String snmpUnderlyingProtocol; - public SnmpTransportService(@Lazy SnmpTransportContext snmpTransportContext, - TransportService transportService) { - this.snmpTransportContext = snmpTransportContext; + public SnmpTransportService(TransportService transportService) { this.transportService = transportService; } @@ -102,7 +100,6 @@ public class SnmpTransportService implements TbTransportService, CommandResponde responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); initializeSnmp(); - initializeTrapsListener(); configureResponseProcessors(); log.info("SNMP transport service initialized"); @@ -122,29 +119,9 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } snmp = new Snmp(transportMapping); snmp.listen(); - } - - private void initializeTrapsListener() throws IOException { - int trapsListeningPort = 1062; - String bindingAddress = "0.0.0.0/" + trapsListeningPort; - TransportMapping transportMapping; - switch (snmpUnderlyingProtocol) { - case "udp": - transportMapping = new DefaultUdpTransportMapping(new UdpAddress(bindingAddress)); - break; - case "tcp": - transportMapping = new DefaultTcpTransportMapping(new TcpAddress(bindingAddress)); - break; - default: - throw new IllegalArgumentException("Underlying protocol " + snmpUnderlyingProtocol + " for SNMP is not supported"); - } - - - Snmp trapsSnmp = new Snmp(transportMapping); - trapsSnmp.addCommandResponder(this); - - transportMapping.listen(); + USM usm = new USM(SecurityProtocols.getInstance(), new OctetString(MPv3.createLocalEngineID()), 0); + SecurityModels.getInstance().addSecurityModel(usm); } public void createQueryingTasks(DeviceSessionContext sessionContext) { @@ -177,12 +154,11 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) throws IOException { - PDU request = createPdu(communicationConfig); - executeRequest(sessionContext, request); + sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) throws IOException { - PDU request = createPduWithValues(communicationConfig, values); + PDU request = createPdu(sessionContext, communicationConfig, values); executeRequest(sessionContext, request); } @@ -193,46 +169,37 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } } - private PDU createPdu(SnmpCommunicationConfig communicationConfig) { - PDU pdu = new PDU(); - pdu.setType(communicationConfig.getMethod().getCode()); - pdu.addAll(communicationConfig.getMappings().stream() - .map(mapping -> new VariableBinding(new OID(mapping.getOid()))) - .collect(Collectors.toList())); - return pdu; - } + private PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU pdu; + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); + switch (snmpVersion) { + case V1: + case V2C: + pdu = new PDU(); + break; + case V3: + ScopedPDU scopedPdu = new ScopedPDU(); + scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); + scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); + pdu = scopedPdu; + break; + default: + throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + } - private PDU createPduWithValues(SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu = new PDU(); pdu.setType(communicationConfig.getMethod().getCode()); pdu.addAll(communicationConfig.getMappings().stream() - .filter(mapping -> values.containsKey(mapping.getKey())) - .map(mapping -> { - String value = values.get(mapping.getKey()); - return new VariableBinding(new OID(mapping.getOid()), new OctetString(value)); - }) + .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) + .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) + .map(value -> new VariableBinding(new OID(mapping.getOid()), new OctetString(values.get(mapping.getKey())))) + .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) .collect(Collectors.toList())); + return pdu; } - private void processTrap(CommandResponderEvent event) { - if (event.getPDU().getType() != PDU.TRAP) return; - - snmpTransportContext.getSessions().stream() - .filter(sessionContext -> { - // TODO: SNMP v3 support - return sessionContext.getTarget().getSecurityName().equals(OctetString.fromByteArray(event.getSecurityName())) && - sessionContext.getTarget().getAddress().equals(event.getPeerAddress()); - }) - .findFirst() - .ifPresentOrElse(sessionContext -> { - responseProcessingExecutor.execute(() -> processResponse(sessionContext, event.getPDU())); - }, () -> { - log.debug("SNMP event is from unknown source: {}", event); - }); - } - public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); @@ -243,7 +210,7 @@ public class SnmpTransportService implements TbTransportService, CommandResponde PDU response = event.getResponse(); if (response == null) { - log.warn("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); + log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); @@ -308,23 +275,17 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } private void configureResponseProcessors() { - Stream.of(SnmpCommunicationSpec.TELEMETRY_QUERYING, SnmpCommunicationSpec.TELEMETRY_TRAPS_RECEIVING) - .forEach(telemetrySpec -> { - responseProcessors.put(telemetrySpec, (response, sessionContext) -> { - TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); - transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); - log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); - }); - }); - - Stream.of(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, SnmpCommunicationSpec.CLIENT_ATTRIBUTES_TRAPS_RECEIVING) - .forEach(clientAttributesSpec -> { - responseProcessors.put(clientAttributesSpec, (response, sessionContext) -> { - TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); - transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); - log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); - }); - }); + responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (response, sessionContext) -> { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); + log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); + }); + + responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (response, sessionContext) -> { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); + log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); + }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { @@ -353,11 +314,6 @@ public class SnmpTransportService implements TbTransportService, CommandResponde } } - @Override - public void processPdu(CommandResponderEvent event) { - processTrap(event); - } - @Override public String getName() { return "SNMP"; diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index c0bbc843bc..c5a329547f 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -18,29 +18,15 @@ package org.thingsboard.server.transport.snmp.session; import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; -import org.snmp4j.AbstractTarget; -import org.snmp4j.CommunityTarget; import org.snmp4j.Target; -import org.snmp4j.UserTarget; import org.snmp4j.event.ResponseEvent; import org.snmp4j.event.ResponseListener; -import org.snmp4j.security.AuthSHA; -import org.snmp4j.security.PrivDES; -import org.snmp4j.security.SecurityLevel; -import org.snmp4j.security.SecurityModel; -import org.snmp4j.security.SecurityModels; -import org.snmp4j.security.USM; -import org.snmp4j.security.UsmUser; -import org.snmp4j.smi.GenericAddress; -import org.snmp4j.smi.OID; -import org.snmp4j.smi.OctetString; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; @@ -50,6 +36,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponse import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; +import org.thingsboard.server.transport.snmp.SnmpAuthService; import org.thingsboard.server.transport.snmp.SnmpTransportContext; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; @@ -77,6 +64,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final SnmpTransportContext snmpTransportContext; private final SnmpTransportService snmpTransportService; + private final SnmpAuthService snmpAuthService; @Getter @Setter @@ -84,7 +72,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final AtomicInteger msgIdSeq = new AtomicInteger(0); @Getter private boolean isActive = true; - private final String snmpUnderlyingProtocol; @Getter @Setter @@ -93,8 +80,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration, - SnmpTransportContext snmpTransportContext, SnmpTransportService snmpTransportService, - String snmpUnderlyingProtocol) { + SnmpTransportContext snmpTransportContext) { super(UUID.randomUUID()); super.setDeviceId(device.getId()); super.setDeviceProfile(deviceProfile); @@ -102,12 +88,12 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.token = token; this.snmpTransportContext = snmpTransportContext; - this.snmpTransportService = snmpTransportService; + this.snmpTransportService = snmpTransportContext.getSnmpTransportService(); + this.snmpAuthService = snmpTransportContext.getSnmpAuthService(); this.profileTransportConfiguration = profileTransportConfiguration; this.deviceTransportConfiguration = deviceTransportConfiguration; - this.snmpUnderlyingProtocol = snmpUnderlyingProtocol; initializeTarget(profileTransportConfiguration, deviceTransportConfiguration); } @@ -133,55 +119,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { log.trace("Initializing target for SNMP session of device {}", device); - - AbstractTarget target; - - SnmpProtocolVersion protocolVersion = deviceTransportConfig.getProtocolVersion(); - switch (protocolVersion) { - case V1: - CommunityTarget communityTargetV1 = new CommunityTarget(); - communityTargetV1.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv1); - communityTargetV1.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); - communityTargetV1.setCommunity(new OctetString(deviceTransportConfig.getSecurityName())); - target = communityTargetV1; - break; - case V2C: - CommunityTarget communityTargetV2 = new CommunityTarget(); - communityTargetV2.setSecurityModel(SecurityModel.SECURITY_MODEL_SNMPv2c); - communityTargetV2.setSecurityLevel(SecurityLevel.NOAUTH_NOPRIV); - communityTargetV2.setCommunity(new OctetString(deviceTransportConfig.getSecurityName())); - target = communityTargetV2; - break; - case V3: - USM usm = new USM(); - SecurityModels.getInstance().addSecurityModel(usm); - - OctetString securityName = new OctetString(deviceTransportConfig.getSecurityName()); - OctetString authenticationPassphrase = new OctetString(deviceTransportConfig.getAuthenticationPassphrase()); - OctetString privacyPassphrase = new OctetString(deviceTransportConfig.getPrivacyPassphrase()); - - OID authenticationProtocol = AuthSHA.ID; - OID privacyProtocol = PrivDES.ID; // FIXME: to config - - UsmUser user = new UsmUser(securityName, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase); - snmpTransportService.getSnmp().getUSM().addUser(user); - - UserTarget userTarget = new UserTarget(); - userTarget.setSecurityName(securityName); - userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); - - target = userTarget; - break; - default: - throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); - } - - target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); - target.setTimeout(profileTransportConfig.getTimeoutMs()); - target.setRetries(profileTransportConfig.getRetries()); - target.setVersion(protocolVersion.getCode()); - - this.target = target; + this.target = snmpAuthService.setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); log.info("SNMP target initialized: {}", target); } diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java index d48eaadce3..c97f47d573 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV2.java @@ -73,32 +73,6 @@ public class SnmpDeviceSimulatorV2 extends BaseAgent { } } - public static void main(String[] args) throws IOException { - SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); - - device.start(); - device.setUpMappings(Map.of( - ".1.3.6.1.2.1.1.1.50", "12", - ".1.3.6.1.2.1.2.1.52", "56", - ".1.3.6.1.2.1.3.1.54", "yes", - ".1.3.6.1.2.1.7.1.58", "" - )); - - -// while (true) { -// new Scanner(System.in).nextLine(); -// device.sendTrap("127.0.0.1", 1062, Map.of(".1.3.6.1.2.87.1.56", "12")); -// System.out.println("sent"); -// } - -// Snmp snmp = new Snmp(device.transportMappings[0]); -// device.snmp.addCommandResponder(event -> { -// System.out.println(event); -// }); - - new Scanner(System.in).nextLine(); - } - private final Target target; private final Address address; diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java index 63ccd7021a..da7f984267 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpDeviceSimulatorV3.java @@ -15,38 +15,731 @@ */ package org.thingsboard.server.transport.snmp; -import org.snmp4j.UserTarget; +import org.snmp4j.MessageDispatcherImpl; +import org.snmp4j.TransportMapping; +import org.snmp4j.agent.BaseAgent; +import org.snmp4j.agent.CommandProcessor; +import org.snmp4j.agent.DuplicateRegistrationException; +import org.snmp4j.agent.MOGroup; +import org.snmp4j.agent.ManagedObject; +import org.snmp4j.agent.mo.DefaultMOMutableRow2PC; +import org.snmp4j.agent.mo.DefaultMOTable; +import org.snmp4j.agent.mo.MOAccessImpl; +import org.snmp4j.agent.mo.MOColumn; +import org.snmp4j.agent.mo.MOMutableColumn; +import org.snmp4j.agent.mo.MOMutableTableModel; +import org.snmp4j.agent.mo.MOScalar; +import org.snmp4j.agent.mo.MOTableIndex; +import org.snmp4j.agent.mo.MOTableRow; +import org.snmp4j.agent.mo.MOTableSubIndex; +import org.snmp4j.agent.mo.ext.AgentppSimulationMib; +import org.snmp4j.agent.mo.snmp.RowStatus; +import org.snmp4j.agent.mo.snmp.SnmpCommunityMIB; +import org.snmp4j.agent.mo.snmp.SnmpNotificationMIB; +import org.snmp4j.agent.mo.snmp.SnmpTargetMIB; +import org.snmp4j.agent.mo.snmp.StorageType; +import org.snmp4j.agent.mo.snmp.TransportDomains; +import org.snmp4j.agent.mo.snmp.VacmMIB; +import org.snmp4j.agent.mo.snmp4j.example.Snmp4jHeartbeatMib; +import org.snmp4j.agent.security.MutableVACM; +import org.snmp4j.mp.MPv1; +import org.snmp4j.mp.MPv2c; +import org.snmp4j.mp.MPv3; +import org.snmp4j.mp.MessageProcessingModel; +import org.snmp4j.security.AuthHMAC192SHA256; +import org.snmp4j.security.AuthMD5; import org.snmp4j.security.AuthSHA; +import org.snmp4j.security.PrivAES128; +import org.snmp4j.security.PrivAES192; +import org.snmp4j.security.PrivAES256; import org.snmp4j.security.PrivDES; import org.snmp4j.security.SecurityLevel; +import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityModels; +import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; import org.snmp4j.security.UsmUser; +import org.snmp4j.smi.Address; +import org.snmp4j.smi.Gauge32; +import org.snmp4j.smi.GenericAddress; +import org.snmp4j.smi.Integer32; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.SMIConstants; +import org.snmp4j.smi.TcpAddress; +import org.snmp4j.smi.TimeTicks; +import org.snmp4j.smi.UdpAddress; +import org.snmp4j.smi.Variable; +import org.snmp4j.transport.DefaultTcpTransportMapping; +import org.snmp4j.transport.TransportMappings; +import org.snmp4j.util.ThreadPool; +import java.io.File; import java.io.IOException; +import java.util.Map; -public class SnmpDeviceSimulatorV3 extends SnmpDeviceSimulatorV2 { - public SnmpDeviceSimulatorV3(int port, String securityName, String authenticationPassphrase, String privacyPassphrase) throws IOException { - super(12, null); -// super(new File("conf.agent"), new File("bootCounter.agent")); +/** + * The TestAgent is a sample SNMP agent implementation of all + * features (MIB implementations) provided by the SNMP4J-Agent framework. + * + * Note, for snmp4s, this code is mostly a copy from snmp4j. + * And don't remove snmp users + * + */ +public class SnmpDeviceSimulatorV3 extends BaseAgent { + protected String address; + private Snmp4jHeartbeatMib heartbeatMIB; + private AgentppSimulationMib agentppSimulationMIB; + + public SnmpDeviceSimulatorV3(CommandProcessor processor) throws IOException { + super(new File("SNMP4JTestAgentBC.cfg"), new File("SNMP4JTestAgentConfig.cfg"), + processor); + agent.setWorkerPool(ThreadPool.create("RequestPool", 4)); + } + + public void setUpMappings(Map oidToResponseMappings) { + unregisterManagedObject(getSnmpv2MIB()); + oidToResponseMappings.forEach((oid, response) -> { + registerManagedObject(new MOScalar<>(new OID(oid), MOAccessImpl.ACCESS_READ_WRITE, new OctetString(response))); + }); + } + protected void registerManagedObject(ManagedObject mo) { + try { + server.register(mo, null); + } catch (DuplicateRegistrationException ex) { + throw new RuntimeException(ex); + } + } + + protected void unregisterManagedObject(MOGroup moGroup) { + moGroup.unregisterMOs(server, getContext(moGroup)); + } + + protected void registerManagedObjects() { + try { + server.register(createStaticIfTable(), null); + server.register(createStaticIfXTable(), null); + agentppSimulationMIB.registerMOs(server, null); + heartbeatMIB.registerMOs(server, null); + } catch (DuplicateRegistrationException ex) { + ex.printStackTrace(); + } + } + + protected void addNotificationTargets(SnmpTargetMIB targetMIB, + SnmpNotificationMIB notificationMIB) { + targetMIB.addDefaultTDomains(); + + targetMIB.addTargetAddress(new OctetString("notificationV2c"), + TransportDomains.transportDomainUdpIpv4, + new OctetString(new UdpAddress("127.0.0.1/162").getValue()), + 200, 1, + new OctetString("notify"), + new OctetString("v2c"), + StorageType.permanent); + targetMIB.addTargetAddress(new OctetString("notificationV3"), + TransportDomains.transportDomainUdpIpv4, + new OctetString(new UdpAddress("127.0.0.1/1162").getValue()), + 200, 1, + new OctetString("notify"), + new OctetString("v3notify"), + StorageType.permanent); + targetMIB.addTargetParams(new OctetString("v2c"), + MessageProcessingModel.MPv2c, + SecurityModel.SECURITY_MODEL_SNMPv2c, + new OctetString("cpublic"), + SecurityLevel.AUTH_PRIV, + StorageType.permanent); + targetMIB.addTargetParams(new OctetString("v3notify"), + MessageProcessingModel.MPv3, + SecurityModel.SECURITY_MODEL_USM, + new OctetString("v3notify"), + SecurityLevel.NOAUTH_NOPRIV, + StorageType.permanent); + notificationMIB.addNotifyEntry(new OctetString("default"), + new OctetString("notify"), + SnmpNotificationMIB.SnmpNotifyTypeEnum.inform, + StorageType.permanent); + } + protected void addViews(VacmMIB vacm) { + vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv1, + new OctetString("cpublic"), + new OctetString("v1v2group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_SNMPv2c, + new OctetString("cpublic"), + new OctetString("v1v2group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHADES"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5DES"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("TEST"), + new OctetString("v3test"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHA"), + new OctetString("v3restricted"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHAAES128"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHAAES192"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("SHAAES256"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5AES128"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5AES192"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("MD5AES256"), + new OctetString("v3group"), + StorageType.nonVolatile); + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("aboba"), + new OctetString("v3group"), + StorageType.nonVolatile); + //============================================// + // agent5-auth-priv + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("agent5"), + new OctetString("v3group"), + StorageType.nonVolatile); + //===========================================// + // agent002 + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("agent002"), + new OctetString("v3group"), + StorageType.nonVolatile); + //===========================================// + // user001-auth-no-priv + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("user001"), + new OctetString("group001"), + StorageType.nonVolatile); + //===========================================// + + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("v3notify"), + new OctetString("v3group"), + StorageType.nonVolatile); + + //===========================================// + // group auth no priv + vacm.addGroup(SecurityModel.SECURITY_MODEL_USM, + new OctetString("v3notify-auth"), + new OctetString("group001"), + StorageType.nonVolatile); + //===========================================// + + + + // my conf + vacm.addAccess(new OctetString("group001"), new OctetString("public"), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.AUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("fullReadView"), + new OctetString("fullWriteView"), + new OctetString("fullNotifyView"), + StorageType.nonVolatile); + + vacm.addAccess(new OctetString("v1v2group"), new OctetString("public"), + SecurityModel.SECURITY_MODEL_ANY, + SecurityLevel.NOAUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("fullReadView"), + new OctetString("fullWriteView"), + new OctetString("fullNotifyView"), + StorageType.nonVolatile); + vacm.addAccess(new OctetString("v3group"), new OctetString(), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.AUTH_PRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("fullReadView"), + new OctetString("fullWriteView"), + new OctetString("fullNotifyView"), + StorageType.nonVolatile); + vacm.addAccess(new OctetString("v3restricted"), new OctetString(), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.NOAUTH_NOPRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("restrictedReadView"), + new OctetString("restrictedWriteView"), + new OctetString("restrictedNotifyView"), + StorageType.nonVolatile); + vacm.addAccess(new OctetString("v3test"), new OctetString(), + SecurityModel.SECURITY_MODEL_USM, + SecurityLevel.AUTH_PRIV, + MutableVACM.VACM_MATCH_EXACT, + new OctetString("testReadView"), + new OctetString("testWriteView"), + new OctetString("testNotifyView"), + StorageType.nonVolatile); + + vacm.addViewTreeFamily(new OctetString("fullReadView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("fullWriteView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("fullNotifyView"), new OID("1.3"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + + vacm.addViewTreeFamily(new OctetString("restrictedReadView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("restrictedWriteView"), + new OID("1.3.6.1.2.1"), + new OctetString(), + VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("restrictedNotifyView"), + new OID("1.3.6.1.6.3.1"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); - USM usm = new USM(); - SecurityModels.getInstance().addSecurityModel(usm); + vacm.addViewTreeFamily(new OctetString("testReadView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("testReadView"), + new OID("1.3.6.1.2.1.1"), + new OctetString(), VacmMIB.vacmViewExcluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("testWriteView"), + new OID("1.3.6.1.2.1"), + new OctetString(), + VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + vacm.addViewTreeFamily(new OctetString("testNotifyView"), + new OID("1.3.6.1.2"), + new OctetString(), VacmMIB.vacmViewIncluded, + StorageType.nonVolatile); + } + + protected void addUsmUser(USM usm) { + UsmUser user = new UsmUser(new OctetString("SHADES"), + AuthSHA.ID, + new OctetString("SHADESAuthPassword"), + PrivDES.ID, + new OctetString("SHADESPrivPassword")); +// usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + usm.addUser(user.getSecurityName(), null, user); + user = new UsmUser(new OctetString("TEST"), + AuthSHA.ID, + new OctetString("maplesyrup"), + PrivDES.ID, + new OctetString("maplesyrup")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHA"), + AuthSHA.ID, + new OctetString("SHAAuthPassword"), + null, + null); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHADES"), + AuthSHA.ID, + new OctetString("SHADESAuthPassword"), + PrivDES.ID, + new OctetString("SHADESPrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("MD5DES"), + AuthMD5.ID, + new OctetString("MD5DESAuthPassword"), + PrivDES.ID, + new OctetString("MD5DESPrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHAAES128"), + AuthSHA.ID, + new OctetString("SHAAES128AuthPassword"), + PrivAES128.ID, + new OctetString("SHAAES128PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHAAES192"), + AuthSHA.ID, + new OctetString("SHAAES192AuthPassword"), + PrivAES192.ID, + new OctetString("SHAAES192PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("SHAAES256"), + AuthSHA.ID, + new OctetString("SHAAES256AuthPassword"), + PrivAES256.ID, + new OctetString("SHAAES256PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + + user = new UsmUser(new OctetString("MD5AES128"), + AuthMD5.ID, + new OctetString("MD5AES128AuthPassword"), + PrivAES128.ID, + new OctetString("MD5AES128PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("MD5AES192"), + AuthHMAC192SHA256.ID, + new OctetString("MD5AES192AuthPassword"), + PrivAES192.ID, + new OctetString("MD5AES192PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //============================================================== + user = new UsmUser(new OctetString("MD5AES256"), + AuthMD5.ID, + new OctetString("MD5AES256AuthPassword"), + PrivAES256.ID, + new OctetString("MD5AES256PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + user = new UsmUser(new OctetString("MD5AES256"), + AuthMD5.ID, + new OctetString("MD5AES256AuthPassword"), + PrivAES256.ID, + new OctetString("MD5AES256PrivPassword")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + + OctetString securityName = new OctetString("aboba"); + OctetString authenticationPassphrase = new OctetString("abobaaboba"); + OctetString privacyPassphrase = new OctetString("abobaaboba"); OID authenticationProtocol = AuthSHA.ID; - OID privacyProtocol = PrivDES.ID; + OID privacyProtocol = PrivDES.ID; // FIXME: to config + user = new UsmUser(securityName, authenticationProtocol, authenticationPassphrase, privacyProtocol, privacyPassphrase); + usm.addUser(user); + + //===============================================================// + user = new UsmUser(new OctetString("agent5"), + AuthSHA.ID, + new OctetString("authpass"), + PrivDES.ID, + new OctetString("privpass")); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //===============================================================// + // user001 + user = new UsmUser(new OctetString("user001"), + AuthSHA.ID, + new OctetString("authpass"), + null, null); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //===============================================================// + // user002 + user = new UsmUser(new OctetString("user001"), + null, + null, + null, null); + usm.addUser(user.getSecurityName(), usm.getLocalEngineID(), user); + //===============================================================// + + user = new UsmUser(new OctetString("v3notify"), + null, + null, + null, + null); + usm.addUser(user.getSecurityName(), null, user); + + this.usm = usm; + } + + private static DefaultMOTable createStaticIfXTable() { + MOTableSubIndex[] subIndexes = + new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) }; + MOTableIndex indexDef = new MOTableIndex(subIndexes, false); + MOColumn[] columns = new MOColumn[19]; + int c = 0; + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, + MOAccessImpl.ACCESS_READ_ONLY); // ifName + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifInMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifInBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifOutMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifOutBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInOctets + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInUcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCInBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutOctets + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutUcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutMulticastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_COUNTER32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHCOutBroadcastPkts + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_WRITE); // ifLinkUpDownTrapEnable + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, + MOAccessImpl.ACCESS_READ_ONLY); // ifHighSpeed + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_WRITE); // ifPromiscuousMode + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifConnectorPresent + columns[c++] = + new MOMutableColumn(c, SMIConstants.SYNTAX_OCTET_STRING, // ifAlias + MOAccessImpl.ACCESS_READ_WRITE, null); + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_TIMETICKS, + MOAccessImpl.ACCESS_READ_ONLY); // ifCounterDiscontinuityTime + + DefaultMOTable ifXTable = + new DefaultMOTable(new OID("1.3.6.1.2.1.31.1.1.1"), indexDef, columns); + MOMutableTableModel model = (MOMutableTableModel) ifXTable.getModel(); + Variable[] rowValues1 = new Variable[] { + new OctetString("Ethernet-0"), + new Integer32(1), + new Integer32(2), + new Integer32(3), + new Integer32(4), + new Integer32(5), + new Integer32(6), + new Integer32(7), + new Integer32(8), + new Integer32(9), + new Integer32(10), + new Integer32(11), + new Integer32(12), + new Integer32(13), + new Integer32(14), + new Integer32(15), + new Integer32(16), + new OctetString("My eth"), + new TimeTicks(1000) + }; + Variable[] rowValues2 = new Variable[] { + new OctetString("Loopback"), + new Integer32(21), + new Integer32(22), + new Integer32(23), + new Integer32(24), + new Integer32(25), + new Integer32(26), + new Integer32(27), + new Integer32(28), + new Integer32(29), + new Integer32(30), + new Integer32(31), + new Integer32(32), + new Integer32(33), + new Integer32(34), + new Integer32(35), + new Integer32(36), + new OctetString("My loop"), + new TimeTicks(2000) + }; + model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1)); + model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2)); + ifXTable.setVolatile(true); + return ifXTable; + } + + private static DefaultMOTable createStaticIfTable() { + MOTableSubIndex[] subIndexes = + new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) }; + MOTableIndex indexDef = new MOTableIndex(subIndexes, false); + MOColumn[] columns = new MOColumn[8]; + int c = 0; + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifIndex + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, + MOAccessImpl.ACCESS_READ_ONLY); // ifDescr + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifType + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifMtu + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, + MOAccessImpl.ACCESS_READ_ONLY); // ifSpeed + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, + MOAccessImpl.ACCESS_READ_ONLY); // ifPhysAddress + columns[c++] = + new MOMutableColumn(c, SMIConstants.SYNTAX_INTEGER, // ifAdminStatus + MOAccessImpl.ACCESS_READ_WRITE, null); + columns[c++] = + new MOColumn(c, SMIConstants.SYNTAX_INTEGER, + MOAccessImpl.ACCESS_READ_ONLY); // ifOperStatus - UsmUser user = new UsmUser(new OctetString(securityName), authenticationProtocol, new OctetString(authenticationPassphrase), privacyProtocol, new OctetString(privacyPassphrase)); + DefaultMOTable ifTable = + new DefaultMOTable(new OID("1.3.6.1.2.1.2.2.1"), indexDef, columns); + MOMutableTableModel model = (MOMutableTableModel) ifTable.getModel(); + Variable[] rowValues1 = new Variable[] { + new Integer32(1), + new OctetString("eth0"), + new Integer32(6), + new Integer32(1500), + new Gauge32(100000000), + new OctetString("00:00:00:00:01"), + new Integer32(1), + new Integer32(1) + }; + Variable[] rowValues2 = new Variable[] { + new Integer32(2), + new OctetString("loopback"), + new Integer32(24), + new Integer32(1500), + new Gauge32(10000000), + new OctetString("00:00:00:00:02"), + new Integer32(1), + new Integer32(1) + }; + model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1)); + model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2)); + ifTable.setVolatile(true); + return ifTable; } - public void initV3(UsmUser user, String securityName) { -// snmp.getUSM().addUser(user); + private static DefaultMOTable createStaticSnmp4sTable() { + MOTableSubIndex[] subIndexes = + new MOTableSubIndex[] { new MOTableSubIndex(SMIConstants.SYNTAX_INTEGER) }; + MOTableIndex indexDef = new MOTableIndex(subIndexes, false); + MOColumn[] columns = new MOColumn[8]; + int c = 0; + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_NULL, MOAccessImpl.ACCESS_READ_ONLY); // testNull + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // testBoolean + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // ifType + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); // ifMtu + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_GAUGE32, MOAccessImpl.ACCESS_READ_ONLY); // ifSpeed + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_OCTET_STRING, MOAccessImpl.ACCESS_READ_ONLY); //ifPhysAddress + columns[c++] = new MOMutableColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_WRITE, + null); + // ifAdminStatus + columns[c++] = new MOColumn(c, SMIConstants.SYNTAX_INTEGER, MOAccessImpl.ACCESS_READ_ONLY); + // ifOperStatus + + DefaultMOTable ifTable = + new DefaultMOTable(new OID("1.3.6.1.4.1.50000.1.1"), indexDef, columns); + MOMutableTableModel model = (MOMutableTableModel) ifTable.getModel(); + Variable[] rowValues1 = new Variable[] { + new Integer32(1), + new OctetString("eth0"), + new Integer32(6), + new Integer32(1500), + new Gauge32(100000000), + new OctetString("00:00:00:00:01"), + new Integer32(1), + new Integer32(1) + }; + Variable[] rowValues2 = new Variable[] { + new Integer32(2), + new OctetString("loopback"), + new Integer32(24), + new Integer32(1500), + new Gauge32(10000000), + new OctetString("00:00:00:00:02"), + new Integer32(1), + new Integer32(1) + }; + model.addRow(new DefaultMOMutableRow2PC(new OID("1"), rowValues1)); + model.addRow(new DefaultMOMutableRow2PC(new OID("2"), rowValues2)); + ifTable.setVolatile(true); + return ifTable; + } + + protected void initTransportMappings() throws IOException { + transportMappings = new TransportMapping[2]; + Address addr = GenericAddress.parse(address); + TransportMapping tm = + TransportMappings.getInstance().createTransportMapping(addr); + transportMappings[0] = tm; + transportMappings[1] = new DefaultTcpTransportMapping(new TcpAddress(address)); + } + + public void start(String ip, String port) throws IOException { + address = ip + "/" + port; + //BasicConfigurator.configure(); + init(); + addShutdownHook(); +// loadConfig(ImportModes.REPLACE_CREATE); + getServer().addContext(new OctetString("public")); + finishInit(); + run(); + sendColdStartNotification(); + } + + protected void unregisterManagedObjects() { + // here we should unregister those objects previously registered... + } + + protected void addCommunities(SnmpCommunityMIB communityMIB) { + Variable[] com2sec = new Variable[] { + new OctetString("public"), // community name + new OctetString("cpublic"), // security name + getAgent().getContextEngineID(), // local engine ID + new OctetString("public"), // default context name + new OctetString(), // transport tag + new Integer32(StorageType.nonVolatile), // storage type + new Integer32(RowStatus.active) // row status + }; + MOTableRow row = + communityMIB.getSnmpCommunityEntry().createRow( + new OctetString("public2public").toSubIndex(true), com2sec); + communityMIB.getSnmpCommunityEntry().addRow((SnmpCommunityMIB.SnmpCommunityEntryRow) row); +// snmpCommunityMIB.setSourceAddressFiltering(true); + } + + protected void registerSnmpMIBs() { + heartbeatMIB = new Snmp4jHeartbeatMib(super.getNotificationOriginator(), + new OctetString(), + super.snmpv2MIB.getSysUpTime()); + agentppSimulationMIB = new AgentppSimulationMib(); + super.registerSnmpMIBs(); + } - UserTarget userTarget = new UserTarget(); - userTarget.setSecurityName(new OctetString(securityName)); - userTarget.setSecurityLevel(SecurityLevel.AUTH_PRIV); + protected void initMessageDispatcher() { + this.dispatcher = new MessageDispatcherImpl(); + this.mpv3 = new MPv3(this.agent.getContextEngineID().getValue()); + this.usm = new USM(SecurityProtocols.getInstance(), this.agent.getContextEngineID(), this.updateEngineBoots()); + SecurityModels.getInstance().addSecurityModel(this.usm); + SecurityProtocols.getInstance().addDefaultProtocols(); + this.dispatcher.addMessageProcessingModel(new MPv1()); + this.dispatcher.addMessageProcessingModel(new MPv2c()); + this.dispatcher.addMessageProcessingModel(this.mpv3); + this.initSnmpSession(); } -} +} \ No newline at end of file diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java similarity index 98% rename from common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java rename to common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java index 29a8c085c8..417204296c 100644 --- a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTest.java +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV2.java @@ -19,7 +19,7 @@ import java.io.IOException; import java.util.Map; import java.util.Scanner; -public class SnmpTest { +public class SnmpTestV2 { public static void main(String[] args) throws IOException { SnmpDeviceSimulatorV2 device = new SnmpDeviceSimulatorV2(1610, "public"); diff --git a/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java new file mode 100644 index 0000000000..8e40deb17d --- /dev/null +++ b/common/transport/snmp/src/test/java/org/thingsboard/server/transport/snmp/SnmpTestV3.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.snmp; + +import org.snmp4j.CommandResponderEvent; +import org.snmp4j.agent.CommandProcessor; +import org.snmp4j.mp.MPv3; +import org.snmp4j.smi.OctetString; + +import java.io.IOException; +import java.util.Map; +import java.util.Scanner; + +public class SnmpTestV3 { + public static void main(String[] args) throws IOException { + SnmpDeviceSimulatorV3 device = new SnmpDeviceSimulatorV3(new CommandProcessor(new OctetString(MPv3.createLocalEngineID())) { + @Override + public void processPdu(CommandResponderEvent event) { + System.out.println("event: " + event); + } + }); + device.start("0.0.0.0", "1610"); + + device.setUpMappings(Map.of( + ".1.3.6.1.2.1.1.1.50", "12", + ".1.3.6.1.2.1.2.1.52", "56", + ".1.3.6.1.2.1.3.1.54", "yes", + ".1.3.6.1.2.1.7.1.58", "" + )); + + new Scanner(System.in).nextLine(); + } +} diff --git a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json index f8291a090b..f74ebca0bf 100644 --- a/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json +++ b/common/transport/snmp/src/test/resources/snmp-device-profile-transport-config.json @@ -38,16 +38,6 @@ "dataType": "STRING" } ] - }, - { - "spec": "TELEMETRY_TRAPS_RECEIVING", - "mappings": [ - { - "oid": ".1.3.6.1.2.8.7.1.56", - "key": "temperature_trap", - "dataType": "LONG" - } - ] } ] } diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json new file mode 100644 index 0000000000..039e03fa53 --- /dev/null +++ b/common/transport/snmp/src/test/resources/snmp-device-transport-config-v3.json @@ -0,0 +1,13 @@ +{ + "address": "192.168.3.23", + "port": 1610, + "protocolVersion": "V3", + + "username": "tb-user", + "engineId": "qwertyuioa", + "securityName": "tb-user", + "authenticationProtocol": "SHA_512", + "authenticationPassphrase": "sdfghjkloifgh", + "privacyProtocol": "DES", + "privacyPassphrase": "rtytguijokod" +} \ No newline at end of file diff --git a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json index 9a97b75e40..c73d817bfb 100644 --- a/common/transport/snmp/src/test/resources/snmp-device-transport-config.json +++ b/common/transport/snmp/src/test/resources/snmp-device-transport-config.json @@ -1,6 +1,6 @@ { "address": "127.0.0.1", "port": 1610, - "password": "public", + "community": "public", "protocolVersion": "V2C" } \ No newline at end of file From 415bf570bac6b422f880622163924464d6ca390b Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 7 Apr 2021 19:22:28 +0300 Subject: [PATCH 2/4] Refactor --- .../SnmpDeviceTransportConfiguration.java | 26 +++++++-- ...mpDeviceProfileTransportConfiguration.java | 18 +++++-- ...eatingQueryingSnmpCommunicationConfig.java | 2 +- ...ributesSettingSnmpCommunicationConfig.java | 5 -- .../snmp/configs/SnmpCommunicationConfig.java | 8 +-- .../transport/snmp/SnmpAuthService.java | 3 +- .../transport/snmp/SnmpTransportContext.java | 54 ++++++++++--------- .../snmp/service/SnmpTransportService.java | 27 ++++++++-- .../snmp/session/DeviceSessionContext.java | 14 ++--- 9 files changed, 98 insertions(+), 59 deletions(-) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 24ab463129..68a9d1f218 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.server.common.data.device.data; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import lombok.ToString; +import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.transport.snmp.AuthenticationProtocol; import org.thingsboard.server.common.data.transport.snmp.PrivacyProtocol; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; +import java.util.Objects; + @Data +@ToString(of = {"host", "port", "protocolVersion"}) public class SnmpDeviceTransportConfiguration implements DeviceTransportConfiguration { - private String address; - private int port; + private String host; + private Integer port; private SnmpProtocolVersion protocolVersion; /* @@ -60,6 +65,21 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur @JsonIgnore private boolean isValid() { - return true; + boolean isValid = StringUtils.isNotBlank(host) && port != null && protocolVersion != null; + if (isValid) { + switch (protocolVersion) { + case V1: + case V2C: + isValid = StringUtils.isNotEmpty(community); + break; + case V3: + isValid = StringUtils.isNotBlank(username) && StringUtils.isNotBlank(securityName) + && contextName != null && authenticationProtocol != null + && StringUtils.isNotBlank(authenticationPassphrase) + && privacyProtocol != null && privacyPassphrase != null && engineId != null; + break; + } + } + return isValid; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index 843a51304e..5d6afc7155 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -17,15 +17,21 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; +import org.apache.commons.lang3.ArrayUtils; import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; +import java.util.Collections; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; @Data public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { - private int timeoutMs; - private int retries; + private Integer timeoutMs; + private Integer retries; private List communicationConfigs; @Override @@ -36,12 +42,16 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra @Override public void validate() { if (!isValid()) { - throw new IllegalArgumentException("Transport configuration is not valid"); + throw new IllegalArgumentException("SNMP transport configuration is not valid"); } } @JsonIgnore private boolean isValid() { - return true; + return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 + && communicationConfigs != null && !communicationConfigs.isEmpty() + && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) + && communicationConfigs.stream().flatMap(config -> config.getMappings().stream()).map(SnmpMapping::getOid) + .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getMappings().size()).sum(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java index 764bf97b15..19a009457a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java @@ -31,6 +31,6 @@ public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommu @Override public boolean isValid() { - return true; + return super.isValid() && queryingFrequencyMs != null && queryingFrequencyMs > 0; } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java index 1c63dae618..74c67f957a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java @@ -28,9 +28,4 @@ public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunic public SnmpMethod getMethod() { return SnmpMethod.SET; } - - @Override - public boolean isValid() { - return true; - } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java index 7e1ee27237..acc0df0dd7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java @@ -49,12 +49,6 @@ public abstract class SnmpCommunicationConfig { @JsonIgnore public boolean isValid() { - return true; - } - - public void validate() { - if (!isValid()) { - throw new IllegalArgumentException("Communication config is not valid"); - } + return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid()); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java index 59b31d593c..81187fb33e 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java @@ -24,7 +24,6 @@ import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; -import org.snmp4j.security.UsmUser; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; @@ -98,7 +97,7 @@ public class SnmpAuthService { throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); } - target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getAddress() + "/" + deviceTransportConfig.getPort())); + target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort())); target.setTimeout(profileTransportConfig.getTimeoutMs()); target.setRetries(profileTransportConfig.getRetries()); target.setVersion(protocolVersion.getCode()); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index 2b0ce984aa..d7441c7c1d 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -89,18 +89,12 @@ public class SnmpTransportContext extends TransportContext { managedDevicesIds.stream() .map(protoEntityService::getDeviceById) .collect(Collectors.toList()) - .forEach(device -> { - try { - establishDeviceSession(device); - } catch (Exception e) { - log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); - } - }); + .forEach(this::establishDeviceSession); } private void establishDeviceSession(Device device) { if (device == null) return; - log.info("Establishing SNMP device session for device {}", device.getId()); + log.info("Establishing SNMP session for device {}", device.getId()); DeviceProfileId deviceProfileId = device.getDeviceProfileId(); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); @@ -114,18 +108,24 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceProfileTransportConfiguration profileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration deviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - DeviceSessionContext deviceSessionContext = new DeviceSessionContext( - device, deviceProfile, credentials.getCredentialsId(), - profileTransportConfiguration, deviceTransportConfiguration, this - ); - registerSessionMsgListener(deviceSessionContext); + DeviceSessionContext deviceSessionContext; + try { + deviceSessionContext = new DeviceSessionContext( + device, deviceProfile, credentials.getCredentialsId(), + profileTransportConfiguration, deviceTransportConfiguration, this + ); + registerSessionMsgListener(deviceSessionContext); + } catch (Exception e) { + log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); + return; + } sessions.put(device.getId(), deviceSessionContext); snmpTransportService.createQueryingTasks(deviceSessionContext); log.info("Established SNMP device session for device {}", device.getId()); } private void updateDeviceSession(DeviceSessionContext sessionContext, Device device, DeviceProfile deviceProfile) { - log.info("Updating SNMP device session for device {}", device.getId()); + log.info("Updating SNMP session for device {}", device.getId()); DeviceCredentials credentials = protoEntityService.getDeviceCredentialsByDeviceId(device.getId()); if (credentials.getCredentialsType() != DeviceCredentialsType.ACCESS_TOKEN) { @@ -137,16 +137,20 @@ public class SnmpTransportContext extends TransportContext { SnmpDeviceProfileTransportConfiguration newProfileTransportConfiguration = (SnmpDeviceProfileTransportConfiguration) deviceProfile.getProfileData().getTransportConfiguration(); SnmpDeviceTransportConfiguration newDeviceTransportConfiguration = (SnmpDeviceTransportConfiguration) device.getDeviceData().getTransportConfiguration(); - if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { - sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); - sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); - snmpTransportService.cancelQueryingTasks(sessionContext); - snmpTransportService.createQueryingTasks(sessionContext); - } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { - sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); - sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); - } else { - log.trace("Configuration of the device {} was not updated", device); + try { + if (!newProfileTransportConfiguration.equals(sessionContext.getProfileTransportConfiguration())) { + sessionContext.setProfileTransportConfiguration(newProfileTransportConfiguration); + sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + snmpTransportService.cancelQueryingTasks(sessionContext); + snmpTransportService.createQueryingTasks(sessionContext); + } else if (!newDeviceTransportConfiguration.equals(sessionContext.getDeviceTransportConfiguration())) { + sessionContext.setDeviceTransportConfiguration(newDeviceTransportConfiguration); + sessionContext.initializeTarget(newProfileTransportConfiguration, newDeviceTransportConfiguration); + } else { + log.trace("Configuration of the device {} was not updated", device); + } + } catch (Exception e) { + log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); } } @@ -156,8 +160,8 @@ public class SnmpTransportContext extends TransportContext { sessionContext.close(); snmpAuthService.cleanUpSnmpAuthInfo(sessionContext); transportService.deregisterSession(sessionContext.getSessionInfo()); - sessions.remove(sessionContext.getDeviceId()); snmpTransportService.cancelQueryingTasks(sessionContext); + sessions.remove(sessionContext.getDeviceId()); log.trace("Unregistered and removed session"); } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 2f26bfa98c..94a18f3d54 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -28,9 +28,11 @@ import org.snmp4j.mp.MPv3; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; +import org.snmp4j.smi.Integer32; import org.snmp4j.smi.Null; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Variable; import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; @@ -143,7 +145,7 @@ public class SnmpTransportService implements TbTransportService { sendRequest(sessionContext, communicationConfig); } } catch (Exception e) { - log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.getMessage()); + log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); } }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); } @@ -192,7 +194,24 @@ public class SnmpTransportService implements TbTransportService { pdu.addAll(communicationConfig.getMappings().stream() .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) - .map(value -> new VariableBinding(new OID(mapping.getOid()), new OctetString(values.get(mapping.getKey())))) + .map(value -> { + Variable variable; + switch (mapping.getDataType()) { + case LONG: + try { + variable = new Integer32(Integer.parseInt(value)); + break; + } catch (NumberFormatException ignored) { + } + case DOUBLE: + case BOOLEAN: + case STRING: + case JSON: + default: + variable = new OctetString(value); + } + return new VariableBinding(new OID(mapping.getOid()), variable); + }) .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) .collect(Collectors.toList())); @@ -267,7 +286,9 @@ public class SnmpTransportService implements TbTransportService { responses.forEach((spec, response) -> { Optional.ofNullable(responseProcessors.get(spec)) .ifPresent(responseProcessor -> { - responseProcessor.accept(response, sessionContext); + if (!response.entrySet().isEmpty()) { + responseProcessor.accept(response, sessionContext); + } }); }); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index c5a329547f..b3a38545a8 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -63,8 +63,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final Device device; private final SnmpTransportContext snmpTransportContext; - private final SnmpTransportService snmpTransportService; - private final SnmpAuthService snmpAuthService; @Getter @Setter @@ -80,7 +78,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, SnmpDeviceTransportConfiguration deviceTransportConfiguration, - SnmpTransportContext snmpTransportContext) { + SnmpTransportContext snmpTransportContext) throws Exception { super(UUID.randomUUID()); super.setDeviceId(device.getId()); super.setDeviceProfile(deviceProfile); @@ -88,8 +86,6 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S this.token = token; this.snmpTransportContext = snmpTransportContext; - this.snmpTransportService = snmpTransportContext.getSnmpTransportService(); - this.snmpAuthService = snmpTransportContext.getSnmpAuthService(); this.profileTransportConfiguration = profileTransportConfiguration; this.deviceTransportConfiguration = deviceTransportConfiguration; @@ -113,13 +109,13 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onResponse(ResponseEvent event) { if (isActive) { - snmpTransportService.processResponseEvent(this, event); + snmpTransportContext.getSnmpTransportService().processResponseEvent(this, event); } } - public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) { + public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) throws Exception { log.trace("Initializing target for SNMP session of device {}", device); - this.target = snmpAuthService.setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); + this.target = snmpTransportContext.getSnmpAuthService().setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); log.info("SNMP target initialized: {}", target); } @@ -152,7 +148,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() )); try { - snmpTransportService.sendRequest(this, communicationConfig, sharedAttributes); + snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); } catch (Exception e) { log.error("Failed to send request with shared attributes to SNMP device {}: {}", getDeviceId(), e.getMessage()); } From 783b959577be49cbf020f379e09a0043dfcd95a9 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Wed, 14 Apr 2021 15:51:13 +0300 Subject: [PATCH 3/4] Refactor --- ...mpDeviceProfileTransportConfiguration.java | 11 +- .../transport/snmp/SnmpCommunicationSpec.java | 21 +- .../data/transport/snmp/SnmpMapping.java | 7 +- ...ltipleMappingsSnmpCommunicationConfig.java | 36 +++ ...eatingQueryingSnmpCommunicationConfig.java | 6 +- .../SnmpCommunicationConfig.java | 24 +- ...ibutesQueryingSnmpCommunicationConfig.java | 5 +- ...ributesSettingSnmpCommunicationConfig.java | 7 +- ...emetryQueryingSnmpCommunicationConfig.java | 5 +- ...CommandSettingSnmpCommunicationConfig.java | 59 +++++ ...sponseQueryingSnmpCommunicationConfig.java | 60 +++++ .../transport/snmp/SnmpTransportContext.java | 7 +- .../transport/snmp/service/PduMapper.java | 150 +++++++++++ .../snmp/{ => service}/SnmpAuthService.java | 8 +- .../snmp/service/SnmpTransportService.java | 244 ++++++------------ .../snmp/session/DeviceSessionContext.java | 38 +-- 16 files changed, 454 insertions(+), 234 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config}/RepeatingQueryingSnmpCommunicationConfig.java (88%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config}/SnmpCommunicationConfig.java (75%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config/impl}/ClientAttributesQueryingSnmpCommunicationConfig.java (89%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config/impl}/SharedAttributesSettingSnmpCommunicationConfig.java (87%) rename common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/{configs => config/impl}/TelemetryQueryingSnmpCommunicationConfig.java (90%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java create mode 100644 common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java rename common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/{ => service}/SnmpAuthService.java (94%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index 5d6afc7155..bbf074eeda 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -17,16 +17,11 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnore; import lombok.Data; -import org.apache.commons.lang3.ArrayUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import java.util.Collections; import java.util.List; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; @Data public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTransportConfiguration { @@ -51,7 +46,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 && communicationConfigs != null && !communicationConfigs.isEmpty() && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) - && communicationConfigs.stream().flatMap(config -> config.getMappings().stream()).map(SnmpMapping::getOid) - .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getMappings().size()).sum(); + && communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid) + .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index a2edf75968..a6643ecf1e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -16,22 +16,11 @@ package org.thingsboard.server.common.data.transport.snmp; public enum SnmpCommunicationSpec { - TELEMETRY_QUERYING(true), - CLIENT_ATTRIBUTES_QUERYING(true), + TELEMETRY_QUERYING, - SHARED_ATTRIBUTES_SETTING; + CLIENT_ATTRIBUTES_QUERYING, + SHARED_ATTRIBUTES_SETTING, - private final boolean isRepeatingQuerying; - - SnmpCommunicationSpec() { - this.isRepeatingQuerying = false; - } - - SnmpCommunicationSpec(boolean isRepeatingQuerying) { - this.isRepeatingQuerying = isRepeatingQuerying; - } - - public boolean isRepeatingQuerying() { - return isRepeatingQuerying; - } + TO_DEVICE_RPC_COMMAND_SETTING, + TO_DEVICE_RPC_RESPONSE_QUERYING } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java index 0269491461..d787f7446f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpMapping.java @@ -16,13 +16,17 @@ package org.thingsboard.server.common.data.transport.snmp; import com.fasterxml.jackson.annotation.JsonIgnore; +import lombok.AllArgsConstructor; import lombok.Data; +import lombok.NoArgsConstructor; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.kv.DataType; import java.util.regex.Pattern; @Data +@AllArgsConstructor +@NoArgsConstructor public class SnmpMapping { private String oid; private String key; @@ -32,7 +36,6 @@ public class SnmpMapping { @JsonIgnore public boolean isValid() { - return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && - StringUtils.isNotBlank(key) && dataType != null; + return StringUtils.isNotEmpty(oid) && OID_PATTERN.matcher(oid).matches() && StringUtils.isNotBlank(key); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java new file mode 100644 index 0000000000..f785c8788d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/MultipleMappingsSnmpCommunicationConfig.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.transport.snmp.config; + +import lombok.Data; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; + +import java.util.List; + +@Data +public abstract class MultipleMappingsSnmpCommunicationConfig implements SnmpCommunicationConfig { + protected List mappings; + + @Override + public boolean isValid() { + return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid()); + } + + @Override + public List getAllMappings() { + return mappings; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java similarity index 88% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java index 19a009457a..38451d4142 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/RepeatingQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/RepeatingQueryingSnmpCommunicationConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config; import lombok.Data; import lombok.EqualsAndHashCode; @@ -21,7 +21,7 @@ import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; @EqualsAndHashCode(callSuper = true) @Data -public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommunicationConfig { +public abstract class RepeatingQueryingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { private Long queryingFrequencyMs; @Override @@ -31,6 +31,6 @@ public abstract class RepeatingQueryingSnmpCommunicationConfig extends SnmpCommu @Override public boolean isValid() { - return super.isValid() && queryingFrequencyMs != null && queryingFrequencyMs > 0; + return queryingFrequencyMs != null && queryingFrequencyMs > 0 && super.isValid(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java similarity index 75% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java index acc0df0dd7..e2ba29abfa 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/SnmpCommunicationConfig.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config; import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -23,6 +23,9 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ClientAttributesQueryingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.SharedAttributesSettingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; import java.util.List; @@ -33,22 +36,19 @@ import java.util.List; @Type(value = ClientAttributesQueryingSnmpCommunicationConfig.class, name = "CLIENT_ATTRIBUTES_QUERYING"), @Type(value = SharedAttributesSettingSnmpCommunicationConfig.class, name = "SHARED_ATTRIBUTES_SETTING") }) -public abstract class SnmpCommunicationConfig { - protected List mappings; +public interface SnmpCommunicationConfig { - public List getMappings() { - return mappings; - } - - public abstract SnmpCommunicationSpec getSpec(); + SnmpCommunicationSpec getSpec(); @JsonIgnore - public SnmpMethod getMethod() { + default SnmpMethod getMethod() { return null; } @JsonIgnore - public boolean isValid() { - return mappings != null && !mappings.isEmpty() && mappings.stream().allMatch(mapping -> mapping != null && mapping.isValid()); - } + List getAllMappings(); + + @JsonIgnore + boolean isValid(); + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java similarity index 89% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java index 16198f8ef6..51bd2198e9 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/ClientAttributesQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ClientAttributesQueryingSnmpCommunicationConfig.java @@ -13,13 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config.impl; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; public class ClientAttributesQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java similarity index 87% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java index 74c67f957a..441395d279 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/SharedAttributesSettingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/SharedAttributesSettingSnmpCommunicationConfig.java @@ -13,12 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config.impl; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.MultipleMappingsSnmpCommunicationConfig; + +public class SharedAttributesSettingSnmpCommunicationConfig extends MultipleMappingsSnmpCommunicationConfig { -public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunicationConfig { @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING; @@ -28,4 +30,5 @@ public class SharedAttributesSettingSnmpCommunicationConfig extends SnmpCommunic public SnmpMethod getMethod() { return SnmpMethod.SET; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java similarity index 90% rename from common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java rename to common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java index 68d3c81ed2..c3f82d64ec 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/configs/TelemetryQueryingSnmpCommunicationConfig.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/TelemetryQueryingSnmpCommunicationConfig.java @@ -13,17 +13,20 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data.transport.snmp.configs; +package org.thingsboard.server.common.data.transport.snmp.config.impl; import lombok.Data; import lombok.EqualsAndHashCode; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; @EqualsAndHashCode(callSuper = true) @Data public class TelemetryQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + @Override public SnmpCommunicationSpec getSpec() { return SnmpCommunicationSpec.TELEMETRY_QUERYING; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..97f3de047b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java @@ -0,0 +1,59 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.transport.snmp.config.impl; + +import lombok.Data; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Data +public class ToDeviceRpcCommandSettingSnmpCommunicationConfig implements SnmpCommunicationConfig { + private SnmpMapping mapping; + + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING; + } + + @Override + public SnmpMethod getMethod() { + return SnmpMethod.SET; + } + + public void setMapping(SnmpMapping mapping) { + this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_COMMAND_KEY_NAME, DataType.STRING) : null; + } + + @Override + public List getAllMappings() { + return Collections.singletonList(mapping); + } + + @Override + public boolean isValid() { + return mapping != null && mapping.isValid(); + } + + public static final String RPC_COMMAND_KEY_NAME = "rpcCommand"; + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java new file mode 100644 index 0000000000..18bce94980 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.transport.snmp.config.impl; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; + +import java.util.Collections; +import java.util.List; + +@EqualsAndHashCode(callSuper = true) +@Data +public class ToDeviceRpcResponseQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { + private SnmpMapping mapping; + + @Override + public SnmpCommunicationSpec getSpec() { + return SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING; + } + + @Override + public SnmpMethod getMethod() { + return SnmpMethod.GET; + } + + public void setMapping(SnmpMapping mapping) { + this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_RESPONSE_KEY_NAME, DataType.STRING) : null; + } + + @Override + public List getAllMappings() { + return Collections.singletonList(mapping); + } + + @Override + public boolean isValid() { + return true; + } + + public static final String RPC_RESPONSE_KEY_NAME = "rpcResponse"; + +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index d7441c7c1d..d66d09ab0b 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -42,6 +42,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.service.ProtoTransportEntityService; +import org.thingsboard.server.transport.snmp.service.SnmpAuthService; import org.thingsboard.server.transport.snmp.service.SnmpTransportBalancingService; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; @@ -116,7 +117,7 @@ public class SnmpTransportContext extends TransportContext { ); registerSessionMsgListener(deviceSessionContext); } catch (Exception e) { - log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.getMessage()); + log.error("Failed to establish session for SNMP device {}: {}", device.getId(), e.toString()); return; } sessions.put(device.getId(), deviceSessionContext); @@ -177,7 +178,9 @@ public class SnmpTransportContext extends TransportContext { ); transportService.registerAsyncSession(sessionInfo, deviceSessionContext); - transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), null); + transportService.process(sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg.newBuilder().build(), TransportServiceCallback.EMPTY); + transportService.process(sessionInfo, TransportProtos.SubscribeToRPCMsg.newBuilder().build(), TransportServiceCallback.EMPTY); + deviceSessionContext.setSessionInfo(sessionInfo); deviceSessionContext.setDeviceInfo(msg.getDeviceInfo()); } else { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java new file mode 100644 index 0000000000..a3b5f77dfb --- /dev/null +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java @@ -0,0 +1,150 @@ +/** + * Copyright © 2016-2021 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.transport.snmp.service; + +import com.google.gson.JsonObject; +import lombok.extern.slf4j.Slf4j; +import org.snmp4j.PDU; +import org.snmp4j.ScopedPDU; +import org.snmp4j.smi.Integer32; +import org.snmp4j.smi.Null; +import org.snmp4j.smi.OID; +import org.snmp4j.smi.OctetString; +import org.snmp4j.smi.Variable; +import org.snmp4j.smi.VariableBinding; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.queue.util.TbSnmpTransportComponent; +import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +@TbSnmpTransportComponent +@Service +@Slf4j +public class PduMapper { + public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU pdu; + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); + switch (snmpVersion) { + case V1: + case V2C: + pdu = new PDU(); + break; + case V3: + ScopedPDU scopedPdu = new ScopedPDU(); + scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); + scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); + pdu = scopedPdu; + break; + default: + throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + } + + pdu.setType(communicationConfig.getMethod().getCode()); + pdu.addAll(communicationConfig.getAllMappings().stream() + .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) + .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) + .map(value -> { + Variable variable = toSnmpVariable(mapping, value); + return new VariableBinding(new OID(mapping.getOid()), variable); + }) + .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) + .collect(Collectors.toList())); + + return pdu; + } + + private Variable toSnmpVariable(SnmpMapping mapping, String value) { + Variable variable; + switch (mapping.getDataType()) { + case LONG: + try { + variable = new Integer32(Integer.parseInt(value)); + break; + } catch (NumberFormatException ignored) { + } + case DOUBLE: + case BOOLEAN: + case STRING: + case JSON: + default: + variable = new OctetString(value); + } + return variable; + } + + + public JsonObject processPdu(PDU pdu, DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { + List variablesBindings = IntStream.range(0, pdu.size()) + .mapToObj(pdu::get) + .filter(Objects::nonNull) + .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) + .collect(Collectors.toList()); + JsonObject data = new JsonObject(); + + Map mappings = new HashMap<>(); + for (SnmpMapping mapping : communicationConfig.getAllMappings()) { + OID oid = new OID(mapping.getOid()); + mappings.put(oid, mapping); + } + + variablesBindings.forEach(variableBinding -> { + log.trace("Processing variable binding: {}", variableBinding); + + OID oid = variableBinding.getOid(); + SnmpMapping mapping = mappings.get(oid); + if (mapping == null) { + log.debug("No SNMP mapping for oid {}", oid); + return; + } + + processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), data); + }); + + return data; + } + + + private void processValue(String key, DataType dataType, String value, JsonObject result) { + switch (dataType) { + case LONG: + result.addProperty(key, Long.parseLong(value)); + break; + case BOOLEAN: + result.addProperty(key, Boolean.parseBoolean(value)); + break; + case DOUBLE: + result.addProperty(key, Double.parseDouble(value)); + break; + case STRING: + case JSON: + default: + result.addProperty(key, value); + } + } +} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java similarity index 94% rename from common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java rename to common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java index 81187fb33e..c3ed188bb0 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpAuthService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpAuthService.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.transport.snmp; +package org.thingsboard.server.transport.snmp.service; import lombok.RequiredArgsConstructor; import org.snmp4j.AbstractTarget; @@ -24,6 +24,7 @@ import org.snmp4j.security.SecurityLevel; import org.snmp4j.security.SecurityModel; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; +import org.snmp4j.smi.Address; import org.snmp4j.smi.GenericAddress; import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; @@ -36,6 +37,8 @@ import org.thingsboard.server.queue.util.TbSnmpTransportComponent; import org.thingsboard.server.transport.snmp.service.SnmpTransportService; import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; +import java.util.Optional; + @Service @TbSnmpTransportComponent @RequiredArgsConstructor @@ -97,7 +100,8 @@ public class SnmpAuthService { throw new UnsupportedOperationException("SNMP protocol version " + protocolVersion + " is not supported"); } - target.setAddress(GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort())); + Address address = GenericAddress.parse(snmpUnderlyingProtocol + ":" + deviceTransportConfig.getHost() + "/" + deviceTransportConfig.getPort()); + target.setAddress(Optional.ofNullable(address).orElseThrow(() -> new IllegalArgumentException("Address of the SNMP device is invalid"))); target.setTimeout(profileTransportConfig.getTimeoutMs()); target.setRetries(profileTransportConfig.getRetries()); target.setVersion(protocolVersion.getCode()); diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index 94a18f3d54..f672e36190 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -16,11 +16,11 @@ package org.thingsboard.server.transport.snmp.service; import com.google.gson.JsonObject; +import lombok.Data; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.snmp4j.PDU; -import org.snmp4j.ScopedPDU; import org.snmp4j.Snmp; import org.snmp4j.TransportMapping; import org.snmp4j.event.ResponseEvent; @@ -28,26 +28,18 @@ import org.snmp4j.mp.MPv3; import org.snmp4j.security.SecurityModels; import org.snmp4j.security.SecurityProtocols; import org.snmp4j.security.USM; -import org.snmp4j.smi.Integer32; -import org.snmp4j.smi.Null; -import org.snmp4j.smi.OID; import org.snmp4j.smi.OctetString; -import org.snmp4j.smi.Variable; -import org.snmp4j.smi.VariableBinding; import org.snmp4j.transport.DefaultTcpTransportMapping; import org.snmp4j.transport.DefaultUdpTransportMapping; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; -import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; -import org.thingsboard.server.common.data.transport.snmp.configs.RepeatingQueryingSnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.configs.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcResponseQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -60,44 +52,37 @@ import javax.annotation.PreDestroy; import java.io.IOException; import java.util.Collections; import java.util.EnumMap; -import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; -import java.util.function.BiConsumer; import java.util.stream.Collectors; @TbSnmpTransportComponent @Service @Slf4j +@RequiredArgsConstructor public class SnmpTransportService implements TbTransportService { private final TransportService transportService; + private final PduMapper pduMapper; @Getter private Snmp snmp; private ScheduledExecutorService queryingExecutor; private ExecutorService responseProcessingExecutor; - private final Map> responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); + private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); @Value("${transport.snmp.response_processing.parallelism_level}") private Integer responseProcessingParallelismLevel; @Value("${transport.snmp.underlying_protocol}") private String snmpUnderlyingProtocol; - public SnmpTransportService(TransportService transportService) { - this.transportService = transportService; - } - @PostConstruct private void init() throws IOException { - log.info("Initializing SNMP transport service"); - queryingExecutor = Executors.newScheduledThreadPool(Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("snmp-querying")); responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); @@ -128,26 +113,23 @@ public class SnmpTransportService implements TbTransportService { public void createQueryingTasks(DeviceSessionContext sessionContext) { List> queryingTasks = sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() - .filter(config -> config.getSpec().isRepeatingQuerying()) + .filter(communicationConfig -> communicationConfig instanceof RepeatingQueryingSnmpCommunicationConfig) .map(config -> { RepeatingQueryingSnmpCommunicationConfig repeatingCommunicationConfig = (RepeatingQueryingSnmpCommunicationConfig) config; - return createQueryingTaskForConfig(sessionContext, repeatingCommunicationConfig); + Long queryingFrequency = repeatingCommunicationConfig.getQueryingFrequencyMs(); + + return queryingExecutor.scheduleWithFixedDelay(() -> { + try { + if (sessionContext.isActive()) { + sendRequest(sessionContext, repeatingCommunicationConfig); + } + } catch (Exception e) { + log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); + } + }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); }) .collect(Collectors.toList()); - sessionContext.setQueryingTasks(queryingTasks); - } - - private ScheduledFuture createQueryingTaskForConfig(DeviceSessionContext sessionContext, RepeatingQueryingSnmpCommunicationConfig communicationConfig) { - Long queryingFrequency = communicationConfig.getQueryingFrequencyMs(); - return queryingExecutor.scheduleWithFixedDelay(() -> { - try { - if (sessionContext.isActive()) { - sendRequest(sessionContext, communicationConfig); - } - } catch (Exception e) { - log.error("Failed to send SNMP request for device {}: {}", sessionContext.getDeviceId(), e.toString()); - } - }, queryingFrequency, queryingFrequency, TimeUnit.MILLISECONDS); + sessionContext.getQueryingTasks().addAll(queryingTasks); } public void cancelQueryingTasks(DeviceSessionContext sessionContext) { @@ -155,67 +137,23 @@ public class SnmpTransportService implements TbTransportService { sessionContext.getQueryingTasks().clear(); } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) throws IOException { + + public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) throws IOException { - PDU request = createPdu(sessionContext, communicationConfig, values); - executeRequest(sessionContext, request); - } + public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU request = pduMapper.createPdu(sessionContext, communicationConfig, values); - private void executeRequest(DeviceSessionContext sessionContext, PDU request) throws IOException { if (request.size() > 0) { log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); - snmp.send(request, sessionContext.getTarget(), sessionContext.getDeviceProfile().getId(), sessionContext); - } - } - - private PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu; - SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); - SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); - switch (snmpVersion) { - case V1: - case V2C: - pdu = new PDU(); - break; - case V3: - ScopedPDU scopedPdu = new ScopedPDU(); - scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); - scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); - pdu = scopedPdu; - break; - default: - throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + RequestInfo requestInfo = new RequestInfo(sessionContext.getDeviceProfile().getId(), communicationConfig); + try { + snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); + } catch (IOException e) { + log.error("Failed to send SNMP request to device {}: {}", sessionContext.getDeviceId(), e.toString()); + } } - - pdu.setType(communicationConfig.getMethod().getCode()); - pdu.addAll(communicationConfig.getMappings().stream() - .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) - .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) - .map(value -> { - Variable variable; - switch (mapping.getDataType()) { - case LONG: - try { - variable = new Integer32(Integer.parseInt(value)); - break; - } catch (NumberFormatException ignored) { - } - case DOUBLE: - case BOOLEAN: - case STRING: - case JSON: - default: - variable = new OctetString(value); - } - return new VariableBinding(new OID(mapping.getOid()), variable); - }) - .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) - .collect(Collectors.toList())); - - return pdu; } @@ -223,7 +161,7 @@ public class SnmpTransportService implements TbTransportService { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); if (event.getError() != null) { - log.warn("Response error: {}", event.getError().getMessage(), event.getError()); + log.warn("SNMP response error: {}", event.getError().toString()); return; } @@ -232,108 +170,65 @@ public class SnmpTransportService implements TbTransportService { log.debug("No response from SNMP device {}, requestId: {}", sessionContext.getDeviceId(), event.getRequest().getRequestID()); return; } - DeviceProfileId deviceProfileId = (DeviceProfileId) event.getUserObject(); - log.debug("[{}] Processing SNMP response for device {} with device profile {}: {}", - response.getRequestID(), sessionContext.getDeviceId(), deviceProfileId, response); - responseProcessingExecutor.execute(() -> processResponse(sessionContext, response)); + RequestInfo requestInfo = (RequestInfo) event.getUserObject(); + responseProcessingExecutor.execute(() -> { + processResponse(sessionContext, response, requestInfo); + }); } - private void processResponse(DeviceSessionContext sessionContext, PDU responsePdu) { - Map mappings = new HashMap<>(); - Map configs = new HashMap<>(); - Map responses = new EnumMap<>(SnmpCommunicationSpec.class); - - for (SnmpCommunicationConfig config : sessionContext.getProfileTransportConfiguration().getCommunicationConfigs()) { - for (SnmpMapping mapping : config.getMappings()) { - OID oid = new OID(mapping.getOid()); - mappings.put(oid, mapping); - configs.put(oid, config); - } - responses.put(config.getSpec(), new JsonObject()); - } - - for (int i = 0; i < responsePdu.size(); i++) { - VariableBinding variableBinding = responsePdu.get(i); - log.trace("Processing variable binding {}: {}", i, variableBinding); - - if (variableBinding.getVariable() instanceof Null) { - log.debug("Response variable is empty"); - continue; - } - - OID oid = variableBinding.getOid(); - if (!mappings.containsKey(oid)) { - log.debug("No SNMP mapping for oid {}", oid); - continue; - } - - SnmpCommunicationSpec spec = configs.get(oid).getSpec(); - if (!responseProcessors.containsKey(spec)) { - log.debug("No response processor found for spec {}", spec); - continue; - } - - SnmpMapping mapping = mappings.get(oid); - processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), responses.get(spec)); + private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { + ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationConfig().getSpec()); + if (responseProcessor == null) { + return; } - if (responses.values().stream().allMatch(response -> response.entrySet().isEmpty())) { - log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), responsePdu.getRequestID()); + JsonObject responseData = pduMapper.processPdu(response, sessionContext, requestInfo.getCommunicationConfig()); + if (responseData.entrySet().isEmpty()) { + log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); return; } - responses.forEach((spec, response) -> { - Optional.ofNullable(responseProcessors.get(spec)) - .ifPresent(responseProcessor -> { - if (!response.entrySet().isEmpty()) { - responseProcessor.accept(response, sessionContext); - } - }); - }); - + responseProcessor.process(responseData, sessionContext); reportActivity(sessionContext.getSessionInfo()); } private void configureResponseProcessors() { responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (response, sessionContext) -> { TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); - transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, TransportServiceCallback.EMPTY); - log.debug("Posted telemetry for device {}: {}", sessionContext.getDeviceId(), response); + transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); + log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), response); }); responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (response, sessionContext) -> { TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); - transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, TransportServiceCallback.EMPTY); - log.debug("Posted attributes for device {}: {}", sessionContext.getDeviceId(), response); + transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); + log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), response); + }); + + responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING, (response, sessionContext) -> { + String rpcResponse = response.get(ToDeviceRpcResponseQueryingSnmpCommunicationConfig.RPC_RESPONSE_KEY_NAME).getAsString(); + TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() + .setPayload(rpcResponse) + .build(); + transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); + log.debug("Processed RPC response from device {}: {}", sessionContext.getDeviceId(), rpcResponse); }); + +// responseProcessors.put(, (response, sessionContext) -> { +// TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(sessionContext.getDeviceId(), response); +// transportService.process(sessionContext.getSessionInfo(), claimDeviceMsg, null); +// }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { transportService.process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() - .setAttributeSubscription(false) - .setRpcSubscription(false) + .setAttributeSubscription(true) + .setRpcSubscription(true) .setLastActivityTime(System.currentTimeMillis()) .build(), TransportServiceCallback.EMPTY); } - private void processValue(String key, DataType dataType, String value, JsonObject result) { - if (StringUtils.isEmpty(value)) return; - - switch (dataType) { - case LONG: - result.addProperty(key, Long.parseLong(value)); - break; - case BOOLEAN: - result.addProperty(key, Boolean.parseBoolean(value)); - break; - case DOUBLE: - result.addProperty(key, Double.parseDouble(value)); - break; - default: - result.addProperty(key, value); - } - } @Override public String getName() { @@ -358,4 +253,15 @@ public class SnmpTransportService implements TbTransportService { } log.info("SNMP transport stopped!"); } + + @Data + private static class RequestInfo { + private final DeviceProfileId deviceProfileId; + private final SnmpCommunicationConfig communicationConfig; + } + + private interface ResponseProcessor { + void process(JsonObject responseData, DeviceSessionContext sessionContext); + } + } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index b3a38545a8..82fdc037ad 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -27,6 +27,8 @@ import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfigu import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcCommandSettingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; @@ -36,13 +38,13 @@ import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeResponse import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; -import org.thingsboard.server.transport.snmp.SnmpAuthService; import org.thingsboard.server.transport.snmp.SnmpTransportContext; -import org.thingsboard.server.transport.snmp.service.SnmpTransportService; +import java.io.IOException; import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; @@ -64,16 +66,12 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S private final SnmpTransportContext snmpTransportContext; - @Getter - @Setter - private long previousRequestExecutedAt = 0; private final AtomicInteger msgIdSeq = new AtomicInteger(0); @Getter private boolean isActive = true; @Getter - @Setter - private List> queryingTasks = new LinkedList<>(); + private final List> queryingTasks = new LinkedList<>(); public DeviceSessionContext(Device device, DeviceProfile deviceProfile, String token, SnmpDeviceProfileTransportConfiguration profileTransportConfiguration, @@ -116,7 +114,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S public void initializeTarget(SnmpDeviceProfileTransportConfiguration profileTransportConfig, SnmpDeviceTransportConfiguration deviceTransportConfig) throws Exception { log.trace("Initializing target for SNMP session of device {}", device); this.target = snmpTransportContext.getSnmpAuthService().setUpSnmpTarget(profileTransportConfig, deviceTransportConfig); - log.info("SNMP target initialized: {}", target); + log.debug("SNMP target initialized: {}", target); } public void close() { @@ -138,20 +136,14 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { - profileTransportConfiguration.getCommunicationConfigs().stream() - .filter(config -> config.getSpec() == SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) - .findFirst() + getCommunicationConfigForSpec(SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) .ifPresent(communicationConfig -> { Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() .collect(Collectors.toMap( Map.Entry::getKey, entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() )); - try { - snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); - } catch (Exception e) { - log.error("Failed to send request with shared attributes to SNMP device {}: {}", getDeviceId(), e.getMessage()); - } + snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); }); } @@ -161,9 +153,23 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { + getCommunicationConfigForSpec(SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING) + .ifPresent(communicationConfig -> { + String value = JsonConverter.toJson(toDeviceRequest, true).toString(); + snmpTransportContext.getSnmpTransportService().sendRequest( + this, communicationConfig, + Map.of(ToDeviceRpcCommandSettingSnmpCommunicationConfig.RPC_COMMAND_KEY_NAME, value) + ); + }); } @Override public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { } + + private Optional getCommunicationConfigForSpec(SnmpCommunicationSpec spec) { + return profileTransportConfiguration.getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == spec) + .findFirst(); + } } From a1e4bbbb31cca6360f77dd838d83872e646ed917 Mon Sep 17 00:00:00 2001 From: Viacheslav Klimov Date: Tue, 20 Apr 2021 17:58:15 +0300 Subject: [PATCH 4/4] Implement RPC for SNMP; refactor --- .../SnmpDeviceTransportConfiguration.java | 2 +- ...mpDeviceProfileTransportConfiguration.java | 2 +- .../transport/snmp/SnmpCommunicationSpec.java | 3 +- ...CommandSettingSnmpCommunicationConfig.java | 59 -------- ...sponseQueryingSnmpCommunicationConfig.java | 60 -------- .../transport/snmp/SnmpTransportContext.java | 1 + .../{PduMapper.java => PduService.java} | 91 +++++++----- .../snmp/service/SnmpTransportService.java | 135 ++++++++++++++---- .../snmp/session/DeviceSessionContext.java | 33 +---- .../transport/adaptor/JsonConverter.java | 11 +- 10 files changed, 175 insertions(+), 222 deletions(-) delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java delete mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java rename common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/{PduMapper.java => PduService.java} (76%) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java index 68a9d1f218..a7bc143d81 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/data/SnmpDeviceTransportConfiguration.java @@ -40,7 +40,7 @@ public class SnmpDeviceTransportConfiguration implements DeviceTransportConfigur private String community; /* - * For SNMP v3 with User Based Security Model + * For SNMP v3 * */ private String username; private String securityName; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java index bbf074eeda..0b8efaf8c7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SnmpDeviceProfileTransportConfiguration.java @@ -44,7 +44,7 @@ public class SnmpDeviceProfileTransportConfiguration implements DeviceProfileTra @JsonIgnore private boolean isValid() { return timeoutMs != null && timeoutMs >= 0 && retries != null && retries >= 0 - && communicationConfigs != null && !communicationConfigs.isEmpty() + && communicationConfigs != null && communicationConfigs.stream().allMatch(config -> config != null && config.isValid()) && communicationConfigs.stream().flatMap(config -> config.getAllMappings().stream()).map(SnmpMapping::getOid) .distinct().count() == communicationConfigs.stream().mapToInt(config -> config.getAllMappings().size()).sum(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java index a6643ecf1e..8d87144ae8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/SnmpCommunicationSpec.java @@ -21,6 +21,5 @@ public enum SnmpCommunicationSpec { CLIENT_ATTRIBUTES_QUERYING, SHARED_ATTRIBUTES_SETTING, - TO_DEVICE_RPC_COMMAND_SETTING, - TO_DEVICE_RPC_RESPONSE_QUERYING + TO_DEVICE_RPC_REQUEST, } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java deleted file mode 100644 index 97f3de047b..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcCommandSettingSnmpCommunicationConfig.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.transport.snmp.config.impl; - -import lombok.Data; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; -import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -@Data -public class ToDeviceRpcCommandSettingSnmpCommunicationConfig implements SnmpCommunicationConfig { - private SnmpMapping mapping; - - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING; - } - - @Override - public SnmpMethod getMethod() { - return SnmpMethod.SET; - } - - public void setMapping(SnmpMapping mapping) { - this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_COMMAND_KEY_NAME, DataType.STRING) : null; - } - - @Override - public List getAllMappings() { - return Collections.singletonList(mapping); - } - - @Override - public boolean isValid() { - return mapping != null && mapping.isValid(); - } - - public static final String RPC_COMMAND_KEY_NAME = "rpcCommand"; - -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java deleted file mode 100644 index 18bce94980..0000000000 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/snmp/config/impl/ToDeviceRpcResponseQueryingSnmpCommunicationConfig.java +++ /dev/null @@ -1,60 +0,0 @@ -/** - * Copyright © 2016-2021 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.data.transport.snmp.config.impl; - -import lombok.Data; -import lombok.EqualsAndHashCode; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; -import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; - -import java.util.Collections; -import java.util.List; - -@EqualsAndHashCode(callSuper = true) -@Data -public class ToDeviceRpcResponseQueryingSnmpCommunicationConfig extends RepeatingQueryingSnmpCommunicationConfig { - private SnmpMapping mapping; - - @Override - public SnmpCommunicationSpec getSpec() { - return SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING; - } - - @Override - public SnmpMethod getMethod() { - return SnmpMethod.GET; - } - - public void setMapping(SnmpMapping mapping) { - this.mapping = mapping != null ? new SnmpMapping(mapping.getOid(), RPC_RESPONSE_KEY_NAME, DataType.STRING) : null; - } - - @Override - public List getAllMappings() { - return Collections.singletonList(mapping); - } - - @Override - public boolean isValid() { - return true; - } - - public static final String RPC_RESPONSE_KEY_NAME = "rpcResponse"; - -} diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java index d66d09ab0b..68d4dd3933 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/SnmpTransportContext.java @@ -152,6 +152,7 @@ public class SnmpTransportContext extends TransportContext { } } catch (Exception e) { log.error("Failed to update session for SNMP device {}: {}", sessionContext.getDeviceId(), e.getMessage()); + destroyDeviceSession(sessionContext); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java similarity index 76% rename from common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java rename to common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java index a3b5f77dfb..720b1e7ae3 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduMapper.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/PduService.java @@ -29,6 +29,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.SnmpProtocolVersion; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; import org.thingsboard.server.queue.util.TbSnmpTransportComponent; @@ -45,32 +46,16 @@ import java.util.stream.IntStream; @TbSnmpTransportComponent @Service @Slf4j -public class PduMapper { +public class PduService { public PDU createPdu(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU pdu; - SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); - SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); - switch (snmpVersion) { - case V1: - case V2C: - pdu = new PDU(); - break; - case V3: - ScopedPDU scopedPdu = new ScopedPDU(); - scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); - scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); - pdu = scopedPdu; - break; - default: - throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); - } + PDU pdu = setUpPdu(sessionContext); pdu.setType(communicationConfig.getMethod().getCode()); pdu.addAll(communicationConfig.getAllMappings().stream() .filter(mapping -> values.isEmpty() || values.containsKey(mapping.getKey())) .map(mapping -> Optional.ofNullable(values.get(mapping.getKey())) .map(value -> { - Variable variable = toSnmpVariable(mapping, value); + Variable variable = toSnmpVariable(value, mapping.getDataType()); return new VariableBinding(new OID(mapping.getOid()), variable); }) .orElseGet(() -> new VariableBinding(new OID(mapping.getOid())))) @@ -79,9 +64,20 @@ public class PduMapper { return pdu; } - private Variable toSnmpVariable(SnmpMapping mapping, String value) { + public PDU createSingleVariablePdu(DeviceSessionContext sessionContext, SnmpMethod snmpMethod, String oid, String value, DataType dataType) { + PDU pdu = setUpPdu(sessionContext); + pdu.setType(snmpMethod.getCode()); + + Variable variable = value == null ? Null.instance : toSnmpVariable(value, dataType); + pdu.add(new VariableBinding(new OID(oid), variable)); + + return pdu; + } + + private Variable toSnmpVariable(String value, DataType dataType) { + dataType = dataType == null ? DataType.STRING : dataType; Variable variable; - switch (mapping.getDataType()) { + switch (dataType) { case LONG: try { variable = new Integer32(Integer.parseInt(value)); @@ -98,37 +94,62 @@ public class PduMapper { return variable; } + private PDU setUpPdu(DeviceSessionContext sessionContext) { + PDU pdu; + SnmpDeviceTransportConfiguration deviceTransportConfiguration = sessionContext.getDeviceTransportConfiguration(); + SnmpProtocolVersion snmpVersion = deviceTransportConfiguration.getProtocolVersion(); + switch (snmpVersion) { + case V1: + case V2C: + pdu = new PDU(); + break; + case V3: + ScopedPDU scopedPdu = new ScopedPDU(); + scopedPdu.setContextName(new OctetString(deviceTransportConfiguration.getContextName())); + scopedPdu.setContextEngineID(new OctetString(deviceTransportConfiguration.getEngineId())); + pdu = scopedPdu; + break; + default: + throw new UnsupportedOperationException("SNMP version " + snmpVersion + " is not supported"); + } + return pdu; + } - public JsonObject processPdu(PDU pdu, DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { - List variablesBindings = IntStream.range(0, pdu.size()) - .mapToObj(pdu::get) - .filter(Objects::nonNull) - .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) - .collect(Collectors.toList()); - JsonObject data = new JsonObject(); + + public JsonObject processPdu(PDU pdu, List responseMappings) { + Map values = processPdu(pdu); Map mappings = new HashMap<>(); - for (SnmpMapping mapping : communicationConfig.getAllMappings()) { - OID oid = new OID(mapping.getOid()); - mappings.put(oid, mapping); + if (responseMappings != null) { + for (SnmpMapping mapping : responseMappings) { + OID oid = new OID(mapping.getOid()); + mappings.put(oid, mapping); + } } - variablesBindings.forEach(variableBinding -> { - log.trace("Processing variable binding: {}", variableBinding); + JsonObject data = new JsonObject(); + values.forEach((oid, value) -> { + log.trace("Processing variable binding: {} - {}", oid, value); - OID oid = variableBinding.getOid(); SnmpMapping mapping = mappings.get(oid); if (mapping == null) { log.debug("No SNMP mapping for oid {}", oid); return; } - processValue(mapping.getKey(), mapping.getDataType(), variableBinding.toValueString(), data); + processValue(mapping.getKey(), mapping.getDataType(), value, data); }); return data; } + public Map processPdu(PDU pdu) { + return IntStream.range(0, pdu.size()) + .mapToObj(pdu::get) + .filter(Objects::nonNull) + .filter(variableBinding -> !(variableBinding.getVariable() instanceof Null)) + .collect(Collectors.toMap(VariableBinding::getOid, VariableBinding::toValueString)); + } private void processValue(String key, DataType dataType, String value, JsonObject result) { switch (dataType) { diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java index f672e36190..5bfee14c41 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/service/SnmpTransportService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.transport.snmp.service; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; import lombok.Data; import lombok.Getter; @@ -35,11 +36,12 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.TbTransportService; -import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.kv.DataType; import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.SnmpMethod; import org.thingsboard.server.common.data.transport.snmp.config.RepeatingQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcResponseQueryingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -50,10 +52,12 @@ import org.thingsboard.server.transport.snmp.session.DeviceSessionContext; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.IOException; +import java.util.Arrays; import java.util.Collections; import java.util.EnumMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; @@ -67,13 +71,14 @@ import java.util.stream.Collectors; @RequiredArgsConstructor public class SnmpTransportService implements TbTransportService { private final TransportService transportService; - private final PduMapper pduMapper; + private final PduService pduService; @Getter private Snmp snmp; private ScheduledExecutorService queryingExecutor; private ExecutorService responseProcessingExecutor; + private final Map responseDataMappers = new EnumMap<>(SnmpCommunicationSpec.class); private final Map responseProcessors = new EnumMap<>(SnmpCommunicationSpec.class); @Value("${transport.snmp.response_processing.parallelism_level}") @@ -87,6 +92,7 @@ public class SnmpTransportService implements TbTransportService { responseProcessingExecutor = Executors.newWorkStealingPool(responseProcessingParallelismLevel); initializeSnmp(); + configureResponseDataMappers(); configureResponseProcessors(); log.info("SNMP transport service initialized"); @@ -138,16 +144,19 @@ public class SnmpTransportService implements TbTransportService { } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { + private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig) { sendRequest(sessionContext, communicationConfig, Collections.emptyMap()); } - public void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { - PDU request = pduMapper.createPdu(sessionContext, communicationConfig, values); + private void sendRequest(DeviceSessionContext sessionContext, SnmpCommunicationConfig communicationConfig, Map values) { + PDU request = pduService.createPdu(sessionContext, communicationConfig, values); + RequestInfo requestInfo = new RequestInfo(communicationConfig.getSpec(), communicationConfig.getAllMappings()); + sendRequest(sessionContext, request, requestInfo); + } + private void sendRequest(DeviceSessionContext sessionContext, PDU request, RequestInfo requestInfo) { if (request.size() > 0) { log.trace("Executing SNMP request for device {}. Variables bindings: {}", sessionContext.getDeviceId(), request.getVariableBindings()); - RequestInfo requestInfo = new RequestInfo(sessionContext.getDeviceProfile().getId(), communicationConfig); try { snmp.send(request, sessionContext.getTarget(), requestInfo, sessionContext); } catch (IOException e) { @@ -156,6 +165,39 @@ public class SnmpTransportService implements TbTransportService { } } + public void onAttributeUpdate(DeviceSessionContext sessionContext, TransportProtos.AttributeUpdateNotificationMsg attributeUpdateNotification) { + sessionContext.getProfileTransportConfiguration().getCommunicationConfigs().stream() + .filter(config -> config.getSpec() == SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) + .findFirst() + .ifPresent(communicationConfig -> { + Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() + .collect(Collectors.toMap( + Map.Entry::getKey, + entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() + )); + sendRequest(sessionContext, communicationConfig, sharedAttributes); + }); + } + + public void onToDeviceRpcRequest(DeviceSessionContext sessionContext, TransportProtos.ToDeviceRpcRequestMsg toDeviceRpcRequestMsg) { + SnmpMethod snmpMethod = SnmpMethod.valueOf(toDeviceRpcRequestMsg.getMethodName()); + JsonObject params = JsonConverter.parse(toDeviceRpcRequestMsg.getParams()).getAsJsonObject(); + + String oid = Optional.ofNullable(params.get("oid")).map(JsonElement::getAsString).orElse(null); + String value = Optional.ofNullable(params.get("value")).map(JsonElement::getAsString).orElse(null); + DataType dataType = Optional.ofNullable(params.get("dataType")).map(e -> DataType.valueOf(e.getAsString())).orElse(DataType.STRING); + + if (oid == null || oid.isEmpty()) { + throw new IllegalArgumentException("OID in to-device RPC request is not specified"); + } + if (value == null && snmpMethod == SnmpMethod.SET) { + throw new IllegalArgumentException("Value must be specified for SNMP method 'SET'"); + } + + PDU request = pduService.createSingleVariablePdu(sessionContext, snmpMethod, oid, value, dataType); + sendRequest(sessionContext, request, new RequestInfo(toDeviceRpcRequestMsg.getRequestId(), SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST)); + } + public void processResponseEvent(DeviceSessionContext sessionContext, ResponseEvent event) { ((Snmp) event.getSource()).cancel(event.getRequest(), sessionContext); @@ -178,47 +220,59 @@ public class SnmpTransportService implements TbTransportService { } private void processResponse(DeviceSessionContext sessionContext, PDU response, RequestInfo requestInfo) { - ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationConfig().getSpec()); - if (responseProcessor == null) { - return; - } + ResponseProcessor responseProcessor = responseProcessors.get(requestInfo.getCommunicationSpec()); + if (responseProcessor == null) return; + + JsonObject responseData = responseDataMappers.get(requestInfo.getCommunicationSpec()).map(response, requestInfo); - JsonObject responseData = pduMapper.processPdu(response, sessionContext, requestInfo.getCommunicationConfig()); if (responseData.entrySet().isEmpty()) { log.debug("No values is the SNMP response for device {}. Request id: {}", sessionContext.getDeviceId(), response.getRequestID()); return; } - responseProcessor.process(responseData, sessionContext); + responseProcessor.process(responseData, requestInfo, sessionContext); reportActivity(sessionContext.getSessionInfo()); } + private void configureResponseDataMappers() { + responseDataMappers.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (pdu, requestInfo) -> { + JsonObject responseData = new JsonObject(); + pduService.processPdu(pdu).forEach((oid, value) -> { + responseData.addProperty(oid.toDottedString(), value); + }); + return responseData; + }); + + ResponseDataMapper defaultResponseDataMapper = (pdu, requestInfo) -> { + return pduService.processPdu(pdu, requestInfo.getResponseMappings()); + }; + Arrays.stream(SnmpCommunicationSpec.values()) + .forEach(communicationSpec -> { + responseDataMappers.putIfAbsent(communicationSpec, defaultResponseDataMapper); + }); + } + private void configureResponseProcessors() { - responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (response, sessionContext) -> { - TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(response); + responseProcessors.put(SnmpCommunicationSpec.TELEMETRY_QUERYING, (responseData, requestInfo, sessionContext) -> { + TransportProtos.PostTelemetryMsg postTelemetryMsg = JsonConverter.convertToTelemetryProto(responseData); transportService.process(sessionContext.getSessionInfo(), postTelemetryMsg, null); - log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), response); + log.debug("Posted telemetry for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); }); - responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (response, sessionContext) -> { - TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(response); + responseProcessors.put(SnmpCommunicationSpec.CLIENT_ATTRIBUTES_QUERYING, (responseData, requestInfo, sessionContext) -> { + TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(responseData); transportService.process(sessionContext.getSessionInfo(), postAttributesMsg, null); - log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), response); + log.debug("Posted attributes for SNMP device {}: {}", sessionContext.getDeviceId(), responseData); }); - responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_RESPONSE_QUERYING, (response, sessionContext) -> { - String rpcResponse = response.get(ToDeviceRpcResponseQueryingSnmpCommunicationConfig.RPC_RESPONSE_KEY_NAME).getAsString(); + responseProcessors.put(SnmpCommunicationSpec.TO_DEVICE_RPC_REQUEST, (responseData, requestInfo, sessionContext) -> { TransportProtos.ToDeviceRpcResponseMsg rpcResponseMsg = TransportProtos.ToDeviceRpcResponseMsg.newBuilder() - .setPayload(rpcResponse) + .setRequestId(requestInfo.getRequestId()) + .setPayload(JsonConverter.toJson(responseData)) .build(); transportService.process(sessionContext.getSessionInfo(), rpcResponseMsg, null); - log.debug("Processed RPC response from device {}: {}", sessionContext.getDeviceId(), rpcResponse); + log.debug("Posted RPC response {} for device {}", responseData, sessionContext.getDeviceId()); }); - -// responseProcessors.put(, (response, sessionContext) -> { -// TransportProtos.ClaimDeviceMsg claimDeviceMsg = JsonConverter.convertToClaimDeviceProto(sessionContext.getDeviceId(), response); -// transportService.process(sessionContext.getSessionInfo(), claimDeviceMsg, null); -// }); } private void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { @@ -256,12 +310,31 @@ public class SnmpTransportService implements TbTransportService { @Data private static class RequestInfo { - private final DeviceProfileId deviceProfileId; - private final SnmpCommunicationConfig communicationConfig; + private Integer requestId; + private SnmpCommunicationSpec communicationSpec; + private List responseMappings; + + public RequestInfo(Integer requestId, SnmpCommunicationSpec communicationSpec) { + this.requestId = requestId; + this.communicationSpec = communicationSpec; + } + + public RequestInfo(SnmpCommunicationSpec communicationSpec) { + this.communicationSpec = communicationSpec; + } + + public RequestInfo(SnmpCommunicationSpec communicationSpec, List responseMappings) { + this.communicationSpec = communicationSpec; + this.responseMappings = responseMappings; + } + } + + private interface ResponseDataMapper { + JsonObject map(PDU pdu, RequestInfo requestInfo); } private interface ResponseProcessor { - void process(JsonObject responseData, DeviceSessionContext sessionContext); + void process(JsonObject responseData, RequestInfo requestInfo, DeviceSessionContext sessionContext); } } diff --git a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java index 82fdc037ad..a59ce020ba 100644 --- a/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java +++ b/common/transport/snmp/src/main/java/org/thingsboard/server/transport/snmp/session/DeviceSessionContext.java @@ -26,11 +26,7 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.device.data.SnmpDeviceTransportConfiguration; import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.transport.snmp.SnmpCommunicationSpec; -import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.config.impl.ToDeviceRpcCommandSettingSnmpCommunicationConfig; import org.thingsboard.server.common.transport.SessionMsgListener; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.session.DeviceAwareSessionContext; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; @@ -40,15 +36,11 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcRequestMs import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.transport.snmp.SnmpTransportContext; -import java.io.IOException; import java.util.LinkedList; import java.util.List; -import java.util.Map; -import java.util.Optional; import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.atomic.AtomicInteger; -import java.util.stream.Collectors; @Slf4j public class DeviceSessionContext extends DeviceAwareSessionContext implements SessionMsgListener, ResponseListener { @@ -136,15 +128,7 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onAttributeUpdate(AttributeUpdateNotificationMsg attributeUpdateNotification) { - getCommunicationConfigForSpec(SnmpCommunicationSpec.SHARED_ATTRIBUTES_SETTING) - .ifPresent(communicationConfig -> { - Map sharedAttributes = JsonConverter.toJson(attributeUpdateNotification).entrySet().stream() - .collect(Collectors.toMap( - Map.Entry::getKey, - entry -> entry.getValue().isJsonPrimitive() ? entry.getValue().getAsString() : entry.getValue().toString() - )); - snmpTransportContext.getSnmpTransportService().sendRequest(this, communicationConfig, sharedAttributes); - }); + snmpTransportContext.getSnmpTransportService().onAttributeUpdate(this, attributeUpdateNotification); } @Override @@ -153,23 +137,10 @@ public class DeviceSessionContext extends DeviceAwareSessionContext implements S @Override public void onToDeviceRpcRequest(ToDeviceRpcRequestMsg toDeviceRequest) { - getCommunicationConfigForSpec(SnmpCommunicationSpec.TO_DEVICE_RPC_COMMAND_SETTING) - .ifPresent(communicationConfig -> { - String value = JsonConverter.toJson(toDeviceRequest, true).toString(); - snmpTransportContext.getSnmpTransportService().sendRequest( - this, communicationConfig, - Map.of(ToDeviceRpcCommandSettingSnmpCommunicationConfig.RPC_COMMAND_KEY_NAME, value) - ); - }); + snmpTransportContext.getSnmpTransportService().onToDeviceRpcRequest(this, toDeviceRequest); } @Override public void onToServerRpcResponse(ToServerRpcResponseMsg toServerResponse) { } - - private Optional getCommunicationConfigForSpec(SnmpCommunicationSpec spec) { - return profileTransportConfiguration.getCommunicationConfigs().stream() - .filter(config -> config.getSpec() == spec) - .findFirst(); - } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java index fe2f3500a6..fce1c2c9b5 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/JsonConverter.java @@ -558,6 +558,14 @@ public class JsonConverter { } } + public static JsonElement parse(String json) { + return JSON_PARSER.parse(json); + } + + public static String toJson(JsonElement element) { + return GSON.toJson(element); + } + public static void setTypeCastEnabled(boolean enabled) { isTypeCastEnabled = enabled; } @@ -599,8 +607,7 @@ public class JsonConverter { .build(); } - private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String - provisionKey, String provisionSecret) { + private static TransportProtos.ProvisionDeviceCredentialsMsg buildProvisionDeviceCredentialsMsg(String provisionKey, String provisionSecret) { return TransportProtos.ProvisionDeviceCredentialsMsg.newBuilder() .setProvisionDeviceKey(provisionKey) .setProvisionDeviceSecret(provisionSecret)