1 /* This Source Code Form is subject to the terms of the Mozilla Public
2  * License, v. 2.0. If a copy of the MPL was not distributed with this
3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 
5 #include "TRRQuery.h"
6 
7 #include "mozilla/StaticPrefs_network.h"
8 #include "mozilla/Telemetry.h"
9 #include "nsQueryObject.h"
10 #include "TRR.h"
11 #include "TRRService.h"
12 #include "ODoH.h"
13 // Put DNSLogging.h at the end to avoid LOG being overwritten by other headers.
14 #include "DNSLogging.h"
15 
16 namespace mozilla {
17 namespace net {
18 
merge_rrset(AddrInfo * rrto,AddrInfo * rrfrom)19 static already_AddRefed<AddrInfo> merge_rrset(AddrInfo* rrto,
20                                               AddrInfo* rrfrom) {
21   MOZ_ASSERT(rrto && rrfrom);
22   // Each of the arguments are all-IPv4 or all-IPv6 hence judging
23   // by the first element. This is true only for TRR resolutions.
24   bool isIPv6 = rrfrom->Addresses().Length() > 0 &&
25                 rrfrom->Addresses()[0].raw.family == PR_AF_INET6;
26 
27   nsTArray<NetAddr> addresses;
28   if (isIPv6) {
29     addresses = rrfrom->Addresses().Clone();
30     addresses.AppendElements(rrto->Addresses());
31   } else {
32     addresses = rrto->Addresses().Clone();
33     addresses.AppendElements(rrfrom->Addresses());
34   }
35   auto builder = rrto->Build();
36   builder.SetAddresses(std::move(addresses));
37   return builder.Finish();
38 }
39 
Cancel(nsresult aStatus)40 void TRRQuery::Cancel(nsresult aStatus) {
41   MutexAutoLock trrlock(mTrrLock);
42   if (mTrrA) {
43     mTrrA->Cancel(aStatus);
44   }
45   if (mTrrAAAA) {
46     mTrrAAAA->Cancel(aStatus);
47   }
48   if (mTrrByType) {
49     mTrrByType->Cancel(aStatus);
50   }
51 }
52 
MarkSendingTRR(TRR * trr,enum TrrType rectype,MutexAutoLock &)53 void TRRQuery::MarkSendingTRR(TRR* trr, enum TrrType rectype, MutexAutoLock&) {
54   if (rectype == TRRTYPE_A) {
55     MOZ_ASSERT(!mTrrA);
56     mTrrA = trr;
57     mTrrAUsed = STARTED;
58   } else if (rectype == TRRTYPE_AAAA) {
59     MOZ_ASSERT(!mTrrAAAA);
60     mTrrAAAA = trr;
61     mTrrAAAAUsed = STARTED;
62   } else {
63     LOG(("TrrLookup called with bad type set: %d\n", rectype));
64     MOZ_ASSERT(0);
65   }
66 }
67 
PrepareQuery(bool aUseODoH,enum TrrType aRecType,nsTArray<RefPtr<TRR>> & aRequestsToSend)68 void TRRQuery::PrepareQuery(bool aUseODoH, enum TrrType aRecType,
69                             nsTArray<RefPtr<TRR>>& aRequestsToSend) {
70   LOG(("TRR Resolve %s type %d\n", mRecord->host.get(), (int)aRecType));
71   RefPtr<TRR> trr;
72   if (aUseODoH) {
73     trr = new ODoH(this, mRecord, aRecType);
74   } else {
75     trr = new TRR(this, mRecord, aRecType);
76   }
77 
78   {
79     MutexAutoLock trrlock(mTrrLock);
80     MarkSendingTRR(trr, aRecType, trrlock);
81     aRequestsToSend.AppendElement(trr);
82   }
83 }
84 
SendQueries(nsTArray<RefPtr<TRR>> & aRequestsToSend)85 bool TRRQuery::SendQueries(nsTArray<RefPtr<TRR>>& aRequestsToSend) {
86   bool madeQuery = false;
87   mTRRRequestCounter = aRequestsToSend.Length();
88   for (const auto& request : aRequestsToSend) {
89     if (NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(request))) {
90       madeQuery = true;
91     } else {
92       mTRRRequestCounter--;
93       MutexAutoLock trrlock(mTrrLock);
94       if (request == mTrrA) {
95         mTrrA = nullptr;
96         mTrrAUsed = INIT;
97       }
98       if (request == mTrrAAAA) {
99         mTrrAAAA = nullptr;
100         mTrrAAAAUsed = INIT;
101       }
102     }
103   }
104   aRequestsToSend.Clear();
105   return madeQuery;
106 }
107 
DispatchLookup(TRR * pushedTRR,bool aUseODoH)108 nsresult TRRQuery::DispatchLookup(TRR* pushedTRR, bool aUseODoH) {
109   if (aUseODoH && pushedTRR) {
110     MOZ_ASSERT(false, "ODoH should not support push");
111     return NS_ERROR_UNKNOWN_HOST;
112   }
113 
114   if (!mRecord->IsAddrRecord()) {
115     return DispatchByTypeLookup(pushedTRR, aUseODoH);
116   }
117 
118   RefPtr<AddrHostRecord> addrRec = do_QueryObject(mRecord);
119   MOZ_ASSERT(addrRec);
120   if (!addrRec) {
121     return NS_ERROR_UNEXPECTED;
122   }
123 
124   mTrrStart = TimeStamp::Now();
125 
126   mTrrAUsed = INIT;
127   mTrrAAAAUsed = INIT;
128 
129   // Always issue both A and AAAA.
130   // When both are complete we filter out the unneeded results.
131   enum TrrType rectype = (mRecord->af == AF_INET6) ? TRRTYPE_AAAA : TRRTYPE_A;
132 
133   if (pushedTRR) {
134     MutexAutoLock trrlock(mTrrLock);
135     rectype = pushedTRR->Type();
136     MarkSendingTRR(pushedTRR, rectype, trrlock);
137     return NS_OK;
138   }
139 
140   // Need to dispatch TRR requests after |mTrrA| and |mTrrAAAA| are set
141   // properly so as to avoid the race when CompleteLookup() is called at the
142   // same time.
143   nsTArray<RefPtr<TRR>> requestsToSend;
144   if ((mRecord->af == AF_UNSPEC || mRecord->af == AF_INET6)) {
145     PrepareQuery(aUseODoH, TRRTYPE_AAAA, requestsToSend);
146   }
147   if (mRecord->af == AF_UNSPEC || mRecord->af == AF_INET) {
148     PrepareQuery(aUseODoH, TRRTYPE_A, requestsToSend);
149   }
150 
151   if (SendQueries(requestsToSend)) {
152     mUsingODoH = aUseODoH;
153     return NS_OK;
154   }
155 
156   return NS_ERROR_UNKNOWN_HOST;
157 }
158 
DispatchByTypeLookup(TRR * pushedTRR,bool aUseODoH)159 nsresult TRRQuery::DispatchByTypeLookup(TRR* pushedTRR, bool aUseODoH) {
160   RefPtr<TypeHostRecord> typeRec = do_QueryObject(mRecord);
161   MOZ_ASSERT(typeRec);
162   if (!typeRec) {
163     return NS_ERROR_UNEXPECTED;
164   }
165 
166   typeRec->mStart = TimeStamp::Now();
167   enum TrrType rectype;
168 
169   // XXX this could use a more extensible approach.
170   if (mRecord->type == nsIDNSService::RESOLVE_TYPE_TXT) {
171     rectype = TRRTYPE_TXT;
172   } else if (mRecord->type == nsIDNSService::RESOLVE_TYPE_HTTPSSVC) {
173     rectype = TRRTYPE_HTTPSSVC;
174   } else if (pushedTRR) {
175     rectype = pushedTRR->Type();
176   } else {
177     MOZ_ASSERT(false, "Not an expected request type");
178     return NS_ERROR_UNKNOWN_HOST;
179   }
180 
181   LOG(("TRR Resolve %s type %d\n", typeRec->host.get(), (int)rectype));
182   RefPtr<TRR> trr;
183   if (aUseODoH) {
184     trr = new ODoH(this, mRecord, rectype);
185   } else {
186     trr = pushedTRR ? pushedTRR : new TRR(this, mRecord, rectype);
187   }
188 
189   if (pushedTRR || NS_SUCCEEDED(TRRService::Get()->DispatchTRRRequest(trr))) {
190     MutexAutoLock trrlock(mTrrLock);
191     MOZ_ASSERT(!mTrrByType);
192     mTrrByType = trr;
193     return NS_OK;
194   }
195 
196   return NS_ERROR_UNKNOWN_HOST;
197 }
198 
CompleteLookup(nsHostRecord * rec,nsresult status,AddrInfo * aNewRRSet,bool pb,const nsACString & aOriginsuffix,nsHostRecord::TRRSkippedReason aReason,TRR * aTRRRequest)199 AHostResolver::LookupStatus TRRQuery::CompleteLookup(
200     nsHostRecord* rec, nsresult status, AddrInfo* aNewRRSet, bool pb,
201     const nsACString& aOriginsuffix, nsHostRecord::TRRSkippedReason aReason,
202     TRR* aTRRRequest) {
203   if (rec != mRecord) {
204     LOG(("TRRQuery::CompleteLookup - Pushed record. Go to resolver"));
205     return mHostResolver->CompleteLookup(rec, status, aNewRRSet, pb,
206                                          aOriginsuffix, aReason, aTRRRequest);
207   }
208 
209   LOG(("TRRQuery::CompleteLookup > host: %s", rec->host.get()));
210 
211   RefPtr<AddrInfo> newRRSet(aNewRRSet);
212   DNSResolverType resolverType = newRRSet->ResolverType();
213   {
214     MutexAutoLock trrlock(mTrrLock);
215     if (newRRSet->TRRType() == TRRTYPE_A) {
216       MOZ_ASSERT(mTrrA);
217       mTRRAFailReason = aReason;
218       mTrrA = nullptr;
219       mTrrAUsed = NS_SUCCEEDED(status) ? OK : FAILED;
220       MOZ_ASSERT(!mAddrInfoA);
221       mAddrInfoA = newRRSet;
222       mAResult = status;
223       LOG(("A query status: 0x%x", static_cast<uint32_t>(status)));
224     } else if (newRRSet->TRRType() == TRRTYPE_AAAA) {
225       MOZ_ASSERT(mTrrAAAA);
226       mTRRAAAAFailReason = aReason;
227       mTrrAAAA = nullptr;
228       mTrrAAAAUsed = NS_SUCCEEDED(status) ? OK : FAILED;
229       MOZ_ASSERT(!mAddrInfoAAAA);
230       mAddrInfoAAAA = newRRSet;
231       mAAAAResult = status;
232       LOG(("AAAA query status: 0x%x", static_cast<uint32_t>(status)));
233     } else {
234       MOZ_ASSERT(0);
235     }
236   }
237 
238   if (NS_SUCCEEDED(status)) {
239     mTRRSuccess++;
240     if (mTRRSuccess == 1) {
241       // Store the duration on first succesful TRR response.  We
242       // don't know that there will be a second response nor can we
243       // tell which of two has useful data.
244       mTrrDuration = TimeStamp::Now() - mTrrStart;
245     }
246   }
247 
248   bool pendingRequest = false;
249   if (mTRRRequestCounter) {
250     mTRRRequestCounter--;
251     pendingRequest = (mTRRRequestCounter != 0);
252   } else {
253     MOZ_DIAGNOSTIC_ASSERT(false, "Request counter is messed up");
254   }
255   if (pendingRequest) {  // There are other outstanding requests
256     LOG(("CompleteLookup: waiting for all responses!\n"));
257     return LOOKUP_OK;
258   }
259 
260   if (mRecord->af == AF_UNSPEC) {
261     // merge successful records
262     if (mTrrAUsed == OK) {
263       LOG(("Have A response"));
264       newRRSet = mAddrInfoA;
265       status = mAResult;
266       if (mTrrAAAAUsed == OK) {
267         LOG(("Merging A and AAAA responses"));
268         newRRSet = merge_rrset(newRRSet, mAddrInfoAAAA);
269       }
270     } else {
271       newRRSet = mAddrInfoAAAA;
272       status = mAAAAResult;
273     }
274 
275     if (NS_FAILED(status) && (mAAAAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST ||
276                               mAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) {
277       status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
278     }
279   } else {
280     // If this is a failed AAAA request, but the server only has a A record,
281     // then we should not fallback to Do53. Instead we also send a A request
282     // and return NS_ERROR_DEFINITIVE_UNKNOWN_HOST if that succeeds.
283     if (NS_FAILED(status) && status != NS_ERROR_DEFINITIVE_UNKNOWN_HOST &&
284         (mTrrAUsed == INIT || mTrrAAAAUsed == INIT)) {
285       if (newRRSet->TRRType() == TRRTYPE_A) {
286         LOG(("A lookup failed. Checking if AAAA record exists"));
287         nsTArray<RefPtr<TRR>> requestsToSend;
288         PrepareQuery(mUsingODoH, TRRTYPE_AAAA, requestsToSend);
289         if (SendQueries(requestsToSend)) {
290           LOG(("Sent AAAA request"));
291           return LOOKUP_OK;
292         }
293       } else if (newRRSet->TRRType() == TRRTYPE_AAAA) {
294         LOG(("AAAA lookup failed. Checking if A record exists"));
295         nsTArray<RefPtr<TRR>> requestsToSend;
296         PrepareQuery(mUsingODoH, TRRTYPE_A, requestsToSend);
297         if (SendQueries(requestsToSend)) {
298           LOG(("Sent A request"));
299           return LOOKUP_OK;
300         }
301       } else {
302         MOZ_ASSERT(false, "Unexpected family");
303       }
304     }
305     bool otherSucceeded =
306         mRecord->af == AF_INET6 ? mTrrAUsed == OK : mTrrAAAAUsed == OK;
307     LOG(("TRRQuery::CompleteLookup other request succeeded"));
308 
309     if (mRecord->af == AF_INET) {
310       // return only A record
311       newRRSet = mAddrInfoA;
312       status = mAResult;
313       if (NS_FAILED(status) &&
314           (otherSucceeded || mAAAAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) {
315         LOG(("status set to NS_ERROR_DEFINITIVE_UNKNOWN_HOST"));
316         status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
317       }
318 
319     } else if (mRecord->af == AF_INET6) {
320       // return only AAAA record
321       newRRSet = mAddrInfoAAAA;
322       status = mAAAAResult;
323 
324       if (NS_FAILED(status) &&
325           (otherSucceeded || mAResult == NS_ERROR_DEFINITIVE_UNKNOWN_HOST)) {
326         LOG(("status set to NS_ERROR_DEFINITIVE_UNKNOWN_HOST"));
327         status = NS_ERROR_DEFINITIVE_UNKNOWN_HOST;
328       }
329 
330     } else {
331       MOZ_ASSERT(false, "Unexpected AF");
332       return LOOKUP_OK;
333     }
334 
335     // If this record failed, but there is a record for the other AF
336     // we prevent fallback to the native resolver.
337   }
338 
339   if (mTRRSuccess && mHostResolver->GetNCS() &&
340       (mHostResolver->GetNCS()->GetNAT64() ==
341        nsINetworkConnectivityService::OK) &&
342       newRRSet) {
343     newRRSet = mHostResolver->GetNCS()->MapNAT64IPs(newRRSet);
344   }
345 
346   if (resolverType == DNSResolverType::TRR) {
347     if (mTrrAUsed == OK) {
348       AccumulateCategoricalKeyed(
349           TRRService::ProviderKey(),
350           Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAOK);
351     } else if (mTrrAUsed == FAILED) {
352       AccumulateCategoricalKeyed(
353           TRRService::ProviderKey(),
354           Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAFail);
355     }
356 
357     if (mTrrAAAAUsed == OK) {
358       AccumulateCategoricalKeyed(
359           TRRService::ProviderKey(),
360           Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAAAAOK);
361     } else if (mTrrAAAAUsed == FAILED) {
362       AccumulateCategoricalKeyed(
363           TRRService::ProviderKey(),
364           Telemetry::LABELS_DNS_LOOKUP_DISPOSITION3::trrAAAAFail);
365     }
366   }
367 
368   mAddrInfoAAAA = nullptr;
369   mAddrInfoA = nullptr;
370 
371   MOZ_DIAGNOSTIC_ASSERT(!mCalledCompleteLookup,
372                         "must not call CompleteLookup more than once");
373   mCalledCompleteLookup = true;
374   return mHostResolver->CompleteLookup(rec, status, newRRSet, pb, aOriginsuffix,
375                                        aReason, aTRRRequest);
376 }
377 
CompleteLookupByType(nsHostRecord * rec,nsresult status,mozilla::net::TypeRecordResultType & aResult,uint32_t aTtl,bool pb)378 AHostResolver::LookupStatus TRRQuery::CompleteLookupByType(
379     nsHostRecord* rec, nsresult status,
380     mozilla::net::TypeRecordResultType& aResult, uint32_t aTtl, bool pb) {
381   if (rec != mRecord) {
382     LOG(("TRRQuery::CompleteLookup - Pushed record. Go to resolver"));
383     return mHostResolver->CompleteLookupByType(rec, status, aResult, aTtl, pb);
384   }
385 
386   {
387     MutexAutoLock trrlock(mTrrLock);
388     mTrrByType = nullptr;
389   }
390 
391   MOZ_DIAGNOSTIC_ASSERT(!mCalledCompleteLookup,
392                         "must not call CompleteLookup more than once");
393   mCalledCompleteLookup = true;
394   return mHostResolver->CompleteLookupByType(rec, status, aResult, aTtl, pb);
395 }
396 
397 }  // namespace net
398 }  // namespace mozilla
399