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