mirror of
https://github.com/openhab/openhab-addons.git
synced 2025-01-26 15:21:41 +01:00
[snmp] SNMP v3 fixes and improvements (#16803)
* [snmp] SNMP v3 fixes and improvements (cherry picked from commit a32af97d785f301ad2253801353af53a9941d6f9) Signed-off-by: Jan N. Klug <github@klug.nrw> Signed-off-by: Ciprian Pascu <contact@ciprianpascu.ro>
This commit is contained in:
parent
d926028614
commit
c2eb8af40e
@ -77,13 +77,8 @@ The default is `v1`.
|
|||||||
|
|
||||||
### `target3`
|
### `target3`
|
||||||
|
|
||||||
The `target3` thing has additional mandatory parameters: `engineId` and `user`.
|
The `target3` thing has an additional mandatory parameter: `user`.
|
||||||
|
This value of this parameter is named "securityName" or "userName" in most agents.
|
||||||
The `engineId` must be given in hexadecimal notation (case-insensitive) without separators (e.g. `80000009035c710dbcd9e6`).
|
|
||||||
The allowed length is 11 to 32 bytes (22 to 64 hex characters).
|
|
||||||
If you encounter problems, please check if your agent prefixes the set engine id (e.g. Mikrotik uses `80003a8c04` and appends the set value to that).
|
|
||||||
|
|
||||||
The `user` parameter is named "securityName" or "userName" in most agents.
|
|
||||||
|
|
||||||
Optional configuration parameters are: `securityModel`, `authProtocol`, `authPassphrase`, `privProtocol` and `privPassphrase`.
|
Optional configuration parameters are: `securityModel`, `authProtocol`, `authPassphrase`, `privProtocol` and `privPassphrase`.
|
||||||
|
|
||||||
@ -99,7 +94,7 @@ If authentication encryption is required, at least `authPassphrase` needs to be
|
|||||||
Other possible values for `authProtocol` are `SHA`, `HMAC128SHA224`, `HMAC192SHA256`, `HMAC256SHA384` and `HMAC384SHA512`.
|
Other possible values for `authProtocol` are `SHA`, `HMAC128SHA224`, `HMAC192SHA256`, `HMAC256SHA384` and `HMAC384SHA512`.
|
||||||
|
|
||||||
If encryption of transmitted data (privacy encryption) is required, at least `privPassphrase` needs to be set, while `privProtocol` defaults to `DES`.
|
If encryption of transmitted data (privacy encryption) is required, at least `privPassphrase` needs to be set, while `privProtocol` defaults to `DES`.
|
||||||
Other possible values for `privProtocol` are `AES128`, `AES192` and `AES256`.
|
Other possible values for `privProtocol` are `DES3`, `AES128`, `AES192` and `AES256`.
|
||||||
|
|
||||||
## Channels
|
## Channels
|
||||||
|
|
||||||
|
@ -18,7 +18,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.snmp4j</groupId>
|
<groupId>org.snmp4j</groupId>
|
||||||
<artifactId>snmp4j</artifactId>
|
<artifactId>snmp4j</artifactId>
|
||||||
<version>2.8.6</version>
|
<version>3.8.2</version>
|
||||||
|
<scope>compile</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.snmp4j</groupId>
|
||||||
|
<artifactId>snmp4j-unix-transport</artifactId>
|
||||||
|
<version>1.1.0</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -16,12 +16,13 @@ import java.io.IOException;
|
|||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol;
|
|
||||||
import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol;
|
|
||||||
import org.snmp4j.CommandResponder;
|
import org.snmp4j.CommandResponder;
|
||||||
import org.snmp4j.PDU;
|
import org.snmp4j.PDU;
|
||||||
import org.snmp4j.Target;
|
import org.snmp4j.Target;
|
||||||
import org.snmp4j.event.ResponseListener;
|
import org.snmp4j.event.ResponseListener;
|
||||||
|
import org.snmp4j.security.UsmUser;
|
||||||
|
import org.snmp4j.smi.Address;
|
||||||
|
import org.snmp4j.smi.OctetString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The {@link SnmpService} is responsible for SNMP communication
|
* The {@link SnmpService} is responsible for SNMP communication
|
||||||
@ -33,12 +34,53 @@ import org.snmp4j.event.ResponseListener;
|
|||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public interface SnmpService {
|
public interface SnmpService {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a listener for received PDUs to the service
|
||||||
|
*
|
||||||
|
* @param listener the listener
|
||||||
|
*/
|
||||||
void addCommandResponder(CommandResponder listener);
|
void addCommandResponder(CommandResponder listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a listener for received PDUs from the service
|
||||||
|
*
|
||||||
|
* @param listener the listener
|
||||||
|
*/
|
||||||
void removeCommandResponder(CommandResponder listener);
|
void removeCommandResponder(CommandResponder listener);
|
||||||
|
|
||||||
void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
|
/**
|
||||||
|
* Send a PDU to the given target
|
||||||
|
*
|
||||||
|
* @param pdu the PDU
|
||||||
|
* @param target the target
|
||||||
|
* @param userHandle an optional user-handle to identify the request
|
||||||
|
* @param listener the listener for the response (always called, even in case of timeout)
|
||||||
|
* @throws IOException when an error occurs
|
||||||
|
*/
|
||||||
|
void send(PDU pdu, Target<?> target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
|
||||||
|
|
||||||
void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase,
|
/**
|
||||||
SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId);
|
* Add a user to the service for a given engine id (v3 only)
|
||||||
|
*
|
||||||
|
* @param user the {@link UsmUser} that should be added
|
||||||
|
* @param engineId the engine id
|
||||||
|
*/
|
||||||
|
void addUser(UsmUser user, OctetString engineId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a user from the service and clear the context engine id for this address (v3 only)
|
||||||
|
*
|
||||||
|
* @param address the remote address
|
||||||
|
* @param user the user
|
||||||
|
* @param engineId the engine id
|
||||||
|
*/
|
||||||
|
void removeUser(Address address, UsmUser user, OctetString engineId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the engine id of a remote system for a given address (v3 only)
|
||||||
|
*
|
||||||
|
* @param address the address of the remote system
|
||||||
|
* @return the engine id or {@code null} when engine id could not be determined
|
||||||
|
*/
|
||||||
|
byte @Nullable [] getEngineId(Address address);
|
||||||
}
|
}
|
||||||
|
@ -22,8 +22,6 @@ import java.util.Set;
|
|||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
import org.openhab.binding.snmp.internal.config.SnmpServiceConfiguration;
|
import org.openhab.binding.snmp.internal.config.SnmpServiceConfiguration;
|
||||||
import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol;
|
|
||||||
import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol;
|
|
||||||
import org.openhab.core.config.core.Configuration;
|
import org.openhab.core.config.core.Configuration;
|
||||||
import org.osgi.service.component.annotations.Activate;
|
import org.osgi.service.component.annotations.Activate;
|
||||||
import org.osgi.service.component.annotations.Component;
|
import org.osgi.service.component.annotations.Component;
|
||||||
@ -37,11 +35,22 @@ import org.snmp4j.Snmp;
|
|||||||
import org.snmp4j.Target;
|
import org.snmp4j.Target;
|
||||||
import org.snmp4j.event.ResponseListener;
|
import org.snmp4j.event.ResponseListener;
|
||||||
import org.snmp4j.mp.MPv3;
|
import org.snmp4j.mp.MPv3;
|
||||||
|
import org.snmp4j.security.AuthHMAC128SHA224;
|
||||||
|
import org.snmp4j.security.AuthHMAC192SHA256;
|
||||||
|
import org.snmp4j.security.AuthHMAC256SHA384;
|
||||||
|
import org.snmp4j.security.AuthHMAC384SHA512;
|
||||||
|
import org.snmp4j.security.AuthMD5;
|
||||||
|
import org.snmp4j.security.AuthSHA;
|
||||||
import org.snmp4j.security.Priv3DES;
|
import org.snmp4j.security.Priv3DES;
|
||||||
|
import org.snmp4j.security.PrivAES128;
|
||||||
|
import org.snmp4j.security.PrivAES192;
|
||||||
|
import org.snmp4j.security.PrivAES256;
|
||||||
|
import org.snmp4j.security.PrivDES;
|
||||||
import org.snmp4j.security.SecurityModels;
|
import org.snmp4j.security.SecurityModels;
|
||||||
import org.snmp4j.security.SecurityProtocols;
|
import org.snmp4j.security.SecurityProtocols;
|
||||||
import org.snmp4j.security.USM;
|
import org.snmp4j.security.USM;
|
||||||
import org.snmp4j.security.UsmUser;
|
import org.snmp4j.security.UsmUser;
|
||||||
|
import org.snmp4j.smi.Address;
|
||||||
import org.snmp4j.smi.OctetString;
|
import org.snmp4j.smi.OctetString;
|
||||||
import org.snmp4j.smi.UdpAddress;
|
import org.snmp4j.smi.UdpAddress;
|
||||||
import org.snmp4j.transport.DefaultUdpTransportMapping;
|
import org.snmp4j.transport.DefaultUdpTransportMapping;
|
||||||
@ -58,7 +67,6 @@ import org.snmp4j.transport.DefaultUdpTransportMapping;
|
|||||||
public class SnmpServiceImpl implements SnmpService {
|
public class SnmpServiceImpl implements SnmpService {
|
||||||
private final Logger logger = LoggerFactory.getLogger(SnmpServiceImpl.class);
|
private final Logger logger = LoggerFactory.getLogger(SnmpServiceImpl.class);
|
||||||
|
|
||||||
private @NonNullByDefault({}) SnmpServiceConfiguration config;
|
|
||||||
private @Nullable Snmp snmp;
|
private @Nullable Snmp snmp;
|
||||||
private @Nullable DefaultUdpTransportMapping transport;
|
private @Nullable DefaultUdpTransportMapping transport;
|
||||||
|
|
||||||
@ -67,9 +75,7 @@ public class SnmpServiceImpl implements SnmpService {
|
|||||||
|
|
||||||
@Activate
|
@Activate
|
||||||
public SnmpServiceImpl(Map<String, Object> config) {
|
public SnmpServiceImpl(Map<String, Object> config) {
|
||||||
SecurityProtocols.getInstance().addDefaultProtocols();
|
addProtocols();
|
||||||
SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
|
|
||||||
|
|
||||||
OctetString localEngineId = new OctetString(MPv3.createLocalEngineID());
|
OctetString localEngineId = new OctetString(MPv3.createLocalEngineID());
|
||||||
USM usm = new USM(SecurityProtocols.getInstance(), localEngineId, 0);
|
USM usm = new USM(SecurityProtocols.getInstance(), localEngineId, 0);
|
||||||
SecurityModels.getInstance().addSecurityModel(usm);
|
SecurityModels.getInstance().addSecurityModel(usm);
|
||||||
@ -79,34 +85,33 @@ public class SnmpServiceImpl implements SnmpService {
|
|||||||
|
|
||||||
@Modified
|
@Modified
|
||||||
protected void modified(Map<String, Object> config) {
|
protected void modified(Map<String, Object> config) {
|
||||||
this.config = new Configuration(config).as(SnmpServiceConfiguration.class);
|
SnmpServiceConfiguration snmpCfg = new Configuration(config).as(SnmpServiceConfiguration.class);
|
||||||
try {
|
try {
|
||||||
shutdownSnmp();
|
shutdownSnmp();
|
||||||
|
|
||||||
final DefaultUdpTransportMapping transport;
|
final DefaultUdpTransportMapping transport;
|
||||||
|
|
||||||
if (this.config.port > 0) {
|
if (snmpCfg.port > 0) {
|
||||||
transport = new DefaultUdpTransportMapping(new UdpAddress(this.config.port), true);
|
transport = new DefaultUdpTransportMapping(new UdpAddress(snmpCfg.port), true);
|
||||||
} else {
|
} else {
|
||||||
transport = new DefaultUdpTransportMapping();
|
transport = new DefaultUdpTransportMapping();
|
||||||
}
|
}
|
||||||
|
|
||||||
SecurityProtocols.getInstance().addDefaultProtocols();
|
addProtocols();
|
||||||
SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
|
|
||||||
|
|
||||||
final Snmp snmp = new Snmp(transport);
|
final Snmp snmp = new Snmp(transport);
|
||||||
listeners.forEach(snmp::addCommandResponder);
|
listeners.forEach(snmp::addCommandResponder);
|
||||||
snmp.listen();
|
snmp.listen();
|
||||||
|
|
||||||
// re-add user entries
|
// re-add user entries
|
||||||
userEntries.forEach(u -> addUser(snmp, u));
|
userEntries.forEach(u -> snmp.getUSM().addUser(u.user, u.engineId));
|
||||||
|
|
||||||
this.snmp = snmp;
|
this.snmp = snmp;
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
|
|
||||||
logger.debug("initialized SNMP at {}", transport.getAddress());
|
logger.debug("initialized SNMP at {}", transport.getAddress());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
logger.warn("could not open SNMP instance on port {}: {}", this.config.port, e.getMessage());
|
logger.warn("could not open SNMP instance on port {}: {}", snmpCfg.port, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,6 +125,21 @@ public class SnmpServiceImpl implements SnmpService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void addProtocols() {
|
||||||
|
SecurityProtocols secProtocols = SecurityProtocols.getInstance();
|
||||||
|
secProtocols.addAuthenticationProtocol(new AuthMD5());
|
||||||
|
secProtocols.addAuthenticationProtocol(new AuthSHA());
|
||||||
|
secProtocols.addAuthenticationProtocol(new AuthHMAC128SHA224());
|
||||||
|
secProtocols.addAuthenticationProtocol(new AuthHMAC192SHA256());
|
||||||
|
secProtocols.addAuthenticationProtocol(new AuthHMAC256SHA384());
|
||||||
|
secProtocols.addAuthenticationProtocol(new AuthHMAC384SHA512());
|
||||||
|
secProtocols.addPrivacyProtocol(new PrivDES());
|
||||||
|
secProtocols.addPrivacyProtocol(new Priv3DES());
|
||||||
|
secProtocols.addPrivacyProtocol(new PrivAES128());
|
||||||
|
secProtocols.addPrivacyProtocol(new PrivAES192());
|
||||||
|
secProtocols.addPrivacyProtocol(new PrivAES256());
|
||||||
|
}
|
||||||
|
|
||||||
private void shutdownSnmp() throws IOException {
|
private void shutdownSnmp() throws IOException {
|
||||||
DefaultUdpTransportMapping transport = this.transport;
|
DefaultUdpTransportMapping transport = this.transport;
|
||||||
if (transport != null) {
|
if (transport != null) {
|
||||||
@ -152,7 +172,7 @@ public class SnmpServiceImpl implements SnmpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener)
|
public void send(PDU pdu, Target<?> target, @Nullable Object userHandle, ResponseListener listener)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Snmp snmp = this.snmp;
|
Snmp snmp = this.snmp;
|
||||||
if (snmp != null) {
|
if (snmp != null) {
|
||||||
@ -164,35 +184,40 @@ public class SnmpServiceImpl implements SnmpService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase,
|
public void addUser(UsmUser user, OctetString engineId) {
|
||||||
SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId) {
|
UserEntry userEntry = new UserEntry(user, engineId);
|
||||||
UsmUser usmUser = new UsmUser(new OctetString(userName),
|
|
||||||
authPassphrase != null ? snmpAuthProtocol.getOid() : null,
|
|
||||||
authPassphrase != null ? new OctetString(authPassphrase) : null,
|
|
||||||
privPassphrase != null ? snmpPrivProtocol.getOid() : null,
|
|
||||||
privPassphrase != null ? new OctetString(privPassphrase) : null);
|
|
||||||
OctetString securityNameOctets = new OctetString(userName);
|
|
||||||
|
|
||||||
UserEntry userEntry = new UserEntry(securityNameOctets, new OctetString(engineId), usmUser);
|
|
||||||
userEntries.add(userEntry);
|
userEntries.add(userEntry);
|
||||||
|
|
||||||
Snmp snmp = this.snmp;
|
Snmp snmp = this.snmp;
|
||||||
if (snmp != null) {
|
if (snmp != null) {
|
||||||
addUser(snmp, userEntry);
|
snmp.getUSM().addUser(user, engineId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addUser(Snmp snmp, UserEntry userEntry) {
|
@Override
|
||||||
snmp.getUSM().addUser(userEntry.securityName, userEntry.engineId, userEntry.user);
|
public void removeUser(Address address, UsmUser user, OctetString engineId) {
|
||||||
|
Snmp snmp = this.snmp;
|
||||||
|
if (snmp != null) {
|
||||||
|
snmp.getUSM().removeAllUsers(user.getSecurityName(), engineId);
|
||||||
|
snmp.removeCachedContextEngineId(address);
|
||||||
|
}
|
||||||
|
userEntries.removeIf(e -> e.engineId.equals(engineId) && e.user.equals(user));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte @Nullable [] getEngineId(Address address) {
|
||||||
|
Snmp snmp = this.snmp;
|
||||||
|
if (snmp != null) {
|
||||||
|
return snmp.discoverAuthoritativeEngineID(address, 15000);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class UserEntry {
|
private static class UserEntry {
|
||||||
public OctetString securityName;
|
|
||||||
public OctetString engineId;
|
public OctetString engineId;
|
||||||
public UsmUser user;
|
public UsmUser user;
|
||||||
|
|
||||||
public UserEntry(OctetString securityName, OctetString engineId, UsmUser user) {
|
public UserEntry(UsmUser user, OctetString engineId) {
|
||||||
this.securityName = securityName;
|
|
||||||
this.engineId = engineId;
|
this.engineId = engineId;
|
||||||
this.user = user;
|
this.user = user;
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ import static org.openhab.binding.snmp.internal.SnmpBindingConstants.*;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
import java.util.concurrent.ScheduledFuture;
|
||||||
@ -53,7 +52,6 @@ import org.openhab.core.types.RefreshType;
|
|||||||
import org.openhab.core.types.State;
|
import org.openhab.core.types.State;
|
||||||
import org.openhab.core.types.UnDefType;
|
import org.openhab.core.types.UnDefType;
|
||||||
import org.openhab.core.types.util.UnitUtils;
|
import org.openhab.core.types.util.UnitUtils;
|
||||||
import org.openhab.core.util.HexUtils;
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import org.snmp4j.AbstractTarget;
|
import org.snmp4j.AbstractTarget;
|
||||||
@ -68,6 +66,8 @@ import org.snmp4j.UserTarget;
|
|||||||
import org.snmp4j.event.ResponseEvent;
|
import org.snmp4j.event.ResponseEvent;
|
||||||
import org.snmp4j.event.ResponseListener;
|
import org.snmp4j.event.ResponseListener;
|
||||||
import org.snmp4j.mp.SnmpConstants;
|
import org.snmp4j.mp.SnmpConstants;
|
||||||
|
import org.snmp4j.security.UsmUser;
|
||||||
|
import org.snmp4j.smi.Address;
|
||||||
import org.snmp4j.smi.Counter64;
|
import org.snmp4j.smi.Counter64;
|
||||||
import org.snmp4j.smi.Integer32;
|
import org.snmp4j.smi.Integer32;
|
||||||
import org.snmp4j.smi.IpAddress;
|
import org.snmp4j.smi.IpAddress;
|
||||||
@ -87,8 +87,8 @@ import org.snmp4j.smi.VariableBinding;
|
|||||||
*/
|
*/
|
||||||
@NonNullByDefault
|
@NonNullByDefault
|
||||||
public class SnmpTargetHandler extends BaseThingHandler implements ResponseListener, CommandResponder {
|
public class SnmpTargetHandler extends BaseThingHandler implements ResponseListener, CommandResponder {
|
||||||
private static final Pattern HEXSTRING_VALIDITY = Pattern.compile("([a-f0-9]{2}[ :-]?)+");
|
private static final Pattern HEX_STRING_VALIDITY = Pattern.compile("([A-Fa-f0-9]{2}[ :-]?)+");
|
||||||
private static final Pattern HEXSTRING_EXTRACTOR = Pattern.compile("[^a-f0-9]");
|
private static final Pattern HEX_STRING_EXTRACTOR = Pattern.compile("[^A-Fa-f0-9]");
|
||||||
|
|
||||||
private final Logger logger = LoggerFactory.getLogger(SnmpTargetHandler.class);
|
private final Logger logger = LoggerFactory.getLogger(SnmpTargetHandler.class);
|
||||||
|
|
||||||
@ -97,13 +97,17 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
private @Nullable ScheduledFuture<?> refresh;
|
private @Nullable ScheduledFuture<?> refresh;
|
||||||
private int timeoutCounter = 0;
|
private int timeoutCounter = 0;
|
||||||
|
|
||||||
private @NonNullByDefault({}) AbstractTarget target;
|
private @NonNullByDefault({}) AbstractTarget<UdpAddress> target;
|
||||||
private @NonNullByDefault({}) String targetAddressString;
|
private @NonNullByDefault({}) String targetAddressString;
|
||||||
|
|
||||||
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> readChannelSet;
|
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> readChannelSet;
|
||||||
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> writeChannelSet;
|
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> writeChannelSet;
|
||||||
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> trapChannelSet;
|
private @NonNullByDefault({}) Set<SnmpInternalChannelConfiguration> trapChannelSet;
|
||||||
|
|
||||||
|
// SNMP v3
|
||||||
|
private @Nullable UsmUser usmUser;
|
||||||
|
private @Nullable OctetString engineId;
|
||||||
|
|
||||||
public SnmpTargetHandler(Thing thing, SnmpService snmpService) {
|
public SnmpTargetHandler(Thing thing, SnmpService snmpService) {
|
||||||
super(thing);
|
super(thing);
|
||||||
this.snmpService = snmpService;
|
this.snmpService = snmpService;
|
||||||
@ -139,7 +143,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Command rawValue = command;
|
Command rawValue = command;
|
||||||
if (command instanceof QuantityType quantityCommand) {
|
if (command instanceof QuantityType<?> quantityCommand) {
|
||||||
Unit<?> channelUnit = channel.unit;
|
Unit<?> channelUnit = channel.unit;
|
||||||
if (channelUnit == null) {
|
if (channelUnit == null) {
|
||||||
rawValue = new DecimalType(quantityCommand.toBigDecimal());
|
rawValue = new DecimalType(quantityCommand.toBigDecimal());
|
||||||
@ -180,7 +184,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
try {
|
try {
|
||||||
if (config.protocol.toInteger() == SnmpConstants.version1
|
if (config.protocol.toInteger() == SnmpConstants.version1
|
||||||
|| config.protocol.toInteger() == SnmpConstants.version2c) {
|
|| config.protocol.toInteger() == SnmpConstants.version2c) {
|
||||||
CommunityTarget target = new CommunityTarget();
|
CommunityTarget<UdpAddress> target = new CommunityTarget<>();
|
||||||
target.setCommunity(new OctetString(config.community));
|
target.setCommunity(new OctetString(config.community));
|
||||||
this.target = target;
|
this.target = target;
|
||||||
} else if (config.protocol.toInteger() == SnmpConstants.version3) {
|
} else if (config.protocol.toInteger() == SnmpConstants.version3) {
|
||||||
@ -189,31 +193,31 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "user not set");
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "user not set");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String engineIdHexString = config.engineId;
|
|
||||||
if (engineIdHexString == null) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "engineId not set");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String authPassphrase = config.authPassphrase;
|
String authPassphrase = config.authPassphrase;
|
||||||
if ((config.securityModel == SnmpSecurityModel.AUTH_PRIV
|
if (config.securityModel != SnmpSecurityModel.NO_AUTH_NO_PRIV
|
||||||
|| config.securityModel == SnmpSecurityModel.AUTH_NO_PRIV)
|
&& (authPassphrase == null || authPassphrase.isBlank())) {
|
||||||
&& (authPassphrase == null || authPassphrase.isEmpty())) {
|
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"Authentication passphrase not configured");
|
"Authentication passphrase not configured");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
String privPassphrase = config.privPassphrase;
|
String privPassphrase = config.privPassphrase;
|
||||||
if (config.securityModel == SnmpSecurityModel.AUTH_PRIV
|
if (config.securityModel == SnmpSecurityModel.AUTH_PRIV
|
||||||
&& (privPassphrase == null || privPassphrase.isEmpty())) {
|
&& (privPassphrase == null || privPassphrase.isBlank())) {
|
||||||
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR,
|
||||||
"Privacy passphrase not configured");
|
"Privacy passphrase not configured");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
byte[] engineId = HexUtils.hexToBytes(engineIdHexString);
|
usmUser = new UsmUser(new OctetString(userName),
|
||||||
snmpService.addUser(userName, config.authProtocol, authPassphrase, config.privProtocol, privPassphrase,
|
config.securityModel != SnmpSecurityModel.NO_AUTH_NO_PRIV ? config.authProtocol.getOid() : null,
|
||||||
engineId);
|
config.securityModel != SnmpSecurityModel.NO_AUTH_NO_PRIV && authPassphrase != null
|
||||||
UserTarget target = new UserTarget();
|
? new OctetString(authPassphrase)
|
||||||
target.setAuthoritativeEngineID(engineId);
|
: null,
|
||||||
|
config.securityModel == SnmpSecurityModel.AUTH_PRIV ? config.privProtocol.getOid() : null,
|
||||||
|
config.securityModel == SnmpSecurityModel.AUTH_PRIV && privPassphrase != null
|
||||||
|
? new OctetString(privPassphrase)
|
||||||
|
: null);
|
||||||
|
|
||||||
|
UserTarget<UdpAddress> target = new UserTarget<>();
|
||||||
target.setSecurityName(new OctetString(config.user));
|
target.setSecurityName(new OctetString(config.user));
|
||||||
target.setSecurityLevel(config.securityModel.getSecurityLevel());
|
target.setSecurityLevel(config.securityModel.getSecurityLevel());
|
||||||
this.target = target;
|
this.target = target;
|
||||||
@ -248,6 +252,16 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
r.cancel(true);
|
r.cancel(true);
|
||||||
}
|
}
|
||||||
snmpService.removeCommandResponder(this);
|
snmpService.removeCommandResponder(this);
|
||||||
|
|
||||||
|
UsmUser user = usmUser;
|
||||||
|
OctetString engineId = this.engineId;
|
||||||
|
Address address = target.getAddress();
|
||||||
|
if (user != null && engineId != null && address != null) {
|
||||||
|
snmpService.removeUser(address, user, engineId);
|
||||||
|
}
|
||||||
|
|
||||||
|
usmUser = null;
|
||||||
|
this.engineId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -260,7 +274,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
// Always cancel async request when response has been received
|
// Always cancel async request when response has been received
|
||||||
// otherwise a memory leak is created! Not canceling a request
|
// otherwise a memory leak is created! Not canceling a request
|
||||||
// immediately can be useful when sending a request to a broadcast
|
// immediately can be useful when sending a request to a broadcast
|
||||||
// address (Comment is taken from the snmp4j API doc).
|
// address (Comment is taken from the SNMP4J API doc).
|
||||||
((Snmp) event.getSource()).cancel(event.getRequest(), this);
|
((Snmp) event.getSource()).cancel(event.getRequest(), this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -350,7 +364,7 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
if (configUnit != null) {
|
if (configUnit != null) {
|
||||||
unit = UnitUtils.parseUnit(configUnit);
|
unit = UnitUtils.parseUnit(configUnit);
|
||||||
if (unit == null) {
|
if (unit == null) {
|
||||||
logger.warn("Failed to parse unit from '{}'for channel '{}'", unit, channel.getUID());
|
logger.warn("Failed to parse unit from '{}'for channel '{}'", configUnit, channel.getUID());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
|
} else if (CHANNEL_TYPE_UID_STRING.equals(channel.getChannelTypeUID())) {
|
||||||
@ -394,8 +408,9 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void generateChannelConfigs() {
|
private void generateChannelConfigs() {
|
||||||
Set<SnmpInternalChannelConfiguration> channelConfigs = Collections.unmodifiableSet(thing.getChannels().stream()
|
Set<SnmpInternalChannelConfiguration> channelConfigs = thing.getChannels().stream()
|
||||||
.map(this::getChannelConfigFromChannel).filter(Objects::nonNull).collect(Collectors.toSet()));
|
.map(this::getChannelConfigFromChannel).filter(Objects::nonNull).map(Objects::requireNonNull)
|
||||||
|
.collect(Collectors.toUnmodifiableSet());
|
||||||
this.readChannelSet = channelConfigs.stream()
|
this.readChannelSet = channelConfigs.stream()
|
||||||
.filter(c -> c.mode == SnmpChannelMode.READ || c.mode == SnmpChannelMode.READ_WRITE)
|
.filter(c -> c.mode == SnmpChannelMode.READ || c.mode == SnmpChannelMode.READ_WRITE)
|
||||||
.collect(Collectors.toSet());
|
.collect(Collectors.toSet());
|
||||||
@ -520,9 +535,9 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
case HEXSTRING -> {
|
case HEXSTRING -> {
|
||||||
if (command instanceof StringType stringCommand) {
|
if (command instanceof StringType stringCommand) {
|
||||||
String commandString = stringCommand.toString().toLowerCase();
|
String commandString = stringCommand.toString().toLowerCase();
|
||||||
Matcher commandMatcher = HEXSTRING_VALIDITY.matcher(commandString);
|
Matcher commandMatcher = HEX_STRING_VALIDITY.matcher(commandString);
|
||||||
if (commandMatcher.matches()) {
|
if (commandMatcher.matches()) {
|
||||||
commandString = HEXSTRING_EXTRACTOR.matcher(commandString).replaceAll("");
|
commandString = HEX_STRING_EXTRACTOR.matcher(commandString).replaceAll("");
|
||||||
return OctetString.fromHexStringPairs(commandString);
|
return OctetString.fromHexStringPairs(commandString);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -541,7 +556,25 @@ public class SnmpTargetHandler extends BaseThingHandler implements ResponseListe
|
|||||||
private boolean renewTargetAddress() {
|
private boolean renewTargetAddress() {
|
||||||
try {
|
try {
|
||||||
target.setAddress(new UdpAddress(InetAddress.getByName(config.hostname), config.port));
|
target.setAddress(new UdpAddress(InetAddress.getByName(config.hostname), config.port));
|
||||||
targetAddressString = ((UdpAddress) target.getAddress()).getInetAddress().getHostAddress();
|
targetAddressString = target.getAddress().getInetAddress().getHostAddress();
|
||||||
|
logger.trace("Determined {} as address for {} (thing {})", target.getAddress(), config.hostname,
|
||||||
|
this.thing.getUID());
|
||||||
|
UsmUser user = usmUser;
|
||||||
|
if (user != null && target instanceof UserTarget<UdpAddress> userTarget) {
|
||||||
|
byte[] engineIdBytes = snmpService.getEngineId(target.getAddress());
|
||||||
|
if (engineIdBytes != null) {
|
||||||
|
OctetString engineIdOctets = new OctetString(engineIdBytes);
|
||||||
|
this.engineId = engineIdOctets;
|
||||||
|
logger.trace("Determined {} as engineId for {}", engineIdOctets, target.getAddress());
|
||||||
|
snmpService.addUser(user, engineIdOctets);
|
||||||
|
userTarget.setAuthoritativeEngineID(engineIdBytes);
|
||||||
|
} else {
|
||||||
|
target.setAddress(null);
|
||||||
|
updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR,
|
||||||
|
"Cannot determine engineId");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
} catch (UnknownHostException e) {
|
} catch (UnknownHostException e) {
|
||||||
target.setAddress(null);
|
target.setAddress(null);
|
||||||
|
@ -41,7 +41,6 @@ public class SnmpTargetConfiguration {
|
|||||||
// v3 only
|
// v3 only
|
||||||
public SnmpSecurityModel securityModel = SnmpSecurityModel.NO_AUTH_NO_PRIV;
|
public SnmpSecurityModel securityModel = SnmpSecurityModel.NO_AUTH_NO_PRIV;
|
||||||
public @Nullable String user;
|
public @Nullable String user;
|
||||||
public @Nullable String engineId;
|
|
||||||
public SnmpAuthProtocol authProtocol = SnmpAuthProtocol.MD5;
|
public SnmpAuthProtocol authProtocol = SnmpAuthProtocol.MD5;
|
||||||
public @Nullable String authPassphrase;
|
public @Nullable String authPassphrase;
|
||||||
public SnmpPrivProtocol privProtocol = SnmpPrivProtocol.DES;
|
public SnmpPrivProtocol privProtocol = SnmpPrivProtocol.DES;
|
||||||
|
@ -13,6 +13,7 @@
|
|||||||
package org.openhab.binding.snmp.internal.types;
|
package org.openhab.binding.snmp.internal.types;
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
|
import org.snmp4j.security.Priv3DES;
|
||||||
import org.snmp4j.security.PrivAES128;
|
import org.snmp4j.security.PrivAES128;
|
||||||
import org.snmp4j.security.PrivAES192;
|
import org.snmp4j.security.PrivAES192;
|
||||||
import org.snmp4j.security.PrivAES256;
|
import org.snmp4j.security.PrivAES256;
|
||||||
@ -29,7 +30,8 @@ public enum SnmpPrivProtocol {
|
|||||||
AES128(PrivAES128.ID),
|
AES128(PrivAES128.ID),
|
||||||
AES192(PrivAES192.ID),
|
AES192(PrivAES192.ID),
|
||||||
AES256(PrivAES256.ID),
|
AES256(PrivAES256.ID),
|
||||||
DES(PrivDES.ID);
|
DES(PrivDES.ID),
|
||||||
|
DES3(Priv3DES.ID);
|
||||||
|
|
||||||
private final OID oid;
|
private final OID oid;
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ thing-type.config.snmp.target3.privProtocol.option.AES128 = AES128
|
|||||||
thing-type.config.snmp.target3.privProtocol.option.AES192 = AES192
|
thing-type.config.snmp.target3.privProtocol.option.AES192 = AES192
|
||||||
thing-type.config.snmp.target3.privProtocol.option.AES256 = AES256
|
thing-type.config.snmp.target3.privProtocol.option.AES256 = AES256
|
||||||
thing-type.config.snmp.target3.privProtocol.option.DES = DES
|
thing-type.config.snmp.target3.privProtocol.option.DES = DES
|
||||||
|
thing-type.config.snmp.target3.privProtocol.option.DES3 = 3DES
|
||||||
thing-type.config.snmp.target3.refresh.label = Refresh Time
|
thing-type.config.snmp.target3.refresh.label = Refresh Time
|
||||||
thing-type.config.snmp.target3.refresh.description = Refresh time in s (default 60s)
|
thing-type.config.snmp.target3.refresh.description = Refresh time in s (default 60s)
|
||||||
thing-type.config.snmp.target3.retries.label = Retries
|
thing-type.config.snmp.target3.retries.label = Retries
|
||||||
@ -85,8 +86,8 @@ channel-type.config.snmp.number.mode.option.READ_WRITE = Read/Write
|
|||||||
channel-type.config.snmp.number.mode.option.TRAP = Trap
|
channel-type.config.snmp.number.mode.option.TRAP = Trap
|
||||||
channel-type.config.snmp.number.oid.label = OID
|
channel-type.config.snmp.number.oid.label = OID
|
||||||
channel-type.config.snmp.number.oid.description = OID in dotted format (eg. .1.3.6.1.4.1.6574.3.1.1.3.0)
|
channel-type.config.snmp.number.oid.description = OID in dotted format (eg. .1.3.6.1.4.1.6574.3.1.1.3.0)
|
||||||
channel-type.config.snmp.number.unit.label = Unit Of Measurement
|
channel-type.config.snmp.number.unit.label = Unit
|
||||||
channel-type.config.snmp.number.unit.description = Unit of measurement (optional). The unit is used for representing the value in the GUI as well as for converting incoming values (like from '°F' to '°C'). Examples: "°C", "°F"
|
channel-type.config.snmp.number.unit.description = The unit of this value.
|
||||||
channel-type.config.snmp.string.datatype.label = Datatype
|
channel-type.config.snmp.string.datatype.label = Datatype
|
||||||
channel-type.config.snmp.string.datatype.description = Content data type
|
channel-type.config.snmp.string.datatype.description = Content data type
|
||||||
channel-type.config.snmp.string.datatype.option.STRING = String
|
channel-type.config.snmp.string.datatype.option.STRING = String
|
||||||
|
@ -64,10 +64,6 @@
|
|||||||
<description>Hostname or IP address of target host</description>
|
<description>Hostname or IP address of target host</description>
|
||||||
<context>network-address</context>
|
<context>network-address</context>
|
||||||
</parameter>
|
</parameter>
|
||||||
<parameter name="engineId" type="text" required="true">
|
|
||||||
<label>Engine ID</label>
|
|
||||||
<description>The authorization engine ID of this target in hexadecimal notation (22-64 characters)</description>
|
|
||||||
</parameter>
|
|
||||||
<parameter name="user" type="text" required="true">
|
<parameter name="user" type="text" required="true">
|
||||||
<label>Username</label>
|
<label>Username</label>
|
||||||
</parameter>
|
</parameter>
|
||||||
@ -106,6 +102,7 @@
|
|||||||
<option value="AES192">AES192</option>
|
<option value="AES192">AES192</option>
|
||||||
<option value="AES256">AES256</option>
|
<option value="AES256">AES256</option>
|
||||||
<option value="DES">DES</option>
|
<option value="DES">DES</option>
|
||||||
|
<option value="DES3">3DES</option>
|
||||||
</options>
|
</options>
|
||||||
<limitToOptions>true</limitToOptions>
|
<limitToOptions>true</limitToOptions>
|
||||||
<default>DES</default>
|
<default>DES</default>
|
||||||
|
@ -21,7 +21,6 @@ import java.io.IOException;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Vector;
|
|
||||||
|
|
||||||
import org.eclipse.jdt.annotation.NonNullByDefault;
|
import org.eclipse.jdt.annotation.NonNullByDefault;
|
||||||
import org.eclipse.jdt.annotation.Nullable;
|
import org.eclipse.jdt.annotation.Nullable;
|
||||||
@ -156,9 +155,8 @@ public abstract class AbstractSnmpTargetHandlerTest extends JavaTest {
|
|||||||
if (refresh) {
|
if (refresh) {
|
||||||
ArgumentCaptor<PDU> pduCaptor = ArgumentCaptor.forClass(PDU.class);
|
ArgumentCaptor<PDU> pduCaptor = ArgumentCaptor.forClass(PDU.class);
|
||||||
verify(snmpService, timeout(500).atLeast(1)).send(pduCaptor.capture(), any(), eq(null), eq(thingHandler));
|
verify(snmpService, timeout(500).atLeast(1)).send(pduCaptor.capture(), any(), eq(null), eq(thingHandler));
|
||||||
Vector<? extends VariableBinding> variables = pduCaptor.getValue().getVariableBindings();
|
List<? extends VariableBinding> variables = pduCaptor.getValue().getVariableBindings();
|
||||||
assertTrue(variables.stream().filter(v -> v.getOid().toDottedString().equals(TEST_OID)).findFirst()
|
assertTrue(variables.stream().anyMatch(v -> v.getOid().toDottedString().equals(TEST_OID)));
|
||||||
.isPresent());
|
|
||||||
} else {
|
} else {
|
||||||
verify(snmpService, never()).send(any(), any(), eq(null), eq(thingHandler));
|
verify(snmpService, never()).send(any(), any(), eq(null), eq(thingHandler));
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user