Skip to content

Commit c2e051e

Browse files
authored
[snmp] SNMP v3 fixes and improvements (openhab#16803)
* [snmp] SNMP v3 fixes and improvements (cherry picked from commit a32af97d785f301ad2253801353af53a9941d6f9) Signed-off-by: Jan N. Klug <[email protected]>
1 parent 359d63f commit c2e051e

File tree

10 files changed

+182
-84
lines changed

10 files changed

+182
-84
lines changed

bundles/org.openhab.binding.snmp/README.md

+3-8
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,8 @@ The default is `v1`.
7777

7878
### `target3`
7979

80-
The `target3` thing has additional mandatory parameters: `engineId` and `user`.
81-
82-
The `engineId` must be given in hexadecimal notation (case-insensitive) without separators (e.g. `80000009035c710dbcd9e6`).
83-
The allowed length is 11 to 32 bytes (22 to 64 hex characters).
84-
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).
85-
86-
The `user` parameter is named "securityName" or "userName" in most agents.
80+
The `target3` thing has an additional mandatory parameter: `user`.
81+
This value of this parameter is named "securityName" or "userName" in most agents.
8782

8883
Optional configuration parameters are: `securityModel`, `authProtocol`, `authPassphrase`, `privProtocol` and `privPassphrase`.
8984

@@ -99,7 +94,7 @@ If authentication encryption is required, at least `authPassphrase` needs to be
9994
Other possible values for `authProtocol` are `SHA`, `HMAC128SHA224`, `HMAC192SHA256`, `HMAC256SHA384` and `HMAC384SHA512`.
10095

10196
If encryption of transmitted data (privacy encryption) is required, at least `privPassphrase` needs to be set, while `privProtocol` defaults to `DES`.
102-
Other possible values for `privProtocol` are `AES128`, `AES192` and `AES256`.
97+
Other possible values for `privProtocol` are `DES3`, `AES128`, `AES192` and `AES256`.
10398

10499
## Channels
105100

bundles/org.openhab.binding.snmp/pom.xml

+7-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,13 @@
1818
<dependency>
1919
<groupId>org.snmp4j</groupId>
2020
<artifactId>snmp4j</artifactId>
21-
<version>2.8.6</version>
21+
<version>3.8.2</version>
22+
<scope>compile</scope>
23+
</dependency>
24+
<dependency>
25+
<groupId>org.snmp4j</groupId>
26+
<artifactId>snmp4j-unix-transport</artifactId>
27+
<version>1.1.0</version>
2228
<scope>compile</scope>
2329
</dependency>
2430
</dependencies>

bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpService.java

+47-5
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,13 @@
1616

1717
import org.eclipse.jdt.annotation.NonNullByDefault;
1818
import org.eclipse.jdt.annotation.Nullable;
19-
import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol;
20-
import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol;
2119
import org.snmp4j.CommandResponder;
2220
import org.snmp4j.PDU;
2321
import org.snmp4j.Target;
2422
import org.snmp4j.event.ResponseListener;
23+
import org.snmp4j.security.UsmUser;
24+
import org.snmp4j.smi.Address;
25+
import org.snmp4j.smi.OctetString;
2526

2627
/**
2728
* The {@link SnmpService} is responsible for SNMP communication
@@ -33,12 +34,53 @@
3334
@NonNullByDefault
3435
public interface SnmpService {
3536

37+
/**
38+
* Add a listener for received PDUs to the service
39+
*
40+
* @param listener the listener
41+
*/
3642
void addCommandResponder(CommandResponder listener);
3743

44+
/**
45+
* Remove a listener for received PDUs from the service
46+
*
47+
* @param listener the listener
48+
*/
3849
void removeCommandResponder(CommandResponder listener);
3950

40-
void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
51+
/**
52+
* Send a PDU to the given target
53+
*
54+
* @param pdu the PDU
55+
* @param target the target
56+
* @param userHandle an optional user-handle to identify the request
57+
* @param listener the listener for the response (always called, even in case of timeout)
58+
* @throws IOException when an error occurs
59+
*/
60+
void send(PDU pdu, Target<?> target, @Nullable Object userHandle, ResponseListener listener) throws IOException;
4161

42-
void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase,
43-
SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId);
62+
/**
63+
* Add a user to the service for a given engine id (v3 only)
64+
*
65+
* @param user the {@link UsmUser} that should be added
66+
* @param engineId the engine id
67+
*/
68+
void addUser(UsmUser user, OctetString engineId);
69+
70+
/**
71+
* Remove a user from the service and clear the context engine id for this address (v3 only)
72+
*
73+
* @param address the remote address
74+
* @param user the user
75+
* @param engineId the engine id
76+
*/
77+
void removeUser(Address address, UsmUser user, OctetString engineId);
78+
79+
/**
80+
* Get the engine id of a remote system for a given address (v3 only)
81+
*
82+
* @param address the address of the remote system
83+
* @return the engine id or {@code null} when engine id could not be determined
84+
*/
85+
byte @Nullable [] getEngineId(Address address);
4486
}

