--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tcpiputils/dnd/src/dns.cpp Tue Jan 26 15:23:49 2010 +0200
@@ -0,0 +1,1213 @@
+// Copyright (c) 2004-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+// dns.cpp - the main client module of the DNS.
+//
+
+#include <e32math.h>
+#include <in_sock.h>
+#include <in6_opt.h>
+#include "dns.h"
+#include "dnd.hrh" // EDndDump
+#include <networking/dnd_err.h>
+#include "engine.h"
+#include "servers.h"
+#include "cache.h"
+#include "inet6log.h"
+#ifdef LLMNR_ENABLED
+# include "llmnrresponder.h"
+#endif
+#include "dnd_ini.h"
+
+#ifdef EXCLUDE_SYMBIAN_DNS_PUNYCODE
+#undef SYMBIAN_DNS_PUNYCODE
+#endif //EXCLUDE_SYMBIAN_DNS_PUNYCODE
+
+class CDnsService : public CDndDnsclient
+ {
+public:
+ CDnsService(CDndEngine &aControl, MDnsServerManager &aServerManager) : CDndDnsclient(aControl, aServerManager) {}
+ void ConstructL();
+ ~CDnsService();
+ virtual void ConfigurationChanged();
+
+ TInt SetHostName(TUint32 aId, const THostName &aName);
+ TInt GetHostName(TUint32 aId, THostName &aName, MDnsResolver &aCallback);
+
+ TInt GetByAddress(TUint32 aId, const TInetAddr &aAddr, TInt aNext, THostName &aName);
+ TInt GetByName(TUint32 aId, const THostName &aName, TInt aNext, TInetAddr &aAddr);
+private:
+#ifdef LLMNR_ENABLED
+ CDndLlmnrResponder *iLlmnrResponder; //< Pointer to the LLMNR responder object
+#endif
+ TUint32 GetNetId(TUint32 aIndex);
+ TInetAddressInfo *GetAddressList(TInt &aN);
+ };
+
+
+void CDnsService::ConstructL()
+ {
+ CDndDnsclient::ConstructL();
+#ifdef LLMNR_ENABLED
+ iLlmnrResponder = new (ELeave) CDndLlmnrResponder(iControl, iServerManager, iHostNames);
+ iLlmnrResponder->ConstructL();
+#endif
+ }
+
+CDnsService::~CDnsService()
+ {
+#ifdef LLMNR_ENABLED
+ delete iLlmnrResponder;
+#endif
+ }
+
+
+void CDnsService::ConfigurationChanged()
+ {
+#ifdef LLMNR_ENABLED
+ iLlmnrResponder->ConfigurationChanged();
+#endif
+ }
+//
+// Gateway hostname processing to the responder
+//
+TInt CDnsService::SetHostName(TUint32 aId, const THostName &aName)
+ {
+ const TInt res = iHostNames.Map(aId, aName);
+#ifdef LLMNR_ENABLED
+ return res == KErrNone ? iLlmnrResponder->SetHostName(aId, iHostNames.Find(aId)) : res;
+#else
+ return res;
+#endif
+ }
+
+TInt CDnsService::GetHostName(TUint32 aId, THostName &aName, MDnsResolver &aCallback)
+ {
+ aName = iHostNames.Find(aId);
+#ifdef LLMNR_ENABLED
+ return iLlmnrResponder->GetHostName(aId, aCallback);
+#else
+ (void)aCallback;
+ return KErrNone;
+#endif
+ }
+
+
+
+TUint32 CDnsService::GetNetId(TUint32 aIndex)
+ {
+ TPckgBuf<TSoInetIfQuery> opt;
+ opt().iIndex = aIndex;
+ return iControl.iSocket.GetOpt(KSoInetIfQueryByIndex, KSolInetIfQuery, opt) == KErrNone
+ ? opt().iZone[15] : 0;
+ }
+
+TInetAddressInfo *CDnsService::GetAddressList(TInt &aN)
+ /**
+ * Returns a list of all current addresses.
+ *
+ * @retval aN The number of addresses (if return is non-NULL).
+ * @return The address list, or NULL if not available.
+ *
+ * The returned address list must be freed by the caller
+ * as "delete[] list".
+ */
+ {
+ TPtr8 empty(NULL, 0);
+ aN = iControl.iSocket.GetOpt(KSoInetAddressInfo, KSolInetIfQuery, empty);
+ if (aN <= 0)
+ return NULL;
+ TInetAddressInfo *p = new TInetAddressInfo[aN];
+ if (p == NULL)
+ return NULL; // No memory available.
+ TPtr8 opt((TUint8 *)p, aN*sizeof(TInetAddressInfo));
+ aN = iControl.iSocket.GetOpt(KSoInetAddressInfo, KSolInetIfQuery, opt);
+ if (aN < 0)
+ aN = 0;
+ else
+ aN = opt.Length() / (TInt)sizeof(TInetAddressInfo);
+ return p;
+ }
+
+
+TInt CDnsService::GetByAddress(TUint32 /*aId*/, const TInetAddr &aAddr, TInt /*aNext*/, THostName &aName)
+ /**
+ * Returns the local hostname, if address is my own.
+ */
+ {
+ TPckgBuf<TSoInetIfQuery> opt;
+ opt().iSrcAddr = aAddr;
+ if (iControl.iSocket.GetOpt(KSoInetIfQueryBySrcAddr, KSolInetIfQuery, opt) != KErrNone)
+ return KErrNotFound;
+ aName = iHostNames.Find(opt().iZone[15]); // Locate hostname for network id.
+ return (aName.Length() > 0) ? KErrNone : KErrNotFound;
+ }
+
+TInt CDnsService::GetByName(TUint32 aId, const THostName &aName, TInt /*aNext*/, TInetAddr &aAddr)
+ /**
+ * Returns one of my own addresses, if the hostname is local.
+ */
+ {
+ const TDesC &name = iHostNames.Find(aId);
+ if (aName.Length() > 0 && name.CompareF(aName) != 0)
+ return KErrNotFound; // The query name is not a local host.
+ //
+ // Locate a local addres from some interface within the specific network.
+ //
+ TInt N;
+ TInetAddressInfo *list = GetAddressList(N);
+ if (list == NULL)
+ return KErrNotFound;
+
+ TUint32 iface = 0;
+ TUint32 netid = 0;
+ TInt scope = -1;
+ TInt best = 0;
+ for (TInt i = 0; i < N; ++i)
+ {
+ if (list[i].iState != TInetAddressInfo::EAssigned)
+ continue;
+ if (list[i].iInterface != iface)
+ {
+ iface = list[i].iInterface;
+ netid = GetNetId(iface);
+ }
+ if (netid != aId)
+ continue; // Not this address.
+ if (list[i].iAddress.Scope() > scope)
+ {
+ scope = list[i].iAddress.Scope();
+ best = i;
+ }
+ }
+ aAddr.SetAddress(list[best].iAddress);
+ aAddr.SetScope(list[best].iScopeId);
+ delete[] list;
+ return scope < 0 ? KErrNotFound : KErrNone;
+ }
+
+//
+//
+
+CDndDnsclient *CDndDnsclient::NewL(CDndEngine &aControl, MDnsServerManager &aServerManager)
+ {
+ LOG(Log::Printf(_L("CDndDnsclient::NewL()")));
+
+ CDnsService *const dns = new (ELeave) CDnsService(aControl, aServerManager);
+ CleanupStack::PushL(dns);
+ dns->ConstructL();
+ CleanupStack::Pop();
+ return dns;
+ }
+
+CDndDnsclient::CDndDnsclient(CDndEngine &aControl, MDnsServerManager &aServerManager)
+ : CDnsSocket(aControl.GetConfig().iEDNS0), iControl(aControl), iServerManager(aServerManager)
+ {
+ }
+
+void CDndDnsclient::ConstructL()
+ {
+ LOG(Log::Printf(_L("CDndDnsclient::ConstructL() size=%d"), (TInt)sizeof(*this)));
+ CDnsSocket::ConstructL();
+
+ iCache = new (ELeave) CDndCache();
+ iCache->ConstructL();
+
+ TPtrC localhost;
+ if(iControl.FindVar(DND_INI_HOST, DND_INI_HOSTNAME, localhost))
+ (void)iHostNames.Reset(localhost);
+ else
+ (void)iHostNames.Reset(KDndIni_Hostname);
+ }
+
+
+
+
+void CDndDnsclient::HandleCommandL(TInt aCommand)
+ {
+#ifdef _LOG
+ switch(aCommand)
+ {
+ case EDndDump:
+ if (iCache)
+ iCache->Dump(iControl);
+ break;
+ default:
+ break;
+ }
+#endif
+ if (aCommand == EDndFlush)
+ iCache->Flush();
+ }
+
+CDndDnsclient::~CDndDnsclient()
+ {
+ DeactivateSocket();
+
+ // Just to be sure, cancel server list notifys (if any)
+ // (this means that SERVER MANAGER MUST NOT BE DELETED
+ // before this object!)
+ for (TUint i = 0; i < KDndNumRequests; i++)
+ iServerManager.CloseList(iDndReqData[i].iFilter);
+ delete iCache;
+ }
+
+// CDndDnsclient::CheckAddress
+// ***************************
+TInt CDndDnsclient::CheckAddress(const TInetAddr &aDestination)
+ {
+ TPckgBuf<TSoInetIfQuery> opt;
+ opt().iDstAddr = aDestination;
+
+ const TInt err = iControl.iSocket.GetOpt(KSoInetIfQueryByDstAddr, KSolInetIfQuery, opt);
+ if (err == KErrNone && TInetAddr::Cast(opt().iSrcAddr).IsUnspecified())
+ return KErrNotFound; // No route or source address.
+ return err;
+ }
+
+
+
+// TDndReqData::PickDefaultServer
+// ********************************
+TBool TDndReqData::PickDefaultServer()
+ {
+ TInetAddr tmp;
+ TBool ret = FALSE;
+
+ if (iCurrentServer == 0)
+ ret = iOwner->iServerManager.OpenList(iFilter, this) > 0;
+ iOwner->iCache->GetServerAddress(iOwner->iServerManager.NameSpace(iFilter, iCurrentServer), tmp);
+ iFilter.iServerId = iOwner->iServerManager.ServerId(tmp);
+ iCurrentServer = iOwner->iServerManager.Next(iFilter, 0);
+#ifdef _LOG
+ if (iCurrentServer)
+ Log::Printf(_L("\t\tDNS session [%u] default nameserver (id=%d) assigned"), (TInt)this, (TInt)iCurrentServer);
+ else
+ Log::Printf(_L("\t\tDNS session [%u] no nameservers available for this session"), (TInt)this);
+#endif
+ return ret || iCurrentServer != 0;
+ }
+
+
+// TDndReqData::PickNewServer
+// ****************************
+TBool TDndReqData::PickNewServer()
+ {
+ if (iCurrentServer == 0)
+ iOwner->iServerManager.BuildServerList();
+ iCurrentServer = iOwner->iServerManager.Next(iFilter, iCurrentServer);
+#ifdef _LOG
+ if (iCurrentServer)
+ Log::Printf(_L("\t\tDNS session [%u] next nameserver (id=%d) assigned"), (TInt)this, (TInt)iCurrentServer);
+ else
+ Log::Printf(_L("\t\tDNS session [%u] next, no alternate nameservers to assign"), (TInt)this);
+#endif
+ return iCurrentServer != 0;
+ }
+
+void TDndReqData::ServerListComplete(const TDnsServerFilter &aFilter, TInt aResult)
+ {
+ if (&aFilter == &iFilter)
+ {
+ iCurrentServer = iOwner->iServerManager.Next(iFilter, 0);
+ if (iIsReqPending)
+ {
+ if (aResult == KErrNone)
+ {
+ if (PickDefaultServer())
+ {
+ iOwner->ReSend(*this);
+ aResult = KDnsNotify_HAVE_SERVERLIST;
+ }
+ else
+ aResult = KErrDndServerUnusable;
+ }
+ SendResponse(aResult);
+ }
+ }
+ }
+
+
+// CDndDnsclient::OpenSession
+// **************************
+/**
+// A session is internally represented by a TDndReqData. Find an unused
+// entry and assign it. Unused entry is recognized by it having no
+// callback function (= NULL).
+*/
+MDnsSession *CDndDnsclient::OpenSession(MDnsResolver *const aCallback)
+ {
+ TUint i;
+ //
+ // Locate available TDndReqData entry...
+ //
+ for (i = 0;; ++i)
+ {
+ if (i >= KDndNumRequests)
+ return NULL; // No "sessions" available
+ // For now, assume the slot is available, if no callback has been installed
+ if (iDndReqData[i].iCallback == NULL)
+ break;
+ }
+ TDndReqData &rq = iDndReqData[i];
+ rq.iCallback = aCallback;
+ rq.iOwner = this;
+ iActivityCount++; // Count each open session as "activity".
+ return &rq;
+ }
+
+
+void CDndDnsclient::Error(const TInetAddr &aServer, TInt /*aError*/)
+ {
+ const TUint server_id = iServerManager.ServerId(aServer);
+ if (server_id == 0)
+ return; // -- nothing to do, address not registered as a server
+
+ //
+ // Try to notify all sessions currently using this server
+ //
+ for (TUint i = 0; i < KDndNumRequests; ++i)
+ {
+ TDndReqData &rq = iDndReqData[i];
+ // For now, assume the slot is in used, if callback has been installed
+ if (rq.iCallback == NULL)
+ continue;
+ if (rq.iCurrentServer == server_id)
+ rq.SendResponse(KErrDndServerUnusable);
+ }
+ }
+
+
+
+// TDndReqData::Close
+// ******************
+void TDndReqData::Close()
+ {
+ CancelQuery();
+ iCallback = NULL;
+ if (iRecord)
+ {
+ iRecord->Unlock(this);
+ iRecord = NULL;
+ }
+ iOwner->iServerManager.CloseList(iFilter);
+ // If this was the last active session, then
+ // cancel all requests
+ ASSERT(iOwner->iActivityCount > 0);
+ if (--iOwner->iActivityCount == 0)
+ iOwner->DeactivateSocket();
+ }
+
+// TDndReqData::NewQuery
+// ***********************
+TInt TDndReqData::NewQuery(const TDnsMessage &aQuery, TDnsServerScope aServerScope, TUint32 aFlags)
+ {
+ iIsReqPending = FALSE;
+ iIsNewQuery = TRUE;
+ iIsUsingTCP = 0;
+ iQdCount = 1;
+ iFlags = aFlags;
+ iFilter.iServerScope = aServerScope;
+ iFilter.iServerId = 0;
+ iNetworkId = aQuery.iId; // Get the networkId information from the Query.
+
+#ifdef SYMBIAN_DNS_PUNYCODE
+ if( (aQuery.iScope & 0x80) == 0x80 )
+ {
+ iIdnEnabled = 1;
+ iQuestion.EnableIdn(ETrue);
+ }
+ else
+ {
+ iIdnEnabled = 0;
+ iQuestion.EnableIdn(EFalse);
+ }
+#endif //SYMBIAN_DNS_PUNYCODE
+
+ switch (aQuery.iType)
+ {
+ case KDnsRequestType_GetByName:
+ case KDnsRequestType_GetByAddress:
+ {
+ const TNameRecord &query = aQuery.NameRecord();
+
+ if (aQuery.iType == KDnsRequestType_GetByName)
+ {
+ iFilter.iLockId = aQuery.iId;
+ iFilter.iLockType = KIp6AddrScopeNetwork;
+#ifdef SYMBIAN_DNS_PUNYCODE
+ TInt err = iQuestion.SetName(query.iName);
+ if( err != KErrNone)
+ {
+ return err;
+ }
+#else
+ iQuestion.SetName(query.iName);
+#endif // SYMBIAN_DNS_PUNYCODE
+ }
+ else
+ {
+ iOwner->iServerManager.LockByAddress(TInetAddr::Cast(query.iAddr), aQuery.iId, iFilter);
+#ifdef SYMBIAN_DNS_PUNYCODE
+ TInt err = iQuestion.SetName(TInetAddr::Cast(query.iAddr));
+ if( err != KErrNone)
+ {
+ return err;
+ }
+#else
+ iQuestion.SetName(TInetAddr::Cast(query.iAddr));
+#endif // SYMBIAN_DNS_PUNYCODE
+ }
+ }
+ break;
+ case KDnsRequestType_TDnsQuery:
+ {
+ const TDnsQuery &query = aQuery.Query();
+ iQuestion.Copy(query.Data());
+ iFilter.iLockId = aQuery.iId;
+ iFilter.iLockType = KIp6AddrScopeNetwork;
+ }
+ break;
+ default:
+ return KErrNotSupported;
+ }
+ iCurrentServer = 0;
+ //
+ // For forward query, the Scope identifier of the aQuery.iAddr is the network id
+ // For pointer query, if the address is not of network scope, then need to find
+ // the network id based on the scope id... (fix later). -- msa
+
+ iOpcode = EDnsOpcode_QUERY; // Only EStandard supported! (EInverse is not same as PTR query!!!)
+ if (iRecord)
+ {
+ iRecord->Unlock(this);
+ iRecord = NULL;
+ }
+ return KErrNone;
+ }
+
+
+// TDndReqData::CancelQuery
+// **************************
+void TDndReqData::CancelQuery()
+ {
+ iIsReqPending = FALSE;
+ Cancel();
+ iOwner->iServerManager.CloseList(iFilter);
+ }
+
+
+// TDndReqData::DoNext
+// *********************
+/**
+// @retval aReply returns the requested Resource Record value, if KErrNone return
+// @param aNext the index of the value to be returned. 0 is the index of the first value
+// @returns
+// @li KErrNotFound, if there is no value at specified index
+// @li KErrDndCache, if the reply from DNS stored in cache was corrupt
+// @li KErrNone, if value successfully returned
+*/
+TInt TDndReqData::DoNext(TDnsMessageBuf &aReply, TInt aNext) const
+ {
+ if (iRecord == NULL)
+ return KErrNotFound;
+ TInt ret = iRecord->ErrorCode();
+ if (ret < 0)
+ return ret; // No usable record present
+
+ TDndRR tempRR(iRecord->Reply());
+#ifdef SYMBIAN_DNS_PUNYCODE
+ tempRR.iIdnEnabled = TBool(iIdnEnabled);
+#endif //SYMBIAN_DNS_PUNYCODE
+
+ const TInet6HeaderDNS &hdr = iRecord->Header();
+ TInt answerCount = hdr.ANCOUNT();
+
+ ret = tempRR.FindRR(iRecord->AnswerOffset(), answerCount, iQuestion.QType(), iQuestion.QClass(), aNext);
+ if (ret < 0)
+ {
+ if (ret == KErrDndCache)
+ iRecord->Invalidate();
+ return ret;
+ }
+
+ TInetAddr *addr = NULL;
+
+ switch (aReply().iType)
+ {
+ case KDnsRequestType_GetByName:
+ case KDnsRequestType_GetByAddress:
+ {
+ TNameRecord &reply = aReply().NameRecord();
+ aReply.SetLength(aReply().HeaderSize() + sizeof(reply));
+ if (hdr.AA())
+ reply.iFlags |= EDnsAuthoritive;
+ ret = GetResponse(tempRR, reply);
+ reply.iFlags |= EDnsServer | (iIsFromCache ? EDnsCache : 0);
+ addr = &TInetAddr::Cast(reply.iAddr);
+ break;
+ }
+#ifndef NO_DNS_QUERY_SUPPORT
+ case KDnsRequestType_TDnsQuery:
+ ret = tempRR.GetResponse(aReply, &addr);
+ break;
+#endif
+ default:
+ return KErrNotSupported;
+ }
+ if (ret < 0)
+ return ret; // Some error detected!
+
+ if (addr)
+ {
+ // Reply contains a TInetAddr. This needs some special processing
+
+ // Supplement IPv6 addresses with the scope value
+ // (Should do the same with IPv4 after converting to IPv4 mapped?)
+ if (addr->Family() == KAfInet)
+ addr->ConvertToV4Mapped();
+ if (addr->Family() == KAfInet6)
+ {
+ const TInt scope_level = addr->Ip6Address().Scope();
+
+ // Add the scope id only for addresses with larger than node
+ // local scope (this leaves the scope id as zero, if a loopback
+ // address is returned from the name server (The id of the node
+ // local scope is the interface index and non-zero value would
+ // bind loopback destination to real interface instead of internal
+ // loopback interface).
+ if (scope_level > KIp6AddrScopeNodeLocal)
+ addr->SetScope(iOwner->iServerManager.Scope(iRecord->Server(), *addr));
+
+#ifdef LLMNR_ENABLED
+ if(iOwner->iControl.GetConfig().iLlmnrLlOnly) // accept only linklocal replies to LLMNR queries
+ if (iFilter.iServerScope == EDnsServerScope_MC_LOCAL)
+ {
+ // Check compliance w. link-local addressing requirements (ipv6/ipv4)
+ if (scope_level != KIp6AddrScopeLinkLocal)
+ return KErrNotFound;
+ }
+#endif
+
+ //
+ // A backward compatibility hack: if address is IPv4 global address
+ // and the network scope in the query matches the scope of the address,
+ // then convert the returned address into old KAfInet format (and lose
+ // lose the scope id).
+ if (addr->IsV4Mapped() &&
+ addr->Scope() == aReply().iId &&
+ scope_level == KIp6AddrScopeNetwork)
+ addr->SetAddress(addr->Address());
+ }
+ }
+ return KErrNone;
+ }
+
+// TDndReqData::DoError
+// **********************
+/**
+// If a session has a DNS reply in cache associated with it, then
+// set the state of this reply to indicated error code.
+//
+// @param aError the error code to be stored
+// @param aTLL the new "Time To Live" for the record (in seconds). The
+// record (and error state) will expire after this time.
+*/
+void TDndReqData::DoError(TInt aError, TUint aTTL)
+ {
+ if (aError >= 0)
+ return; // Only errors can be set!
+
+ if (iRecord == NULL)
+ return;
+ iRecord->FillErrorCode(aError, aTTL);
+ }
+
+// TDndReqData::DoQueryL
+// ***********************
+/**
+// Activate a query of specified type for the loaded query information
+// (enable RecvReply callback). Note: the callback may occur already within call!
+//
+// @param aRequestTime is the time when the request is received from the application
+// @param aQType type of the query (all queries assume IN class)
+// @returns
+// @li < 0, serious error, (resolving process should be aborted)
+// @li = 0, reply found from cache (reply callback has been called)
+// @li = 1, DNS Query message has been queued for transmission
+// @execption
+// LEAVE on any serious error (resolving process should be aborted)
+*/
+TInt TDndReqData::DoQueryL(const TTime &aRequestTime, const EDnsQType aQType)
+ {
+ // -- class is now always IN,
+ // -- should only look from cache if aQType is not a "wildcard" type
+ // (however, if wildcard, cache should not give hits...)
+ iQuestion.SetQType(aQType);
+ // -- only IN class queries are supported
+ iQuestion.SetQClass(EDnsQClass_IN);
+
+ if (iRecord)
+ {
+ iRecord->Unlock(this);
+ iRecord = NULL;
+ }
+
+ if (iCurrentServer == 0)
+ {
+ if (!PickDefaultServer())
+ {
+ SendResponse(KErrDndServerUnusable);
+ return 0;
+ }
+ if (iCurrentServer == 0)
+ {
+ // Cannot check cache nor start any query, if the
+ // name space id cannot be determined (no interfaces
+ // up). Return "query queued" anyway, and let the
+ // resolver retry process try it again later.
+ iIsReqPending = TRUE;
+ return 1;
+ }
+ }
+ TUint32 id = iOwner->iServerManager.NameSpace(iFilter, iCurrentServer);
+
+ if (id == 0)
+ {
+ SendResponse(KErrDndServerUnusable);
+ return 0;
+ }
+
+ // Check in the cache first. If the record does not exist
+ // in the cache, it will be created now (empty with KErrNotFound).
+ iRecord = iOwner->iCache->FindL(
+ id,
+ iQuestion,
+ iQuestion.QType(),
+ // *HACK WARNING* To achieve independent caching of answers which
+ // are result of queries where RD=1 or RD=0 (recursion desired),
+ // the Class value is made different depending on the RD state.
+ (EDnsQClass)(iQuestion.QClass() | ((iFlags & KDnsModifier_RD) ? 0 : 0x80)),
+ aRequestTime);
+ // The above FindL must either leave of return a valid
+ // record pointer. Just as a safety measure, if record
+ // is not returned, then panic on in DEBUG builds, and
+ // in release, leave with KErrDndNoRecord
+ // (** however, this should never happen! **)
+ ASSERT(iRecord != NULL);
+ if (iRecord == NULL)
+ User::Leave(KErrDndNoRecord);
+
+ // Prevent record from being deleted while the
+ // iRecord pointer exists...
+ iRecord->Lock();
+ switch(iRecord->ErrorCode())
+ {
+ // If no error in the record, retrieve the informations and send
+ case KErrNone:
+ // For certain errors, send the error code
+ case KErrDndBadName:
+ case KErrDndNotImplemented:
+ case KErrDndRefused:
+ case KErrDndNoRecord:
+ iIsFromCache = TRUE;
+ iIsNewQuery = FALSE;
+ if (IsQueued())
+ Cancel();
+ SendResponse(iRecord->ErrorCode());
+ return 0;
+
+ // For other errors, try sending the query to the DNS
+ default:
+ break;
+ }
+
+ // Automatic restart of DNS socket, if closed for some reason
+ iOwner->ActivateSocketL(iNetworkId); // pass on the networkId information
+
+ iIsReqPending = TRUE;
+ // If the query is probe, do not AssignWork but continue..
+ if (!(iFlags & KDnsModifier_PQ) && !iRecord->AssignWork(this))
+ return 1; // just let the other worker do the job.
+
+ // Try to detect retransmissions of the same query and
+ // reuse old ID in such case... (don't generate a new)
+ if (iIsNewQuery ||
+ iQuestion.QType() != aQType ||
+ iQuestion.QClass() != EDnsQClass_IN)
+ Cancel(); // A new ID required
+
+ iIsNewQuery = FALSE;
+ iIsFromCache = FALSE;
+
+ // If the DNS "mode" is Multicast DNS, then assume that PTR queries
+ // which are generated from IP address are to be made via TCP. Test
+ // this and use TCP if query is PTR query for valid IP address.
+ // [specified in draft-ietf-dnsext-mdns-22.txt, but generalized here
+ // also for any future Multicast DNS]
+ for ( ;iFilter.IsMulticast() && iQuestion.QType() == EDnsQType_PTR;)
+ {
+ // Use "for" just for easy "break" exits!
+ TInetAddr server;
+
+ // Get correct DNS port number into 'server' (actual address is thrown away)
+ if (iOwner->iServerManager.Address(iCurrentServer, server) != KErrNone)
+ break;
+ // Use server address type as a flag, whether Ipv4 or IPv6 is done
+ const TInt is_ipv4 = server.IsV4Mapped();
+
+ if (!iQuestion.GetAddress(server) || !server.IsUnicast())
+ break;
+ // Usually LLMNR has both IPv4 and IPv6 multicast addresses as "servers".
+ // There is no point in doing PTR query for the same address twice, thus
+ // only do it once per matching "server" address (thus, if there is no
+ // IPv4 multicast "server", no IPv4 reverse queries are done either).
+ if (is_ipv4 != server.IsV4Mapped())
+ {
+ SendResponse(KErrDndServerUnusable);
+ return 0;
+ }
+
+ // Supplement 'server' address with a scope id based on current server
+ server.SetScope(iOwner->iServerManager.Scope(iCurrentServer, server));
+
+ // For link local multicast, use TTL = 1 (otherwise, system default is used)
+ const TInt ttl = iFilter.iServerScope == EDnsServerScope_MC_LOCAL ? 1 : -1;
+ if (iOwner->Queue(*this, server, -1, ttl) != KErrNone)
+ break;
+ iIsUsingTCP = 1;
+ SendResponse(KDnsNotify_USING_TCP);
+ return 1;
+ }
+ iOwner->ReSend(*this); // (uses old ID, if request queued already)
+ return 1;
+ }
+
+// TDndReqData::UpdateCacheData
+// ******************************
+/**
+// @param aQuery the session from which the reply is updated
+// @param aMsg the reply from the DNS server
+// @param aAnswerOffset the start offset to the andwer secion in the reply
+// @param aErr the status code of the reply
+// @returns
+// @li TRUE, if cache record updated
+// @li FALSE, if not updated (error code is "transient", concerns single query)
+*/
+TBool TDndReqData::UpdateCacheData(const TMsgBuf &aMsg, const TInt aAnswerOffset, const TInt aErr)
+ {
+ if (iRecord == NULL)
+ return FALSE;
+
+#ifdef _LOG
+ THostName name;
+ iQuestion.GetName(name);
+ Log::Printf(_L("\t\tDNS session [%u] -- Update cache: %S (offset=%d) aErr=%d"), (TInt)this, &name, aAnswerOffset, aErr);
+#endif
+ if (aErr == KErrDndDiscard)
+ return FALSE;
+
+ // If there is already have valid answer cached, decide whether the new
+ // answer is better and should replace the old?
+ const TInet6HeaderDNS &new_hdr = aMsg.Header();
+ if (iRecord->ErrorCode() == KErrNone && !new_hdr.AA())
+ {
+ // If new header is not authoritative, then it will replace the value
+ // only if old is non-authoritative and there is no error.
+ const TInet6HeaderDNS &old_hdr = iRecord->Header();
+ if (old_hdr.AA() || aErr != KErrNone)
+ return FALSE; // Not updated, keep previous valid value in cache.
+ }
+
+ TBool updated = FALSE;
+ if (aErr == KErrNone || aErr == KErrDndBadName || aErr == KErrDndNotImplemented || aErr == KErrDndNoRecord)
+ {
+ // Try to locate the SOA record from the authority section and
+ // use the minttl as the ttl of the negative caching.
+ TInt ttl;
+ TDndRR soa(aMsg);
+#ifdef SYMBIAN_DNS_PUNYCODE
+ soa.iIdnEnabled = (TBool) iIdnEnabled;
+#endif //SYMBIAN_DNS_PUNYCODE
+ TInt off = soa.LocateRR(aAnswerOffset, new_hdr.ANCOUNT()+new_hdr.NSCOUNT(), EDnsQType_SOA, EDnsQClass_IN, new_hdr.ANCOUNT());
+ if (off < 0)
+ {
+ ttl = iOwner->iControl.GetConfig().iMaxTime;
+ LOG(Log::Printf(_L("\t\tDNS session [%u] -- Update cache: no SOA, default ttl = %d"), (TInt)this, ttl));
+ }
+ else if ((off = aMsg.SkipName(soa.iRd)) > 0 &&
+ (off = aMsg.SkipName(off)) > 0 &&
+ off >= (TInt)soa.iRd &&
+ off + 20 <= (TInt)(soa.iRd + soa.iRdLength))
+ {
+ ttl = BigEndian::Get32(aMsg.Ptr()+off+16);
+ LOG(Log::Printf(_L("\t\tDNS session [%u] -- Update cache: SOA minttl = %d"), (TInt)this, ttl));
+ }
+ else
+ {
+ ttl = 0; // The reply is broken in some way, use ttl = 0
+ LOG(Log::Printf(_L("\t\tDNS session [%u] -- Update cache: SOA access failed"), (TInt)this));
+ }
+ iRecord->FillData(aMsg, aAnswerOffset, iCurrentServer, aErr, ttl);
+#ifdef SYMBIAN_DNS_PUNYCODE
+
+ LOG(iRecord->Print(iOwner->iControl,(TBool)iIdnEnabled));
+#else
+ LOG(iRecord->Print(iOwner->iControl));
+#endif //SYMBIAN_DNS_PUNYCODE
+ updated = TRUE;
+ }
+ //
+ // Got some answer from a server, update the "good" server (unless error indicates "bad"
+ //
+ if (iFilter.IsUnicast() && aErr != KErrDndRefused && aErr != KErrDndServerUnusable)
+ {
+ TInetAddr addr;
+ if (iOwner->iServerManager.Address(iCurrentServer, addr) == KErrNone)
+ {
+#ifdef _LOG
+ // borrow the 'name' from earlier LOG section!
+ addr.OutputWithScope(name);
+ Log::Printf(_L("\t\tDNS session [%u] -- Update cache: preferred server = %S port=%d ns=%d"),
+ (TInt)this, &name, addr.Port(), iOwner->iServerManager.NameSpace(iFilter, iCurrentServer));
+#endif
+ iOwner->iCache->SetServerAddress(iOwner->iServerManager.NameSpace(iFilter, iCurrentServer), addr, KErrNone);
+ }
+ }
+
+ return updated;
+ }
+
+
+void CDndDnsclient::QueryBegin()
+ {
+ ++iActivityCount;
+ }
+
+
+void CDndDnsclient::QueryEnd()
+ {
+ ASSERT(iActivityCount > 0);
+ if (--iActivityCount == 0)
+ DeactivateSocket();
+ }
+
+
+//
+// TDndReqData
+//
+
+// TDndReqData::TranslateRCODE
+// ***************************
+/**
+//
+// @param aHdr The fixed DNS reply header
+//
+// @returns
+// @li KErrNone, if reply is ok
+// @li KErrDndDiscard/KErrDndUnknown
+// if reply does not match the query, has errors
+// in the format or RCODE was unknown
+// @li KErrDndFormat, RCODE was EDnsRcode_FORMAT_ERROR
+// @li KErrDndServerFailure, RCODE was EDnsRcode_SERVER_FAILURE
+// @li KErrDndBadName, RCODE was EDnsRcode_NAME_ERROR
+// @li KErrDndNotImplemented, RCODE was EDnsRcode_NOT_IMPLEMENTED
+// @li KErrDndRefuced, RCODE was EDnsRcode_REFUSED
+// @li KErrDndServerUnusable, if empty reply is not authoritative
+*/
+TInt TDndReqData::TranslateRCODE(const TDndHeader &aHdr, TInt aRCode) const
+ {
+ switch (aRCode)
+ {
+ case EDnsRcode_NOERROR:
+ break;
+ case EDnsRcode_FORMAT_ERROR:
+ return KErrDndFormat;
+ case EDnsRcode_SERVER_FAILURE:
+ return KErrDndServerFailure;
+ case EDnsRcode_NAME_ERROR:
+ return KErrDndBadName;
+ case EDnsRcode_NOT_IMPLEMENTED:
+ return KErrDndNotImplemented;
+ case EDnsRcode_REFUSED:
+ return KErrDndRefused;
+ default:
+ return KErrDndUnknown;
+ }
+
+ if (iOpcode == EDnsOpcode_QUERY && aHdr.QDCOUNT() != iQdCount)
+ return KErrDndDiscard;
+ //
+ // A special heuristics: discard empty replies, if the selected server does
+ // not do recursion as requested, and if it is not authority on the queried
+ // name. => return a special "server unusable for this query" error
+ if (aHdr.ANCOUNT() == 0 && (iFlags & KDnsModifier_RD) != 0 && !aHdr.RA() && !aHdr.AA())
+ return KErrDndServerUnusable;
+ return KErrNone;
+ }
+
+// TDndReqData::CheckQuestion
+// **************************
+/**
+// @param aOffset starting offset of the question section in the message
+// @param aMsg the reply message from a DNS server
+//
+// @returns
+// @li > 0,
+// if message checks ok, the value is the new offset pointing
+// to the next section after the question.
+// @li = 0, reply does not match the question.
+// @li < 0, bad DNS reply
+*/
+TInt TDndReqData::CheckQuestion(const TMsgBuf &aMsg, TInt &aRCode) const
+ {
+ if (!iIsReqPending)
+ return 0; // Not for me.
+
+ TDndQuestion question;
+ const TInt offset = aMsg.VerifyMessage(aRCode, question);
+ if (offset < 0)
+ return offset; // Invalid message format, just ignore.
+ const TDndHeader &hdr = aMsg.Header();
+
+ // Only ONE question supported, sematics of receiving a reply
+ // with more than one Question are hairy... [which answers
+ // relate to which question?] (and multiple questions are
+ // not supported by current servers anyway -- msa)
+ if (hdr.QDCOUNT() != 1)
+ return KErrDndUnknown;
+
+ // This is supposed to be a REPLY, if not, then "no match".
+ if (!hdr.QR())
+ return 0;
+ // Reply OPCODE match the query?
+ if (hdr.OPCODE() != iOpcode)
+ return KErrDndDiscard;
+
+ // Does the question match the query?
+ //
+ if (question.CheckQuestion(iQuestion) != KErrNone)
+ return 0;
+ return offset;
+ }
+
+
+// TDndReqData::GetResponse
+// ************************
+/**
+// Map the contents of single resource record into TNameRecord.
+//
+// @param aRR the resource from which the reply is extracted
+// @retval aAnswer receives the extracted value
+// @returns
+// @li KErrNone, if extraction successful
+// @li KErrDndNameTooBig, if answer cannot be fit into aAnswer
+// @li and other errors
+*/
+TInt TDndReqData::GetResponse(const TDndRR &aRR, TNameRecord &aAnswer) const
+ {
+ TInt err = aRR.GetResponse(aAnswer.iName, TInetAddr::Cast(aAnswer.iAddr));
+ if (err != KErrNone)
+ return err;
+ aAnswer.iFlags |= (aRR.iType == EDnsType_CNAME) ? (EDnsAlias | EDnsServer) : EDnsServer;
+ return KErrNone;
+ }
+
+
+// TDndReqData::SendResponce
+// *************************
+/**
+// @param aErr is the status,
+// @li = 0, the request has completed successfully
+// @li > 0, request being processed, just a progress noticification
+// @li < 0, the request has completed with an error
+*/
+void TDndReqData::SendResponse(TInt aErr)
+ {
+ if (aErr <= 0)
+ {
+ iIsReqPending = FALSE; // Current request completed
+ Cancel();
+ }
+ if (iCallback)
+ iCallback->ReplyCallback(aErr);
+ }
+
+// TDndReqData::Build
+// ******************
+/**
+// @retval aMsg
+// contains the fully constructed message to be sent to the DNS server,
+// if Build succeeds
+// @retval aServer
+// contains the server address for which the message should be sent
+// @param aMaxMessage
+// the size of the current receive buffer (if UDP, zero for TCP)
+//
+// @returns TRUE, successful Build, and error (< 0) otherwise
+*/
+TBool TDndReqData::Build(CDnsSocket &aSource, TMsgBuf &aMsg, TInetAddr &aServer, TInt aMaxMessage)
+ {
+ CDndDnsclient &dns = (CDndDnsclient &)aSource;
+
+ if (dns.iServerManager.Address(iCurrentServer, aServer) != KErrNone)
+ return 0;
+ ASSERT(aServer.Port() != 0);
+
+ aMsg.SetLength(sizeof(TDndHeader));
+ TDndHeader &hdr = (TDndHeader &)aMsg.Header();
+ if (aServer.IsMulticast())
+ {
+ ASSERT(iFilter.IsMulticast());
+ hdr.SetRD(0);
+ if (aServer.IsV4Mapped())
+ {
+ if(iQuestion.QType() == EDnsQType_AAAA)
+ {
+ SendResponse(KErrDndServerUnusable);
+ return 0;
+ }
+ }
+ else
+ {
+
+ if(iQuestion.QType() == EDnsQType_A)
+ {
+ SendResponse(KErrDndServerUnusable);
+ return 0;
+ }
+ }
+#ifdef LLMNR_ENABLED
+ dns.SetHoplimit(dns.iControl.GetConfig().iLlmnrHoplimit);
+#endif
+ }
+ else
+ {
+#ifdef LLMNR_ENABLED
+ dns.SetHoplimit(-1);
+#endif
+ hdr.SetRD(iFlags & KDnsModifier_RD);
+ }
+ hdr.SetQdCount(1);
+ if (iQuestion.Append(aMsg) < 0)
+ return 0;
+
+ // Assume EDNS0 enabled, if the current receive buffer
+ // is larger than KDnsMaxMessage (all smaller
+ // values are treated as "no EDNS0".
+ if (aMaxMessage > KDnsMaxMessage)
+ {
+ TDndRROut opt(aMsg);
+#ifdef SYMBIAN_DNS_PUNYCODE
+ opt.iIdnEnabled = (TBool) iIdnEnabled;
+#endif //SYMBIAN_DNS_PUNYCODE
+ opt.iType = (TUint16)EDnsQType_OPT;
+ opt.iClass = (TUint16)aMaxMessage;
+ opt.iTTL = 0; // RCODE = 0, version = 0, Z = 0
+ if (opt.Append(KNullDesC8, 0) == KErrNone)
+ {
+ hdr.SetArCount(1);
+ opt.AppendRData();
+ }
+ }
+ return 1;
+ }
+
+
+void TDndReqData::Sent(CDnsSocket &aSource)
+ {
+ CDndDnsclient &dns = (CDndDnsclient &)aSource;
+
+ dns.iServerManager.CloseList(iFilter);
+
+ SendResponse(KDnsNotify_QUERY_SENT);
+ }
+
+
+TBool TDndReqData::Reply(CDnsSocket &aSource, const TMsgBuf &aBuf, const TInetAddr &aServer)
+ {
+ TInt rcode = 0;
+ const TInt offset = CheckQuestion(aBuf, rcode);
+ if (offset < 0)
+ return 1; // Invalid message format, just ignore.
+ const TDndHeader &hdr = aBuf.Header();
+ TInt err = TranslateRCODE(hdr, rcode);
+
+ CDndDnsclient &dns = (CDndDnsclient &)aSource;
+ ASSERT(&dns == iOwner);
+ // If configuration requests that "Not found" replies from a server
+ // are not to be cached, but ignored, then substitue err with
+ // KErrDndServerUnusable (meaning that this server is not usable for
+ // this query) This error is not cached!
+ //
+ if (err == KErrDndBadName && dns.iControl.GetConfig().iSkipNotFound)
+ err = KErrDndServerUnusable;
+ else if (iIsUsingTCP == 0 && hdr.TC() != 0)
+ {
+ // The current query was not TCP and got truncated reply,
+ // restart query with TCP (if we get truncated reply with
+ // TCP, something is broken...)
+ //
+
+ // It is assumed that the aServer has the correct port
+ // already set (it should be the remote port of the original
+ // UDP query)
+ ASSERT(aServer.IsUnicast()); // Should always be true.
+ if (aServer.IsUnicast())
+ {
+ if (iOwner->Queue(*this, aServer, hdr.ID()) == KErrNone)
+ {
+ iIsUsingTCP = 1;
+ SendResponse(KDnsNotify_USING_TCP);
+ return 1;
+ }
+ }
+ // If cannot use the TCP, then just use the trunctated
+ // answer as is...
+ }
+ //
+ // UpdateCacheData updates data in cache only, if err is KErrNone, or
+ // updates error status for some specific err codes,
+ // otherwise, it does nothing.
+ TBool updated = UpdateCacheData(aBuf, offset, err);
+
+#ifdef DEBUG_CACHE
+ iCache->Dump(*iControl);
+#endif
+
+ if (updated)
+ {
+ // Cache modified! In addition to the original query,
+ // send the responce to every request that is waiting for
+ // the same reply... (iRecord pointers are same).
+ for (TUint i = 0; i < KDndNumRequests; ++i)
+ {
+ TDndReqData &rq = dns.iDndReqData[i];
+ if (rq.iIsReqPending && iRecord == rq.iRecord)
+ {
+ rq.Cancel(); // No need to send the query
+ rq.SendResponse(err);
+ }
+ }
+ return 1;
+ }
+
+ // Cache not updated, send to original query only.
+ SendResponse(err);
+ return 1;
+ }
+
+void TDndReqData::Abort(CDnsSocket &/*aSource*/, const TInt aReason)
+ {
+ SendResponse(aReason);
+ }