001/* 002 * Copyright 2015-2024 the original author or authors 003 * 004 * This software is licensed under the Apache License, Version 2.0, 005 * the GNU Lesser General Public License version 2 or later ("LGPL") 006 * and the WTFPL. 007 * You may choose either license to govern your use of this software only 008 * upon the condition that you accept all of the terms of either 009 * the Apache License 2.0, the LGPL 2.1+ or the WTFPL. 010 */ 011package org.minidns.hla; 012 013import java.io.IOException; 014import java.net.InetAddress; 015import java.util.ArrayList; 016import java.util.Collection; 017import java.util.Collections; 018import java.util.IdentityHashMap; 019import java.util.List; 020import java.util.Set; 021 022import org.minidns.AbstractDnsClient.IpVersionSetting; 023import org.minidns.MiniDnsException.NullResultException; 024import org.minidns.dnsname.DnsName; 025import org.minidns.hla.srv.SrvServiceProto; 026import org.minidns.record.A; 027import org.minidns.record.AAAA; 028import org.minidns.record.InternetAddressRR; 029import org.minidns.record.SRV; 030import org.minidns.util.SrvUtil; 031 032public class SrvResolverResult extends ResolverResult<SRV> { 033 034 private final ResolverApi resolver; 035 private final IpVersionSetting ipVersion; 036 private final SrvServiceProto srvServiceProto; 037 038 private List<ResolvedSrvRecord> sortedSrvResolvedAddresses; 039 040 SrvResolverResult(ResolverResult<SRV> srvResult, SrvServiceProto srvServiceProto, ResolverApi resolver) throws NullResultException { 041 super(srvResult.question, srvResult.result, srvResult.unverifiedReasons); 042 this.resolver = resolver; 043 this.ipVersion = resolver.getClient().getPreferedIpVersion(); 044 this.srvServiceProto = srvServiceProto; 045 } 046 047 /** 048 * Get a list ordered by priority and weight of the resolved SRV records. This method will throw if there was an 049 * error response or if subsequent {@link A} or {@link AAAA} resource record lookups fail. It will return 050 * {@code null} in case the service is decidedly not available at this domain. 051 * 052 * @return a list ordered by priority and weight of the related SRV records. 053 * @throws IOException in case an I/O error occurs. 054 */ 055 public List<ResolvedSrvRecord> getSortedSrvResolvedAddresses() throws IOException { 056 if (sortedSrvResolvedAddresses != null) { 057 return sortedSrvResolvedAddresses; 058 } 059 060 throwIseIfErrorResponse(); 061 062 if (isServiceDecidedlyNotAvailableAtThisDomain()) { 063 return null; 064 } 065 066 List<SRV> srvRecords = SrvUtil.sortSrvRecords(getAnswers()); 067 068 List<ResolvedSrvRecord> res = new ArrayList<>(srvRecords.size()); 069 for (SRV srvRecord : srvRecords) { 070 ResolverResult<A> aRecordsResult = null; 071 ResolverResult<AAAA> aaaaRecordsResult = null; 072 Set<A> aRecords = Collections.emptySet(); 073 if (ipVersion.v4) { 074 aRecordsResult = resolver.resolve(srvRecord.target, A.class); 075 if (aRecordsResult.wasSuccessful() && !aRecordsResult.hasUnverifiedReasons()) { 076 aRecords = aRecordsResult.getAnswers(); 077 } 078 } 079 080 Set<AAAA> aaaaRecords = Collections.emptySet(); 081 if (ipVersion.v6) { 082 aaaaRecordsResult = resolver.resolve(srvRecord.target, AAAA.class); 083 if (aaaaRecordsResult.wasSuccessful() && !aaaaRecordsResult.hasUnverifiedReasons()) { 084 aaaaRecords = aaaaRecordsResult.getAnswers(); 085 } 086 } 087 088 if (aRecords.isEmpty() && aaaaRecords.isEmpty()) { 089 // TODO Possibly check for (C|D)NAME usage and throw a meaningful exception that it is not allowed for 090 // the target of an SRV to be an alias as per RFC 2782. 091 /* 092 ResolverResult<CNAME> cnameRecordResult = resolve(srvRecord.name, CNAME.class); 093 if (cnameRecordResult.wasSuccessful()) { 094 } 095 */ 096 continue; 097 } 098 099 List<InternetAddressRR<? extends InetAddress>> srvAddresses = new ArrayList<>(aRecords.size() + aaaaRecords.size()); 100 switch (ipVersion) { 101 case v4only: 102 srvAddresses.addAll(aRecords); 103 break; 104 case v6only: 105 srvAddresses.addAll(aaaaRecords); 106 break; 107 case v4v6: 108 srvAddresses.addAll(aRecords); 109 srvAddresses.addAll(aaaaRecords); 110 break; 111 case v6v4: 112 srvAddresses.addAll(aaaaRecords); 113 srvAddresses.addAll(aRecords); 114 break; 115 } 116 117 ResolvedSrvRecord resolvedSrvAddresses = new ResolvedSrvRecord(question.name, srvServiceProto, srvRecord, srvAddresses, 118 aRecordsResult, aaaaRecordsResult); 119 res.add(resolvedSrvAddresses); 120 } 121 122 sortedSrvResolvedAddresses = res; 123 124 return res; 125 } 126 127 public boolean isServiceDecidedlyNotAvailableAtThisDomain() { 128 Set<SRV> answers = getAnswers(); 129 if (answers.size() != 1) { 130 return false; 131 } 132 133 SRV singleAnswer = answers.iterator().next(); 134 return !singleAnswer.isServiceAvailable(); 135 } 136 137 public static final class ResolvedSrvRecord { 138 public final DnsName name; 139 public final SrvServiceProto srvServiceProto; 140 public final SRV srv; 141 public final List<InternetAddressRR<? extends InetAddress>> addresses; 142 public final ResolverResult<A> aRecordsResult; 143 public final ResolverResult<AAAA> aaaaRecordsResult; 144 145 /** 146 * The port announced by the SRV RR. This is simply a shortcut for <code>srv.port</code>. 147 */ 148 public final int port; 149 150 private ResolvedSrvRecord(DnsName name, SrvServiceProto srvServiceProto, SRV srv, 151 List<InternetAddressRR<? extends InetAddress>> addresses, ResolverResult<A> aRecordsResult, 152 ResolverResult<AAAA> aaaaRecordsResult) { 153 this.name = name; 154 this.srvServiceProto = srvServiceProto; 155 this.srv = srv; 156 this.addresses = Collections.unmodifiableList(addresses); 157 this.port = srv.port; 158 this.aRecordsResult = aRecordsResult; 159 this.aaaaRecordsResult = aaaaRecordsResult; 160 } 161 } 162 163 /** 164 * Convenience method to sort multiple resolved SRV RRs. This is for example required by XEP-0368, where 165 * {@link org.minidns.hla.srv.SrvService#xmpp_client} and {@link org.minidns.hla.srv.SrvService#xmpps_client} may be 166 * sorted together. 167 * 168 * @param resolvedSrvRecordCollections a collection of resolved SRV records. 169 * @return a list ordered by priority and weight of the related SRV records. 170 */ 171 @SafeVarargs 172 public static List<ResolvedSrvRecord> sortMultiple(Collection<ResolvedSrvRecord>... resolvedSrvRecordCollections) { 173 int srvRecordsCount = 0; 174 for (Collection<ResolvedSrvRecord> resolvedSrvRecords : resolvedSrvRecordCollections) { 175 if (resolvedSrvRecords == null) { 176 continue; 177 } 178 srvRecordsCount += resolvedSrvRecords.size(); 179 } 180 181 List<SRV> srvToSort = new ArrayList<>(srvRecordsCount); 182 IdentityHashMap<SRV, ResolvedSrvRecord> identityMap = new IdentityHashMap<>(srvRecordsCount); 183 for (Collection<ResolvedSrvRecord> resolvedSrvRecords : resolvedSrvRecordCollections) { 184 if (resolvedSrvRecords == null) { 185 continue; 186 } 187 for (ResolvedSrvRecord resolvedSrvRecord : resolvedSrvRecords) { 188 srvToSort.add(resolvedSrvRecord.srv); 189 identityMap.put(resolvedSrvRecord.srv, resolvedSrvRecord); 190 } 191 } 192 193 List<SRV> sortedSrvs = SrvUtil.sortSrvRecords(srvToSort); 194 assert sortedSrvs.size() == srvRecordsCount; 195 196 List<ResolvedSrvRecord> res = new ArrayList<>(srvRecordsCount); 197 for (SRV sortedSrv : sortedSrvs) { 198 ResolvedSrvRecord resolvedSrvRecord = identityMap.get(sortedSrv); 199 res.add(resolvedSrvRecord); 200 } 201 202 return res; 203 } 204}