package com.verisign.iot.discovery.services; import com.verisign.iot.discovery.DnsDiscovery; import com.verisign.iot.discovery.commons.Configurable; import com.verisign.iot.discovery.commons.Constants; import com.verisign.iot.discovery.commons.LookupContext; import com.verisign.iot.discovery.commons.StatusChangeEvent; import com.verisign.iot.discovery.commons.StatusCode; import com.verisign.iot.discovery.domain.CertRecord; import com.verisign.iot.discovery.domain.Fqdn; import com.verisign.iot.discovery.domain.PointerRecord; import com.verisign.iot.discovery.domain.RecordsContainer; import com.verisign.iot.discovery.domain.ServiceInstance; import com.verisign.iot.discovery.domain.ServiceRecord; import com.verisign.iot.discovery.domain.TLSAPrefix; import com.verisign.iot.discovery.domain.TextRecord; import com.verisign.iot.discovery.exceptions.ConfigurationException; import com.verisign.iot.discovery.exceptions.LookupException; import com.verisign.iot.discovery.utils.DnsUtil; import com.verisign.iot.discovery.utils.ExceptionsUtil; import com.verisign.iot.discovery.utils.FormattingUtil; import com.verisign.iot.discovery.utils.RDataUtil; import com.verisign.iot.discovery.utils.ValidatorUtil; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.xbill.DNS.Cache; import org.xbill.DNS.DClass; import org.xbill.DNS.Lookup; import org.xbill.DNS.PTRRecord; import org.xbill.DNS.Record; import org.xbill.DNS.Resolver; import org.xbill.DNS.SRVRecord; import org.xbill.DNS.TLSARecord; import org.xbill.DNS.TXTRecord; import org.xbill.DNS.Type; /** * Class encapsulating the DNS-SD Service Lookup facilities. * * @author pmaresca <pmaresca@verisign.com> * @version 1.0 * @since 2015/05/02 */ public class DnsServicesDiscovery extends Configurable implements DnsDiscovery { /** * Lookup Cache. */ private Cache smimeACache; /** * Thread-owned Errors trace. */ private ThreadLocal<Map<String, StatusCode>> errorsTrace; /** * DNS Lookup helper. */ private ServicesLookupHelper helper; public DnsServicesDiscovery() { this(Constants.CACHE_SIZE, Constants.CACHE_TIME_LIMIT); } /** * Overloaded constructor taking as argument Cache size and TTL. * * @param cacheSize Unsigned <code>int</code> defining the Cache size * @param cacheTTL Unsigned <code>int</code> defining the Cache TTL */ public DnsServicesDiscovery(int cacheSize, int cacheTTL) { this.smimeACache = new Cache(DClass.ANY); this.smimeACache.setMaxEntries(cacheSize); this.smimeACache.setMaxNCache(cacheTTL); this.helper = this.new ServicesLookupHelper(); this.errorsTrace = new ThreadLocal<Map<String, StatusCode>>() { @Override protected Map<String, StatusCode> initialValue() { return new LinkedHashMap<>(); } }; } @Override public Set<String> listServiceTypes(Fqdn browsingDomain, boolean secValidation) throws LookupException, ConfigurationException { ValidatorUtil.isValidDomainName(browsingDomain); validatedConf(); Set<String> result = null; try { result = new TreeSet<>(); result.addAll(this.helper.serviceTypes(browsingDomain, secValidation)); if (result.isEmpty() && !ExceptionsUtil.onlyNameResolutionTrace(this.errorsTrace.get())) { throw ExceptionsUtil.build(StatusCode.RESOURCE_LOOKUP_ERROR, FormattingUtil.unableToResolve(browsingDomain.fqdn()), errorsTrace.get()); } } catch (LookupException | ConfigurationException exception) { throw exception; } finally { errorsTrace.remove(); } return result; } @Override public Set<ServiceInstance> listServiceInstances(Fqdn browsingDomain, String type, boolean secValidation) throws LookupException, ConfigurationException { ValidatorUtil.isValidDomainName(browsingDomain); ValidatorUtil.isValidLabel(type); validatedConf(); Set<ServiceInstance> result = null; try { result = new TreeSet<>(); result.addAll(this.helper.serviceInstances(browsingDomain, type, secValidation)); if (result.isEmpty() && !ExceptionsUtil.onlyNameResolutionTrace(this.errorsTrace.get())) { throw ExceptionsUtil.build(StatusCode.RESOURCE_LOOKUP_ERROR, FormattingUtil.unableToResolve(browsingDomain.fqdnWithPrefix(type)), errorsTrace.get()); } } catch (LookupException | ConfigurationException exception) { throw exception; } finally { errorsTrace.remove(); } return result; } @Override public Set<TextRecord> listTextRecords(Fqdn browsingDomain, String label, boolean secValidation) throws LookupException, ConfigurationException { ValidatorUtil.isValidDomainName(browsingDomain); ValidatorUtil.isValidLabel(label); validatedConf(); Set<TextRecord> result = null; try { result = new TreeSet<>(); Fqdn txtFqdn = new Fqdn(label, browsingDomain.domain()); result.addAll(this.helper.serviceTexts(txtFqdn, label, secValidation)); if (result.isEmpty() && !ExceptionsUtil.onlyNameResolutionTrace(this.errorsTrace.get())) { throw ExceptionsUtil.build(StatusCode.RESOURCE_LOOKUP_ERROR, FormattingUtil.unableToResolve(browsingDomain.fqdnWithPrefix(label)), errorsTrace.get()); } } catch (LookupException | ConfigurationException exception) { throw exception; } finally { errorsTrace.remove(); } return result; } @Override public Set<CertRecord> listTLSARecords(Fqdn browsingDomain, TLSAPrefix tlsaPrefix, boolean secValidation) throws LookupException, ConfigurationException { ValidatorUtil.isValidDomainName(browsingDomain); validatedConf(); Set<CertRecord> result = null; try { result = new TreeSet<>(); result.addAll(this.helper.tlsaRecords(browsingDomain, tlsaPrefix, secValidation)); if (result.isEmpty() && !ExceptionsUtil.onlyNameResolutionTrace(this.errorsTrace.get())) { throw ExceptionsUtil.build(StatusCode.RESOURCE_LOOKUP_ERROR, FormattingUtil.unableToResolve(browsingDomain.fqdn()), errorsTrace.get()); } } catch (LookupException exception) { throw exception; } finally { errorsTrace.remove(); } return result; } @Override public boolean isDnsSecValid(Fqdn name) throws LookupException, ConfigurationException { ValidatorUtil.isValidDomainName(name); validatedConf(); if (name == null || name.fqdn().isEmpty()) { name = new Fqdn(this.dnsSecDomain); } Map<String, Resolver> resolvers = retrieveResolvers(true); Iterator<String> itrResolvers = resolvers.keySet().iterator(); boolean validated = false; String server = null; do { server = itrResolvers.next(); statusChange(FormattingUtil.server(server)); statusChange(FormattingUtil.query(name, "", "A")); try { validated = DnsUtil.checkDnsSec(name, resolvers.get(server)); if (validated) { statusChange(FormattingUtil.response(FormattingUtil.authenticData(name.fqdn()))); } else { statusChange(FormattingUtil.response(FormattingUtil.networkError(name.fqdn()))); } } catch (LookupException le) { if (le.dnsError() == StatusCode.RESOURCE_LOOKUP_ERROR) { statusChange(FormattingUtil.response(FormattingUtil.unableToResolve(name.fqdn()))); } else { statusChange(FormattingUtil.response(FormattingUtil.unableToValidate(name.fqdn()))); } throw le; } } while (itrResolvers.hasNext() && !validated); return validated; } /** * Private helper to retrieve a set of one or more instances of <code>Resolver</code> to carry * out the lookup. * * @param secValidation <code>true</code> iff DNSSEC validation id needed * @return Instance(s) of <code>Resolver</code> * @throws ConfigurationException In case instance(s) of <code>Resolver</code> cannot he * instantiated. */ private Map<String, Resolver> retrieveResolvers(boolean secValidation) throws ConfigurationException { Map<String, Resolver> resolvers = new LinkedHashMap<>(); if (this.dnsServer != null && (!this.dnsServer.getHostAddress().isEmpty() || !this.dnsServer.getCanonicalHostName().isEmpty())) { String server = ((this.dnsServer.getHostAddress().isEmpty()) ? this.dnsServer.getCanonicalHostName() : this.dnsServer.getHostAddress()); resolvers.put(server, DnsUtil.getResolver(secValidation, this.trustAnchorDefault, server)); } else { resolvers.putAll(DnsUtil.getResolvers(secValidation, this.trustAnchorDefault)); } return resolvers; } /** * Resource Record holder type enumeration. It enumerates the types hold by DNS RRs. */ private enum RrHolderType { NAMES, ZONES, TYPES, OTHER; } /** * Private inner helper class to implement DNS-specific lookup operations. * * @author pmaresca <pmaresca@verisign.com> * @version 1.0 * @since 2015/05/02 */ private class ServicesLookupHelper { public ServicesLookupHelper() { super(); } /** * Retrieve a set of Service Types from the browsing domain. * * @param browsingDomain <code>Fqdn</code> representing the browsing domain * @param secValidation <code>true</code> in case secure browsing is needed * @return A set of <code>String</code> identifying the retrieved Service Types. * @throws LookupException In case of any unrecoverable error during the lookup process. * @throws ConfigurationException In case of wrong/faulty static and/or runtime * configuration. */ public Set<String> serviceTypes(Fqdn browsingDomain, boolean secValidation) throws LookupException, ConfigurationException { Map<String, Resolver> resolvers = retrieveResolvers(secValidation); RecordsContainer set = new RecordsContainer(); errorsTrace.get().clear(); Iterator<String> itrResolvers = resolvers.keySet().iterator(); LookupContext ctx = context(browsingDomain, Constants.SERVICES_DNS_SD_UDP, "", "", Type.PTR, secValidation ); String server = null; do { server = itrResolvers.next(); Resolver resolver = resolvers.get(server); ctx.setResolver(resolver); statusChange(FormattingUtil.server(server)); try { Record[] records = lookup(ctx); set.getLabels().addAll(helper.getServiceTypeNamesFromRecords(records, ctx)); statusChange(StatusChangeEvent.build(browsingDomain.fqdn(), Type.string(Type.PTR), StatusChangeEvent.castedList(set.getLabels()))); } catch (LookupException le) { if (le.dnsError().equals(StatusCode.NETWORK_ERROR) && !itrResolvers.hasNext()) { throw le; } else if (le.dnsError().equals(StatusCode.SERVER_ERROR) || le.dnsError().equals(StatusCode.RESOURCE_INSECURE_ERROR)) { throw le; } else { errorsTrace.get().put( ExceptionsUtil.traceKey(resolver, browsingDomain.fqdn(), "Retrieving-Types"), le.dnsError()); } } } while (itrResolvers.hasNext() && set.getLabels().isEmpty()); return set.getLabels(); } /** * Retrieve a set of Text Resource Records from the browsing domain for the specified * <i>label</i>. * * @param browsingDomain <code>Fqdn</code> representing the browsing domain * @param label A label to be looked up * @param secValidation <code>true</code> in case secure browsing is needed * @return A set of <code>String</code> identifying the retrieved Text records * @throws LookupException In case of any unrecoverable error during the lookup process. * @throws ConfigurationException In case of wrong/faulty static and/or runtime * configuration. */ public Set<TextRecord> serviceTexts(Fqdn browsingDomain, String label, boolean secValidation) throws LookupException, ConfigurationException { Map<String, Resolver> resolvers = retrieveResolvers(secValidation); RecordsContainer set = new RecordsContainer(); errorsTrace.get().clear(); Iterator<String> itrResolvers = resolvers.keySet().iterator(); LookupContext ctx = context(browsingDomain, label, label, "", Type.TXT, secValidation); String server = null; do { server = itrResolvers.next(); Resolver resolver = resolvers.get(server); ctx.setResolver(resolver); statusChange(FormattingUtil.server(server)); try { Record[] records = lookup(ctx); parseRecords(records, set, "", RrHolderType.OTHER); statusChange(StatusChangeEvent.build(browsingDomain.fqdnWithPrefix(label), Type.string(Type.TXT), StatusChangeEvent.castedList(set.getTexts()))); } catch (LookupException le) { if (le.dnsError().equals(StatusCode.NETWORK_ERROR) && !itrResolvers.hasNext()) { throw le; } else if (le.dnsError().equals(StatusCode.SERVER_ERROR) || le.dnsError().equals(StatusCode.RESOURCE_INSECURE_ERROR)) { throw le; } else { errorsTrace.get().put( ExceptionsUtil.traceKey(resolver, browsingDomain.fqdnWithPrefix(label), "Retrieving-Texts"), le.dnsError()); } } } while (itrResolvers.hasNext() && set.getTexts().isEmpty()); return set.getTexts(); } /** * Retrieve a set of Service Resource Records from the browsing domain, according to the * specified <i>type</i>. * * @param browsingDomain <code>Fqdn</code> representing the browsing domain * @param type A <code>String</code> defining the Service Type to be looked up * @param secValidation <code>true</code> in case secure browsing is needed * * @return A set of <code>String</code> identifying the retrieve Service records. * * @throws LookupException In case of any unrecoverable error during the lookup process. * @throws ConfigurationException In case of wrong/faulty static and/or runtime * configuration. */ public Set<ServiceInstance> serviceInstances(Fqdn browsingDomain, String type, boolean secValidation) throws LookupException, ConfigurationException { Map<String, Resolver> resolvers = retrieveResolvers(secValidation); Set<ServiceInstance> instances = new TreeSet<>(); errorsTrace.get().clear(); Iterator<String> itrResolvers = resolvers.keySet().iterator(); LookupContext ctx = context(browsingDomain, "", "", type, Type.PTR, secValidation); String server = null; do { server = itrResolvers.next(); Resolver resolver = resolvers.get(server); ctx.setResolver(resolver); statusChange(FormattingUtil.server(server)); try { String dnsLabel = retrieveDnsLabel(ctx); ctx.setDnsLabel(dnsLabel); statusChange(StatusChangeEvent.build(ctx.getDomainName().fqdnWithPrefix(ctx.getPrefix()), Type.string(ctx.getRrType()), StatusChangeEvent.castedValue(ctx.getDomainName().fqdnWithPrefix(dnsLabel)))); Set<String> zones = retrieveDnsZones(ctx); ctx.setDomainName(browsingDomain); statusChange(StatusChangeEvent.build(ctx.getDomainName().fqdnWithPrefix(ctx.getPrefix()), Type.string(ctx.getRrType()), StatusChangeEvent.castedList(zones))); Set<String> names = retrieveDnsNames(ctx, zones); ctx.setDomainName(browsingDomain); statusChange(StatusChangeEvent.build(ctx.getDomainName().fqdnWithPrefix(ctx.getPrefix()), Type.string(ctx.getRrType()), StatusChangeEvent.castedList(names))); instances.addAll(retrieveDnsInstances(ctx, names)); } catch (LookupException le) { if (le.dnsError().equals(StatusCode.NETWORK_ERROR) && !itrResolvers.hasNext()) { throw le; } else if (le.dnsError().equals(StatusCode.SERVER_ERROR) || le.dnsError().equals(StatusCode.RESOURCE_INSECURE_ERROR)) { throw le; } else { errorsTrace.get().put( ExceptionsUtil.traceKey(resolver, browsingDomain.fqdnWithPrefix(type), "Retrieving-Instances"), le.dnsError()); } } } while (itrResolvers.hasNext() && instances.isEmpty()); return instances; } /** * * Retrieve a set of TLSA Records from the browsing domain, according to the * specified <i>options</i>. * * @param browsingDomain <code>Fqdn</code> representing the browsing domain * @param tlsaPrefix A <code>String</code> defining the TLSA prefix as couple * <code>port:protocol</code> * @param secValidation <code>true</code> in case secure browsing is needed * * @return A set of <code>String</code> identifying the retrieve Service records. * * @throws LookupException In case of any unrecoverable error during the lookup process. * @throws ConfigurationException In case of wrong/faulty static and/or runtime configuration. */ public Set<CertRecord> tlsaRecords(Fqdn browsingDomain, TLSAPrefix tlsaPrefix, boolean secValidation) throws LookupException, ConfigurationException { Map<String, Resolver> resolvers = retrieveResolvers(secValidation); Set<CertRecord> tlsaDiscoveryRecords = new TreeSet<>(); errorsTrace.get().clear(); Iterator<String> itrResolvers = resolvers.keySet().iterator(); String tlsaFqdn = tlsaPrefix.toString() + Constants.DNS_LABEL_DELIMITER + browsingDomain.fqdn(); Fqdn browsingDomainWithTLSAPrefix = new Fqdn(tlsaFqdn); LookupContext ctx = context(browsingDomainWithTLSAPrefix, "", "", "", Type.TLSA, secValidation); String server; do { server = itrResolvers.next(); Resolver resolver = resolvers.get(server); ctx.setResolver(resolver); statusChange(FormattingUtil.server(server)); try { Record[] records = lookup(ctx); for (Record record : records) { if (record instanceof TLSARecord) { tlsaDiscoveryRecords.add(new CertRecord((TLSARecord) record)); } } } catch (LookupException le) { if (le.dnsError().equals(StatusCode.NETWORK_ERROR) && !itrResolvers.hasNext()) { throw le; } else if (le.dnsError().equals(StatusCode.SERVER_ERROR) || le.dnsError().equals(StatusCode.RESOURCE_INSECURE_ERROR)) { throw le; } else { errorsTrace.get().put( ExceptionsUtil.traceKey(resolver, browsingDomain.domain(), "Retrieving-Instances"), le.dnsError()); } } } while (itrResolvers.hasNext() && tlsaDiscoveryRecords.isEmpty()); return tlsaDiscoveryRecords; } /** * Instantiate and trigger a DNS lookup according to the defined input parameters. * * @param ctx A <code>LookupContext</code> defining this lookup parameters * @return A set of one or more Resource <code>Record</code> * @throws LookupException In case of unsuccessful DNS lookup; the <code>StatusCode</code> * is returned as part of this error. */ private Record[] lookup(LookupContext ctx) throws LookupException { Lookup lookup = DnsUtil.instantiateLookup(ctx.getDomainName().fqdnWithPrefix(ctx.getPrefix()), ctx.getResolver(), ctx.getRrType(), smimeACache); ctx.setLookup(lookup); if(ctx.isSecure()) DnsUtil.checkDnsSec(ctx.getDomainName(), ctx.getResolver()); Record[] records = lookup.run(); statusChange(FormattingUtil.query(ctx.getDomainName(), ctx.getPrefix(), Type.string(ctx.getRrType()))); StatusCode outcome = DnsUtil.checkLookupStatus(lookup); if (outcome.equals(StatusCode.SERVER_ERROR) || outcome.equals(StatusCode.NETWORK_ERROR)) { throw ExceptionsUtil.build(outcome, FormattingUtil.unableToResolve(ctx.getDomainName().fqdn()), errorsTrace.get()); } else { errorsTrace.get().put( ExceptionsUtil.traceKey(ctx.getResolver(), ctx.getResolver().toString() + ctx.getDomainName(), "Checking-Lookup-Status"), outcome); } return (records == null?new Record[0]:records); } /** * Retrieve the DNS domain label for the browsing domain and specified Service Type. * * @param ctx A <code>LookupContext</code> defining this lookup parameters * @return A <code>String</code> containing the DNS domain label * @throws LookupException In case of unsuccessful DNS lookup; the <code>StatusCode</code> * is returned as part of this error. */ private String retrieveDnsLabel(LookupContext ctx) throws LookupException { ctx.setPrefix(ctx.getType() + Constants.NAME); ctx.setRrType(Type.PTR); Record[] records = lookup(ctx); String dnsLabel = null; if (records != null) { for (Record record : records) { if (record instanceof PTRRecord) { dnsLabel = PointerRecord.build((PTRRecord) record).getDnsLabel(); } } } if (dnsLabel == null) { throw ExceptionsUtil.build(StatusCode.RESOLUTION_NAME_ERROR, FormattingUtil.unableToRetrieveLabel(ctx.getDomainName().fqdnWithPrefix(ctx.getPrefix())), errorsTrace.get()); } else { return dnsLabel; } } /** * Retrieve the DNS Service's Zones. * * @param ctx A <code>LookupContext</code> defining this lookup parameters * @return A set of <code>String</code> containing the DNS zones * @throws LookupException In case of unsuccessful DNS lookup; the <code>StatusCode</code> * is returned as part of this error. */ private Set<String> retrieveDnsZones(LookupContext ctx) throws LookupException { ctx.setPrefix(Constants.SERVICES_DNS_SD_UDP); ctx.setRrType(Type.PTR); Record[] records = lookup(ctx); RecordsContainer set = new RecordsContainer(); parseRecords(records, set, ctx.getDnsLabel(), RrHolderType.ZONES); return set.getLabels(); } /** * Retrieve the DNS Service's Names. * * @param ctx A <code>LookupContext</code> defining this lookup parameters * @return A set of <code>String</code> containing the DNS service names * @throws LookupException In case of unsuccessful DNS lookup; the <code>StatusCode</code> * is returned as part of this error. */ private Set<String> retrieveDnsNames(LookupContext ctx, Set<String> zones) throws LookupException { ctx.setPrefix(ctx.getDnsLabel()); ctx.setRrType(Type.PTR); RecordsContainer set = new RecordsContainer(); for (String zone : zones) { ctx.setDomainName(new Fqdn(zone)); Record[] records = lookup(ctx); parseRecords(records, set, ctx.getDnsLabel(), RrHolderType.NAMES); } return set.getLabels(); } /** * Retrieve the Service's records. * * @param ctx A <code>LookupContext</code> defining this lookup parameters * @return A set of <code>String</code> containing the service's records * @throws LookupException In case of unsuccessful DNS lookup; the <code>StatusCode</code> * is returned as part of this error. */ private Set<ServiceRecord> retrieveDnsRecords(LookupContext ctx, Set<String> svcNames) throws LookupException { RecordsContainer set = new RecordsContainer(); ctx.setPrefix(""); ctx.setRrType(Type.SRV); for (String svcName : svcNames) { ctx.setDomainName(new Fqdn(svcName)); Record[] records = lookup(ctx); parseRecords(records, set, ctx.getDnsLabel(), RrHolderType.OTHER); } return set.getRecords(); } /** * Retrieve the Service's instances. * * @param ctx A <code>LookupContext</code> defining this lookup parameters * @return A set of <code>String</code> containing the service's records * @throws LookupException In case of unsuccessful DNS lookup; the <code>StatusCode</code> * is returned as part of this error. */ private Set<ServiceInstance> retrieveDnsInstances(LookupContext ctx, Set<String> svcNames) throws LookupException { Set<ServiceInstance> svcInstances = new TreeSet<>(); Set<String> aName = new LinkedHashSet<>(); RecordsContainer set = new RecordsContainer(); for (String svcName : svcNames) { aName.clear(); set.getTexts().clear(); aName.add(svcName); ctx.setPrefix(ctx.getDnsLabel()); Set<ServiceRecord> svcRecords = retrieveDnsRecords(ctx, aName); statusChange(StatusChangeEvent.build(svcName, "", StatusChangeEvent.castedList(svcRecords))); if (svcRecords.isEmpty()) { continue; } ctx.setPrefix(""); ctx.setRrType(Type.TXT); ctx.setDomainName(new Fqdn(svcName)); Record[] records = lookup(ctx); parseRecords(records, set, ctx.getLabel(), RrHolderType.OTHER); statusChange(StatusChangeEvent.build(svcName, "", StatusChangeEvent.castedList(set.getTexts()))); if (set.getTexts().isEmpty()) { continue; } svcInstances.add(new ServiceInstance(ctx.getType(), svcRecords.iterator().next(), set.getTexts().iterator().next())); } return svcInstances; } /** * Scrapes the Discovery Service Records according to their nature. * * @param records An array of <code>Record</code> retrieve upon a lookup * @param set A <code>ResourcesContainer</code> * @param dnsLabel A <code>String</code> containing the extracted DNS Label * @param pht A Resource Record Type holder */ private void parseRecords(Record[] records, final RecordsContainer set, String dnsLabel, RrHolderType pht) { if (records != null) { for (Record record : records) { if (record instanceof PTRRecord && pht == RrHolderType.ZONES) { String zone = PointerRecord.build((PTRRecord) record).getServiceZone(dnsLabel); if (zone != null) { set.getLabels().add(zone); } } else if (record instanceof PTRRecord && pht == RrHolderType.NAMES) { String name = PointerRecord.build((PTRRecord) record).getServiceName(dnsLabel); if (name != null) { set.getLabels().add(name); } } else if (record instanceof PTRRecord && pht == RrHolderType.TYPES) { set.getLabels().add(PointerRecord.build((PTRRecord) record).getServiceType()); } else if (record instanceof SRVRecord) { ServiceRecord svcRecord = ServiceRecord.build((SRVRecord) record); if (svcRecord != null) { set.getRecords().add(svcRecord); } } else if (record instanceof TXTRecord) { set.getTexts().add(TextRecord.build((TXTRecord) record)); } else { errorsTrace.get().put( ExceptionsUtil.traceKey(record.toString(), dnsLabel, "Parsing-Service-Records"), StatusCode.RESOURCE_UNEXPECTED); } } } } public Set<String> getServiceTypeNamesFromRecords(Record[] records, LookupContext lookupContext) throws LookupException { Set<String> serviceTypeNames = new HashSet<>(); if (records.length > 0) { for (Record record : records) { String dnsLabel = RDataUtil.getDnsLabelFromRData(record.rdataToString()); if (dnsLabel == null) { continue; } String labelRecordName = dnsLabel + "._label"; LookupContext labelRecordContext = context(lookupContext.getDomainName(), labelRecordName, "", "", Type.PTR, lookupContext.isSecure()); labelRecordContext.setResolver(lookupContext.getResolver()); Record[] nameRecordArray = lookup(labelRecordContext); if (nameRecordArray.length == 0 || nameRecordArray[0] == null) { continue; } Record nameRecord = nameRecordArray[0]; String serviceTypeName = RDataUtil.getServiceTypeNameFromRData(nameRecord.rdataToString()); if (serviceTypeName == null) { continue; } serviceTypeNames.add(serviceTypeName); } } return serviceTypeNames; } /** * Create a Lookup Context to be passed over the nested calls. * * @param name A browsing domain * @param prefix The prefix label to be used * @param type An <code>int</code> specifying the Resource Record Type * @param sec <code>true</code> iff DNSSEC validation is needed * @return A <code>LookupContext</code> created accordingly */ // TODO RecordsContainer might be handled by the Context private LookupContext context(Fqdn name, String prefix, String label, String type, int rrType, boolean sec) { LookupContext ctx = new LookupContext(); ctx.setDomainName(name); ctx.setPrefix(prefix); ctx.setLabel(label); ctx.setType(type); ctx.setRrType(rrType); ctx.setSecure(sec); return ctx; } } }