Skip to content

Commit

Permalink
#56: BdxlLocator and BusdoxLocator reports network-level erros as par…
Browse files Browse the repository at this point in the history
…ticipant not found
  • Loading branch information
aaron-kumar committed Aug 2, 2024
1 parent f43b48b commit d6a1c0f
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 72 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,14 @@
import network.oxalis.vefa.peppol.lookup.util.DynamicHostnameGenerator;
import network.oxalis.vefa.peppol.lookup.util.EncodingUtils;
import network.oxalis.vefa.peppol.mode.Mode;
import org.apache.commons.lang3.StringUtils;
import org.xbill.DNS.*;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Record;

import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -44,11 +44,18 @@
*/
public class BdxlLocator extends AbstractLocator {

private long timeout = 30L;
private long timeout = 20L;
private int maxRetries = 3;

private DynamicHostnameGenerator hostnameGenerator;
private static final List<InetAddress> customDNSServers = new ArrayList<>();
//Google DNS: faster, supported by multiple data centers all around the world
public static InetAddress GOOGLE_PRIMARY_DNS;
public static InetAddress GOOGLE_SECONDARY_DNS;
//Cloudflare DNS: internet’s fastest DNS directory
public static InetAddress CLOUDFLARE_PRIMARY_DNS;
public static InetAddress CLOUDFLARE_SECONDARY_DNS;

private final DynamicHostnameGenerator hostnameGenerator;

public BdxlLocator(Mode mode) {
this(
Expand All @@ -59,6 +66,21 @@ public BdxlLocator(Mode mode) {
);
maxRetries = Integer.parseInt(mode.getString("lookup.locator.bdxl.maxRetries"));
timeout = Long.parseLong(mode.getString("lookup.locator.bdxl.timeout"));

try {
GOOGLE_PRIMARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (8 & 0xff), (byte) (8 & 0xff), (byte) (8 & 0xff), (byte) (8 & 0xff)}));
GOOGLE_SECONDARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (8 & 0xff), (byte) (8 & 0xff), (byte) (4 & 0xff), (byte) (4 & 0xff)}));

CLOUDFLARE_PRIMARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (1 & 0xff), (byte) (1 & 0xff), (byte) (1 & 0xff), (byte) (1 & 0xff)}));
CLOUDFLARE_SECONDARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (1 & 0xff), (byte) (0 & 0xff), (byte) (0 & 0xff), (byte) (1 & 0xff)}));
} catch (UnknownHostException e) {
//Unable to initialize Custom DNS server Primary DNS lookup fail, now try with default resolver
}

customDNSServers.add(GOOGLE_PRIMARY_DNS);
customDNSServers.add(GOOGLE_SECONDARY_DNS);
customDNSServers.add(CLOUDFLARE_PRIMARY_DNS);
customDNSServers.add(CLOUDFLARE_SECONDARY_DNS);
}

