diff -r 000000000000 -r af10295192d8 tcpiputils/dnd/src/resolver.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tcpiputils/dnd/src/resolver.cpp Tue Jan 26 15:23:49 2010 +0200 @@ -0,0 +1,1789 @@ +// 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: +// resolver.cpp - name resolver +// + +#include +#include +#include "listener.h" +#include "resolver.h" +#include "engine.h" +#include "hosts.h" +#include +#include "dns_ext.h" +#include "inet6log.h" + +#ifdef DND_DCM_EXTENSION +#include "dnd.hrh" // EDndFlush +#endif + +#ifdef EXCLUDE_SYMBIAN_DNS_PUNYCODE +#undef SYMBIAN_DNS_PUNYCODE +#endif //EXCLUDE_SYMBIAN_DNS_PUNYCODE + +struct SQueryStep + { + EDnsQType iQType; // the query type + }; + +struct SQueryExtension + { + const TDesC *iPrefix; + SQueryStep iQuery; + }; + +// ...for GetByAddr, perform only PTR query (for each server) +const SQueryStep KGetByAddress[1] = + { + { EDnsQType_PTR } + }; + +// ...for GetByName, try first AAAA and then A (for each server) +const SQueryStep KGetByName1[2] = + { + { EDnsQType_AAAA }, + { EDnsQType_A } + }; + +// ...for GetByName, try first A and then AAAA (for each server) +const SQueryStep KGetByName2[2] = + { + { EDnsQType_A }, + { EDnsQType_AAAA } + }; + +// Define the search order of the sources +const TDnsServerScope KSourceOrder[] = + { + EDnsServerScope_HOSTFILE, // 1. Search hosts file first + EDnsServerScope_UC_GLOBAL, // 2. Search global DNS +#ifdef LLMNR_ENABLED + EDnsServerScope_MC_LOCAL, // 3. Try Link Local Multicast +#endif + }; + +const SQueryExtension KQueryExtension[] = + { +#if 0 + { &KDnsExtQType_A, EDnsQType_A }, + { &KDnsExtQType_AAAA, EDnsQType_AAAA }, + { &KDnsExtQType_MX, EDnsQType_MX }, + { &KDnsExtQType_NS, EDnsQType_NS }, + { &KDnsExtQType_SOA, EDnsQType_SOA }, + { &KDnsExtQType_CNAME, EDnsQType_CNAME }, + { &KDnsExtQType_PTR, EDnsQType_PTR }, + { &KDnsExtQType_SRV, EDnsQType_SRV }, + { &KDnsExtQType_NAPTR, EDnsQType_NAPTR }, + { &KDnsExtQType_ANY, EDnsQType_ANY } +#else + // Workaround for ARMV5 compiler bug? Above declaration always + // produces 80 bytes of writable global data. The following + // ugly definition does not. + { (const TDesC *)&KDnsExtQType_A.iTypeLength, EDnsQType_A }, + { (const TDesC *)&KDnsExtQType_AAAA.iTypeLength, EDnsQType_AAAA }, + { (const TDesC *)&KDnsExtQType_MX.iTypeLength, EDnsQType_MX }, + { (const TDesC *)&KDnsExtQType_NS.iTypeLength, EDnsQType_NS }, + { (const TDesC *)&KDnsExtQType_SOA.iTypeLength, EDnsQType_SOA }, + { (const TDesC *)&KDnsExtQType_CNAME.iTypeLength, EDnsQType_CNAME }, + { (const TDesC *)&KDnsExtQType_PTR.iTypeLength, EDnsQType_PTR }, + { (const TDesC *)&KDnsExtQType_SRV.iTypeLength, EDnsQType_SRV }, + { (const TDesC *)&KDnsExtQType_NAPTR.iTypeLength, EDnsQType_NAPTR }, + { (const TDesC *)&KDnsExtQType_ANY.iTypeLength, EDnsQType_ANY } +#endif + }; + +// KMaxQuerySessions +// ***************** +/** +// The maximum number of query sessions for single resolver, +// for example, supporting A and AAAA query, requires this +// to be at least 2. +// (if someone ever adds A6 to the list, it needs to be 3). +*/ +const TUint KMaxQuerySessions = 2; + +// KMaxDeprecated +// ************** +/** +// The size of the deprecated answers buffer. When processing +// the replies for GetByName, the resolver will first return +// those A and AAAA answers, for which there is an existing +// route and source address in the current system. Other answers +// are put into deprecated buffer, and returned after the +// "good" answers. +*/ +const TUint KMaxDeprecated = 20; + +class CDndResolver; + +/** +// The resolver state automaton for one DNS query. +// +// This executes the state automaton for a single DNS query +// (for example A, AAAA, MX, etc.). The processing includes +// automatic retransmissions (potentially to multiple DNS +// servers) and timeout handling +// +// The query process is started by Start() and it will +// then proceed independently to the conclusion. +// When the query has completed or failed, +// CDndResolver::GetNextAnswer() is called. +// +// In some cases an error is considered to be non-recoverable, +// and instead of calling GetNextAnswer(), CDndResolver::QueryDone(error) +// is called instead. This cancel the main query processing and +// return the indicated error directly to the client process +// (RHostResolver). +// +*/ +class TQuerySession: public MDnsResolver + { + friend class TQuerySessionTimeoutLinkage; +public: + // Default constructor + TQuerySession(); + // Supplement default constructor + void Init(CDndResolver *const aResolver); + // Open a new session to the DNS handler + void Open(); + // Close the session with the DNS handler + void Close(); + // Start a DNS query on the session + TBool Start(EDnsQType aQType); + + MDnsSession *iSession; //< Session handle with DndDnsclient + TInt iStatus; //< Session status / next record to retrieve (if >= 0) + EDnsQType iQType; //< the query type +private: + static inline TInt Max(const TInt a, const TInt b) { return a > b ? a : b;} + + // A callback method when the timeout expires + void Timeout(const TTime &aNow); + // Request a call to Timeout after specified time (seconds) + void SetTimer(TUint aTimeout); + // A callback method when the request to DNS handler completes + void ReplyCallback(const TInt aErr); + + // Cancel pending operations + void Cancel(); + // Activate a query in the DNS handler + void SendDnsQuery(); + + // Internal, handles the common part of timeout and reply callbacks + void Event(TBool aTimeout); + + CDndResolver *iResolver; //< The back pointer to resolver + + TUint8 iQueryRetry; //< Current retry index (for the current step) + TUint8 iQueryTimeouts; //< Number of true timeouts (used by some resolver modes) + TUint8 iServerCount; //< used in "spray mode" to count the servers. + TUint iWaitingQuerySent:1; //< used in detecting and timing out interface startup. + TUint iWaitingAfterSpray:1; //< used only in "spray mode" + TUint iSendingQuery:1; //< set to 1, when DoQueryL is being called (recursion safeguard in SendDnsQuery) + TUint iPendingQuery:1; //< set to 1, when DoQueryL is required (recursion safeguard in SendDnsQuery) + TUint iQueryTimeoutValue; //< The current timeout for waiting the reply AFTER packet is sent +public: // Needed for gcc! + RTimeout iTimeout; +#ifdef _LOG + TBuf<2> iName; +#endif + }; + +// This ungainly manoevure is forced on us because the offset is not evaluated early enough by GCC3.4 to be +// passed as a template parameter +#if defined(__X86GCC__) || defined(__GCCE__) +#define KQuerySessionTimeoutLinkageOffset 28 +__ASSERT_COMPILE(KQuerySessionTimeoutLinkageOffset == _FOFF(TQuerySession, iTimeout)); +#else +#define KQuerySessionTimeoutLinkageOffset _FOFF(TQuerySession, iTimeout) +#endif + +class TQuerySessionTimeoutLinkage : public TimeoutLinkage + { +public: + static void Timeout(RTimeout &aLink, const TTime &aNow, TAny * /*aPtr*/) + { + Object(aLink)->Timeout(aNow); + } + }; + +/** +// A structure to remember an answer to be retrieved later. +*/ +class TDeprecatedAnswer + { +public: + inline TDeprecatedAnswer() {} + inline TDeprecatedAnswer(TUint8 aStep, TUint8 aIndex) : iStep(aStep), iIndex(aIndex) {} + + TUint8 iStep; //< query step # (selects session) + TUint8 iIndex; //< answer index within the query reply + }; + +/** +The real implementation of the MDndResolver. + +Function: + +@li Accepts the request (see TDnsRequestBase) from the application for name resolving. +@li Tries to resolve the request from host files or DNS sources +@li Once the request is over (response obtained or time out), + it sends back the response (again TDnsRequestBase) and/or error code + to the application which requested it. + +See also \ref resolvers + +This class is designed so that it can be reused for serving +different clients. It has the following simplified state diagram: + +@verbatim + _________________________ + / reuse \ + V | + construct -> Start() ---> WaitQuery() --> Stop() --> destruct + / \ ^ + | | | + \ / / + QueryDone() ___/ + +@endverbatim +@li Start() + serving a specific RHostResolver session. This method is called + from CDndListener, after it has received a new query request + from the stack. +@li WaitQuery() + the resolver is waiting for next incoming request + from the RHostResolver. +@li QueryDone() + queue a result to be returned to the RHostResolver. +@li Stop() + serving the current rquest. This may be called either + internally or from CDndListener. +*/ +class CDndResolver : public CBase, public MDndResolver, public MDnsResolver + { + friend class TQuerySession; +public: + CDndResolver(const TInt aId, CDndEngine &aControl, MDndListener &aListener, + MDnsSource &aDnsclient); + + void Start(const TDnsMessageBuf &aMsg); // Starts the resolver + const TDesC8 &ReplyMessage(); // Return reference to the reply buffer. + TUint16 Session() const; // Return session id of the current query. + void Stop(); // Stop the resolver + virtual ~CDndResolver(); + + static inline TInt Max(const TInt a, const TInt b) { return a > b ? a : b;} + + const TTime &RequestTime() const { return iRequestTime; } + // only used for GetHostName + void ReplyCallback(const TInt aResult); + +private: + // Return a result to the application + void QueryDone(TInt aResult); + // Wait for the next query from the application + void WaitQuery(); + // Select next source for the name resolving (HOSTS, DNS, LLMNR) + void NextSource(); + // Cancel all activity and resources being used for the current source + void StopSource(); + TBool StartSource(); + // Return next result from HOSTS file source + void HOSTFILE_Next(); + +#ifdef _LOG + // Print out debugging information + void ShowQuery(const TDnsMessage &aName, TInt aReply); +#endif + + // Retrieve next answer, possibly starting retrieval of additional information from source + void GetNextAnswer(); + // Used to find alternate DNS servers (in some operating modes) + void ProbeServers(); +private: + const TInt iId; //< Resolver instance id (for debugging use) + TTime iRequestTime; //< Time at which the request was received from the application + + // The Query buffer for the reply + TDnsMessageBuf iBuffer; + // The current orignal query being processed. + TDnsMessageBuf iCurrentQuery; + + const TDnsServerScope *iSourceList; //< Information sources + TDnsServerScope iSourceNow; //< The current source + TUint iQueryStepMaxTime; //< Maximum time allowed for single query step to complete + TUint iQueryStepMinTime; //< Minimum time for query step (usage depends on mode) + TUint8 iQueryStepRetries; //< Additional retries after the primary attempt + TInt iSourceCount; //< Remaining number of sources. + TInt iNext; //< Non-zero, if executing Next operation + + // Specify Current Query state + + const SQueryStep *iQueryStep; //< The Current Query Process (what queries to make) + SQueryStep iSpecificQuery; //< Used in implementing the Specific Single Queries + TQuerySession iSession[KMaxQuerySessions]; //< Currently active queries or completed results + TDeprecatedAnswer iDeprecate[KMaxDeprecated]; //< Buffer to store the delayed answers + TUint iPhase; + TUint8 iDeprecatedTop; //< Number of delayed top answers in iDeprecate[] + TUint8 iDeprecatedBottom; //< Number of delayed bottom answers in iDeprecate[] + TUint8 iQuerySteps; //< Number of Query Steps in the process + TUint8 iStepCount; //< Number of Query Steps activated so far + TUint8 iSessionCount; //< Number of Query Sessions currently active + /* + // The scope level of the current query. + // + // Currently the level for GetByName and Query is always "NETWORK". + // But, if implemented, it could be "LINK LOCAL", based on the + // queried name, for example, if it ends with ".local". + // + // The level of GetByAddress is the scope level of the address being + // queried. + // + // The query scope limits the sources available to the query: the scope + // of the source must be less or equal to the query scope. (For example, + // GetByAddress for a link local address is only asked from link local + // name resolvers). + */ + TUint8 iQueryScope; + TUint iQueryActive:1; //< set to 1, when a query is being processed + TUint iQueryIsAddr:1; //< set to 1, when current query is GetByAddr, = 0, otherwise + TUint iQueryIsSpecial:1; //< set to 1, when current query is special (for exact DNS query type) + TUint iReadyForAnswer:1; //< set to 1, when an answer can be delivered back to RHostResolver. + TUint iQueryDoneWait:1; //< Tells what active object is currently waiting: = 0, if WaitQuery, = 1, if QueryDone + TUint iQueryComplete:1; //< set to 1, when query is completed fully (no Next allowed) + TUint32 iQueryFlags; //< Flags to the NewQuery (KDnsMofidier_RD, ...) + + TQuerySession iProbe; //< Use for server probing + CDndEngine &iControl; //< The control context (for sending errors etc.) + MDndListener &iListener; //< Only for calling KeepRunning() + MDnsSource &iDnsclient; //< DNS protocol handler + }; + + +// CDndResolverBase::New +// ********************* +MDndResolver *MDndResolver::New(const TInt aId, CDndEngine &aControl, MDndListener &aListener, MDnsSource &aDnsClient) + /** + * Create a new instance of CDndResolver. + * + * This static function creates the real object instance of + * the class that has been derived from CDndResolverBase. + * + * This way, the real implementation of the class is hidden + * from any other module (the real class CDndResolver is only + * declared and known within resolver.cpp module). + * + * @param aId identifies the resolver instance (only for debuggind purpose) + * @param aControl provides access to the DND environment + * @param aListener + * provides access to the socket listener, which manages the + * pool of CDndResolver instances and delivers the requests + * to the resolvers. + * @param aDnsClient + * provides access to the DNS protocol driver, which implements + * the DNS protocol details, and manages the associated UDP and + * TCP sockets. + * @return + * @li NULL, if object could not be created (out of memory) + * @li non-NULL, is a pointer to the newly constructed CDndResolver instance + */ + { + return new(ELeave) CDndResolver(aId, aControl, aListener, aDnsClient); + } + +// Constructor +CDndResolver::CDndResolver(const TInt aId, CDndEngine &aControl, MDndListener &aListener, MDnsSource &aDnsclient) + : iId(aId), iControl(aControl), iListener(aListener), + iDnsclient(aDnsclient) + { + // Because array elements cannot have other than default constructors, + // need to initialize the iSession[] and iProbe here.. + for (TInt i = KMaxQuerySessions; --i >= 0; ) + { + iSession[i].Init(this); + LOG(iSession[i].iName.Format(_L("%d"), i)); + } + iProbe.Init(this); + LOG(iProbe.iName = _L("*")); + } + +// Destructor +CDndResolver::~CDndResolver() + { + // Need to cancel TQuerySession's (if any active) + for (TInt i = KMaxQuerySessions; --i >= 0; ) + iSession[i].Close(); + } + + +#ifdef _LOG +// CDndResolver::ShowQuery(aName) +// ****************************** +void CDndResolver::ShowQuery(const TDnsMessage &aPacket, TInt aReply) + /** + * Displays the current query or reply (only a debugging tool). + * + * @param aPacket Query or Reply data + * @param aReply Query vs. reply + */ + { + _LIT(KReply, " Reply "); + _LIT(KQuery, " Query "); + + const TDesC &mode = aReply ? KReply : KQuery; + + switch (aPacket.iType) + { + case KDnsRequestType_GetByName: + case KDnsRequestType_GetByAddress: + { + const TNameRecord &rec = aPacket.NameRecord(); + TBuf<50> tmp; + TInetAddr::Cast(rec.iAddr).OutputWithScope(tmp); + if (aReply) + + Log::Printf(_L("\tresolver[%d] SESSION %d Reply [%d], %S, [%S] result=%d"), iId, aPacket.iSession, rec.iFlags, &rec.iName, &tmp, aPacket.iNext); + else + Log::Printf(_L("\tresolver[%d] SESSION %d Query (%d), %S, [%S]"), iId, aPacket.iSession, aPacket.iNext, &rec.iName, &tmp); + } + break; + case KDnsRequestType_TDnsQuery: + { + if (aReply) + { + const TDndReply &reply = aPacket.Reply(); + Log::Printf(_L("\tresolver[%d] SESSION %d Special Reply, type=%d result=%d"), iId, aPacket.iSession, (TInt)reply.RRType(), aPacket.iNext); + } + else + { + const TDnsQuery &query = aPacket.Query(); + Log::Printf(_L8("\tresolver[%d] SESSION %d Special Query (%d), %S type=%d"), iId, aPacket.iSession, aPacket.iNext, &query.Data(), (TInt)query.Type()); + } + } + break; + case KDnsRequestType_SetHostName: + Log::Printf(_L("\tresolver[%d] SESSION %d SetHostName(%S)%S%d"), iId, aPacket.iSession, &aPacket.HostName(), &mode, aPacket.iNext); + break; + case KDnsRequestType_GetHostName: + Log::Printf(_L("\tresolver[%d] SESSION %d GetHostName(%S)%S%d"), iId, aPacket.iSession, &aPacket.HostName(), &mode, aPacket.iNext); + break; + default: + Log::Printf(_L("\tresolver[%d] SESSION %d Bad TDnsMessagePacket%S(%d,%d)"), iId, aPacket.iSession, &mode, aPacket.iType, aPacket.iNext); + break; + } + } +#endif + +// CDndResolver::Stop +// ****************** +void CDndResolver::Stop() + /** + * Stop serving the current request. + * + * Terminate all pending activity (if any) and mark the + * CDndResolver instace available for a new session. + */ + { + iReadyForAnswer = 0; // ...just to be safe. + StopSource(); // stop all DNS activity related to this client, if any + if (iQueryActive) + { + iQueryActive = 0; + iDnsclient.QueryEnd(); + } + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d Stopped"), iId, iCurrentQuery().iSession)); + iCurrentQuery().iSession = 0; + iQueryDoneWait = 0; + } + + +// CDndResolver::WaitQuery +// *********************** +void CDndResolver::WaitQuery() + /** + * Put the CDndResolver to wait a next request from the RHostResolver. + * + * The next request could be a Next, or a new GetByName, GetByAddr or + * Query. + */ + { + iReadyForAnswer = 0; + if (iQueryActive) + { + // There could be active sessions + // doing some queries while we wait for + // client to request something (doing + // parallel queries). + // Leave "query active" on, if any found. + // The information is retrieved during this + // time is/will be available only for + // the Next() query. + for (TInt i = 0;; ++i) + { + if (i == iSessionCount) + { + iQueryActive = 0; + iDnsclient.QueryEnd(); + break; + } + if (iSession[i].iStatus == KRequestPending) + break; + }; + } + iQueryDoneWait = 0; + } + +// CDndResolver::QueryDone +// *********************** +void CDndResolver::QueryDone(TInt aResult) + /** + * A query has completed, return a result to the RHostResolver. + * + * Prepare for the reply into iBuffer for writing to the + * "gateway socket". When the reply sender of the listener + * is ready to start the transfer, it calls ReplyMessage() + * for the buffer to be sent. + * + * @param aResult + * the result code of the query. If KErrNone, the buffer + * will contain one answer (TNameRecord or some + * TDnsQryRespBase variant). + * + * If the reply is an error (< 0), this stops all resolving + * activity that might be still going on. An error reply is + * always final, continuation with a Next() is not possible. + */ + { + if (!iReadyForAnswer) + return; // Gateway is not expecting answer now... + iReadyForAnswer = 0; + iQueryDoneWait = 1; + iBuffer().iNext = aResult; + if (aResult < 0) + { + StopSource(); + iBuffer.SetLength(iBuffer().HeaderSize()); + } + LOG(ShowQuery(iBuffer(), 1)); + iListener.PostReply(); + } + +// CDndResolver::ReplyMessage +// ************************** +const TDesC8 &CDndResolver::ReplyMessage() + /** + * Return the reply message buffer. + * + * Called by listener sender when it is ready to send the + * reply message to the gateway socket. If this resolver + * has a reply to send, this must return the buffer. + * If no reply is to be sent for this resolver, this must + * return an empty buffer reference. + * + * @return The reply buffer (empty = no reply to send). + */ + { + _LIT8(KNoReply, ""); + if (iQueryDoneWait) + { + const TInt result = iBuffer().iNext; + // Only set iQueryComplete, *never* clear it, if already set! + if (result != KErrCompletion && result < 0) + { + iQueryComplete = 1; + } + iQueryDoneWait = 0; + return iBuffer; + } + else + return KNoReply; + } + +// CDndResolver::Session +// ********************* +TUint16 CDndResolver::Session() const + /** + * Return the current session id. + * + * This function is used by the listener to find out the + * session id of the request that is currently being + * processed by this resolver. + * + * If the resolver is idle (no request under process), then + * this must return 0. All valid session ids are non-zero. + * + * @return The session (or 0, if none). + */ + { + return iCurrentQuery().iSession; + } + +void CDndResolver::GetNextAnswer() + /** + * Collect, sort and return answers to the application. + * + * Collecting answers from DNS. The answers are sorted into + * three classes: + * + * @li (1) + * usable addresses (current system has a route and source address for them) + * @li (2) + * addresses, for which no route exists are put into "TOP" category (if application + * uses one of these, then most likely, a new interface needs to be activated). These + * are returned after all usable addresses in class (1) have been returned.. + * @li (3) + * other (non-address) answers to the query + * + * If a GetByName request needs two queries (for example A and AAAA), + * the queries may be performed in parallel or one after the other. + * If queries are performed one at time, this method will automaticly + * start the second query, after the answers (if any) from the first query + * have been processed. + * + * If a query produces any answer belonging to the class (1), they are + * returned to the application as soon as they become available. + * If queries are not done in parallel, and application is satisfied + * with the first answer (does not issue Next()), then the second + * query is never sent. + * + * If a query produces no answers that sort into class (1), both queries + * are always executed fully, before any answers are returned (from class + * (2) or (3)) to the application. + */ + { + if (!iReadyForAnswer) + return; // Gateway not expecting answer now... + + iBuffer = iCurrentQuery; // Initialize reply to orginal query + TInt pending = 0; + TInt notfound = 0; + + // Initialize query buffer flags. + iBuffer().NameRecord().iFlags = 0; + + if (iPhase == 0) + { + // + // Collect Phase + // + for (TInt i = 0; i < iSessionCount; ++i) + { + TQuerySession &session = iSession[i]; + + if (session.iStatus < 0) + { + // Either pending or no results available (all processed or some error state) + // Detect pending queries (only possible to happen if doing queries in parallel) + pending |= (session.iStatus == KRequestPending); + notfound |= (session.iStatus == KErrDndBadName); + continue; + } + for (;;) + { + // + // Values iSession[i].iStatus >= 0 [0..n] is used for remembering + // the number of the next answer to retrieve from the record + const TInt ret = session.iSession->DoNext(iBuffer, session.iStatus); + if (ret != KErrNone) + { + // All available answers have been retrieved. + session.iStatus = ret; + break; + } + // NOTE: session.iStatus++!!! + const TDeprecatedAnswer answer((TUint8)(i), (TUint8)(session.iStatus++)); + + if (iQueryIsSpecial) + { + // All answers are returned as is for special query + // (no sorting or any other processing is done) + QueryDone(KErrNone); + return; + } + + const TInetAddr &addr = TInetAddr::Cast(iBuffer().NameRecord().iAddr); + if (addr.IsUnspecified() || (addr.Family() != KAfInet && addr.Family() != KAfInet6)) + { + // ...things like CNAME's fall into here. If running out of + // space, then just ignore these (Top entries are more important) + if (iDeprecatedTop < iDeprecatedBottom) + { + iDeprecate[--iDeprecatedBottom] = answer; + } + } + else if (iDnsclient.CheckAddress(addr) != KErrNone) + { + // There is no route for the address, don't return this yet + if (iDeprecatedTop < KMaxDeprecated) + { + iDeprecate[iDeprecatedTop++] = answer; + // If running out of space, overwrite deprecated bottom entries + // as needed... + if (iDeprecatedTop > iDeprecatedBottom) + iDeprecatedBottom = iDeprecatedTop; + } + } + else + { + // This is a usable address as is, return immediately + QueryDone(KErrNone); + return; + } + } + } + // + // RR records from the current query have been processed. + // + if (pending) + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d Request(s) pending"), iId, iCurrentQuery().iSession)); + // At least one query session is still in progress, continue waiting + // Should set some timeout? what? + return; + } + else if (!notfound && iStepCount < iQuerySteps) + { + // ... but there are alternate steps left, try the next step... + // + // If all DNS servers worked right, there should be no reason to probe + // servers with A, if the probe for AAAA fails. However, sadly, there + // broken things out there, which timeout on AAAA, but do reply to A. + // Thus, if probing is active, restart it for new query type. + if (iProbe.iSession) + (void)iProbe.iSession->PickDefaultServer(); + + TQuerySession &session = iSession[iSessionCount++]; + session.Open(); + if (session.Start(iQueryStep[iStepCount++].iQType)) + return; + } + } + // + // Return TOP deprecated + // + while (iPhase < iDeprecatedTop) + { + const MDnsSession *session = iSession[iDeprecate[iPhase].iStep].iSession; + const TInt ret = session->DoNext(iBuffer, iDeprecate[iPhase].iIndex); + // There is no route for the address, add info for application. + iBuffer().NameRecord().iFlags |= EDnsNoRoute; + iPhase++; + if (ret == KErrNone) + { + QueryDone(KErrNone); + return; + } + } + // + // Return BOTTOM deprecated + // (only for GetByAddr or for Next() after GetByName) + // + iPhase = KMaxDeprecated; + if (iQueryIsAddr || iNext) + { + while (iDeprecatedBottom < KMaxDeprecated) + { + const MDnsSession *session = iSession[iDeprecate[iDeprecatedBottom].iStep].iSession; + const TInt ret = session->DoNext(iBuffer, iDeprecate[iDeprecatedBottom].iIndex); + iDeprecatedBottom++; + if (ret == KErrNone) + { + QueryDone(KErrNone); + return; + } + } + } + if (iNext) + QueryDone(iQueryIsAddr ? KErrDndAddrNotFound : KErrDndNameNotFound); + else + // No answers returned from current source, try next + NextSource(); + } + +// CDndResolver::ProbeServers +// ************************** +void CDndResolver::ProbeServers() + /** + * Probing servers. + * + * Send a dummy duplicate query to the next alternate + * server. Used in some resolver modes. The result is ignored + * by resolver, but the side effect is that the DNS protocol + * handler will detect a good server, and make it the default + * for the next try. + */ + { + if (iSessionCount == 0) + return; + if (iProbe.iSession == NULL) + { + // Use the first primary session (iSession[0]) as a template + // for the "probing". + if (iSession[0].iSession == NULL) + return; // ...oops!? + iProbe.iQType = iSession[0].iQType; + iProbe.iSession = iDnsclient.OpenSession(&iProbe); + if (iProbe.iSession == NULL) + return; + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d Probing QType=%d (%S) -- Assigned DNS session [%u]"), + iId, (TInt)iCurrentQuery().iSession, (TInt)iProbe.iQType, &iProbe.iName, (TInt)iProbe.iSession->Instance())); + // Use the current query as a probe starting point + //(void)iProbe.iSession->NewQuery(*iSession[0].iSession); + (void)iProbe.iSession->NewQuery(iCurrentQuery(),iSourceNow,iQueryFlags | KDnsModifier_PQ); + + // initialize server (which is not tried) + (void)iProbe.iSession->PickDefaultServer(); + } + // Start query, but only if there really is another server address + if (iProbe.iSession->PickNewServer()) + { + // Note: if DoQueryL fails, we do nothing. This is just probing... + TRAP_IGNORE(iProbe.iSession->DoQueryL(iRequestTime, iProbe.iQType)); + } + } + + +// CDndResolver::StopSource +// ************************ +void CDndResolver::StopSource() + /** + * Stop all resolving activity from current source. + * + * The answer can be searched from different sources (such as hosts file, + * global DNS or link local name resolution). Answers from different sources are + * never mixed, and the first source that gives an answer is used. + * + * StopSource() cancels all activity (if any) from current source. + */ + { + // Release sessions, if they were created + while (iSessionCount > 0) + iSession[--iSessionCount].Close(); + iProbe.Close(); + } + + +// CDndResolver::NextSource +// ************************ +void CDndResolver::NextSource() + /** + * Activate search from next source. + * + * The answer can be searched from different sources (such as hosts file, + * global DNS or link local name resolution). Answers from different sources are + * never mixed, and the first source that gives an answer is used. + * + * NextSource activates the next query source or terminates the + * query, if all sources have been tried. + */ + { + + while (iSourceCount > 0) + { + StopSource(); // Cleanup from previous source, if any + + // Replies from different source are never mixed + // Restart collecting answers too. + iPhase = 0; + iDeprecatedTop = 0; + iDeprecatedBottom = KMaxDeprecated; + iSourceNow = *iSourceList++; + iSourceCount -= 1; + + // Is this source valid for the query scope (e.g. if source is global + // DNS, one should not try to query site or link local queries from it!) + // On the other hand, it is legal to make global scope query to link + // local or site scope servers. + const TUint level = (TUint)Abs((TInt)iSourceNow); + if (iQueryScope < level) + continue; // Query scope is less than servers scope, try another source! + + if (StartSource()) + return; + } + + // The name could not be resolved by any source + QueryDone(iQueryIsAddr ? KErrDndAddrNotFound : KErrDndNameNotFound); + } + + +// CDndResolver::StartSource +// ************************* +TBool CDndResolver::StartSource() + /** + * Initiate a source. + * + * Start/Activate retrieval of query answers from the current source, + * as defined by CDndResolver::iSourceNow. Three classes of + * sources are supported: + * + * @li HOST file (= 0) + * @li Unicast DNS server ( < 0) + * @li Multicast name resolution service ( > 0) + * + * The absolute value of iSourceNow is the scope + * level of the name resolution source (2 = link local, etc..) + * + * @returns + * @li TRUE, if process has been started (or answer already resolved). + * @li FALSE, if the source is not available or usable + */ + { + // If you define more complex queries, needing more than current max of + // steps, then bump up the KMaxQuerySessions! Panic on debug, on release + // just ignore the extra steps. + ASSERT(iQuerySteps <= KMaxQuerySessions); + if (iQuerySteps > KMaxQuerySessions) + iQuerySteps = KMaxQuerySessions; + + + const TDndConfigParameters &cf = iControl.GetConfig(); + + TUint query_order = cf.iQueryOrder; + + if (iSourceNow == 0) + { + // HOST file or hostname source + if (!iQueryIsSpecial) + { + // Check if the query is for local host name or my own address. + TInt res; + if (iQueryIsAddr) + res = iDnsclient.GetByAddress(iBuffer().iId, TInetAddr::Cast(iBuffer().NameRecord().iAddr), iNext, iBuffer().NameRecord().iName); + else + res = iDnsclient.GetByName(iBuffer().iId, iBuffer().NameRecord().iName, iNext, TInetAddr::Cast(iBuffer().NameRecord().iAddr)); + if (res == KErrNone) + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d matched local hostname/address"), iId, iCurrentQuery().iSession)); + iBuffer().NameRecord().iFlags = EDnsHostName; + QueryDone(KErrNone); + return TRUE; + } + // ..and now the HOSTS file + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d SEARCHING hosts file"), iId, iCurrentQuery().iSession)); + TRequestStatus tmp; + if (iQueryIsAddr) + iControl.iHostsFile.GetByAddress(iBuffer().NameRecord(), tmp); + else + iControl.iHostsFile.GetByName(iBuffer().NameRecord(), tmp); + if (tmp.Int() == KErrNone) + { + iBuffer().NameRecord().iFlags = EDnsHostsFile; + QueryDone(KErrNone); + return TRUE; + } + } + + if (iBuffer().iId == 0) + { + // If there is no scope is specified, then only hosts file + // is looked. + QueryDone(KErrCompletion); + return TRUE; + } + // No Answer from the host file, move on to the next + return FALSE; + } + else if (iSourceNow < 0) + { + // + // Unicast DNS source + // + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d SEARCHING unicast DNS"), iId, iCurrentQuery().iSession)); + // Setup parameters for basic query step (steps are done one after another) + iQueryStepMaxTime = Max(cf.iMinTime, cf.iMaxTime / iQuerySteps); + iQueryStepMinTime = cf.iMinTime; + iQueryStepRetries = (TUint8)cf.iRetries; + } + else + { + // + // Multicast DNS source + // + // Setup parameters for basic query step + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d SEARCHING multicast DNS"), iId, iCurrentQuery().iSession)); + query_order |= KQueryOrder_Parallel; // Multicast steps are always done in parallel +#ifdef LLMNR_ENABLED + // Assumed: "multicast DNS" == LLMNR! + iQueryStepMaxTime = Max(cf.iLlmnrMinTime, cf.iLlmnrMaxTime); + iQueryStepMinTime = cf.iLlmnrMinTime; + iQueryStepRetries = (TUint8)cf.iLlmnrRetries; +#else + iQueryStepMaxTime = Max(cf.iMinTime, cf.iMaxTime); + iQueryStepMinTime = cf.iMinTime; + iQueryStepRetries = (TUint8)cf.iRetries; +#endif + } + // + // Start the query/queries + // + TBool started = FALSE; + iSessionCount = 0; + iStepCount = 0; + while (iStepCount < iQuerySteps) + { + TQuerySession &session = iSession[iSessionCount++]; + session.Open(); + if (session.iSession == NULL) + { + // If the required session cannot be allocated, + // abort the request totally. + QueryDone(session.iStatus); + return TRUE; + } + started |= session.Start(iQueryStep[iStepCount++].iQType); + if (started && (query_order & KQueryOrder_Parallel) == 0) + break; + } + return started; + } + +// CDndResolver::HOSTFILE_Next +// *************************** +void CDndResolver::HOSTFILE_Next() + /** + * Return next answer from HOST file. + * + * The HOST file answers are not currently sorted in any way. The + * answers are returned as is, in whatever order the host file + * module returns them. + * + * Only TNameRecord based queries are asked from the HOST file + * (e.g. the standart RHostResolver::GetByName and RHostResolver::GetByAddr). + * No special queries. + */ + { + // Should never get called with special queries, but if that + // happens, just return KErrEof as a safety measure... + TRequestStatus tmp(KErrEof); + if (!iQueryIsSpecial) + { + // Temp. kludge: next indicator for hosts file reader is passed in iFlags! + iBuffer().NameRecord().iFlags = iNext; + if (iQueryIsAddr) + iControl.iHostsFile.GetByAddress(iBuffer().NameRecord(), tmp); + else + iControl.iHostsFile.GetByName(iBuffer().NameRecord(), tmp); + // Note: iHostFile returns the result code in TRequestStatus. + iBuffer().NameRecord().iFlags = EDnsHostsFile; + } + QueryDone(tmp.Int()); + } + +// CDndResolver::Start +// ******************* +void CDndResolver::Start(const TDnsMessageBuf &aMsg) + /** + * Process a new request from listener/RHostResolver. + * + * @param aMsg The request message + */ + { + const TDndConfigParameters &cf = iControl.GetConfig(); + + // Sanity checks -- determine the min size of a proper request + const TInt min_size = aMsg().HeaderSize() + + TInt(aMsg().iType == KDnsRequestType_TDnsQuery ? sizeof(TDnsQuery) : + aMsg().iType == KDnsRequestType_GetByName ? sizeof(TNameRecord) : + aMsg().iType == KDnsRequestType_GetByAddress ? sizeof(TNameRecord) : + sizeof(THostName)); + if (aMsg.Length() < min_size) // If short query => treat as cancel! + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d Cancelled by client (len=%d)"), iId, iCurrentQuery().iSession, aMsg.Length())); + Stop(); + return; + } + + // Stack should never start new request before delivering the response. + // Exception is the Cancel-request which is treated above. + ASSERT(iQueryDoneWait == 0); + + iBuffer = aMsg; + // + // A new command/query from the application + // + LOG(ShowQuery(iBuffer(), 0)); + + // If query is not active, it will be after this + if (iQueryActive == 0) + { + iQueryActive = 1; + iDnsclient.QueryBegin(); + } + + iReadyForAnswer = 1; // an answer can now be returned... + iRequestTime.UniversalTime(); // Set the time of request + + iNext = iBuffer().iNext; + if (iNext) + { + // This is Next() processing, retrieving + // the additional records. + if (iQueryComplete) + { + // An application/something is stubbornly calling Next() after + // receiving an error (query complete) or the query was "one shot" + // that returned "KErrNone", and was complete at that (no Next). + // This error should terminate the Next() loop in app! + QueryDone(KErrNotFound); + return; + } + iBuffer = iCurrentQuery; // Reset to orginal query (Next() cannot change the query) + if (iSourceNow == 0) + HOSTFILE_Next(); + else + GetNextAnswer(); + } + else + { + // + // Start processing of new query, not Next() + // + iQueryComplete = 0; + iCurrentQuery = iBuffer; // Remember current original query + iQueryFlags = KDnsModifier_RD; // By default, request recursive querying from the server(s). + StopSource(); + switch (iBuffer().iType) + { + case KDnsRequestType_GetByName: + case KDnsRequestType_GetByAddress: + { + TNameRecord &rec = iCurrentQuery().NameRecord(); + + if (iCurrentQuery().iType == KDnsRequestType_GetByAddress) + { + iQueryIsAddr = 1; + // The scope of GetByAddress query is determined from the scope level + // of the address being queried. + iQueryScope = (TUint8)TInetAddr::Cast(rec.iAddr).Ip6Address().Scope(); + iQueryIsSpecial = 0; + iQueryStep = KGetByAddress; + iQuerySteps = sizeof(KGetByAddress) / sizeof(KGetByAddress[0]); + } + else + { + iQueryIsAddr = 0; + // All getbyname queries are global for now. As a future feature + // It might be considered, that single label or names ending with + // ".local" would have have local scope -- msa + iQueryScope = KIp6AddrScopeNetwork; + +#ifdef DND_DCM_EXTENSION + iQueryIsSpecial = 0; + TInt i; + while ((i = rec.iName.Locate('?')) >= 0) + { + const TPtrC prefix = rec.iName.Left(i+1); + if (prefix.Compare(KDnsNonrecursive) == 0) + { + iQueryFlags &= ~KDnsModifier_RD; // Reset recursive flag. + } + else if (cf.iQueryHack) + { + // Special Query + iQueryIsSpecial = 1; + TInt q = sizeof(KQueryExtension) / sizeof(KQueryExtension[0]); + while (--q >= 0 && prefix.Compare(*KQueryExtension[q].iPrefix) != 0) + /* NOTHING */; + if (q < 0) + { + // Requested query is not supported + QueryDone(KErrNotSupported); + return; + } + iQueryStep = &KQueryExtension[q].iQuery; + iQuerySteps = 1; + } + else + { + QueryDone(KErrNotSupported); + return; + } + rec.iName.Delete(0,i+1); + } + + if (!iQueryIsSpecial) + { + // Standard GetByName + if ((cf.iQueryOrder & KQueryOrder_PreferA) == 0) + { + iQueryStep = KGetByName1; + iQuerySteps = sizeof(KGetByName1) / sizeof(KGetByName1[0]); + } + else + { + iQueryStep = KGetByName2; + iQuerySteps = sizeof(KGetByName2) / sizeof(KGetByName2[0]); + } + // If query is to be limited to only one (either A or AAAA), do + // the preferred query only (the first in the chosen list). + if (cf.iQueryOrder & KQueryOrder_OneQuery) + iQuerySteps = 1; + } +#else + const TInt i = rec.iName.Locate('?'); + + if (i < 0) + { + // Standard GetByName + iQueryIsSpecial = 0; + if ((cf.iQueryOrder & KQueryOrder_PreferA) == 0) + { + iQueryStep = KGetByName1; + iQuerySteps = sizeof(KGetByName1) / sizeof(KGetByName1[0]); + } + else + { + iQueryStep = KGetByName2; + iQuerySteps = sizeof(KGetByName2) / sizeof(KGetByName2[0]); + } + // If query is to be limited to only one (either A or AAAA), do + // the preferred query only (the first in the chosen list). + if (cf.iQueryOrder & KQueryOrder_OneQuery) + iQuerySteps = 1; + } + else if (cf.iQueryHack) + { + // Special Query + iQueryIsSpecial = 1; + const TPtrC prefix = rec.iName.Left(i+1); + TInt q = sizeof(KQueryExtension) / sizeof(KQueryExtension[0]); + while (--q >= 0 && prefix.Compare(*KQueryExtension[q].iPrefix) != 0) + /* NOTHING */; + rec.iName.Delete(0,i+1); + if (q < 0) + { + // Requested query is not supported + QueryDone(KErrNotSupported); + return; + } + iQueryStep = &KQueryExtension[q].iQuery; + iQuerySteps = 1; + } + else + { + QueryDone(KErrNotSupported); + return; + } +#endif + } + } + break; + case KDnsRequestType_TDnsQuery: + // Special Query + iQueryIsSpecial = 1; + iQueryIsAddr = 0; + iQueryScope = KIp6AddrScopeNetwork; + iSpecificQuery.iQType = EDnsQType(iBuffer().Query().Type()); + iQueryStep = &iSpecificQuery; + iQuerySteps = 1; +#ifdef DND_DCM_EXTENSION + if (iBuffer().Query().Type() == KDnsQTypeCacheClear) + { + TRAPD(err, iListener.HandleCommandL(EDndFlush)); + iQueryComplete = 1; // "Next()" is not allowed! + QueryDone(err); + return; + } +#endif + break; + case KDnsRequestType_SetHostName: + iQueryComplete = 1; // "Next()" is not allowed (only needed for ret == KErrNone case) + QueryDone(iDnsclient.SetHostName(iBuffer().iId, iBuffer().HostName())); + return; + case KDnsRequestType_GetHostName: + { + const TInt ret = iDnsclient.GetHostName(iBuffer().iId, iBuffer().HostName(), *this); + if (ret <= 0) + { + iQueryComplete = 1; // "Next()" is not allowed (only needed for ret == KErrNone case) + QueryDone(ret); // GetHostName completed, we are done! + } + // GetHostName is pending on uniquenes test (ret == 1) + return; + } + default: + QueryDone(KErrNotSupported); + return; + } + // + // Setup NS source (now fixed, make configurable later) + // + iSourceList = KSourceOrder; + iSourceCount = sizeof(KSourceOrder) / sizeof(KSourceOrder[0]); + NextSource(); + } + } + +// Only used for GetHostName +void CDndResolver::ReplyCallback(const TInt aResult) + { + iBuffer = iCurrentQuery; + QueryDone(aResult); + } + +// +// + +TQuerySession::TQuerySession() : iTimeout(TQuerySessionTimeoutLinkage::Timeout) + { + } + +// TQuerySession::Init +// ******************* +void TQuerySession::Init(CDndResolver *const aResolver) + /** + * Initialize structure. + * + * A separate Init is required, because arrays cannot have non-defaulf + * constructors. Need to patch the iResolver link "manually". + */ + { + iResolver = aResolver; + // ...for other members, the default constructor is sufficient + } + + +// TQuerySession::Open +// ******************* +void TQuerySession::Open() + /** + * Activate a query session. + * + * Assume CDndResolver::iCurrentQuery holds the request from the client. + * Establish a session with the DNS protocol provides and initialize + * it with the current question (NewQuery). + */ + { + iSession = iResolver->iDnsclient.OpenSession(this); + if (iSession == NULL) + { + // Cannot allocate a session + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- OUT OF DNS SESSIONS!"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + iStatus = KErrNoMemory; + } + else + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- Assigned DNS session [%u]"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, (TInt)iSession->Instance())); + // ..if NewQuery returns an error, it will be the result of the query. Otherwise + // use the KErrEof as initial value. + iStatus = iSession->NewQuery(iResolver->iCurrentQuery(), iResolver->iSourceNow, iResolver->iQueryFlags); + if (iStatus == KErrNone) + iStatus = KErrEof; // No content yet. +#ifdef SYMBIAN_DNS_PUNYCODE + else // in case the Query String is UTF-16 encoded + { + iResolver->QueryDone(iStatus); + } +#endif //SYMBIAN_DNS_PUNYCODE + } + } + +// TQuerySession::SetTimer +// *********************** +void TQuerySession::SetTimer(TUint aTimeout) + /** + * Request a Timeout() call after the specied number of seconds. + */ + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- set timeout = %ds"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, (TInt)aTimeout)); + iResolver->iControl.Timer().Set(iTimeout, aTimeout); + } + +// TQuerySession::Start +// ******************** +TBool TQuerySession::Start(EDnsQType aQType) + /** + * Start the query process for a specific query type. + * + * @param aQType The query type (A, AAAA, MX, etc.) + */ + { + iQType = aQType; + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- Start"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + iServerCount = 1; + iQueryRetry = 0; + iQueryTimeouts = 0; + + if (!iSession) + return FALSE; + + iStatus = KErrEof; + + if (iSession->PickDefaultServer()) + { + SendDnsQuery(); + return TRUE; + } + return FALSE; + } + +// TQuerySession::Cancel +// ********************* +void TQuerySession::Cancel() + /** + * Cancel and stop all activity (if any) with this session. + * + * This cancels set timers and any DNS queries that are in + * progress. + */ + { + iTimeout.Cancel(); + + if (!iSession) + return; + + iSession->CancelQuery(); + if (iStatus == KRequestPending) + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- CANCELED"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + iStatus = KErrCancel; + } + } + + +// TQuerySession::Close +// ******************** +void TQuerySession::Close() + /** + * Close session. + * + * First calls Cancel() to call all activity, and then also + * closes the session with DNS protocol provider. + */ + { + iPendingQuery = 0; // Cancel pending queries too! + Cancel(); + if (!iSession) + return; + iSession->Close(); + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- DNS [%u] closed"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, (TInt)iSession)); + iSession = NULL; + } + + +// TQuerySession::RecvReply +// *********************** +void TQuerySession::ReplyCallback(TInt aErr) + /** + * Callback from DNS client. + * + * DNS client has completed something. + * + * @param aErr The completion result or some notify. + */ + { + if (iStatus != KRequestPending) + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- RecvReply(%d) Ignore event"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, aErr)); + return; // Unexpected event, ignore! + } + + if (aErr > 0) + { + // Notify Messages, just ignore if not recognised. + if (aErr == KDnsNotify_QUERY_SENT) + { + if (iWaitingQuerySent) + { + iWaitingQuerySent = 0; // got it... + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- RecvReply(%d) Query Sent"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, aErr)); + SetTimer(iQueryTimeoutValue); + } + } + else if (aErr == KDnsNotify_HAVE_SERVERLIST) + { + // Currently does nothing with this. + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- RecvReply(%d) Has serverlist"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, aErr)); + } + else if (aErr == KDnsNotify_USING_TCP) + { + iQueryRetry = iResolver->iQueryStepRetries; + iQueryTimeoutValue = iResolver->iQueryStepMaxTime; + iWaitingQuerySent = 0; + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- RecvReply(%d) Query using TCP, disable retransmit"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, aErr)); + SetTimer(iQueryTimeoutValue); + } + } + else if (aErr != KErrNone && (aErr > KErrDndNameNotFound || aErr < KErrDndServerUnusable)) + { + // The error is not query specific DND error code, assume it is + // something general and abort the query totally. + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- RecvReply(%d) Query completed"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, aErr)); + iResolver->QueryDone(aErr); + } + else + { + iStatus = aErr; + iTimeout.Cancel(); + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- RecvReply(%d) Handle event"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, aErr)); + Event(FALSE); + } + } + +// TQuerySession::Timeout +// ********************** +void TQuerySession::Timeout(const TTime &aNow) + /** + * No response from the DNS handler within specified time, handle timeout. + * + * @param aNow The current time + */ + { + LOG(Log::Printf(_L("-->\tresolver[%d] SESSION %d QType=%d (%S) -- timeout start"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + if (iWaitingQuerySent) + { + // + // Don't wait forever, abort resolving if max setup time exceeded + // + const TDndConfigParameters &cf = iResolver->iControl.GetConfig(); + TTimeIntervalSeconds seconds; + if (aNow.SecondsFrom(iResolver->iRequestTime, seconds) != KErrNone || + seconds.Int() > STATIC_CAST(TInt, cf.iSetupTime)) + { + iStatus = KErrDndServerUnusable; + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- Timeout, max wait expired [%d > %d]: %d"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, seconds.Int(), cf.iSetupTime, iStatus)); + } + else + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- Timeout, has waited %ds [max %d]"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName, seconds.Int(), cf.iSetupTime)); + } + } + else + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- timeout"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + } + // Note that Timeout alone does not touch the iStatus, nor does it + // cancel the request, if any is active. It's up to Event to decide + // on these issues. + Event(TRUE); + LOG(Log::Printf(_L("<--\tresolver[%d] SESSION %d QType=%d (%S) -- timeout exit"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + } + + +// TQuerySession::Event +// ******************** +void TQuerySession::Event(TBool aTimeOut) + /** + * Shared handling of session events: timeouts and DNS query completions. + * + * @param aTimeOut is TRUE, if this is a timeout event; and FALSE, if + * event caused by DNS completion. + */ + { + const TDndConfigParameters &cf = iResolver->iControl.GetConfig(); + + // Handle events from DNS or timeout + // ********************************* + // TIMEOUT or REPLY to a pending query has been received + // + const TBool server_unusable = (iStatus == KErrDndRefused || iStatus == KErrDndServerUnusable); + if (!aTimeOut && !server_unusable) + { + // This query has been completed with some definite result, notify + // the main resolver to handle the reply. + // + iResolver->GetNextAnswer(); + return; + } + + + if (iWaitingQuerySent && !server_unusable) + { + // Still waiting for the first query packet to be sent. Request a + // resend attempt of the same query again (without counting it as + // retry). This is a wait for getting interfaces up (normal timeouts + // do not apply yet). + + // Reload the query too (this is only sometimes needed with PTR + // queries if the filter iLockId cannot be determined due to + // missing interfaces. And only happens if query address was + // without scope id. + (void)iSession->NewQuery(iResolver->iCurrentQuery(), iResolver->iSourceNow, iResolver->iQueryFlags); + SendDnsQuery(); + return; + } + + if (aTimeOut) + iQueryTimeouts++; // Timeout occurred while resolving the DNS Query + + + // Careful with iStatus: if timeout occurs, it is probably KRequestPending. + // Needs to be changed to something else, if GetNextAnswer is called (otherwise + // it will think that there are pending query and may do nothing!) + // + if (cf.iSprayMode || iResolver->iSourceNow > 0 /* == multicast resolution mode */) + { + if (iWaitingAfterSpray) + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- Delay after spray completed"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + // + // The delay after "spray" has expired, now start a new round (a retry) + // (unless maximum retries has been reached) + // + iWaitingAfterSpray = 0; + iQueryTimeouts = 0; + (void)iSession->PickDefaultServer(); + iStatus = KErrTimedOut; + iServerCount = 1; + ++iQueryRetry; + if (iQueryRetry > iResolver->iQueryStepRetries) + iResolver->GetNextAnswer(); + else + SendDnsQuery(); + } + else if (iSession->PickNewServer()) + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- More servers to spray"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + // + // Yet another server to spray, replicate query + // + iWaitingAfterSpray = 0; + ++iServerCount; + SendDnsQuery(); // Replicate query to another server + } + else if (iQueryTimeouts > 0) + { + // All servers have been sprayed and at least one of them + // has not answered, now wait maxtime - (servers * mintime), + // before starting the next spraying round + // + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- All servers sprayed, now wait results"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + iWaitingAfterSpray = 1; + const TInt maxtime = Max(iResolver->iQueryStepMinTime, iResolver->iQueryStepMaxTime / (iResolver->iQueryStepRetries+1)); + SetTimer(Max(iResolver->iQueryStepMinTime, maxtime - iResolver->iQueryStepMinTime * iServerCount)); + } + else + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- All servers replied not found"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + // All servers sprayed, and none of the short spray timeouts expired + // (make a heuristic GUESS, that all servers replied, but all were either + // unusable or "skip not found" is active. Just goto to the next step or + // return not found. + iResolver->GetNextAnswer(); + } + } + else + { + ++iQueryRetry; // This is a true retry + if (iQueryRetry > iResolver->iQueryStepRetries || + (server_unusable && !iSession->PickNewServer())) + { + iStatus = KErrTimedOut; + iResolver->GetNextAnswer();// Activates next query step or returns not found. + } + else + { + iResolver->ProbeServers(); // ...keep probing for alternate servers in parallel (at each timeout) + SendDnsQuery(); + } + } + } + + +// TQuerySession::SendDnsQuery +// *************************** +void TQuerySession::SendDnsQuery() + /** + * Activate a new DNS query to be sent. + */ + { + if (iSendingQuery) + { + // Doing everyhing in callbacks forces extra work + // to be done to prevent deep recursion... + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- NEW QUERY PENDING"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + + iPendingQuery = 1; + return; + } + // Create an UDP message to the DNS + do + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- SENDING DNS QUERY"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + iStatus = KRequestPending; + iSendingQuery = 1; // ..to detect dangerous recursion + iPendingQuery = 0; // ..to detect that a new query should be sent + iWaitingQuerySent = 1; // Mark: waiting for QUERY_SENT + TRAPD(err, (void)iSession->DoQueryL(iResolver->iRequestTime, iQType)); + iSendingQuery = 0; + if (err != KErrNone) + { + // Assume some serious error situation and forget about + // possible pending query, cancel everything with error. + iResolver->QueryDone(err); + return; + } + } + while (iPendingQuery); + + if (iStatus == KRequestPending) + { + const TDndConfigParameters &cf = iResolver->iControl.GetConfig(); + const TUint maxtime = iResolver->iQueryStepMaxTime; + + // Compute the timeout for the next query (starts ticking + // when the packet has been sent (after QUERY_SENT) + // + if (cf.iSprayMode || iResolver->iSourceNow > 0) + iQueryTimeoutValue = iResolver->iQueryStepMinTime; // Send to all servers using mintime + else if (cf.iRetries == 0) + iQueryTimeoutValue = maxtime; // No retries, just single query + else if (iQueryRetry == 0) + iQueryTimeoutValue = iResolver->iQueryStepMinTime; // This is the first send -- use min time + else + { + // This a retransmission (divide the remaining time even with the retries) + iQueryTimeoutValue = Max(iResolver->iQueryStepMinTime, (maxtime - iResolver->iQueryStepMinTime) / iResolver->iQueryStepRetries); + } + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- WAITING FOR SEND"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + // Setup "poll" timer, if the + // DNS side does not send the QUERY_SENT notification. + // (retry count is not incremented, so it will "poll" + // as long as notification/reply arrives or user cancels + // the resolving). + if (iWaitingQuerySent) + SetTimer(cf.iSetupPoll); + } + else + { + LOG(Log::Printf(_L("\tresolver[%d] SESSION %d QType=%d (%S) -- COMPLETED WITHIN"), + iResolver->iId, iResolver->iCurrentQuery().iSession, (TInt)iQType, &iName)); + } + }