--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/tcpiputils/dnd/src/llmnrresponder.cpp Tue Jan 26 15:23:49 2010 +0200
@@ -0,0 +1,1081 @@
+// 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:
+// llmnrresponder.cpp - DND Link-local Multicast Name Resolution
+// responder module
+//
+
+#ifdef LLMNR_ENABLED
+#include <e32math.h>
+#include <in_iface.h>
+#include <in6_if.h>
+#include "engine.h"
+#include "llmnrresponder.h"
+#include "llmnrconflict.h"
+#include <networking/dnd_err.h>
+#include "serverconf.h"
+#include "inet6log.h"
+
+
+enum TEntryState
+ {
+ EInactive = 0, //< Entry is not yet available
+ EUnique = 1, //< Entry is unique and alive
+ EDisabled = -1, //< Entry is faulty, disabled
+ EConflict = -2 //< Entry has conflict with another host
+ };
+
+class CNotifyConflict : public CActive
+ /**
+ Exists while a conflict notifier process is active.
+ */
+ {
+private:
+ CNotifyConflict(CDndLlmnrResponder &aResponder, CLlmnrEntry &aEntry);
+ void DoStart();
+public:
+ ~CNotifyConflict();
+ static void Start(CDndLlmnrResponder &aResponder, CLlmnrEntry &aEntry);
+
+ void DoCancel();
+ void RunL();
+
+ CDndLlmnrResponder &iResponder;
+ CLlmnrEntry &iEntry;
+ TPckgBuf<TLlmnrConflictNotify> iInfo;
+ RNotifier iNotifier;
+ TUint iConnected:1;
+ };
+
+class CLlmnrEntry : public CBase
+ /**
+ Describes an answer (RR) that can be asked from this node.
+
+ An instance of this object is created for each unique answer
+ which this node is responsible for at this point of time.
+ Each entry describes an answer for a specific interface.
+ If the same name is defined for multiple interfaces,
+ then multiple instances are created
+ (one for each interface).
+
+ These entries are created and deleted dynamically, as
+ interfaces go up and down.
+ */
+ {
+public:
+ ~CLlmnrEntry();
+
+ CLlmnrEntry *iNext; //< Links the entry set together (head is CDndLlmnrResponder::iEntryList)
+ TDndQuestion iQuestion; //< The question that is answered by this entry
+ TInetScopeIds iZone; //< The scope vector of the related interface.
+ TIpVer iVersion:8; //< Which protocol: IPv4 or IPv6
+ TEntryState iState:8; //< State of this entry.
+ TUint iDown:1; //< Used in interface tracking
+ TUint iHostName:1; //< Set 1, if the name depends on the local hostname.
+ CNotifyConflict *iNotify; //< Non-NULL when conlict nofitication is in progress
+ };
+
+
+
+// TRawAddr
+// *********
+// Lightweight internal help class for handling the link layer addresses
+// (modified from ipadm engine.cpp)
+class TRawAddr : public TSockAddr
+ {
+public:
+
+ TInt Append(TDes8& aBuf) const
+ {
+ TUint8* p = (TUint8*)UserPtr();
+ TInt len = ((TSockAddr *)this)->GetUserLen();
+
+ if (len == 0)
+ return KErrNotSupported;
+ if (aBuf.MaxLength() < aBuf.Length() + len * 2)
+ return KErrNoMemory;
+
+ while (--len >= 0)
+ aBuf.AppendFormat(_L8("%02X"), *p++ & 0xFF);
+
+ return KErrNone;
+ }
+
+ inline static TRawAddr& Cast(const TSockAddr& aAddr)
+ {
+ return *((TRawAddr *)&aAddr);
+ }
+ };
+
+
+CLlmnrEntry::~CLlmnrEntry()
+ {
+ delete iNotify;
+ }
+
+
+CDndLlmnrResponder::CDndLlmnrResponder(CDndEngine &aControl,
+ MDnsServerManager &aServerManager,
+ THostNames &aHostNames)
+ : iControl(aControl),
+ iServerManager(aServerManager),
+ iHostNames(aHostNames)
+ {
+
+ // Need to initialize the request blocks with "parent" pointer
+ for (TUint i = 0; i < sizeof(iLlmnrDataList) / sizeof(iLlmnrDataList[0]); ++i)
+ iLlmnrDataList[i].iParent = this;
+ }
+
+CDndLlmnrResponder::~CDndLlmnrResponder()
+ {
+ // Make sure all requests are totally idle
+ for (TUint i = 0; i < sizeof(iLlmnrDataList) / sizeof(iLlmnrDataList[0]); ++i)
+ {
+ iLlmnrDataList[i].iTimeout.Cancel();
+ iLlmnrDataList[i].Cancel();
+ }
+ while (iEntryList)
+ {
+ CLlmnrEntry *p = iEntryList;
+ iEntryList = p->iNext;
+ delete p;
+ }
+ delete iLlmnrNotifyHandler;
+ delete iLlmnrConf;
+ }
+
+void CDndLlmnrResponder::ConstructL()
+ {
+ LOG(Log::Printf(_L("CDndLlmnrResponder::ConstructL() size=%d\r\n"), (TInt)sizeof(*this)));
+ CDnsSocket::ConstructL ();
+ iLlmnrConf = new (ELeave) CLlmnrConf(iControl);
+ iLlmnrConf->ConstructL();
+ iLlmnrConf->GetHostNamesL();
+
+ iLlmnrNotifyHandler = new (ELeave) CDndLlmnrNotifyHandler(*this);
+ iLlmnrNotifyHandler->ConstructL();
+ }
+
+void CDndLlmnrResponder::ConfigurationChanged()
+ {
+ iLlmnrNotifyHandler->ConfigurationChanged();
+ }
+
+void CDndLlmnrResponder::ActivateSocketL()
+ {
+ if (iConnected && IsOpened())
+ return; // Assume all done
+
+ const TDndConfigParameters &cf = iControl.GetConfig();
+ const TInetAddr bind(TInetAddr(cf.iLlmnrPort));
+
+ CDnsSocket::ActivateSocketL(bind);
+
+ // Disable multicast loopback to lessen the possibility to receive our own replies
+ (void)Socket().SetOpt(KSoIp6MulticastLoop, KSolInetIp, 0);
+ // Set Unicast and Multicast TTL/Hop limit to 1 for LLMNR responses
+ (void)Socket().SetOpt(KSoIp6UnicastHops, KSolInetIp, cf.iLlmnrHoplimit);
+ (void)Socket().SetOpt(KSoIp6MulticastHops, KSolInetIp, cf.iLlmnrHoplimit);
+ (void)Socket().SetOpt(KSoUserSocket, KSolInetIp, 0);
+ // THe LLMNR responder socket does not need to keep the interfaces up.
+ (void)Socket().SetOpt(KSoKeepInterfaceUp, KSolInetIp, 0);
+ ActivateListenL(bind, 1);
+ }
+
+// Join multicast group to LLMNR responder iSocket
+TInt CDndLlmnrResponder::JoinMulticastGroup(TUint32 aIndex, TIpVer aIpVer)
+ {
+
+ TPckgBuf<TIp6Mreq> opt;
+
+ TRAPD(err, ActivateSocketL()); // Need socket opened...
+ if (err != KErrNone)
+ return err;
+
+ if(aIpVer == EIPv4)
+ opt().iAddr = iControl.GetConfig().iLlmnrIpv4;
+ else
+ opt().iAddr = iControl.GetConfig().iLlmnrIpv6;
+ opt().iInterface = aIndex;
+
+ err=Socket().SetOpt(KSoIp6JoinGroup, KSolInetIp, opt);
+
+ if ((err != KErrNone)&&(err != KErrInUse)) // may return KErrInUse when multicast hook is on
+ {
+ LOG(Log::Printf(_L("CDndLlmnrResponder::JoinMulticastGroup SetOpt KSoIp6JoinGroup if=%d error: %d"), aIndex, err));
+ return err;
+ }
+
+ return KErrNone;
+ }
+
+
+TInt CDndLlmnrResponder::MakeMyAddr(const TUint32 aIfIndex, const TInetAddr &aTarget, EDnsType aType, TInetAddr &aAddr)
+ {
+ const EDnsType target_type = (aTarget.Family() == KAfInet || aTarget.IsV4Mapped()) ? EDnsType_A : EDnsType_AAAA;
+
+ TPckgBuf<TSoInetIfQuery> opt;
+ opt().iIndex = aIfIndex;
+
+ for (;;)
+ {
+ TInt err;
+
+ if (aType == target_type)
+ {
+ // The target address matches the address type being queried,
+ // try obtaining the "best address" that matches the target.
+ opt().iDstAddr = aTarget;
+ if ((err = iControl.iSocket.GetOpt(KSoInetIfQueryByIndex, KSolInetIfQuery, opt)) != KErrNone)
+ return err;
+ if (!opt().iSrcAddr.IsUnspecified())
+ break; // Success!
+ }
+
+ // Try finding an acceptable link local or any higher scope address
+
+ if (aType == EDnsType_A)
+ opt().iDstAddr.SetV4MappedAddress(KInetAddrLinkLocalNet);
+ else if (aType == EDnsType_AAAA)
+ opt().iDstAddr.SetAddress(KInet6AddrLinkLocal);
+ else
+ return KErrNotSupported;
+
+ if ((err = iControl.iSocket.GetOpt(KSoInetIfQueryByIndex, KSolInetIfQuery, opt)) != KErrNone)
+ {
+ LOG(Log::Printf(_L("CDndLlmnrResponder::MakeMyAddr GetOpt KSoInetIfQueryByIndex error: %d"),err));
+ return err;
+ }
+ break;
+ // *NEVER GET HERE!*
+ }
+ //
+ // Address is unspecified or some valid source address
+ //
+ aAddr = opt().iSrcAddr;
+ return KErrNone;
+ }
+
+
+#ifdef _LOG
+void CDndLlmnrResponder::LogPrint(const TDesC &aStr, const CLlmnrEntry &aEntry)
+ {
+ TBuf<KDnsMaxName> name;
+
+ aEntry.iQuestion.GetName(name);
+
+ Log::Printf(_L("%S%S QType=%d State=%d ip%d for if=%u\r\n"),
+ &aStr,
+ &name, (TInt)aEntry.iQuestion.QType(),
+ (TInt)aEntry.iState,
+ (TInt)aEntry.iVersion,
+ (TInt)aEntry.iZone[0]);
+ }
+
+void CDndLlmnrResponder::LogPrint(const TDesC &aStr, const TDndQuestion &aQuestion)
+ {
+ TBuf<KDnsMaxName> name;
+
+ aQuestion.GetName(name);
+
+ Log::Printf(_L("%S%S QType=%d\r\n"),
+ &aStr,
+ &name, (TInt)aQuestion.QType());
+ }
+#endif
+
+// Mark all entries as "unupdated"
+void CDndLlmnrResponder::UpdateStart()
+ {
+ for(CLlmnrEntry *entry = iEntryList; entry != NULL; entry = entry->iNext)
+ entry->iDown = 1;
+ }
+
+// Remove all entries that were not updated (interface has gone down)
+void CDndLlmnrResponder::UpdateFinish()
+ {
+ for(CLlmnrEntry **h = &iEntryList;;)
+ {
+ CLlmnrEntry *entry = *h;
+ if (entry == NULL)
+ break; // -- all done!
+ if (entry->iDown ||
+ JoinMulticastGroup(entry->iZone[0], STATIC_CAST(TIpVer, entry->iVersion)) != KErrNone)
+ {
+ //
+ // Remove entry. Either interface is down (not exist) or
+ // it does not support multicast, in which case it cannot
+ // be used for LLMNR.
+ //
+ *h = entry->iNext;
+ //
+ // If entry has any associated requests outstanding,
+ // they must be cancelled.
+ CancelAll(*entry);
+ LOG(LogPrint(_L("\tLLMNR removed: "), *entry));
+ delete entry;
+ }
+ else
+ {
+ //
+ // Entry is still active and usable
+ //
+ h = &entry->iNext;
+ continue;
+ }
+ }
+ if (iEntryList == NULL)
+ {
+ // Nothing enabled for LLMNR, no need to keep the socket either
+ DeactivateSocket();
+ LOG(Log::Printf(_L("CDndLlmnrResponder::UpdateFinish - no entries, socket deactivated\r\n")));
+ }
+ }
+
+TInt CDndLlmnrResponder::UpdateInterface
+ (const TName &aIfName, const TIpVer aIpVer, const TInetScopeIds &aZone, const TSockAddr &aHwAddr, TInt aLlmnrDisable)
+ /**
+ * Update the LLMNR entries for the specified interface.
+ *
+ * @param aIfName The interface name
+ * @parma aIpVer The IP version (either EIPv4 or EIPv6)
+ * @param aZone The zone ids
+ * @param aHwAddr The hardware address (if present)
+ * @param aLlmnrDisable =1, if LLMNR is to be disabled on this interface
+ */
+ {
+
+ // Check that this is not a loopback interface
+ if(!aZone[1])
+ return KErrNone;
+
+ // An interface is open. Check if it is LLMNR enabled and if update(s)
+ // have not been sent or disabled
+
+ TInt is_active = 0;
+ for(CLlmnrEntry *entry = iEntryList; entry != NULL; entry = entry->iNext)
+ {
+ if (entry->iZone[0] == aZone[0])
+ {
+ is_active = 1;
+ // If LLMNR is to be disable, this keeps iDown as 1 and
+ // the entry will be deleted in UpdateFinish.
+ entry->iDown = aLlmnrDisable;
+ //
+ // Copy the scope vector each time, in case it has been modified
+ //
+ for (TUint k = 0; k < sizeof(TInetScopeIds) / sizeof(entry->iZone[0]); k++)
+ entry->iZone[k] = aZone[k];
+ }
+ }
+ if (is_active || aLlmnrDisable)
+ return KErrNone; // Interface already activated or being disabled, nothing else to do.
+
+ // Interface is not yet LLMNR enabled, Check if there
+ // are hostnames enabled that match this interface
+
+ const EDnsQType qt = aIpVer == EIPv4 ? EDnsQType_A : aIpVer == EIPv6 ? EDnsQType_AAAA : EDnsQType(0);
+
+ const TInt N = iLlmnrConf->iHostList->Count();
+ for (TInt i = 0; i < N; i++)
+ {
+ const THostNameEntry &host = iLlmnrConf->iHostList->At(i);
+
+ if (host.iVersion != EIPany && host.iVersion != aIpVer)
+ continue; // Does not apply to this entry, go look next
+ if (host.iIfName.Length() > 0 && host.iIfName.Compare(aIfName) != 0)
+ continue; // Entry is for a specific interface (not this), go look next
+ //
+ // Build a new LLMNR entry
+ //
+ TInetAddr addr(iControl.GetConfig().iLlmnrPort);
+ // Give name and address to the server manager
+ if(aIpVer == EIPv4)
+ addr.SetAddress(iControl.GetConfig().iLlmnrIpv4);
+ else
+ addr.SetAddress(iControl.GetConfig().iLlmnrIpv6);
+ iServerManager.AddServerAddress(aIfName, addr);
+
+ // Make an entry to the list
+ CLlmnrEntry *entry = new CLlmnrEntry();
+ if (entry == NULL)
+ break; // Ooops, out of memory...
+ entry->iNext = iEntryList;
+ iEntryList = entry;
+
+ entry->iQuestion.SetName(host.iName);
+ entry->iQuestion.SetQType(qt);
+ entry->iQuestion.SetQClass(EDnsQClass_IN);
+ entry->iVersion = aIpVer;
+ //
+ // Copy the scope vector
+ //
+ for (TUint k = 0; k < sizeof(TInetScopeIds) / sizeof(entry->iZone[0]); k++)
+ entry->iZone[k] = aZone[k];
+
+ entry->iState = EInactive;
+ if(FormatHostName(*entry, aHwAddr) == KErrNone)
+ (void)DoUpdate(*entry);
+ else
+ entry->iState = EDisabled;
+ LOG(LogPrint(_L("\tLLMNR Created: "), *entry));
+ }
+ return KErrNone;
+ }
+
+TInt CDndLlmnrResponder::FormatHostName(CLlmnrEntry &aEntry, const TSockAddr &aHwAddr)
+ {
+ TInt i;
+ aEntry.iHostName = 0;
+ if((i = aEntry.iQuestion.Find(FORMATHWADDR)) != KErrNotFound)
+ { // hostname contains hardware address format string
+ if(aHwAddr.Family() == KAFUnspec)
+ {
+ LOG(Log::Printf(_L("CDndLlmnrResponder::FormatHostName - no hw address\r\n")));
+ return KErrNotFound;
+ };
+
+ // Insert hw address into hostname
+ // (We assume, that there is only one hw address insert in the hostname)
+ TDndName tmpstr;
+ tmpstr.Append(aEntry.iQuestion.Left(i));
+ TInt err;
+ if((err = TRawAddr::Cast(aHwAddr).Append(tmpstr)) != KErrNone)
+ return err;
+ tmpstr.Append(aEntry.iQuestion.Mid(i + FORMATHWADDR().Length()));
+ (TDndName &)aEntry.iQuestion = tmpstr;
+ return KErrNone;
+ }
+ else if((i = aEntry.iQuestion.Compare(WILDCARDPRIMARYHOSTNAME)) == 0)
+ {
+ // Entry requests use of local hostname. Find a matching local name
+ // based on the network id.
+ aEntry.iHostName = 1;
+ const TUint32 nid = aEntry.iZone[KIp6AddrScopeNetwork-1];
+ (void)iHostNames.Refresh(nid); // Reread hostname from Comms Database.
+ aEntry.iQuestion.SetName(iHostNames.Find(nid));
+ return aEntry.iQuestion.Length() > 0 ? KErrNone : KErrNotFound;
+ }
+ else
+ return KErrNone; // No format string in hostname
+ }
+
+
+void CDndLlmnrResponder::CancelAll(const CLlmnrEntry &aEntry)
+ {
+
+ // Abort all associated requests, if any
+
+ for (TInt session = 0; session < KLlmnrMaxSessions; ++session)
+ if(iLlmnrDataList[session].iLlmnrEntry == &aEntry)
+ {
+ iLlmnrDataList[session].iTimeout.Cancel();
+ iLlmnrDataList[session].Cancel();
+ iLlmnrDataList[session].iLlmnrEntry = NULL;
+ }
+ }
+
+
+TInt CDndLlmnrResponder::DoUpdate(CLlmnrEntry &aEntry)
+ {
+ if (JoinMulticastGroup(aEntry.iZone[0], STATIC_CAST(TIpVer, aEntry.iVersion)) != KErrNone)
+ {
+ // The interface does not support multicast, disable entry.
+ LOG(LogPrint(_L("CDndLlmnrResponder::DoUpdate() No multicast, disable: "), aEntry));
+ aEntry.iState = EDisabled;
+ return KErrNotSupported;
+ }
+
+ TInt session = 0;
+ for(session=0; ; session++)
+ {
+ if(session == KLlmnrMaxSessions)
+ {
+ LOG(Log::Printf(_L("CDndLlmnrResponder::DoUpdate - No free slots in iLlmnrDataList")));
+ return KErrNoMemory;
+ }
+ if(!iLlmnrDataList[session].iLlmnrEntry)
+ break;
+ }
+ TLlmnrMsgData &upd = iLlmnrDataList[session];
+ upd.iLlmnrEntry = &aEntry;
+
+ // Do Unique check by sending a query for the name, and if nobody answers within
+ // specified time, assume the name is valid to use for this node.
+ upd.iQuestion = aEntry.iQuestion;
+ upd.iQR = 0;
+ upd.iAA = 0;
+ upd.iRCode = EDnsRcode_NOERROR;
+ if (aEntry.iVersion == EIPv4)
+ upd.iDstAddr.SetAddress(iControl.GetConfig().iLlmnrIpv4);
+ else
+ upd.iDstAddr.SetAddress(iControl.GetConfig().iLlmnrIpv6);
+ upd.iDstAddr.SetScope(aEntry.iZone[upd.iDstAddr.Ip6Address().Scope()-1]);
+ upd.iDstAddr.SetPort(iControl.GetConfig().iLlmnrPort);
+ upd.iRepeat = 1 + iControl.GetConfig().iLlmnrRetries;
+ Queue(upd);
+ return KErrNone;
+ }
+
+void CDndLlmnrResponder::Query(const TMsgBuf &aBuf, const TInetAddr &aServer, const RSocket &aSocket)
+ {
+ ASSERT(aServer.Port() != 0);
+
+ // Preselect the the session...
+ TInt session = 0;
+ for(;; session++)
+ {
+ if(session == KLlmnrMaxSessions)
+ {
+ LOG(Log::Printf(_L("\tLLMNR No free slots in iLlmnrDataList\r\n")));
+ return;
+ }
+ if(!iLlmnrDataList[session].iLlmnrEntry)
+ break;
+ }
+ TLlmnrMsgData &resp = iLlmnrDataList[session];
+ TInt rcode;
+ TInt offset = aBuf.VerifyMessage(rcode, resp.iQuestion);
+ if (offset < 0)
+ {
+ LOG(Log::Printf(_L("\tLLMNR Corrupt message\r\n")));
+ return; // A corrupted message
+ }
+ const TDndHeader &hdr = aBuf.Header();
+ if (hdr.QR() || hdr.ANCOUNT() != 0)
+ return; // This is responce, Not a query. Ignore.
+
+ CLlmnrEntry *entry = iEntryList;
+ TUint32 index = 0;
+
+ // Note: there are other PTR queries than just for address!
+ // (fall to normal query processing for non address PTR queries).
+ if (resp.iQuestion.QType() == EDnsQType_PTR &&
+ (index = IsMyAddress(resp.iQuestion)) != 0)
+ {
+ // See if this interface is active...
+ for(;; entry = entry->iNext)
+ {
+ if (entry == NULL)
+ {
+ LOG(LogPrint(_L("\tLLMNR No match: "), resp.iQuestion));
+ return; // No match, ignore
+ }
+ if (entry->iState > 0)
+ {
+ if (entry->iZone[0] == index)
+ break;
+ }
+ }
+ }
+ else
+ {
+
+ // The question must match one of the entries
+
+ const TInt scopelevel = aServer.Ip6Address().Scope()-1;
+ const TUint32 scopeid = aServer.Scope();
+ for(CLlmnrEntry *my_name = NULL; ;entry = entry->iNext)
+ {
+ if (entry == NULL)
+ {
+ if (my_name == NULL)
+ {
+ LOG(LogPrint(_L("\tLLMNR Query does not match: "), resp.iQuestion));
+ return; // No match, ignore
+ }
+ //
+ // No exact match to the query, but the name
+ // matched to at least one of my unique names.
+ // Reply with empty RR-set, using one of those entries.
+ entry = my_name;
+ break;
+ }
+ // A iState > 0 means valid entry.
+ if (entry->iState > 0)
+ {
+ if (scopeid == entry->iZone[scopelevel])
+ {
+ if (DnsCompareNames(resp.iQuestion, entry->iQuestion))
+ {
+ my_name = entry;
+ if (resp.iQuestion.QClass() == entry->iQuestion.QClass() &&
+ resp.iQuestion.QType() == entry->iQuestion.QType())
+ break; // Full Match, answer using this entry!
+ }
+ }
+ }
+ }
+ }
+ //
+ // Actually answering the question, assign the request
+ //
+ LOG(LogPrint(_L("\tLLMNR Sending reply to matched query: "), *entry));
+ resp.iLlmnrEntry = entry;
+ resp.iQR = 1;
+ resp.iAA = 1;
+ resp.iRCode = EDnsRcode_NOERROR;
+ resp.iDstAddr = aServer;
+ resp.iRepeat = 0;
+ Queue(resp, aSocket, hdr.ID());
+ }
+
+
+TUint32 CDndLlmnrResponder::IsMyAddress(const TDndQuestion &aQuestion)
+ {
+ TPckgBuf<TSoInetIfQuery> opt;
+ if (!aQuestion.GetAddress(opt().iSrcAddr))
+ return 0; // Not a parsable PTR for IP address, ignore.
+ if(Socket().GetOpt(KSoInetIfQueryBySrcAddr, KSolInetIfQuery, opt) != KErrNone)
+ return 0;// not my address, ignore
+ return opt().iIndex;
+ }
+
+TInt CDndLlmnrResponder::SetHostName(TUint32 aId, const TDesC &aName)
+ {
+ for (CLlmnrEntry *e = iEntryList; e; e = e->iNext)
+ {
+ if (e->iHostName && e->iZone[KIp6AddrScopeNetwork-1] == aId)
+ {
+ if (aName.Length() > 0)
+ {
+ e->iState = EInactive;
+ e->iQuestion.SetName(aName);
+ (void)DoUpdate(*e);
+ }
+ else
+ {
+ e->iState = EDisabled;
+ CancelAll(*e);
+ }
+ }
+ }
+ return KErrNone;
+ }
+
+
+TInt CDndLlmnrResponder::GetHostName(TUint32 aId, MDnsResolver &aCallback)
+ {
+ const TInt result = DoHostNameState(aId);
+
+ if (result > 0)
+ {
+ //
+ // Pending entries present, install callback
+ //
+ for (CHostCallback *c = iCallbacks; ; c = c->iNext)
+ {
+ if (c == NULL)
+ {
+ c = new CHostCallback(aCallback);
+ if (c == NULL)
+ return KErrNoMemory;
+ c->iNext = iCallbacks;
+ iCallbacks = c;
+ break;
+ }
+ else if (&aCallback == &c->iCallback)
+ break; // Already installed.
+ }
+ }
+ return result;
+ }
+
+
+
+/**
+* Determines the state of the hostname.
+*
+* Scans through all LLMNR names and checks state of the
+* entries that have been generated from the host name.
+* Ignore entries in EDisabled state.
+*
+* @return result as follows:
+*
+* - KErrNone, if all existing entries are currently
+* marked as "unique" (note: this includes the case,
+* where none of the names are generated from the
+* host name). No entry is in conflict state.
+* - KErrAlreadyExists, if at least one of the entries
+* is in conflict.
+* - = 1 (Pending), if at least one of the entries is
+* still being tested for uniqueness, and none of the
+* entries is in conflict.
+*/
+TInt CDndLlmnrResponder::DoHostNameState(TUint32 aId)
+ {
+ TInt result = KErrNone;
+ for (CLlmnrEntry *e = iEntryList; e; e = e->iNext)
+ {
+ if (e->iZone[KIp6AddrScopeNetwork-1] == aId && e->iHostName)
+ {
+ if (e->iState == EInactive)
+ {
+ // Pending state, unique test not yet complete
+ result = 1;
+ }
+ else if (e->iState == EConflict)
+ {
+ // Conflict state, assume a name collision has occurred
+ return KErrAlreadyExists;
+ }
+ }
+ }
+ return result;
+ }
+
+/**
+* Do callbacks for pending GetHostName.
+*
+* Determines the state of the hostname and does the
+* callbacks, if the state is not pending.
+*/
+void CDndLlmnrResponder::DoCallbacks(TUint32 aId)
+ {
+ if (iCallbacks == NULL)
+ return;
+
+ const TInt result = DoHostNameState(aId);
+ if (result > 0)
+ return;
+
+ CHostCallback *c = iCallbacks;
+ iCallbacks = NULL;
+ while (c)
+ {
+ CHostCallback *cb = c;
+ c = cb->iNext;
+ cb->iCallback.ReplyCallback(result);
+ delete cb;
+ }
+ }
+
+//****************** TLlmnrMsgData ****************************
+
+// constructor needed only to setup the timeout framework
+TLlmnrMsgData::TLlmnrMsgData() : iTimeout(TLlmnrMsgDataTimeoutLinkage::Timeout)
+ {
+ }
+
+void TLlmnrMsgData::Timeout(const TTime & /*aNow*/)
+ {
+ LOG(Log::Printf(_L("--> TLlmnrMsgData::Timeout() -start-\r\n")));
+ if (iLlmnrEntry)
+ {
+ if (iRepeat > 0)
+ {
+ LOG(iParent->LogPrint(_L("\tLLMNR Resend query: "), *iLlmnrEntry));
+ iParent->ReSend(*this);
+ }
+ else
+ {
+ Cancel();
+ // The timeout is only activated with Unique testing, thus
+ // when final repeat has copleted without conflict, mark
+ // entry as unique
+ LOG(iParent->LogPrint(_L("\tLLMNR Name is unique: "), *iLlmnrEntry));
+ iLlmnrEntry->iState = EUnique;
+ if (iLlmnrEntry->iHostName)
+ {
+ // If entry depended on the hostname and there are pending GetHostNames,
+ // then check if this was the last such entry in pending state, and
+ // if so, generate callbacks.
+ iParent->DoCallbacks(iLlmnrEntry->iZone[KIp6AddrScopeNetwork-1]);
+ }
+ iLlmnrEntry = NULL;
+ }
+ }
+ LOG(Log::Printf(_L("<-- TLlmnrMsgData::Timeout() -exit-\r\n")));
+ }
+
+TBool TLlmnrMsgData::Reply(CDnsSocket &aSource, const TMsgBuf &aBuf, const TInetAddr & /*aServer*/)
+ {
+ if (!iLlmnrEntry)
+ return FALSE; // Nothing to do if no entry associated
+
+ TDndQuestion question;
+ TInt rcode;
+ TInt offset = aBuf.VerifyMessage(rcode, question);
+ if (offset <= 0)
+ return FALSE;
+
+ const TDndHeader &hdr = aBuf.Header();
+ if (!hdr.QR())
+ return FALSE; // This is query, not a response. Ignore.
+ if (hdr.OPCODE() != EDnsOpcode_QUERY || rcode != EDnsRcode_NOERROR)
+ return FALSE; // Not an OK reply
+
+ if (question.CheckQuestion(iQuestion) != KErrNone)
+ return FALSE;
+
+ // This is a matching reply to query of our own name, there is
+ // a collision.
+ iLlmnrEntry->iState = EConflict;
+ if (iLlmnrEntry->iHostName)
+ iParent->DoCallbacks(iLlmnrEntry->iZone[KIp6AddrScopeNetwork-1]);
+ CNotifyConflict::Start(*iParent, *iLlmnrEntry);
+
+ Cancel();
+ CDndLlmnrResponder &responder = (CDndLlmnrResponder &)aSource;
+ responder.CancelAll(*iLlmnrEntry);
+ return TRUE;
+ }
+
+
+void TLlmnrMsgData::Sent(CDnsSocket &/*aSource*/)
+ {
+ if (iRepeat == 0)
+ {
+ iLlmnrEntry = NULL;
+ Cancel();
+ }
+ else
+ {
+ iRepeat -= 1;
+ iTimeout.Set(&iParent->iControl.Timer(), iParent->iControl.GetConfig().iLlmnrMinTime);
+ }
+ }
+
+void TLlmnrMsgData::Abort(CDnsSocket &/*aSource*/, const TInt aReason)
+ {
+ LOG(Log::Printf(_L("TLlmnrMsgData::Abort - reason: %d\r\n"),aReason));
+ (void)aReason; // silence compiler warning
+ iLlmnrEntry = NULL;
+ }
+
+// TLlmnrMsgData::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
+//
+// @returns TRUE, successful Build, and error (< 0) otherwise
+*/
+TBool TLlmnrMsgData::Build(CDnsSocket &/*aSource*/, TMsgBuf &aMsg, TInetAddr &aServer, TInt /*aMaxMessage*/)
+ {
+ TInt ret = KErrNone;
+ for (;;)
+ {
+ if (iLlmnrEntry == NULL)
+ {
+ LOG(Log::Printf(_L("TLlmnrMsgData::Build() - Faulty component\r\n")));
+ ret = KErrArgument;
+ break;
+ }
+
+ aServer = iDstAddr;
+ aMsg.SetLength(sizeof(TDndHeader));
+ TDndHeader &hdr = (TDndHeader &)aMsg.Header();
+ hdr.SetRD(0); // Part of zero field
+ hdr.SetOpcode(EDnsOpcode_QUERY);
+ hdr.SetQR(iQR);
+ hdr.SetAA(iAA);
+ hdr.SetRCode(iRCode);
+ hdr.SetQdCount(1);
+ const TInt qname_offset = aMsg.Length();
+ ret = iQuestion.Append(aMsg);
+ if (ret != KErrNone)
+ break;
+
+ if (iQR)
+ {
+ // This is a reply message
+ TDndRROut rr(aMsg);
+ rr.iType = (TUint16)iQuestion.QType();
+ rr.iClass = (TUint16)iQuestion.QClass();
+ rr.iTTL = iParent->iLlmnrConf->iTTL;
+
+ if (rr.iType == EDnsType_PTR)
+ {
+ // answer contains one or several hostnames, which
+ // are configured for the same interface as the
+ // queried address.
+ TUint16 count = 0;
+ for(CLlmnrEntry *entry = iParent->iEntryList; entry != NULL; entry = entry->iNext)
+ {
+ if (entry->iState <= 0)
+ continue;
+ if (entry->iZone[0] != iLlmnrEntry->iZone[0])
+ continue;
+ //
+ // Build an RR
+ //
+ ret = rr.Append(KNullDesC8, qname_offset);
+ if (ret != KErrNone)
+ break; // -- no reply sent!
+ ret = rr.AppendRData(entry->iQuestion, 0);
+ if (ret != KErrNone)
+ break; // -- no reply sent!
+ count++;
+ }
+ hdr.SetAnCount(count); // set answer or prerequisite count
+ }
+ else if (iQuestion.QType() == EDnsQType_ANY)
+ {
+ // For ANY query, return one address
+ // (experimental, not fully worked out yet)
+ TUint16 count = 0;
+ TInetAddr addr;
+ rr.iType = EDnsType_A;
+ ret = iParent->MakeMyAddr(iLlmnrEntry->iZone[0], iDstAddr, (EDnsType)rr.iType, addr);
+ if (ret != KErrNone)
+ break;
+ if (!addr.IsUnspecified())
+ {
+ ret = rr.Append(KNullDesC8, qname_offset);// compressed name and answer
+ if (ret != KErrNone)
+ ret = rr.AppendRData(addr);
+ ++count;
+ }
+ rr.iType = EDnsType_AAAA;
+ ret = iParent->MakeMyAddr(iLlmnrEntry->iZone[0], iDstAddr, (EDnsType)rr.iType, addr);
+ if (ret != KErrNone)
+ break;
+ if (!addr.IsUnspecified())
+ {
+ ret = rr.Append(KNullDesC8, qname_offset);// compressed name and answer
+ if (ret != KErrNone)
+ ret = rr.AppendRData(addr);
+ ++count;
+ }
+ hdr.SetAnCount(count);
+ }
+ else if (iQuestion.QType() != iLlmnrEntry->iQuestion.QType())
+ {
+ // The question and entry do not match. Assume this is
+ // query for RR that we don't have, but the name matched.
+ // Reply with empty RR-set.
+ hdr.SetAnCount(0);
+ }
+ else if (rr.iType == EDnsType_A || rr.iType == EDnsType_AAAA)
+ {
+ // answer contains one address
+ TInetAddr addr;
+ ret = iParent->MakeMyAddr(iLlmnrEntry->iZone[0], iDstAddr, (EDnsType)rr.iType, addr);
+ if (ret != KErrNone)
+ break;
+ if (addr.IsUnspecified())
+ hdr.SetAnCount(0); // No address
+ else
+ {
+ ret = rr.Append(KNullDesC8, qname_offset);// compressed name and answer
+ if (ret == KErrNone)
+ ret = rr.AppendRData(addr);
+ hdr.SetAnCount(1); // set answer count
+ }
+ }
+ else
+ break; // Unsupported RR type, no answer!
+ }
+ if (ret != KErrNone)
+ break;
+ return 1;
+ }
+ //
+ // Failed to build the message
+ //
+ iLlmnrEntry = NULL;
+ Cancel();
+ return 0;
+ }
+
+
+// **********************************************************************
+
+CNotifyConflict::CNotifyConflict(CDndLlmnrResponder &aResponder, CLlmnrEntry &aEntry)
+ : CActive(0), iResponder(aResponder), iEntry(aEntry)
+ {
+ LOG(Log::Printf(_L("\tnotifier[%u] new"), (TUint)this));
+ CActiveScheduler::Add(this);
+ }
+
+CNotifyConflict::~CNotifyConflict()
+ {
+ Cancel();
+ if (iConnected)
+ {
+ iNotifier.Close();
+ }
+
+ // Detach from the entry.
+ if (iEntry.iNotify == this)
+ iEntry.iNotify = NULL;
+ // Deque from the active scheduler
+ if (IsAdded())
+ Deque();
+ LOG(Log::Printf(_L("\tnotifier[%u] deleted"), (TUint)this));
+ }
+
+void CNotifyConflict::Start(CDndLlmnrResponder &aResponder, CLlmnrEntry &aEntry)
+ {
+ if (aEntry.iNotify)
+ {
+ return; // Notify process is already active for this hostname, nothing to do.
+ }
+ aEntry.iNotify = new CNotifyConflict(aResponder, aEntry);
+ if (aEntry.iNotify)
+ {
+ aEntry.iNotify->DoStart();
+ }
+ }
+
+void CNotifyConflict::DoStart()
+ {
+ if (iNotifier.Connect() == KErrNone)
+ {
+ iConnected = 1;
+ // Fill in information
+ iInfo().iIAPId = iEntry.iZone[1]; // IAP is iZone[1]
+ iInfo().iNetworkId = iEntry.iZone[15]; // NET is iZone[15]
+ (void)iEntry.iQuestion.GetName(iInfo().iName);
+ LOG(Log::Printf(_L("\tnotifier[%u] activated IAP=%d NET=%d HOST='%S'"), (TUint)this,
+ iInfo().iIAPId, iInfo().iNetworkId, &iInfo().iName));
+ iNotifier.StartNotifierAndGetResponse(iStatus, TUid::Uid(KLlmnrConflictNotifyUid), iInfo, iInfo);
+ SetActive();
+ }
+ else
+ {
+ // Cancel Notifier activity, cannot get it to work.
+ delete this;
+ }
+ }
+
+void CNotifyConflict::DoCancel()
+ {
+ LOG(Log::Printf(_L("\tnotifier[%u] canceling"), (TUint)this));
+ iNotifier.CancelNotifier(TUid::Uid(KLlmnrConflictNotifyUid));
+ }
+
+void CNotifyConflict::RunL()
+ {
+ LOG(Log::Printf(_L("-->\tnotifier[%u] Completion result=%d"), (TUint)this, iStatus.Int()));
+ if (iStatus.Int() == KErrNone && iInfo().IsSafe() && iInfo().iName.Length() > 0)
+ {
+ if (iEntry.iQuestion.SetName(iInfo().iName) == KErrNone)
+ {
+ LOG(Log::Printf(_L("-->\tnotifier[%u] Trying new name '%S'"), (TUint)this, &iInfo().iName));
+ iEntry.iState = EInactive;
+ // Detach from the entry.
+ if (iEntry.iNotify == this)
+ iEntry.iNotify = NULL;
+ (void)iResponder.DoUpdate(iEntry);
+ }
+ }
+ delete this;
+ }
+
+#endif