/**
Expand Down Expand Up @@ -110,53 +132,41 @@ public URI lookup(ParticipantIdentifier participantIdentifier) throws LookupExce
String hostname = hostnameGenerator.generate(participantIdentifier).replaceAll("=*", "");

try {
// Fetch all records of type NAPTR registered on hostname.
final Lookup lookup = new Lookup(hostname, Type.NAPTR);
Record[] records;
ExtendedResolver extendedResolver = CustomExtendedDNSResolver.createExtendedResolver(customDNSServers, timeout, maxRetries);
extendedResolver.setRetries(maxRetries);
extendedResolver.setTimeout(Duration.ofSeconds(timeout));

ExtendedResolver extendedResolver = new ExtendedResolver();
try {
if (StringUtils.isNotBlank(hostname)) {
extendedResolver.addResolver(new SimpleResolver(hostname));
}
} catch (final UnknownHostException ex) {
//Primary DNS lookup fail, now try with default resolver
}
extendedResolver.addResolver (Lookup.getDefaultResolver ());
extendedResolver.setRetries (maxRetries);
extendedResolver.setTimeout (Duration.ofSeconds (timeout));
lookup.setResolver (extendedResolver);
// Fetch all records of type NAPTR registered on hostname.
final Lookup naptrLookup = new Lookup(hostname, Type.NAPTR);
naptrLookup.setResolver(extendedResolver);

Record[] records;
int retryCountLeft = maxRetries;
// Retry = The lookup may fail due to a network error. Repeating the lookup might be helpful
// Retry, the NAPTR lookup may fail due to a network error. Repeating the lookup might be helpful
do {
records = lookup.run();
records = naptrLookup.run();
--retryCountLeft;
} while (lookup.getResult () == Lookup.TRY_AGAIN && retryCountLeft >= 0);
} while (naptrLookup.getResult() == Lookup.TRY_AGAIN && retryCountLeft >= 0);

// Retry with TCP as well
if (lookup.getResult () == Lookup.TRY_AGAIN) {
extendedResolver.setTCP (true);
if (naptrLookup.getResult() == Lookup.TRY_AGAIN) {
extendedResolver.setTCP(true);

retryCountLeft = maxRetries;
do {
records = lookup.run();
records = naptrLookup.run();
--retryCountLeft;
} while (lookup.getResult () == Lookup.TRY_AGAIN && retryCountLeft >= 0);
} while (naptrLookup.getResult() == Lookup.TRY_AGAIN && retryCountLeft >= 0);
}

if (lookup.getResult () != Lookup.SUCCESSFUL) {
// HOST_NOT_FOUND = The host does not exist
// TYPE_NOT_FOUND = The host exists, but has no records associated with the queried type
// Since we already tried couple of times with TRY_AGAIN for TCP and UDP, now giving up ...
if(lookup.getResult() == Lookup.HOST_NOT_FOUND || lookup.getResult() == Lookup.TRY_AGAIN
|| lookup.getResult() == Lookup.TYPE_NOT_FOUND) {
throw new NotFoundException(
String.format("Identifier '%s' is not registered in SML.", participantIdentifier.toString()));
} else {
// Attribute to UNRECOVERABLE error, repeating the lookup would not be helpful
throw new LookupException(
String.format("Error when looking up identifier '%s' in SML.", participantIdentifier.toString()));
if (naptrLookup.getResult() != Lookup.SUCCESSFUL) {
switch (naptrLookup.getResult()) {
case Lookup.HOST_NOT_FOUND: // HOST_NOT_FOUND = The host does not exist
case Lookup.TYPE_NOT_FOUND: // TYPE_NOT_FOUND = The host exists, but has no records associated with the queried type
throw new NotFoundException(String.format("Identifier '%s' is not registered in SML.", participantIdentifier.getIdentifier()));
case Lookup.TRY_AGAIN: // Since we already tried a couple of times with TRY_AGAIN for TCP and UDP, now giving up ...
default:
throw new LookupException(String.format("Error when looking up identifier '%s' in SML. DNS-Lookup-Err: %s", participantIdentifier.getIdentifier(), naptrLookup.getErrorString()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,22 +24,31 @@
import network.oxalis.vefa.peppol.lookup.api.NotFoundException;
import network.oxalis.vefa.peppol.lookup.util.DynamicHostnameGenerator;
import network.oxalis.vefa.peppol.mode.Mode;
import org.apache.commons.lang3.StringUtils;
import org.xbill.DNS.ExtendedResolver;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;

import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;

public class BusdoxLocator extends AbstractLocator {

private long timeout = 30L;
private long timeout = 20L;
private int maxRetries = 3;

private DynamicHostnameGenerator hostnameGenerator;
private static final List<InetAddress> customDNSServers = new ArrayList<>();
//Google DNS: faster, supported by multiple data centers all around the world
public static InetAddress GOOGLE_PRIMARY_DNS;
public static InetAddress GOOGLE_SECONDARY_DNS;
//Cloudflare DNS: internet’s fastest DNS directory
public static InetAddress CLOUDFLARE_PRIMARY_DNS;
public static InetAddress CLOUDFLARE_SECONDARY_DNS;

private final DynamicHostnameGenerator hostnameGenerator;

public BusdoxLocator(Mode mode) {
this(
Expand All @@ -49,6 +58,22 @@ public BusdoxLocator(Mode mode) {
);
maxRetries = Integer.parseInt(mode.getString("lookup.locator.busdox.maxRetries"));
timeout = Long.parseLong(mode.getString("lookup.locator.busdox.timeout"));

try {
GOOGLE_PRIMARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (8 & 0xff), (byte) (8 & 0xff), (byte) (8 & 0xff), (byte) (8 & 0xff)}));
GOOGLE_SECONDARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (8 & 0xff), (byte) (8 & 0xff), (byte) (4 & 0xff), (byte) (4 & 0xff)}));

CLOUDFLARE_PRIMARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (1 & 0xff), (byte) (1 & 0xff), (byte) (1 & 0xff), (byte) (1 & 0xff)}));
CLOUDFLARE_SECONDARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (1 & 0xff), (byte) (0 & 0xff), (byte) (0 & 0xff), (byte) (1 & 0xff)}));
// CLOUDFLARE_SECONDARY_DNS = InetAddress.getByAddress((new byte[]{(byte) (1 & 0xff), (byte) (0), (byte) (0), (byte) (1 & 0xff)}));
} catch (UnknownHostException e) {
//Unable to initialize Custom DNS server Primary DNS lookup fail, now try with default resolver
}

customDNSServers.add(GOOGLE_PRIMARY_DNS);
customDNSServers.add(GOOGLE_SECONDARY_DNS);
customDNSServers.add(CLOUDFLARE_PRIMARY_DNS);
customDNSServers.add(CLOUDFLARE_SECONDARY_DNS);
}

@SuppressWarnings("unused")
Expand All @@ -66,51 +91,39 @@ public URI lookup(ParticipantIdentifier participantIdentifier) throws LookupExce
String hostname = hostnameGenerator.generate(participantIdentifier);

try {
final Lookup lookup = new Lookup(hostname);
ExtendedResolver extendedResolver = CustomExtendedDNSResolver.createExtendedResolver(customDNSServers, timeout, maxRetries);
extendedResolver.setRetries(maxRetries);
extendedResolver.setTimeout(Duration.ofSeconds(timeout));

ExtendedResolver extendedResolver = new ExtendedResolver();
try {
if (StringUtils.isNotBlank(hostname)) {
extendedResolver.addResolver(new SimpleResolver(hostname));
}
} catch (final UnknownHostException ex) {
//Primary DNS lookup fail, now try with default resolver
}
extendedResolver.addResolver (Lookup.getDefaultResolver ());
extendedResolver.setRetries (maxRetries);
extendedResolver.setTimeout (Duration.ofSeconds (timeout));
lookup.setResolver (extendedResolver);
final Lookup lookup = new Lookup(hostname);
lookup.setResolver(extendedResolver);

int retryCountLeft = maxRetries;
// Retry = The lookup may fail due to a network error. Repeating the lookup might be helpful
do {
lookup.run();
--retryCountLeft;
} while (lookup.getResult () == Lookup.TRY_AGAIN && retryCountLeft >= 0);
} while (lookup.getResult() == Lookup.TRY_AGAIN && retryCountLeft >= 0);

// Retry with TCP as well
if (lookup.getResult () == Lookup.TRY_AGAIN) {
extendedResolver.setTCP (true);
if (lookup.getResult() == Lookup.TRY_AGAIN) {
extendedResolver.setTCP(true);

retryCountLeft = maxRetries;
do {
lookup.run();
--retryCountLeft;
} while (lookup.getResult () == Lookup.TRY_AGAIN && retryCountLeft >= 0);
} while (lookup.getResult() == Lookup.TRY_AGAIN && retryCountLeft >= 0);
}

if (lookup.getResult () != Lookup.SUCCESSFUL) {
// HOST_NOT_FOUND = The host does not exist
// TYPE_NOT_FOUND = The host exists, but has no records associated with the queried type
// Since we already tried couple of times with TRY_AGAIN for TCP and UDP, now giving up ...
if(lookup.getResult() == Lookup.HOST_NOT_FOUND || lookup.getResult() == Lookup.TRY_AGAIN
|| lookup.getResult() == Lookup.TYPE_NOT_FOUND) {
throw new NotFoundException(
String.format("Identifier '%s' is not registered in SML.", participantIdentifier.getIdentifier()));
} else {
// Attribute to UNRECOVERABLE error, repeating the lookup would not be helpful
throw new LookupException(
String.format("Error when looking up identifier '%s' in SML.", participantIdentifier.getIdentifier()));
if (lookup.getResult() != Lookup.SUCCESSFUL) {
switch (lookup.getResult()) {
case Lookup.HOST_NOT_FOUND: // HOST_NOT_FOUND = The host does not exist
case Lookup.TYPE_NOT_FOUND: // TYPE_NOT_FOUND = The host exists, but has no records associated with the queried type
throw new NotFoundException(String.format("Identifier '%s' is not registered in SML.", participantIdentifier.getIdentifier()));
case Lookup.TRY_AGAIN: // Since we already tried a couple of times with TRY_AGAIN for TCP and UDP, now giving up ...
default:
throw new LookupException(String.format("Error when looking up identifier '%s' in SML. DNS-Lookup-Err: %s", participantIdentifier.getIdentifier(), lookup.getErrorString()));
}
}
} catch (TextParseException e) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2015-2017 Direktoratet for forvaltning og IKT
*
* This source code is subject to dual licensing:
*
*
* Licensed under the EUPL, Version 1.1 or – as soon they
* will be approved by the European Commission - subsequent
* versions of the EUPL (the "Licence");
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*
*
* See the Licence for the specific language governing
* permissions and limitations under the Licence.
*/

