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.Inet4Address;
015import java.net.Inet6Address;
016import java.net.InetAddress;
017
018import org.minidns.AbstractDnsClient;
019import org.minidns.DnsClient;
020import org.minidns.dnslabel.DnsLabel;
021import org.minidns.dnsmessage.Question;
022import org.minidns.dnsname.DnsName;
023import org.minidns.dnsqueryresult.DnsQueryResult;
024import org.minidns.hla.srv.SrvProto;
025import org.minidns.hla.srv.SrvService;
026import org.minidns.hla.srv.SrvServiceProto;
027import org.minidns.hla.srv.SrvType;
028import org.minidns.iterative.ReliableDnsClient;
029import org.minidns.record.Data;
030import org.minidns.record.PTR;
031import org.minidns.record.SRV;
032import org.minidns.record.Record.TYPE;
033
034/**
035 * The high-level MiniDNS resolving API. It is designed to be easy to use.
036 * <p>
037 * A simple exammple how to resolve the IPv4 address of a given domain:
038 * </p>
039 * <pre>
040 * {@code
041 * ResolverResult<A> result = DnssecResolverApi.INSTANCE.resolve("verteiltesysteme.net", A.class);
042 * if (!result.wasSuccessful()) {
043 *   RESPONSE_CODE responseCode = result.getResponseCode();
044 *   // Perform error handling.
045 *   …
046 *   return;
047 * }
048 * if (!result.isAuthenticData()) {
049 *   // Response was not secured with DNSSEC.
050 *   …
051 *   return;
052 * }
053 * Set<A> answers = result.getAnswers();
054 * for (A a : answers) {
055 *   InetAddress inetAddress = a.getInetAddress();
056 *   // Do someting with the InetAddress, e.g. connect to.
057 *   …
058 * }
059 * }
060 * </pre>
061 * <p>
062 * MiniDNS also supports SRV resource records as first class citizens:
063 * </p>
064 * <pre>
065 * {@code
066 * SrvResolverResult result = DnssecResolverApi.INSTANCE.resolveSrv(SrvType.xmpp_client, "example.org")
067 * if (!result.wasSuccessful()) {
068 *   RESPONSE_CODE responseCode = result.getResponseCode();
069 *   // Perform error handling.
070 *   …
071 *   return;
072 * }
073 * if (!result.isAuthenticData()) {
074 *   // Response was not secured with DNSSEC.
075 *   …
076 *   return;
077 * }
078 * List<ResolvedSrvRecord> srvRecords = result.getSortedSrvResolvedAddresses();
079 * // Loop over the domain names pointed by the SRV RR. MiniDNS will return the list
080 * // correctly sorted by the priority and weight of the related SRV RR.
081 * for (ResolvedSrvRecord srvRecord : srvRecord) {
082 *   // Loop over the Internet Address RRs resolved for the SRV RR. The order of
083 *   // the list depends on the prefered IP version setting of MiniDNS.
084 *   for (InternetAddressRR inetAddressRR : srvRecord.addresses) {
085 *     InetAddress inetAddress = inetAddressRR.getInetAddress();
086 *     int port = srvAddresses.port;
087 *     // Try to connect to inetAddress at port.
088 *     …
089 *   }
090 * }
091 * }
092 * </pre>
093 *
094 * @author Florian Schmaus
095 *
096 */
097public class ResolverApi {
098
099    public static final ResolverApi INSTANCE = new ResolverApi(new ReliableDnsClient());
100
101    private final AbstractDnsClient dnsClient;
102
103    public ResolverApi(AbstractDnsClient dnsClient) {
104        this.dnsClient = dnsClient;
105    }
106
107    public final <D extends Data> ResolverResult<D> resolve(String name, Class<D> type) throws IOException {
108        return resolve(DnsName.from(name), type);
109    }
110
111    public final <D extends Data> ResolverResult<D> resolve(DnsName name, Class<D> type) throws IOException {
112        TYPE t = TYPE.getType(type);
113        Question q = new Question(name, t);
114        return resolve(q);
115    }
116
117    public <D extends Data> ResolverResult<D> resolve(Question question) throws IOException {
118        DnsQueryResult dnsQueryResult = dnsClient.query(question);
119
120        return new ResolverResult<D>(question, dnsQueryResult, null);
121    }
122
123    public SrvResolverResult resolveSrv(SrvType type, String serviceName) throws IOException {
124        return resolveSrv(type.service, type.proto, DnsName.from(serviceName));
125    }
126
127    public SrvResolverResult resolveSrv(SrvType type, DnsName serviceName) throws IOException {
128        return resolveSrv(type.service, type.proto, serviceName);
129    }
130
131    public SrvResolverResult resolveSrv(SrvService service, SrvProto proto, String name) throws IOException {
132        return resolveSrv(service.dnsLabel, proto.dnsLabel, DnsName.from(name));
133    }
134
135    public SrvResolverResult resolveSrv(SrvService service, SrvProto proto, DnsName name) throws IOException {
136        return resolveSrv(service.dnsLabel, proto.dnsLabel, name);
137    }
138
139    public SrvResolverResult resolveSrv(DnsLabel service, DnsLabel proto, DnsName name) throws IOException {
140        SrvServiceProto srvServiceProto = new SrvServiceProto(service, proto);
141        return resolveSrv(name, srvServiceProto);
142    }
143
144    public SrvResolverResult resolveSrv(String name) throws IOException {
145        return resolveSrv(DnsName.from(name));
146    }
147
148    public ResolverResult<PTR> reverseLookup(CharSequence inetAddressCs) throws IOException {
149        InetAddress inetAddress = InetAddress.getByName(inetAddressCs.toString());
150        return reverseLookup(inetAddress);
151    }
152
153    public ResolverResult<PTR> reverseLookup(InetAddress inetAddress) throws IOException {
154        if (inetAddress instanceof Inet4Address) {
155            return reverseLookup((Inet4Address) inetAddress);
156        } else if (inetAddress instanceof Inet6Address) {
157            return reverseLookup((Inet6Address) inetAddress);
158        } else {
159            throw new IllegalArgumentException("The given InetAddress '" + inetAddress + "' is neither of type Inet4Address or Inet6Address");
160        }
161    }
162
163    public ResolverResult<PTR> reverseLookup(Inet4Address inet4Address) throws IOException {
164        Question question = DnsClient.getReverseIpLookupQuestionFor(inet4Address);
165        return resolve(question);
166    }
167
168    public ResolverResult<PTR> reverseLookup(Inet6Address inet6Address) throws IOException {
169        Question question = DnsClient.getReverseIpLookupQuestionFor(inet6Address);
170        return resolve(question);
171    }
172
173    /**
174     * Resolve the {@link SRV} resource record for the given name. After ensuring that the resolution was successful
175     * with {@link SrvResolverResult#wasSuccessful()} , and, if DNSSEC was used, that the results could be verified with
176     * {@link SrvResolverResult#isAuthenticData()}, simply use {@link SrvResolverResult#getSortedSrvResolvedAddresses()} to
177     * retrieve the resolved IP addresses.
178     * <p>
179     * The name of SRV records is "_[service]._[protocol].[serviceDomain]", for example "_xmpp-client._tcp.example.org".
180     * </p>
181     *
182     * @param srvDnsName the name to resolve.
183     * @return a <code>SrvResolverResult</code> instance which can be used to retrieve the IP addresses.
184     * @throws IOException if an IO exception occurs.
185     */
186    public SrvResolverResult resolveSrv(DnsName srvDnsName) throws IOException {
187        final int labelCount = srvDnsName.getLabelCount();
188        if (labelCount < 3) {
189            throw new IllegalArgumentException();
190        }
191
192        DnsLabel service = srvDnsName.getLabel(labelCount - 1);
193        DnsLabel proto = srvDnsName.getLabel(labelCount - 2);
194        DnsName name = srvDnsName.stripToLabels(labelCount - 2);
195
196        SrvServiceProto srvServiceProto = new SrvServiceProto(service, proto);
197
198        return resolveSrv(name, srvServiceProto);
199    }
200
201    /**
202     * Resolve the {@link SRV} resource record for the given service name, service and protcol. After ensuring that the
203     * resolution was successful with {@link SrvResolverResult#wasSuccessful()} , and, if DNSSEC was used, that the
204     * results could be verified with {@link SrvResolverResult#isAuthenticData()}, simply use
205     * {@link SrvResolverResult#getSortedSrvResolvedAddresses()} to retrieve the resolved IP addresses.
206     *
207     * @param name the DNS name of the service.
208     * @param srvServiceProto the service and protocol to lookup.
209     * @return a <code>SrvResolverResult</code> instance which can be used to retrieve the IP addresses.
210     * @throws IOException if an I/O error occurs.
211     */
212    public SrvResolverResult resolveSrv(DnsName name, SrvServiceProto srvServiceProto) throws IOException {
213        DnsName srvDnsName = DnsName.from(srvServiceProto.service, srvServiceProto.proto, name);
214        ResolverResult<SRV> result = resolve(srvDnsName, SRV.class);
215
216        return new SrvResolverResult(result, srvServiceProto, this);
217    }
218
219    public final AbstractDnsClient getClient() {
220        return dnsClient;
221    }
222}