bundles/org.openhab.binding.snmp/src/main/java/org/openhab/binding/snmp/internal/SnmpServiceImpl.java

+55-30
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,6 @@
2222
import org.eclipse.jdt.annotation.NonNullByDefault;
2323
import org.eclipse.jdt.annotation.Nullable;
2424
import org.openhab.binding.snmp.internal.config.SnmpServiceConfiguration;
25-
import org.openhab.binding.snmp.internal.types.SnmpAuthProtocol;
26-
import org.openhab.binding.snmp.internal.types.SnmpPrivProtocol;
2725
import org.openhab.core.config.core.Configuration;
2826
import org.osgi.service.component.annotations.Activate;
2927
import org.osgi.service.component.annotations.Component;
@@ -37,11 +35,22 @@
3735
import org.snmp4j.Target;
3836
import org.snmp4j.event.ResponseListener;
3937
import org.snmp4j.mp.MPv3;
38+
import org.snmp4j.security.AuthHMAC128SHA224;
39+
import org.snmp4j.security.AuthHMAC192SHA256;
40+
import org.snmp4j.security.AuthHMAC256SHA384;
41+
import org.snmp4j.security.AuthHMAC384SHA512;
42+
import org.snmp4j.security.AuthMD5;
43+
import org.snmp4j.security.AuthSHA;
4044
import org.snmp4j.security.Priv3DES;
45+
import org.snmp4j.security.PrivAES128;
46+
import org.snmp4j.security.PrivAES192;
47+
import org.snmp4j.security.PrivAES256;
48+
import org.snmp4j.security.PrivDES;
4149
import org.snmp4j.security.SecurityModels;
4250
import org.snmp4j.security.SecurityProtocols;
4351
import org.snmp4j.security.USM;
4452
import org.snmp4j.security.UsmUser;
53+
import org.snmp4j.smi.Address;
4554
import org.snmp4j.smi.OctetString;
4655
import org.snmp4j.smi.UdpAddress;
4756
import org.snmp4j.transport.DefaultUdpTransportMapping;
@@ -58,7 +67,6 @@
5867
public class SnmpServiceImpl implements SnmpService {
5968
private final Logger logger = LoggerFactory.getLogger(SnmpServiceImpl.class);
6069

61-
private @NonNullByDefault({}) SnmpServiceConfiguration config;
6270
private @Nullable Snmp snmp;
6371
private @Nullable DefaultUdpTransportMapping transport;
6472

@@ -67,9 +75,7 @@ public class SnmpServiceImpl implements SnmpService {
6775

6876
@Activate
6977
public SnmpServiceImpl(Map<String, Object> config) {
70-
SecurityProtocols.getInstance().addDefaultProtocols();
71-
SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
72-
78+
addProtocols();
7379
OctetString localEngineId = new OctetString(MPv3.createLocalEngineID());
7480
USM usm = new USM(SecurityProtocols.getInstance(), localEngineId, 0);
7581
SecurityModels.getInstance().addSecurityModel(usm);
@@ -79,34 +85,33 @@ public SnmpServiceImpl(Map<String, Object> config) {
7985

8086
@Modified
8187
protected void modified(Map<String, Object> config) {
82-
this.config = new Configuration(config).as(SnmpServiceConfiguration.class);
88+
SnmpServiceConfiguration snmpCfg = new Configuration(config).as(SnmpServiceConfiguration.class);
8389
try {
8490
shutdownSnmp();
8591

8692
final DefaultUdpTransportMapping transport;
8793

88-
if (this.config.port > 0) {
89-
transport = new DefaultUdpTransportMapping(new UdpAddress(this.config.port), true);
94+
if (snmpCfg.port > 0) {
95+
transport = new DefaultUdpTransportMapping(new UdpAddress(snmpCfg.port), true);
9096
} else {
9197
transport = new DefaultUdpTransportMapping();
9298
}
9399

94-
SecurityProtocols.getInstance().addDefaultProtocols();
95-
SecurityProtocols.getInstance().addPrivacyProtocol(new Priv3DES());
100+
addProtocols();
96101

97102
final Snmp snmp = new Snmp(transport);
98103
listeners.forEach(snmp::addCommandResponder);
99104
snmp.listen();
100105

101106
// re-add user entries
102-
userEntries.forEach(u -> addUser(snmp, u));
107+
userEntries.forEach(u -> snmp.getUSM().addUser(u.user, u.engineId));
103108

104109
this.snmp = snmp;
105110
this.transport = transport;
106111

107112
logger.debug("initialized SNMP at {}", transport.getAddress());
108113
} catch (IOException e) {
109-
logger.warn("could not open SNMP instance on port {}: {}", this.config.port, e.getMessage());
114+
logger.warn("could not open SNMP instance on port {}: {}", snmpCfg.port, e.getMessage());
110115
}
111116
}
112117

@@ -120,6 +125,21 @@ public void deactivate() {
120125
}
121126
}
122127

128+
private void addProtocols() {
129+
SecurityProtocols secProtocols = SecurityProtocols.getInstance();
130+
secProtocols.addAuthenticationProtocol(new AuthMD5());
131+
secProtocols.addAuthenticationProtocol(new AuthSHA());
132+
secProtocols.addAuthenticationProtocol(new AuthHMAC128SHA224());
133+
secProtocols.addAuthenticationProtocol(new AuthHMAC192SHA256());
134+
secProtocols.addAuthenticationProtocol(new AuthHMAC256SHA384());
135+
secProtocols.addAuthenticationProtocol(new AuthHMAC384SHA512());
136+
secProtocols.addPrivacyProtocol(new PrivDES());
137+
secProtocols.addPrivacyProtocol(new Priv3DES());
138+
secProtocols.addPrivacyProtocol(new PrivAES128());
139+
secProtocols.addPrivacyProtocol(new PrivAES192());
140+
secProtocols.addPrivacyProtocol(new PrivAES256());
141+
}
142+
123143
private void shutdownSnmp() throws IOException {
124144
DefaultUdpTransportMapping transport = this.transport;
125145
if (transport != null) {
@@ -152,7 +172,7 @@ public void removeCommandResponder(CommandResponder listener) {
152172
}
153173

154174
@Override
155-
public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseListener listener)
175+
public void send(PDU pdu, Target<?> target, @Nullable Object userHandle, ResponseListener listener)
156176
throws IOException {
157177
Snmp snmp = this.snmp;
158178
if (snmp != null) {
@@ -164,35 +184,40 @@ public void send(PDU pdu, Target target, @Nullable Object userHandle, ResponseLi
164184
}
165185

166186
@Override
167-
public void addUser(String userName, SnmpAuthProtocol snmpAuthProtocol, @Nullable String authPassphrase,
168-
SnmpPrivProtocol snmpPrivProtocol, @Nullable String privPassphrase, byte[] engineId) {
169-
UsmUser usmUser = new UsmUser(new OctetString(userName),
170-
authPassphrase != null ? snmpAuthProtocol.getOid() : null,
171-
authPassphrase != null ? new OctetString(authPassphrase) : null,
172-
privPassphrase != null ? snmpPrivProtocol.getOid() : null,
173-
privPassphrase != null ? new OctetString(privPassphrase) : null);
174-
OctetString securityNameOctets = new OctetString(userName);
175-
176-
UserEntry userEntry = new UserEntry(securityNameOctets, new OctetString(engineId), usmUser);
187+
public void addUser(UsmUser user, OctetString engineId) {
188+
UserEntry userEntry = new UserEntry(user, engineId);
177189
userEntries.add(userEntry);
178190

179191
Snmp snmp = this.snmp;
180192
if (snmp != null) {
181-
addUser(snmp, userEntry);
193+
snmp.getUSM().addUser(user, engineId);
182194
}
183195
}
184196

185-
private static void addUser(Snmp snmp, UserEntry userEntry) {
186-
snmp.getUSM().addUser(userEntry.securityName, userEntry.engineId, userEntry.user);
197+
@Override
198+
public void removeUser(Address address, UsmUser user, OctetString engineId) {
199+
Snmp snmp = this.snmp;
200+
if (snmp != null) {
201+
snmp.getUSM().removeAllUsers(user.getSecurityName(), engineId);
202+
snmp.removeCachedContextEngineId(address);
203+
}
204+
userEntries.removeIf(e -> e.engineId.equals(engineId) && e.user.equals(user));
205+
}
206+
207+
@Override
208+
public byte @Nullable [] getEngineId(Address address) {
209+
Snmp snmp = this.snmp;
210+
if (snmp != null) {
211+
return snmp.discoverAuthoritativeEngineID(address, 15000);
212+
}
213+
return null;
187214
}
188215

189216
private static class UserEntry {
190-
public OctetString securityName;
191217
public OctetString engineId;
192218
public UsmUser user;
193219

194-
public UserEntry(OctetString securityName, OctetString engineId, UsmUser user) {
195-
this.securityName = securityName;
220+
public UserEntry(UsmUser user, OctetString engineId) {
196221
this.engineId = engineId;
197222
this.user = user;
198223
}

0 commit comments

Comments
 (0)