From 0464876b1c1b8435a04b2c82d319161e8950e738 Mon Sep 17 00:00:00 2001 From: Tomas Dvorak Date: Tue, 14 Feb 2017 18:29:12 +0100 Subject: [PATCH] DNS lookup of EET endpoints limited by timeout (default 2s) --- .../tomasdvorak/eet/client/EETClientImpl.java | 59 +++++++----- .../client/concurency/CompletedFuture.java | 34 +++++++ .../client/dto/WebserviceConfiguration.java | 26 +++++- .../exceptions/DnsLookupFailedException.java | 10 +++ .../exceptions/DnsTimeoutException.java | 10 +++ .../eet/client/networking/DnsResolver.java | 8 ++ .../networking/DnsResolverWithTimeout.java | 63 +++++++++++++ .../security/SecureEETCommunication.java | 15 +++- .../client/security/crl/InMemoryCRLStore.java | 2 +- .../DnsResolverWithTimeoutTest.java | 89 +++++++++++++++++++ 10 files changed, 288 insertions(+), 28 deletions(-) create mode 100644 src/main/java/cz/tomasdvorak/eet/client/concurency/CompletedFuture.java create mode 100644 src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsLookupFailedException.java create mode 100644 src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsTimeoutException.java create mode 100644 src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolver.java create mode 100644 src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeout.java create mode 100644 src/test/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeoutTest.java diff --git a/src/main/java/cz/tomasdvorak/eet/client/EETClientImpl.java b/src/main/java/cz/tomasdvorak/eet/client/EETClientImpl.java index 2ba807ef..9474bab8 100644 --- a/src/main/java/cz/tomasdvorak/eet/client/EETClientImpl.java +++ b/src/main/java/cz/tomasdvorak/eet/client/EETClientImpl.java @@ -1,15 +1,14 @@ package cz.tomasdvorak.eet.client; import cz.etrzby.xml.*; +import cz.tomasdvorak.eet.client.concurency.CompletedFuture; import cz.tomasdvorak.eet.client.config.CommunicationMode; import cz.tomasdvorak.eet.client.config.EndpointType; import cz.tomasdvorak.eet.client.config.SubmissionType; import cz.tomasdvorak.eet.client.dto.ResponseCallback; import cz.tomasdvorak.eet.client.dto.SubmitResult; import cz.tomasdvorak.eet.client.dto.WebserviceConfiguration; -import cz.tomasdvorak.eet.client.exceptions.CommunicationException; -import cz.tomasdvorak.eet.client.exceptions.CommunicationTimeoutException; -import cz.tomasdvorak.eet.client.exceptions.DataSigningException; +import cz.tomasdvorak.eet.client.exceptions.*; import cz.tomasdvorak.eet.client.security.ClientKey; import cz.tomasdvorak.eet.client.security.SecureEETCommunication; import cz.tomasdvorak.eet.client.security.SecurityCodesGenerator; @@ -22,7 +21,7 @@ import java.net.SocketTimeoutException; import java.util.Date; import java.util.UUID; -import java.util.concurrent.Future; +import java.util.concurrent.*; class EETClientImpl extends SecureEETCommunication implements EETClient { @@ -36,7 +35,14 @@ class EETClientImpl extends SecureEETCommunication implements EETClient { public SubmitResult submitReceipt(final TrzbaDataType receipt, final CommunicationMode mode, final EndpointType endpointType, final SubmissionType submissionType) throws DataSigningException, CommunicationException { final TrzbaType request = prepareData(receipt, mode, submissionType); - final EET port = getPort(mode, endpointType); + final EET port; + try { + port = getPort(endpointType); + } catch (DnsLookupFailedException e) { + throw new CommunicationException(request, e); + } catch (DnsTimeoutException e) { + throw new CommunicationTimeoutException(request, e); + } try { final OdpovedType response = port.odeslaniTrzby(request); @@ -47,7 +53,7 @@ public SubmitResult submitReceipt(final TrzbaDataType receipt, final Communicati } return new SubmitResult(request, response); } catch (final Exception e) { - if(ExceptionUtils.containsExceptionType(e, SocketTimeoutException.class)) { + if (ExceptionUtils.containsExceptionType(e, SocketTimeoutException.class)) { throw new CommunicationTimeoutException(request, e); } throw new CommunicationException(request, e); @@ -55,27 +61,34 @@ public SubmitResult submitReceipt(final TrzbaDataType receipt, final Communicati } - public Future submitReceipt(final TrzbaDataType receipt, final CommunicationMode mode, final EndpointType endpointType, final SubmissionType submissionType, final ResponseCallback handler) throws DataSigningException { final TrzbaType request = prepareData(receipt, mode, submissionType); - final EET port = getPort(mode, endpointType); - return port.odeslaniTrzbyAsync(request, new AsyncHandler() { - @Override - public void handleResponse(final Response res) { - try { - final OdpovedType response = res.get(); - final SubmitResult submitResult = new SubmitResult(request, response); - handler.onComplete(submitResult); - - } catch (final Exception e) { - if(ExceptionUtils.containsExceptionType(e, SocketTimeoutException.class)) { - handler.onTimeout(new CommunicationTimeoutException(request, e)); - } else { - handler.onError(new CommunicationException(request, e)); + final EET port; + try { + port = getPort(endpointType); + return port.odeslaniTrzbyAsync(request, new AsyncHandler() { + @Override + public void handleResponse(final Response res) { + try { + final OdpovedType response = res.get(); + final SubmitResult submitResult = new SubmitResult(request, response); + handler.onComplete(submitResult); + + } catch (final Exception e) { + if (ExceptionUtils.containsExceptionType(e, SocketTimeoutException.class)) { + handler.onTimeout(new CommunicationTimeoutException(request, e)); + } else { + handler.onError(new CommunicationException(request, e)); + } } } - } - }); + }); + } catch (DnsLookupFailedException e) { + handler.onError(new CommunicationException(request, e)); + } catch (DnsTimeoutException e) { + handler.onError(new CommunicationTimeoutException(request, e)); + } + return new CompletedFuture(); } private TrzbaType prepareData(final TrzbaDataType data, final CommunicationMode mode, final SubmissionType submissionType) throws DataSigningException { diff --git a/src/main/java/cz/tomasdvorak/eet/client/concurency/CompletedFuture.java b/src/main/java/cz/tomasdvorak/eet/client/concurency/CompletedFuture.java new file mode 100644 index 00000000..cd09735d --- /dev/null +++ b/src/main/java/cz/tomasdvorak/eet/client/concurency/CompletedFuture.java @@ -0,0 +1,34 @@ +package cz.tomasdvorak.eet.client.concurency; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +public class CompletedFuture implements Future { + + @Override + public boolean cancel(final boolean mayInterruptIfRunning) { + return false; + } + + @Override + public boolean isCancelled() { + return false; + } + + @Override + public boolean isDone() { + return false; + } + + @Override + public T get() throws InterruptedException, ExecutionException { + return null; + } + + @Override + public T get(final long timeout, final TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException { + return null; + } +} diff --git a/src/main/java/cz/tomasdvorak/eet/client/dto/WebserviceConfiguration.java b/src/main/java/cz/tomasdvorak/eet/client/dto/WebserviceConfiguration.java index 5b6ee023..a694058c 100644 --- a/src/main/java/cz/tomasdvorak/eet/client/dto/WebserviceConfiguration.java +++ b/src/main/java/cz/tomasdvorak/eet/client/dto/WebserviceConfiguration.java @@ -1,20 +1,42 @@ package cz.tomasdvorak.eet.client.dto; +/** + * TODO: create builder for the configuration! + */ public class WebserviceConfiguration { + + public static final long RECEIVE_TIMEOUT = 2000L; + public static final long DNS_LOOKUP_TIMEOUT = 2000L; + public static final WebserviceConfiguration DEFAULT = new WebserviceConfiguration( - 2000L // timeout for webservice call in millis = 2s, required by the current laws + RECEIVE_TIMEOUT, // timeout for webservice call in millis = 2s, required by the current laws + DNS_LOOKUP_TIMEOUT // timeout for DNS lookup in millis = 2s ); - private final long receiveTimeout; + private long receiveTimeout; + private long dnsLookupTimeout; /** * @param receiveTimeout receiving timeout of the Webservice call in millis */ public WebserviceConfiguration(final long receiveTimeout) { + this(receiveTimeout, DNS_LOOKUP_TIMEOUT); + } + + /** + * @param receiveTimeout receiving timeout of the Webservice call in millis + * @param dnsLookupTimeout timeout for DNS lookup in millis + */ + public WebserviceConfiguration(final long receiveTimeout, final long dnsLookupTimeout) { this.receiveTimeout = receiveTimeout; + this.dnsLookupTimeout = dnsLookupTimeout; } public long getReceiveTimeout() { return receiveTimeout; } + + public long getDnsLookupTimeout() { + return dnsLookupTimeout; + } } diff --git a/src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsLookupFailedException.java b/src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsLookupFailedException.java new file mode 100644 index 00000000..92131abf --- /dev/null +++ b/src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsLookupFailedException.java @@ -0,0 +1,10 @@ +package cz.tomasdvorak.eet.client.exceptions; + +/** + * DNS lookup failed from some other reason than timeout. It may be UnknownHostException, MalformedURLException or similar. + */ +public class DnsLookupFailedException extends Exception { + public DnsLookupFailedException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsTimeoutException.java b/src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsTimeoutException.java new file mode 100644 index 00000000..e6345e49 --- /dev/null +++ b/src/main/java/cz/tomasdvorak/eet/client/exceptions/DnsTimeoutException.java @@ -0,0 +1,10 @@ +package cz.tomasdvorak.eet.client.exceptions; + +/** + * DNS lookup failed after timeout reached. + */ +public class DnsTimeoutException extends Exception { + public DnsTimeoutException(final String message, final Throwable cause) { + super(message, cause); + } +} diff --git a/src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolver.java b/src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolver.java new file mode 100644 index 00000000..73008425 --- /dev/null +++ b/src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolver.java @@ -0,0 +1,8 @@ +package cz.tomasdvorak.eet.client.networking; + +import cz.tomasdvorak.eet.client.exceptions.DnsLookupFailedException; +import cz.tomasdvorak.eet.client.exceptions.DnsTimeoutException; + +public interface DnsResolver { + String resolveAddress(String url) throws DnsLookupFailedException, DnsTimeoutException; +} diff --git a/src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeout.java b/src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeout.java new file mode 100644 index 00000000..973a50e7 --- /dev/null +++ b/src/main/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeout.java @@ -0,0 +1,63 @@ +package cz.tomasdvorak.eet.client.networking; + +import cz.tomasdvorak.eet.client.exceptions.DnsLookupFailedException; +import cz.tomasdvorak.eet.client.exceptions.DnsTimeoutException; +import cz.tomasdvorak.eet.client.security.SecureEETCommunication; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.net.InetAddress; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.UnknownHostException; +import java.util.concurrent.*; + +public class DnsResolverWithTimeout implements DnsResolver { + + private static final Logger logger = LogManager.getLogger(SecureEETCommunication.class); + + private final long timeoutMillis; + + public DnsResolverWithTimeout(long timeoutMillis) { + this.timeoutMillis = timeoutMillis; + } + + @Override + public String resolveAddress(final String url) throws DnsLookupFailedException, DnsTimeoutException { + final String host; + try { + host = new URL(url).getHost(); + } catch (MalformedURLException e) { + throw new DnsLookupFailedException(String.format("URL %s is malformed", url), e); + } + + ExecutorService executor = Executors.newSingleThreadExecutor(); + try { + Future result = executor.submit(new Callable() { + @Override + public String call() throws Exception { + return doResolve(host); + } + }); + return result.get(timeoutMillis, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { + logger.warn("Unexpected interrupton while resolving host " + host, e); + Thread.currentThread().interrupt(); + throw new DnsLookupFailedException("Unexpected interruption while resolving host " + host, e); + } catch (ExecutionException e) { + throw new DnsLookupFailedException("Failed resolving host " + host, e.getCause()); + } catch (TimeoutException e) { + throw new DnsTimeoutException(String.format("DNS Lookup for host %s timed out", host), e); + } finally { + executor.shutdownNow(); + } + } + + /** + * Internal method to allow easier unit testing without dependency on internet connection + */ + protected String doResolve(final String host) throws UnknownHostException { + InetAddress address = InetAddress.getByName(host); + return address.getHostAddress(); + } +} \ No newline at end of file diff --git a/src/main/java/cz/tomasdvorak/eet/client/security/SecureEETCommunication.java b/src/main/java/cz/tomasdvorak/eet/client/security/SecureEETCommunication.java index 99979904..ac2fc9dc 100644 --- a/src/main/java/cz/tomasdvorak/eet/client/security/SecureEETCommunication.java +++ b/src/main/java/cz/tomasdvorak/eet/client/security/SecureEETCommunication.java @@ -5,6 +5,10 @@ import javax.xml.ws.BindingProvider; +import cz.tomasdvorak.eet.client.exceptions.DnsLookupFailedException; +import cz.tomasdvorak.eet.client.exceptions.DnsTimeoutException; +import cz.tomasdvorak.eet.client.networking.DnsResolver; +import cz.tomasdvorak.eet.client.networking.DnsResolverWithTimeout; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; @@ -12,11 +16,12 @@ import org.apache.cxf.transports.http.configuration.HTTPClientPolicy; import org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor; import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; import org.apache.wss4j.dom.handler.WSHandlerConstants; import cz.etrzby.xml.EET; import cz.etrzby.xml.EETService; -import cz.tomasdvorak.eet.client.config.CommunicationMode; import cz.tomasdvorak.eet.client.config.EndpointType; import cz.tomasdvorak.eet.client.dto.WebserviceConfiguration; import cz.tomasdvorak.eet.client.logging.WebserviceLogging; @@ -25,6 +30,8 @@ public class SecureEETCommunication { + private static final Logger logger = LogManager.getLogger(SecureEETCommunication.class); + /** * Key used to store crypto instance in the configuration params of Merlin crypto instance. */ @@ -66,7 +73,11 @@ protected SecureEETCommunication(final ClientKey clientKey, final ServerKey serv this.wsConfiguration = wsConfiguration; } - protected EET getPort(final CommunicationMode mode, final EndpointType endpointType) { + protected EET getPort(final EndpointType endpointType) throws DnsTimeoutException, DnsLookupFailedException { + final DnsResolver resolver = new DnsResolverWithTimeout(wsConfiguration.getDnsLookupTimeout()); + final String ip = resolver.resolveAddress(endpointType.getWebserviceUrl()); + logger.info(String.format("DNS lookup resolved %s to %s", endpointType, ip)); + final JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean(); factory.setServiceClass(EET.class); factory.getClientFactoryBean().getServiceFactory().setWsdlURL(WEBSERVICE.getWSDLDocumentLocation()); diff --git a/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java b/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java index e1e026ab..d7b08664 100644 --- a/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java +++ b/src/main/java/cz/tomasdvorak/eet/client/security/crl/InMemoryCRLStore.java @@ -32,7 +32,7 @@ */ public class InMemoryCRLStore { - private static final int CRL_RETRIEVE_TIMEOUT_SECONDS = 10; + private static final int CRL_RETRIEVE_TIMEOUT_SECONDS = 2; public static final InMemoryCRLStore INSTANCE = new InMemoryCRLStore(); diff --git a/src/test/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeoutTest.java b/src/test/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeoutTest.java new file mode 100644 index 00000000..83eac202 --- /dev/null +++ b/src/test/java/cz/tomasdvorak/eet/client/networking/DnsResolverWithTimeoutTest.java @@ -0,0 +1,89 @@ +package cz.tomasdvorak.eet.client.networking; + +import cz.tomasdvorak.eet.client.config.EndpointType; +import cz.tomasdvorak.eet.client.exceptions.DnsLookupFailedException; +import cz.tomasdvorak.eet.client.exceptions.DnsTimeoutException; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.net.MalformedURLException; +import java.net.UnknownHostException; +import java.util.concurrent.TimeoutException; + +public class DnsResolverWithTimeoutTest { + + private DnsResolver resolver; + + @Before + public void setUp() throws Exception { + resolver = new DnsResolverWithTimeout(100) { + @Override + protected String doResolve(final String host) throws UnknownHostException { + if("pg.eet.cz".equals(host)) { + return "5.145.105.129"; + } else if ("nonsense-timeouting.tomas-dvorak.cz".equals(host)) { + try { + Thread.sleep(10000); + return "5.145.105.129"; + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } else { + throw new UnknownHostException(host); + } + } + }; + + } + + @Test(timeout = 10000) + public void resolveOk() throws Exception { + final String resolved = resolver.resolveAddress(EndpointType.PLAYGROUND.getWebserviceUrl()); + Assert.assertEquals("5.145.105.129", resolved); + } + + @Test(timeout = 10000) + public void testResolveTimeout() { + try { + resolver.resolveAddress("http://nonsense-timeouting.tomas-dvorak.cz/ws/"); + Assert.fail("Should throw an exception"); + } catch (DnsLookupFailedException e) { + Assert.fail("Should throw an timeout exception"); + } catch (DnsTimeoutException e) { + final Throwable cause = e.getCause(); + Assert.assertEquals(TimeoutException.class, cause.getClass()); + Assert.assertEquals("DNS Lookup for host nonsense-timeouting.tomas-dvorak.cz timed out", e.getMessage()); + } + } + + @Test(timeout = 10000) + public void testResolveUnknownHost() { + try { + resolver.resolveAddress("http://nonsense-nonexistent.tomas-dvorak.cz/ws/"); + Assert.fail("Should throw an exception"); + } catch (DnsLookupFailedException e) { + final Throwable cause = e.getCause(); + Assert.assertEquals(UnknownHostException.class, cause.getClass()); + Assert.assertEquals("nonsense-nonexistent.tomas-dvorak.cz", cause.getMessage()); + } catch (DnsTimeoutException e) { + Assert.fail("Should throw an timeout exception"); + + } + } + + @Test(timeout = 10000) + public void testResolveMalformedUrl() { + try { + resolver.resolveAddress("/foo/bar"); + Assert.fail("Should throw an exception"); + } catch (DnsLookupFailedException e) { + final Throwable cause = e.getCause(); + Assert.assertEquals(MalformedURLException.class, cause.getClass()); + Assert.assertEquals("no protocol: /foo/bar", cause.getMessage()); + } catch (DnsTimeoutException e) { + Assert.fail("Should throw an timeout exception"); + + } + } +} \ No newline at end of file