diff -r 000000000000 -r af10295192d8 tcpiputils/dnd/src/dns.cpp --- /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 +#include +#include +#include "dns.h" +#include "dnd.hrh" // EDndDump +#include +#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 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 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 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); + }