package network.oxalis.vefa.peppol.lookup.locator;

import org.xbill.DNS.ExtendedResolver;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.ResolverConfig;
import org.xbill.DNS.SimpleResolver;

import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class CustomExtendedDNSResolver {

private CustomExtendedDNSResolver() {
}

public static void setCustomizeResolverTimeout(Resolver resolver, long timeout) {
// Set query timeout
resolver.setTimeout(Duration.ofSeconds(timeout));
}

public static void setCustomizeResolverTimeoutAndMaxRetries(ExtendedResolver extendedResolver, long timeout, int maxRetries) {
setCustomizeResolverTimeout(extendedResolver, timeout);
// Set the default retries
extendedResolver.setRetries(maxRetries);
}

public static void iterateEachDefaultResolverAndConfigure(Consumer<? super SimpleResolver> consumer, long timeout) {
for (final InetSocketAddress inetSocketAddress : ResolverConfig.getCurrentConfig().servers())
if (inetSocketAddress != null) {
SimpleResolver simpleResolver = new SimpleResolver(inetSocketAddress);
setCustomizeResolverTimeout(simpleResolver, timeout);
consumer.accept(simpleResolver);
}
}

public static void iterateEachResolverAndConfigure(Iterable<? extends InetAddress> customDNSServersAddress,
Consumer<? super SimpleResolver> aConsumer, long timeout) {
if (customDNSServersAddress != null)
for (final InetAddress inetAddress : customDNSServersAddress)
if (inetAddress != null) {
// Use the default port
final SimpleResolver simpleResolver = new SimpleResolver(inetAddress);
setCustomizeResolverTimeout(simpleResolver, timeout);
aConsumer.accept(simpleResolver);
}
}

private static boolean isResolverAlreadyAvailable(List<Resolver> resolverList, SimpleResolver simpleResolver) {
final InetSocketAddress aSearchAddr = simpleResolver.getAddress();

// Use Stream API to check if any resolver has the same address
return resolverList.stream()
.filter(x -> x instanceof SimpleResolver)
.map(x -> (SimpleResolver) x)
.anyMatch(x -> x.getAddress().equals(aSearchAddr));
}

public static ExtendedResolver createExtendedResolver(final Iterable<? extends InetAddress> customDNSServersAddress, long timeout, int maxRetries) {
List<Resolver> resolverList = new ArrayList<>();

iterateEachResolverAndConfigure(customDNSServersAddress, x -> {
if (!isResolverAlreadyAvailable(resolverList, x))
resolverList.add(x);
}, timeout);

iterateEachDefaultResolverAndConfigure(x -> {
if (!isResolverAlreadyAvailable(resolverList, x))
resolverList.add(x);
}, timeout);

ExtendedResolver extendedResolver = new ExtendedResolver(resolverList);
setCustomizeResolverTimeoutAndMaxRetries(extendedResolver, timeout, maxRetries);
return extendedResolver;
}
}


1 comment on commit d6a1c0f

@pekniada
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@aaron-kumar why are GOOGLE and CLOUDFLARE DNS servers practically enforced on first 4 lookup attempts with this commit?
Can't see this being configurable. At least in some enterprise environments these are not really allowed by default and enterprise DNS has to be used for various reasons, resulting in huge lookup times if lib is used as is.

Please sign in to comment.