// 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);
}