Skip to content

Commit 69c52a4

Browse files
committed
feat(plc4j/opcua): Remove additional hostname resolution within connect attempts.
Closes #1897. Removed code which caused additional DNS lookups in favor of endpoint-port and endpoint-host options provided with plc4j 0.13. Updated fleaky unit test. Signed-off-by: Łukasz Dywicki <[email protected]>
1 parent 5c73dfe commit 69c52a4

File tree

4 files changed

+45
-63
lines changed

4 files changed

+45
-63
lines changed

plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/config/OpcuaConfiguration.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -136,11 +136,11 @@ public class OpcuaConfiguration implements PlcConnectionConfiguration {
136136
private Limits limits;
137137

138138
@ConfigurationParameter("endpoint-host")
139-
@Description("Endpoint host used to establish secure channel.")
139+
@Description("Endpoint host used to establish secure channel connection. Used when client made connection to server which advertises different hostname than one used for network connection.")
140140
private String endpointHost;
141141

142142
@ConfigurationParameter("endpoint-port")
143-
@Description("Endpoint port used to establish secure channel")
143+
@Description("Endpoint port used to establish secure channel. Used when client made connection to server which advertises different port number than one used for network connection.")
144144
private Integer endpointPort;
145145

146146
public String getProtocolCode() {

plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/OpcuaDriverContext.java

-7
Original file line numberDiff line numberDiff line change
@@ -139,13 +139,6 @@ public void setConfiguration(OpcuaConfiguration configuration) {
139139
port = matcher.group("transportPort");
140140
transportEndpoint = matcher.group("transportEndpoint");
141141

142-
if (configuration.getEndpointHost() != null) {
143-
host = configuration.getEndpointHost();
144-
}
145-
if (configuration.getEndpointPort() != null) {
146-
port = String.valueOf(configuration.getEndpointPort());
147-
}
148-
149142
String portAddition = port != null ? ":" + port : "";
150143
endpoint = "opc." + code + "://" + host + portAddition + transportEndpoint;
151144

plc4j/drivers/opcua/src/main/java/org/apache/plc4x/java/opcua/context/SecureChannel.java

+24-43
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@
2828
import java.security.cert.CertificateEncodingException;
2929
import java.security.cert.CertificateFactory;
3030
import java.security.cert.X509Certificate;
31-
import java.util.Collection;
3231
import java.util.Comparator;
33-
import java.util.HashSet;
3432
import java.util.Map.Entry;
3533
import java.util.Set;
3634
import java.util.concurrent.ScheduledExecutorService;
@@ -54,8 +52,6 @@
5452
import org.slf4j.Logger;
5553
import org.slf4j.LoggerFactory;
5654

57-
import java.net.InetAddress;
58-
import java.net.UnknownHostException;
5955
import java.nio.ByteBuffer;
6056
import java.nio.ByteOrder;
6157
import java.util.ArrayList;
@@ -95,7 +91,6 @@ public class SecureChannel {
9591
private final OpcuaDriverContext driverContext;
9692
private final Conversation conversation;
9793
private ScheduledFuture<?> keepAlive;
98-
private final Set<String> endpoints = new HashSet<>();
9994
private double sessionTimeout;
10095
private long revisedLifetime;
10196

@@ -118,17 +113,6 @@ public SecureChannel(Conversation conversation, RequestTransactionManager tm, Op
118113
this.password = configuration.getPassword();
119114
}
120115

121-
// Generate a list of endpoints we can use.
122-
try {
123-
InetAddress address = InetAddress.getByName(driverContext.getHost());
124-
this.endpoints.add("opc.tcp://" + address.getHostAddress() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint());
125-
this.endpoints.add("opc.tcp://" + address.getHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint());
126-
this.endpoints.add("opc.tcp://" + address.getCanonicalHostName() + ":" + driverContext.getPort() + driverContext.getTransportEndpoint());
127-
} catch (UnknownHostException e) {
128-
LOGGER.warn("Unable to resolve host name. Using original host from connection string which may cause issues connecting to server");
129-
this.endpoints.add(driverContext.getHost());
130-
}
131-
132116
if (conversation.getSecurityPolicy() == SecurityPolicy.NONE) {
133117
this.localCertificateString = NULL_BYTE_STRING;
134118
this.remoteCertificateThumbprint = NULL_BYTE_STRING;
@@ -314,23 +298,10 @@ private CompletableFuture<ActivateSessionResponse> onConnectActivateSessionReque
314298
conversation.setRemoteCertificate(getX509Certificate(sessionResponse.getServerCertificate().getStringValue()));
315299
conversation.setRemoteNonce(sessionResponse.getServerNonce().getStringValue());
316300

317-
List<String> contactPoints = new ArrayList<>(3);
318-
String port = driverContext.getPort() == null ? "" : ":" + driverContext.getPort();
319-
try {
320-
InetAddress address = InetAddress.getByName(driverContext.getHost());
321-
contactPoints.add("opc.tcp://" + address.getHostAddress() + port + driverContext.getTransportEndpoint());
322-
contactPoints.add("opc.tcp://" + address.getHostName() + port + driverContext.getTransportEndpoint());
323-
contactPoints.add("opc.tcp://" + address.getCanonicalHostName() + port + driverContext.getTransportEndpoint());
324-
} catch (UnknownHostException e) {
325-
// fall back to declared host
326-
contactPoints.add("opc.tcp://" + driverContext.getHost() + port + driverContext.getTransportEndpoint());
327-
LOGGER.warn("Could not reach host {}, possible network failure", driverContext.getHost(), e);
328-
}
329-
330-
Entry<EndpointDescription, UserTokenPolicy> selectedEndpoint = selectEndpoint(sessionResponse.getServerEndpoints(), contactPoints,
301+
Entry<EndpointDescription, UserTokenPolicy> selectedEndpoint = selectEndpoint(sessionResponse.getServerEndpoints(),
331302
configuration.getSecurityPolicy(), configuration.getMessageSecurity());
332303
if (selectedEndpoint == null) {
333-
throw new PlcRuntimeException("Unable to find endpoint matching " + contactPoints.get(0));
304+
throw new PlcRuntimeException("Unable to find endpoint matching " + driverContext.getEndpoint());
334305
}
335306

336307
PascalString policyId = selectedEndpoint.getValue().getPolicyId();
@@ -421,7 +392,8 @@ public CompletableFuture<EndpointDescription> onDiscoverGetEndpointsRequest() {
421392
);
422393

423394
return conversation.submit(endpointsRequest, GetEndpointsResponse.class).thenApply(response -> {
424-
Entry<EndpointDescription, UserTokenPolicy> entry = selectEndpoint(response.getEndpoints(), this.endpoints, this.configuration.getSecurityPolicy(), this.configuration.getMessageSecurity());
395+
Entry<EndpointDescription, UserTokenPolicy> entry = selectEndpoint(response.getEndpoints(),
396+
this.configuration.getSecurityPolicy(), this.configuration.getMessageSecurity());
425397

426398
if (entry == null) {
427399
Set<String> endpointUris = response.getEndpoints().stream()
@@ -494,19 +466,18 @@ private static ReadBufferByteBased toBuffer(Supplier<Payload> supplier) {
494466
* Selects the endpoint and authentication policy based on client settings.
495467
*
496468
* @param extensionObjects Endpoint descriptions returned by the server.
497-
* @param contactPoints Contact points expected by client.
498469
* @param securityPolicy Security policy searched in endpoints.
499470
* @param messageSecurity Message security needed by client.
500471
* @return Endpoint matching given.
501472
*/
502-
private Entry<EndpointDescription, UserTokenPolicy> selectEndpoint(List<EndpointDescription> extensionObjects, Collection<String> contactPoints,
473+
private Entry<EndpointDescription, UserTokenPolicy> selectEndpoint(List<EndpointDescription> extensionObjects,
503474
SecurityPolicy securityPolicy, MessageSecurity messageSecurity) throws PlcRuntimeException {
504475
// Get a list of the endpoints which match ours.
505476
MessageSecurityMode effectiveMessageSecurity = SecurityPolicy.NONE == securityPolicy ? MessageSecurityMode.messageSecurityModeNone : messageSecurity.getMode();
506477
List<Entry<EndpointDescription, UserTokenPolicy>> serverEndpoints = new ArrayList<>();
507478

508479
for (EndpointDescription endpointDescription : extensionObjects) {
509-
if (isMatchingEndpoint(endpointDescription, contactPoints)) {
480+
if (isMatchingEndpointDescription(endpointDescription)) {
510481
boolean policyMatch = endpointDescription.getSecurityPolicyUri().getStringValue().equals(securityPolicy.getSecurityPolicyUri());
511482
boolean msgSecurityMatch = endpointDescription.getSecurityMode().equals(effectiveMessageSecurity);
512483

@@ -530,22 +501,32 @@ private Entry<EndpointDescription, UserTokenPolicy> selectEndpoint(List<Endpoint
530501
return serverEndpoints.get(0);
531502
}
532503

504+
private boolean isMatchingEndpointDescription(EndpointDescription endpointDescription) {
505+
if (isMatchingEndpoint(endpointDescription, driverContext.getHost(), driverContext.getPort(), driverContext.getTransportEndpoint())) {
506+
return true;
507+
}
508+
if (configuration.getEndpointHost() != null) {
509+
return isMatchingEndpoint(endpointDescription, configuration.getEndpointHost(), configuration.getEndpointPort() == null ? driverContext.getPort() : String.valueOf(configuration.getEndpointPort()), driverContext.getTransportEndpoint());
510+
} else if (configuration.getEndpointPort() != null) {
511+
return isMatchingEndpoint(endpointDescription, driverContext.getHost(), configuration.getEndpointPort().toString(), driverContext.getTransportEndpoint());
512+
}
513+
return false;
514+
}
515+
533516
/**
534517
* Checks each component of the return endpoint description against the connection string.
535518
* If all are correct then return true.
536519
*
537520
* @param endpoint - EndpointDescription returned from server
521+
* @param host Permitted host
522+
* @param port Permitted port
523+
* @param transportEndpoint Transport endpoint
538524
* @return true if this endpoint matches our configuration
539525
* @throws PlcRuntimeException - If the returned endpoint string doesn't match the format expected
540526
*/
541-
private static boolean isMatchingEndpoint(EndpointDescription endpoint, Collection<String> contactPoints) throws PlcRuntimeException {
542-
// Split up the connection string into it's individual segments.
543-
for (String contactPoint : contactPoints) {
544-
if (endpoint.getEndpointUrl().getStringValue().startsWith(contactPoint)) {
545-
return true;
546-
}
547-
}
548-
return false;
527+
private static boolean isMatchingEndpoint(EndpointDescription endpoint, String host, String port, String transportEndpoint) throws PlcRuntimeException {
528+
String portAddition = port == null ? "" : ":" + port;
529+
return endpoint.getEndpointUrl().getStringValue().startsWith("opc.tcp://" + host + portAddition + transportEndpoint);
549530
}
550531

551532
/**

plc4j/drivers/opcua/src/test/java/org/apache/plc4x/java/opcua/OpcuaPlcDriverTest.java

+19-11
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
import java.util.Map;
2929
import java.util.Map.Entry;
3030
import java.util.concurrent.ConcurrentLinkedDeque;
31+
import java.util.concurrent.CountDownLatch;
3132
import java.util.concurrent.TimeUnit;
3233
import java.util.stream.Collectors;
3334
import org.apache.plc4x.java.DefaultPlcDriverManager;
@@ -500,9 +501,11 @@ public void writeVariables(SecurityPolicy policy, MessageSecurity messageSecurit
500501
public void multipleThreads() throws Exception {
501502
class ReadWorker extends Thread {
502503
private final PlcConnection connection;
504+
private final CountDownLatch latch;
503505

504-
public ReadWorker(PlcConnection opcuaConnection) {
506+
public ReadWorker(PlcConnection opcuaConnection, CountDownLatch latch) {
505507
this.connection = opcuaConnection;
508+
this.latch = latch;
506509
}
507510

508511
@Override
@@ -516,21 +519,24 @@ public void run() {
516519
PlcReadResponse read_response = read_request.execute().get();
517520
assertThat(read_response.getResponseCode("Bool")).isEqualTo(PlcResponseCode.OK);
518521
}
519-
522+
this.latch.countDown();
520523
} catch (ExecutionException e) {
521524
LOGGER.error("run aborted", e);
525+
this.latch.countDown();
522526
} catch (InterruptedException e) {
523-
Thread.currentThread().interrupt();
524-
throw new RuntimeException(e);
527+
LOGGER.error("thread interrupted", e);
528+
this.latch.countDown();
525529
}
526530
}
527531
}
528532

529533
class WriteWorker extends Thread {
530534
private final PlcConnection connection;
535+
private final CountDownLatch latch;
531536

532-
public WriteWorker(PlcConnection opcuaConnection) {
537+
public WriteWorker(PlcConnection opcuaConnection, CountDownLatch latch) {
533538
this.connection = opcuaConnection;
539+
this.latch = latch;
534540
}
535541

536542
@Override
@@ -544,11 +550,13 @@ public void run() {
544550
PlcWriteResponse write_response = write_request.execute().get();
545551
assertThat(write_response.getResponseCode("Bool")).isEqualTo(PlcResponseCode.OK);
546552
}
553+
this.latch.countDown();
547554
} catch (ExecutionException e) {
548555
LOGGER.error("run aborted", e);
556+
this.latch.countDown();
549557
} catch (InterruptedException e) {
550-
Thread.currentThread().interrupt();
551-
throw new RuntimeException(e);
558+
LOGGER.error("thread interrupted", e);
559+
this.latch.countDown();
552560
}
553561
}
554562
}
@@ -558,13 +566,13 @@ public void run() {
558566
Condition<PlcConnection> is_connected = new Condition<>(PlcConnection::isConnected, "is connected");
559567
assertThat(opcuaConnection).is(is_connected);
560568

561-
ReadWorker read_worker = new ReadWorker(opcuaConnection);
562-
WriteWorker write_worker = new WriteWorker(opcuaConnection);
569+
CountDownLatch latch = new CountDownLatch(2);
570+
ReadWorker read_worker = new ReadWorker(opcuaConnection, latch);
571+
WriteWorker write_worker = new WriteWorker(opcuaConnection, latch);
563572
read_worker.start();
564573
write_worker.start();
565574

566-
read_worker.join();
567-
write_worker.join();
575+
latch.await();
568576

569577
opcuaConnection.close();
570578
assert !opcuaConnection.isConnected();

0 commit comments

Comments
 (0)