// 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:
// message.cpp - name resolver DNS message interpreter
//
#include <dns_qry.h>
#include "res_sock.h"
#include "message.h"
#include <networking/dnd_err.h>
#include "dns_ext.h"
#ifdef EXCLUDE_SYMBIAN_DNS_PUNYCODE
#undef SYMBIAN_DNS_PUNYCODE
#endif //EXCLUDE_SYMBIAN_DNS_PUNYCODE
// Constants
_LIT8(KIPv4AddrToHost, ".in-addr.arpa");
#ifdef DNS_REVERSE_IP6_INT
_LIT8(KIPv6AddrToHost, ".ip6.int");
#else
_LIT8(KIPv6AddrToHost, ".ip6.arpa");
#endif
#ifdef SYMBIAN_DNS_PUNYCODE
/* This macro is to check whether the character passed is of UTF encoded */
#define ISUTF16(x) ((x>= 0xD800 && x<= 0xDFFF)? ETrue : EFalse)
#endif //SYMBIAN_DNS_PUNYCODE
//
// DnsCompareNames
// ***************
// A global utility function
//
/**
// The DNS domain names have a special case folding rules. The names
// are BINARY data (can include anything in range [0..255], but when
// comparing two names, ASCII lower and and upper case letters should
// compare equal, e.g A-Z == a-z.
//
// @param aName1 to be compared
// @param aName2 to be compared
// @returns TRUE, if names are equal under DNS case folding rules
*/
TBool DnsCompareNames(const TDesC8 &aName1, const TDesC8 &aName2)
{
static const TUint8 KAscii_A = 0x41;
static const TUint8 KAscii_Z = 0x5a;
static const TUint8 KAscii_lower = 0x20;
TInt n = aName1.Length();
if (n != aName2.Length())
return 0; // not same, lengths differ
//
// Need to do the special "DNS case insensitive" compare
//
const TUint8 *const p = aName1.Ptr();
const TUint8 *const q = aName2.Ptr();
while (--n >= 0)
{
TUint8 x = p[n];
TUint8 y = q[n];
if (x != y)
{
if (x >= KAscii_A && x <= KAscii_Z)
x += KAscii_lower;
if (y >= KAscii_A && y <= KAscii_Z)
y += KAscii_lower;
if (x != y)
return 0;
}
}
return 1;
}
//
// Initialize TDndHeader
//
void TDndHeader::Init(const TUint16 aID, const TUint8 aOpcode)
{
// Set ID to aID
i[0] = (TUint8)(aID / 0x0100);
i[1] = (TUint8)(aID % 0x0100);
// Set QR = 0; Opcode = aOpcode; AA = 0; TC = 0; RD = 1
i[2] = 1;
i[2] |= (aOpcode << 3);
// Set RA = 0; Z = 0; RCode = 0
i[3] = 0;
// Set QDCOUNT = 1
i[4] = 0;
i[5] = 1;
// Set all other counts as zero.
// ANCOUNT = 0;
// NSCOUNT = 0;
// ARCOUNT = 0;
i[6] = 0;
i[7] = 0;
i[8] = 0;
i[9] = 0;
i[10] = 0;
i[11] = 0;
}
// to set the ID field of header
void TDndHeader::SetId(const TUint16 aID)
{
i[0] = (TUint8)(aID / 0x0100);
i[1] = (TUint8)(aID % 0x0100);
}
#ifdef LLMNR_ENABLED
// TDndHeader::SetQR() - To set the query/response bit of the header
void TDndHeader::SetQR(const TUint aQR)
{
i[2] |= ((aQR & 0x1) << 7);
}
#endif
// TDndHeader::SetOpcode() - To set the opcode field of the header
void TDndHeader::SetOpcode(const TUint8 aOpcode)
{
i[2] |= ((aOpcode & 0xF) << 3);
}
#ifdef LLMNR_ENABLED
// TDndHeader::SetAA() - To set the authoritative answer bit of the header
void TDndHeader::SetAA(const TUint aAA)
{
i[2] |= ((aAA & 0x1) << 2);
}
// TDndHeader::SetRCode() - To set the value of RCode field of the header
void TDndHeader::SetRCode(const TUint aRCode)
{
i[3] |= aRCode & 0xF;
}
#endif
// TDndHeader::SetQdCount() - To set the QdCount field
void TDndHeader::SetQdCount(const TUint16 aQdCount)
{
i[4] = (TUint8)(aQdCount / 0x0100);
i[5] = (TUint8)(aQdCount % 0x0100);
}
// TDndHeader::SetAnCount() - To set the answer count field
void TDndHeader::SetAnCount(const TUint16 aAnCount)
{
i[6] = (TUint8)(aAnCount / 0x0100);
i[7] = (TUint8)(aAnCount % 0x0100);
}
void TDndHeader::SetArCount(const TUint16 aArCount)
{
i[10] = (TUint8)(aArCount / 0x0100);
i[11] = (TUint8)(aArCount % 0x0100);
}
#ifdef SYMBIAN_DNS_PROXY
void TDndHeader::SetNsCount(const TUint16 aNsCount)
{
i[8] = (TUint8)(aNsCount / 0x0100);
i[9] = (TUint8)(aNsCount % 0x0100);
}
#endif
// TMsgBuf::VerifyMessage
// **********************
/**
// Verify basic header fields in the responce.
//
// @retval aRCode The RCode from the message (if return > 0)
// @retval aQuestion The question from the message (if return > 0)
//
// @return
// @li < 0, if there are any format errors with the message
// @li > 0, offset to the first answer RR after the question section
*/
TInt TMsgBuf::VerifyMessage(TInt &aRCode, TDndQuestion &aQuestion) const
{
// Check that message is actually long enough for the header
// Return length of the fixed header (= offset pointing to next part)
ASSERT(sizeof(TDndHeader) == KDnsMinHeader);
if (Length() < TInt(sizeof(TDndHeader)))
return KErrDndDiscard;
const TDndHeader &hdr = Header();
// ..do some simple insanity tests, and discard if odd things
// detected.
if (hdr.ANCOUNT() < 0)
return KErrDndDiscard;
if (hdr.NSCOUNT() < 0)
return KErrDndDiscard;
if (hdr.ARCOUNT() < 0)
return KErrDndDiscard;
TInt offset = aQuestion.Create(*this, sizeof(TDndHeader));
if (offset > 0)
{
// Question extracted, now we can look for EDNS0 OPT record
// to find the real RCODE...
TDndRR opt_rr(*this);
const TInt start = hdr.ANCOUNT() + hdr.NSCOUNT();
if (opt_rr.LocateRR(offset, start + hdr.ARCOUNT(), EDnsQType_OPT, EDnsQClass_ANY, start) > offset)
{
// OPT RR has been located
// ..return extend RCODE.
aRCode = ((opt_rr.iTTL >> 20) & 0xFF0) | hdr.RCODE();
}
else
{
// NO OPT RR present, RCODE is as it
aRCode = hdr.RCODE();
}
}
return offset;
}
// TMsgBuf::GetNextName
// ********************
/**
// @param aOffset from the start of the message to the domain name
// @retval aName receives the extracted domain name (in raw DNS 8bit encoding)
// @param aDepth ("internal" paramater) recursion count to catch looped compression
// @returns
// @li > 0, updated offset pointing past the name portion
// @li < 0, an error was detected
*/
TInt TMsgBuf::GetNextName(TInt aOffset, TDes8 &aName, const TUint aDepth) const
{
TInt tag;
TInt err;
if (aDepth >= KDndMaxLoopCount || // Prevent infinite recursion...
aOffset >= Length() || // ...offsets beyond the buffer end,
aOffset < 0) // ...or before buffer start (someone didn't check error return!)
return KErrDndDiscard;
const TUint8 *p = Ptr() + aOffset;
while ((tag = *p) != 0)
{
if ((tag & 0xC0) == 0xC0)
{
if (Length() < aOffset + 2)
return KErrDndDiscard;
const TInt tempOffset = (tag - 0xC0) * 256 + *(p+1);
err = GetNextName(tempOffset, aName, aDepth+1);
if (err < KErrNone)
return err;
p += 2;
aOffset += 2;
break;
}
else
{
p++;
aOffset++;
if (aName.Length() + tag >= aName.MaxLength())
return KErrDndNameTooBig;
if (Length() < aOffset + tag)
return KErrDndUnknown;
aName.Append(TPtrC8(p, tag));
aName.Append('.');
p += tag;
aOffset += tag;
}
}
if (tag == 0)
{
p++;
aOffset++;
// if there is a 'dot' at the end, get rid of it
if (aName.Length() != 0)
aName.Delete(aName.Length() - 1, 1);
}
return aOffset;
}
// TMsgBuf::GetNextString
// **********************
/**
// @param aOffset from the start of the message to the character string
// @retval aString receives the extracted string (in raw DNS 8bit encoding). This
// is only a reference to the original DNS message buffer, which must be
// kept around while this is used.
// @returns
// @li > 0, updated offset pointing past extracted string
// @li < 0, an error was detected
*/
TInt TMsgBuf::GetNextString(TInt aOffset, TPtrC8 &aString) const
{
if (aOffset >= Length())
return KErrDndUnknown; // corrupt data
const TUint8 *p = Ptr() + aOffset;
//
// Extract <character-string> length
//
TInt length = *p++;
aOffset += length + 1;
if (aOffset > Length())
return KErrDndUnknown; // corrupt data
aString.Set(p, length);
return aOffset;
}
// TMsgBuf::GetName
// ****************
/**
// @param aOffset from the start of the message to the start of domain name
#ifdef SYMBIAN_DNS_PUNYCODE
// @param aIdnEnabled
#endif
// @retval aName the extracted domain name (system dependent encoding)
// @returns
// @li > 0, offset updated pointing past the name portion
// @li < 0, an error was detected
*/
#ifdef SYMBIAN_DNS_PUNYCODE
TInt TMsgBuf::GetName(TInt aOffset, TDes &aName,TBool aIdnEnabled) const
#else
TInt TMsgBuf::GetName(TInt aOffset, TDes &aName) const
#endif //SYMBIAN_DNS_PUNYCODE
{
// Use TDndName as temporary buffer. This seemingly
// unnecessary juggling is present because of the potentially
// hairy/complex issues of DNS <-> UNICODE translation. The idea
// is to have one clean location which implements it:
// TDndName::SetName and TDndName::GetName methods.
TDndName tmp;
#ifdef SYMBIAN_DNS_PUNYCODE
tmp.EnableIdn(aIdnEnabled);
#endif
TInt i = tmp.SetName(*this, aOffset);
if (i < 0)
return i;
TInt err = tmp.GetName(aName);
// This function must return offset when no errors
return err == KErrNone ? i : err;
}
// TMsgBuf::AppendName
// *******************
/**
// @param aName
// domain name to be appended
// @param aCompressed
// @li = 0,
// the name is assumed to be complete name will be terminated with "root label"
// @li > 0,
// the name is assumed to be a prefix to another name already stored into the
// message starting at indicated offset.
// The name (prefix) is terminated with a "compression ptr".
// The value must be in range [1..Length()-1].
//
// @returns
// @li KErrNone
// if name appended succesfully
// @li KErrDndNameTooBig,
// if name does not fit the message buffer
// @li KErrBadName,
// if name is invalid, e.g. has empty or too long (> 63)
// components
// @li KErrDndUnknown
// if aCompressed is illegal, e.g. not in range [0..Length()-1]
*/
TInt TMsgBuf::AppendName(const TDesC8 &aName, const TUint aCompressed)
{
if (aCompressed >= (TUint)Length())
return KErrDndUnknown; // Don't allow forward references in compression pointers.
// The space requirement
// + length of the iName
// and
// + 1 (for the terminator NUL byte, root)
// or
// + 2 (for the compression pointer)
//
// [note: if the name already has a trailing '.', the code below
// will handle it, but the spacerequirement computation is too
// large by one byte -- a minor discrepancy, which is
// not worth coding -- msa]
if (Length() + aName.Length() + 1 + (aCompressed > 0) > MaxLength())
return KErrDndNameTooBig;
//
// Append <domain-name> as labels
//
TInt i;
TPtrC8 name(aName);
while ((i = name.Locate('.')) != KErrNotFound)
{
// a valid label contains at least one and at most 63 characters
if (i < 1 || i > 63)
return KErrDndBadName; // ..abort message build!
Append((TChar)i);
Append(name.Left(i));
name.Set(name.Mid(i+1)); // Skip the dot.
}
i = name.Length();
if (i > 0)
{
// append the last component...
if (i > 63)
return KErrDndBadName;
Append((TChar)i);
Append(name);
}
//
// Append terminator
//
if (!aCompressed)
Append((TChar)0); //Root
else
{
Append((TChar)((aCompressed / 0x100) | 0xC0));
Append((TChar)(aCompressed % 0x100));
}
return KErrNone;
}
// TMsgBuf::GetRR
// **************
/**
// @param aOffset of the recource record (points to NAME)
// @retval aType RR type
// @retval aClass RR class
// @retval aTTL RR TTL (time to live)
// @retval aRD offset to the beginning of the RDATA
// @retval aRDLength the length of the RDATA
//
// @return offset pointing after the RR.
*/
TInt TMsgBuf::GetRR(const TInt aOffset, TUint16 &aType, TUint16 &aClass, TUint32 &aTTL, TUint &aRd, TUint &aRdLength) const
{
return Header().Resource(aOffset, Length(), aType, aClass, aTTL, aRd, aRdLength);
}
TInt TMsgBuf::SkipName(TInt aOffset) const
{
return Header().NameSkip(aOffset, Length());
}
// TDndName::SetName
// *****************
// Set PTR name from address
//
/**
// Translate the address into a query name for the PTR query.
// For example 172.21.33.80 -> 80.33.21.172.in-addr.arpa.
//
// @param aAddr the address to be translated into PTR name
// @returns
// @li KErrnone, if translation succeeded
// @li KErrNotSupported, if translation not supported
// (bad address or unknown address family)
*/
TInt TDndName::SetName(const TInetAddr &aAddr)
{
if (aAddr.Family() == KAfInet ||
aAddr.IsV4Mapped() || // V4 Mapped is internal, and always means IPv4
aAddr.IsV4Compat()) // Treating V4 Compatible as IPv4 might be questionable?
{
TUint32 address = aAddr.Address();
if (address == KInetAddrNone)
return KErrNotSupported;
Format(_L8("%d.%d.%d.%d"),
(TUint8)(address),
(TUint8)(address >> 8),
(TUint8)(address >> 16),
(TUint8)(address >> 24));
Append(KIPv4AddrToHost);
return KErrNone;
}
else if (aAddr.Family() == KAfInet6) // for IPv6
{
SetLength(0);
const TIp6Addr &address = aAddr.Ip6Address();
// ..are there any addresses that should never be
// PTR queried? unspecified? loopback? anything else?
// Or, should even these tests removed and let the
// query just fail normally? -- msa
if (address.IsUnspecified() ||
address.IsLoopback())
return KErrNotSupported;
for (TInt i = 15; i >= 0; i--)
{
for (TInt j = 0; j < 2; j++)
{
TChar c;
TUint8 digit = (TUint8)((address.u.iAddr8[i] >> (4 * j)) & 0x0f);
if (digit < 10)
c = (TChar)('0' + digit);
else
c = (TChar)('a' + digit - 10);
Append(c);
if (i != 0 || j != 1)
Append('.');
}
}
Append(KIPv6AddrToHost);
return KErrNone;
}
return KErrNotSupported;
}
//
// TDndName::SetName
// *****************
/**
// Uncompres the <domain-name> from a DNS message into name
//
// @param aBuf buffer containing the DNS message packet
// @param aOffset from the start of the message to the name
// @returns
// @li > 0, updated offset past the extracted name, if succesful
// @li < 0, if some error
*/
TInt TDndName::SetName(const TMsgBuf &aBuf, const TInt aOffset)
{
Zero();
return aBuf.GetNextName(aOffset, *this);
}
// TDndName::SetName
// *****************
// Set name from THostName
//
/**
// Translate system encoding into query name. In Unicode this
// means converting the Unicode to equivalent DNS name. For
// narrow builds, this is usually a straight copy.
//
// @param aName the name to be translated into DNS format
// @return
// @li KErrNone, if translation succeeded
// @li KErrNotSupported, if translation failed
*/
TInt TDndName::SetName(const THostName &aName)
{
#ifdef SYMBIAN_DNS_PUNYCODE
/* The UTF-16 encoded domain names are not supported irrespective
* of whether the IDN support is enable or not.
*/
TBool isNameIDN= EFalse;
TUint8 nameLen = aName.Length();
for (TUint8 idxCounter = 0; idxCounter < nameLen; idxCounter++)
{
const TUint16 currVal = aName[idxCounter];
if (currVal >= 0x80 )
{
if(ISUTF16(currVal))
{
return KErrDndBadName; // UTF-16 encoding is currently not supported.
}
isNameIDN = ETrue;
}
}
if(isNameIDN && iIdnEnabled)
{
TInt err = iPunyCodeName.IdnToPunycode(aName);
// Set this to offset any previous setting (if any)
iPunyCodeConverted = EFalse;
if (err == KErrNone)
{
Copy(iPunyCodeName);
iPunyCodeConverted = ETrue;
}
else
return KErrDndBadName; // returning error that PunyCode conversion failed.
}
else
{
#endif
Copy(aName);
#ifdef SYMBIAN_DNS_PUNYCODE
}
#endif //SYMBIAN_DNS_PUNYCODE
//
// Eliminate single trailing '.', if present
const TInt index = Length() - 1;
if (index >= 0 && (*this)[index] == '.')
SetLength(index);
return KErrNone;
}
// TDndName::GetName
// *****************
// Get name into buffer
/**
// This is reverse of the SetName and translates DNS name
// into system specific encoding.
//
// This function behaves like a "copy", if aStart = 0 (the default),
// or "append", if aStart = aName.Length().
//
// @retval aName
// receives the translated name. The length of the
// buffer is determined by the end of the translated
// name.
// @param aStart a start offset for the name
// @return
// @li KErrNone, if translation succeeded
// @li KErrDndNameTooBig, name does not fit the buffer
*/
TInt TDndName::GetName(TDes &aBuf, const TInt aStart) const
{
#ifdef SYMBIAN_DNS_PUNYCODE
TPunyCodeDndName tempPunycodeName(iPunyCodeName);
TInt checkPrefix = tempPunycodeName.Find(KAcePrefix());
if( checkPrefix != KErrNotFound && iPunyCodeConverted && iIdnEnabled)
{
TInt err = tempPunycodeName.PunycodeToIdn(aBuf,aStart);
if (err == KErrNone)
{
const TInt punycodeLength = tempPunycodeName.Length();
if (aStart + punycodeLength > aBuf.MaxLength())
{
return KErrDndNameTooBig;
}
aBuf.SetLength(aStart + punycodeLength);
TUint8 nameLen = aBuf.Length();
for (TUint8 idxCounter = aStart; idxCounter < nameLen; idxCounter++)
{
const TUint16 currVal = aBuf[idxCounter];
if (currVal >= 0x80 && ISUTF16(currVal))
{
// UTF-16 encoding is not supported.
// so copying the punycode as it is.
for (TInt i = 0; i < punycodeLength; ++i)
{
aBuf[aStart+i] = (TText)((tempPunycodeName)[i]);
}
return err;
}
}
return err;
}
else
{
return KErrDndBadName; // returning error that PunyCode conversion failed.
}
}
else
{
#endif //SYMBIAN_DNS_PUNYCODE
const TInt N = Length();
if (aStart + N > aBuf.MaxLength())
return KErrDndNameTooBig;
aBuf.SetLength(aStart + N);
for (TInt i = 0; i < N; ++i)
aBuf[aStart+i] = (TText)((*this)[i]);
return KErrNone;
#ifdef SYMBIAN_DNS_PUNYCODE
}
#endif //SYMBIAN_DNS_PUNYCODE
}
// TDndName::GetAddress
// ********************
/**
// This is reverse of SetName with TInetAddr and translates
// the content of *.ip6.arpa or *.in-addr.arpa into TInetAddr,
// if the address is complete.
//
// @retval aAddr The address, if conversion is possible
// @return TRUE, if conversion was possible, and FALSE otherwise.
*/
TBool TDndName::GetAddress(TInetAddr &aAddr) const
{
TInt k;
TLex8 decode(*this);
// Initialize to V4 mapped format, so that it is ready
// to be "patched" with IPv4 address. If the address
// is IPv6, all of the address will be overwritten.
TIp6Addr addr = {{{0,0,0,0,0,0,0,0,0,0,0xff,0xff,0,0,0,0}}};
if ((k = Find(KIPv4AddrToHost)) >= 0)
{
if (k + KIPv4AddrToHost().Length() != Length())
return FALSE;
for (TInt i = sizeof(addr); --i >= (TInt)(sizeof(addr) - 4); )
{
// Note: the check is loose on extra leading zeroes
// (which should not be present, but are accepted
// here).
TUint32 octet;
if (decode.Val(octet, EDecimal, 255) != KErrNone)
return FALSE; // Too large value
if (decode.Get() != '.')
return FALSE;
addr.u.iAddr8[i] = (TUint8)octet;
}
}
else if ((k = Find(KIPv6AddrToHost)) >= 0)
{
if (k + KIPv6AddrToHost().Length() != Length())
return FALSE;
TInt j = sizeof(addr);
for (TUint i = 0; i < sizeof(addr)*2; ++i)
{
// Note: the check is loose on extra leading zeroes
// (which should not be present, but are accepted
// here).
TUint32 nibble;
if (decode.Val(nibble, EHex, 0xF) != KErrNone)
return FALSE;
if (decode.Get() != '.')
return FALSE;
if (i & 1)
{
// high nibble (always after low nibble, just OR high bits in)
addr.u.iAddr8[j] |= (TUint8)(nibble << 4);
}
else
{
ASSERT(j > 0);
// low nibble (this will happen first)
j -= 1;
addr.u.iAddr8[j] = (TUint8)nibble;
}
}
}
// The decoding must eat everything upto in-addr.arpa or ip6.arpa!
if (decode.Offset() != k+1)
return FALSE;
aAddr.SetAddress(addr);
return TRUE;
}
// Creating TDndQuestion from the question section of the UDP message. The question
// section starts at position 'aIndex' of the buffer 'aBuf'. The function returns the
// offset pointing over the question section on return (or error).
//
// Returns
// > 0, the offset pointing over the question section
// < 0, an error is detected
//
TInt TDndQuestion::Create(const TMsgBuf &aBuf, TInt aIndex)
{
Zero();
TInt i = aBuf.GetNextName(aIndex, *this);
if (i > 0)
{
if (aBuf.Length() < i + 4)
return KErrDndDiscard;
iQType = EDnsQType((aBuf[i] << 8) | aBuf[i+1]);
iQClass = EDnsQClass((aBuf[i+2] << 8) | aBuf[i+3]);
i += 4;
}
return i;
}
// TDndQuestion::Append(aMsgBuf, aFlags)
// *************************************
// Append a Question section to aMsgBuf
/**
//
// @retval aMsgBuf the DNS message to be modified
// @param aCompressed
// value of is used to convey whether the name contains a full name or only
// a prefix part.
// @li = 0,
// the name is full name
// @li > 0,
// the name is only a prefix to the name already stored in to the
// message start at this offset.
// @returns
// @li KErrNone
// if question section appended succesfully
// @li KErrDndNameTooBig,
// if question does not fit the message buffer
// @li KErrBadName,
// if name is invalid, e.g. has empty or too long (> 63)
// components
// @li KErrDndUnknown
// if aFlags is illegal, not in [0..Length()-1]
*/
TInt TDndQuestion::Append(TMsgBuf& aMsgBuf, TUint aCompressed) const
{
const TInt err = aMsgBuf.AppendName(*this, aCompressed);
if (err != KErrNone)
return err;
if (aMsgBuf.MaxLength() < aMsgBuf.Length() + 4)
return KErrDndNameTooBig;
// Append the QType
aMsgBuf.Append((TChar)(iQType / 0x100));
aMsgBuf.Append((TChar)(iQType % 0x100));
// Append the QClass
aMsgBuf.Append((TChar)(iQClass / 0x100));
aMsgBuf.Append((TChar)(iQClass % 0x100));
return KErrNone;
}
// TDndQuestion::CheckQuestion
// ***************************
/**
// Compares the name, query type and query class.
//
// @param aQuestion to be compared with
// @returns
// @li KErrNone, if questions are equal
// @li KErrDndDiscard, if questions are not equal
*/
TInt TDndQuestion::CheckQuestion(const TDndQuestion &aQuestion) const
{
return
(
aQuestion.iQClass == iQClass &&
aQuestion.iQType == iQType &&
DnsCompareNames(*this, aQuestion)) ? KErrNone : KErrDndDiscard;
}
// TDndRR::GetSockAddr
// *******************
/**
// @retval aName contains the resulting name, if return is OK
// @param aOffset to the start of name, relative to the RDATA
// @returns offset pointing to next octet after the extracted value (this
// offset is relative to the DNS message start). Negative returns
// are error returns.
*/
TInt TDndRR::GetSockAddr(TInetAddr& aSockAddr) const
{
if (iType == EDnsType_A && iClass == EDnsClass_IN && iRdLength == 4)
{
const TUint32 KInetAddr = INET_ADDR(iBuf[iRd+0], iBuf[iRd+1], iBuf[iRd+2], iBuf[iRd+3]);
aSockAddr.SetAddress(KInetAddr);
return iRd + 4;
}
else if(iType == EDnsType_AAAA && iClass == EDnsClass_IN)
{
if (iRdLength < sizeof(TIp6Addr))
return KErrDndUnknown;
// TIp6Addr byteorder is network order, so can just copy
// bytes as is from the resource data... but, as RR is
// not necessarily aligned properly, must use a temporary
// address buffer for the transfer..
TIp6Addr addr;
Mem::Copy(addr.u.iAddr8, &iBuf[iRd], sizeof(addr.u.iAddr8));
aSockAddr.SetAddress(addr);
return iRd + sizeof(addr.u.iAddr8);
}
return KErrDndUnknown;
}
// TDndRR::GetNameFromRDATA
// ************************
/**
//
// @retval aName contains the extracted domain name.
// @param aOffset to the start of the name, relative to the RDATA
// @returns offset pointing to next octet after the extracted value (this
// offset is relative to the DNS message start). Negative returns
// are error returns.
*/
TInt TDndRR::GetNameFromRDATA(TDes8 &aName, TUint aOffset) const
{
// return aName.SetName(iBuf, iRd + aOffset);
aName.Zero();
return iBuf.GetNextName(iRd + aOffset, aName);
}
// TDndRR::GetStringFromRDATA
// **************************
/**
// @retval aString
// is set to point to the string content in the message buffer.
// No copying occurs, the message buffer must exist when this
// return value is used!
// @param aOffset to the start of the character string, relative to the RDATA
// @returns offset pointing to next octet after the extracted value (this
// offset is relative to the DNS message start). Negative returns
// are error returns.
*/
TInt TDndRR::GetStringFromRDATA(TPtrC8 &aString, TUint aOffset) const
{
return iBuf.GetNextString(iRd + aOffset, aString);
}
// Return TUint16 from a buffer
static TUint16 Get16(const TUint8 *p)
{
return (TUint16)(p[0] << 8 | p[1]);
}
// Return TUint32 from a buffer
static TUint32 Get32(const TUint8 *p)
{
return (TUint32)(p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3]);
}
// TDndRR::GetResponse
// *******************
// Extract content of RR
//
/**
// What exactly is returned, depends on the type of the RR (A, AAAA, CNAME and
// PTR return "normal" results, for others some special values may returned,
// see TInetDnsRR).
//
// @retval aName normally the domain name (system encoding)
// @retval aAddr normally the address (but, also see TInetDnsRR)
// @returns KErrNone if extraction succeeded, and error otherwise.
*/
TInt TDndRR::GetResponse(TDes &aName, TInetAddr &aAddr) const
{
TInt err = iName; // (just to inialize, to offset of this RR record)
TDndName name;
#ifdef SYMBIAN_DNS_PUNYCODE
name.EnableIdn(iIdnEnabled);
#endif //SYMBIAN_DNS_PUNYCODE
if ((iType == EDnsType_A || iType == EDnsType_AAAA) && iClass == EDnsClass_IN)
{
err = name.SetName(iBuf, iName);
if (err < 0)
return err;
err = GetSockAddr(aAddr);
}
else if (iType == EDnsType_PTR && iClass == EDnsClass_IN)
{
//
// For the PTR record, it is assumed the aAddr is already
// initialized to the original queried address (do not
// try to recompute address from the name part of the
// PTR record -- that is *NOT* always the address due
// to some CNAME games for the PTR records... -- msa)
//
// ..and get the real name from RData.
err = GetNameFromRDATA(name);
}
else if (iType == EDnsType_CNAME && iClass == EDnsClass_IN)
{
aAddr.SetAddress(0);
// ..and get the cname from RDATA
err = GetNameFromRDATA(name);
}
else
{
//
// Special RR values
//
TInetDnsRR::Cast(aAddr) = TInetDnsRR();
SDnsRR &rr = TInetDnsRR::Cast(aAddr).RR();
rr.iClass = (TUint16)iClass;
rr.iType = (TUint16)iType;
if (iClass == EDnsClass_IN)
{
const TUint8 *const rd = iBuf.Ptr() + iRd;
//
// Only a select subset of RR's are supported
//
if (iType == EDnsType_SRV && iRdLength > 4)
{
rr.iSRV.iPriority = Get16(rd);
rr.iSRV.iWeight = Get16(rd + 2);
aAddr.SetPort(Get16(rd + 4));
err = GetNameFromRDATA(name, 6);
}
else if (iType == EDnsType_NAPTR && iRdLength > 7)
{
rr.iNAPTR.iOrder = Get16(rd);
rr.iNAPTR.iPreference = Get16(rd + 2);
// get FLAGS
TPtrC8 flags;
err = GetStringFromRDATA(flags, 4);
if (err < 0)
return err;
TInt flagLength = sizeof(rr.iNAPTR.iFlags);
//The buffer size of TInetAddr(0x20 bytes) may not be sufficient to store the
//realtime length(upto 0xFF) of FLAGS field, so it is limited to 12 bytes. Hence if the
//flag length is more than 12 bytes, error KErrDndNameTooBig is raised.
if(flagLength < (err - iRd - 5))
return KErrDndNameTooBig;
TPtr8 rr_flags(rr.iNAPTR.iFlags, flagLength , flagLength);
rr_flags.FillZ();
rr_flags = flags;
// get SERVICES
TPtrC8 services;
err = GetStringFromRDATA(services, err - iRd);
if (err < 0)
return err;
// get REGEXP
TPtrC8 regexp;
err = GetStringFromRDATA(regexp, err - iRd);
if (err < 0)
return err;
// Fetch REPLACEMENT
err = GetNameFromRDATA(name, err - iRd);
if (err < 0)
return err;
// Try to append SERVICES and REGEXP into name
TInt length = name.Length() + services.Length() + regexp.Length();
if (length > name.MaxLength())
return KErrDndNameTooBig;
// ***** WARNING ********
// The iL1 and iL2 will be problematic if the future
// UNICODE conversion of the name is not 1 to 1!
// Should use separators instead? -- msa
rr.iNAPTR.iL1 = (TUint8)name.Length();
rr.iNAPTR.iL2 = (TUint8)services.Length();
name.Append(services);
name.Append(regexp);
}
else if (iType == EDnsType_MX && iRdLength > 2)
{
rr.iMX.iPreference = Get16(rd);
err = GetNameFromRDATA(name, 2);
}
else if (iType == EDnsType_NS)
{
err = GetNameFromRDATA(name);
}
else if (iType == EDnsType_SOA)
{
err = GetNameFromRDATA(name);
if (err < 0)
return err;
if (name.Length() == name.MaxLength())
return KErrDndNameTooBig;
name.Append('@');
err = iBuf.GetNextName(err, name);// *APPEND* RNAME to hostname!
if (err < 0)
return err;
err -= iRd;
if (err < 0 || err + 20 > (TInt)iRdLength)
return KErrDndUnknown;
rr.iSOA.iSerial = Get32(rd+err);
rr.iSOA.iRefresh = Get32(rd+err+4);
rr.iSOA.iRetry = Get32(rd+err+8);
rr.iSOA.iExpire = Get32(rd+err+12);
rr.iSOA.iMinimum = Get32(rd+err+16);
}
}
}
return err > 0 ? name.GetName(aName) : err;
}
#ifndef NO_DNS_QUERY_SUPPORT
// TDndRR::GetResponse
// *******************
// Extract content of RR
//
/**
// What exactly is returned, depends on the type of the RR
//
// @retval aAnswer the "payload" will receive the RR content in TDnsQryRespBase format
// @retval aAddr a pointer to "TInetAddr *" within the reply (only for A and AAAA, NULL otherwise)
// @returns offset pointing to next octet after the extracted value (this
// offset is relative to the DNS message start). Negative returns
// are error returns.
*/
TInt TDndRR::GetResponse(TDnsMessageBuf &aAnswer, TInetAddr **aAddr) const
{
TInt err = KErrNotSupported;
const TInt max_len = aAnswer.MaxLength() - aAnswer().HeaderSize();
TInt len = 0;
*aAddr = NULL;
const TUint8 *const rd = iBuf.Ptr() + iRd;
switch ((TUint16)iType)
{
case KDnsRRTypeA:
{
TDndRespA &a = aAnswer().A();
len = sizeof(a);
if (len > max_len)
return KErrOverflow;
(void)new (&a) TDndRespA();
err = GetSockAddr(a.HostAddress());
*aAddr = &a.HostAddress();
}
break;
case KDnsRRTypeAAAA:
{
TDndRespAAAA &aaaa = aAnswer().AAAA();
len = sizeof(aaaa);
if (len > max_len)
return KErrOverflow;
(void)new (&aaaa) TDndRespAAAA();
err = GetSockAddr(aaaa.HostAddress());
*aAddr = &aaaa.HostAddress();
}
break;
#if 0
case KDnsRRTypeNS:
{
TDndRespNS &ns = aAnswer().NS();
len = sizeof(ns);
if (len > max_len)
return KErrOverflow;
(void)new (&ns) TDndRespNS();
err = GetNameFromRDATA(ns.HostName());
}
break;
case KDnsRRTypeCNAME:
break;
case KDnsRRTypeWKS:
break;
#endif
case KDnsRRTypePTR:
{
TDndRespPTR &ptr = aAnswer().PTR();
len = sizeof(ptr);
if (len > max_len)
return KErrOverflow;
(void)new (&ptr) TDndRespPTR();
err = GetNameFromRDATA(ptr.HostName());
}
break;
#if 0
case KDnsRRTypeHINFO:
break;
#endif
case KDnsRRTypeMX:
if (iRdLength > 2)
{
TDndRespMX &mx = aAnswer().MX();
len = sizeof(mx);
if (len > max_len)
return KErrOverflow;
(void)new (&mx) TDndRespMX();
mx.SetPref(Get16(rd));
err = GetNameFromRDATA(mx.HostName(), 2);
}
else
err = KErrDndUnknown;
break;
#if 0
case KDnsRRTypeTXT:
break;
#endif
case KDnsRRTypeSRV:
if (iRdLength > 4)
{
TDndRespSRV &srv = aAnswer().SRV();
len = sizeof(srv);
if (len > max_len)
return KErrOverflow;
(void)new (&srv) TDndRespSRV();
srv.SetPriority(Get16(rd));
srv.SetWeight(Get16(rd + 2));
srv.SetPort(Get16(rd + 4));
err = GetNameFromRDATA(srv.Target(), 6);
}
else
err = KErrDndUnknown;
break;
case KDnsRRTypeNAPTR:
if (iRdLength > 7)
{
TDndRespNAPTR &naptr = aAnswer().NAPTR();
len = sizeof(naptr);
if (len > max_len)
return KErrOverflow;
(void)new (&naptr) TDndRespNAPTR();
naptr.SetOrder(Get16(rd));
naptr.SetPref(Get16(rd + 2));
// get FLAGS
TPtrC8 flags;
err = GetStringFromRDATA(flags, 4);
if (err < 0)
break;
naptr.SetFlags(flags);
// get SERVICES
TPtrC8 services;
err = GetStringFromRDATA(services, err - iRd);
if (err < 0)
break;
naptr.SetService(services);
// get REGEXP
TPtrC8 regexp;
err = GetStringFromRDATA(regexp, err - iRd);
if (err < 0)
break;
naptr.SetRegexp(regexp);
// Fetch REPLACEMENT
err = GetNameFromRDATA(naptr.Replacement(), err - iRd);
}
else
err = KErrDndUnknown;
break;
#if 0
case EDnsType_SOA:
{
TDndRespSOA &soa = aAnswer().SOA();
len = sizeof(soa);
if (len > max_len)
return KErrOverflow;
new (&soa) TDndRespSOA();
err = GetNameFromRDATA(soa.MName());
if (err < 0)
return err;
err = GetNameFromRDATA(soa.RName(), err - iRd);
if (err < 0)
return err;
const TInt i = err - iRd;
if (i < 0 || i + 20 > (TInt)iRdLength)
return KErrDndUnknown;
soa.SetSerial(Get32(rd+i));
soa.SetRefresh(Get32(rd+i+4));
soa.SetRetry(Get32(rd+i+8));
soa.SetExpire(Get32(rd+i+12));
soa.SetMininum(Get32(rd+i+16));
}
break;
#endif
default:
// For all yet unsupported replies, return only
// the basic reply: TDnsQryRespBase
{
TDndReply &basic = aAnswer().Reply();
len = sizeof(basic);
if (len > max_len)
return KErrOverflow;
(void)new (&basic) TDndReply((TUint16)iType, (TUint16)iClass);
basic.SetRRTtl(iTTL);
}
break;
}
aAnswer.SetLength(len + aAnswer().HeaderSize());
// Return the TTL
aAnswer().Reply().SetRRTtl(iTTL);
return err;
}
#endif
//
// TDndRR::FindRR
// **************
// Locate Nth (aNext) RR entry from the message
//
/**
// The RR's are "virtually" numbered from 0 to aNumRR-1.
// All RR's matching the original query are numbered
// first, and all others follow them.
//
// @param aOffset from the start of the message to the beginning of the RR section
// @param aNumRR the number of the RR's in this section
// @param aType the original query type
// @param aClass the original query class
// @param aNext select the Nth RR to be returned [0..aNumRR-1]
// @returns
// @li > 0, Nth resource entry located (value is offset to first octet following this RR)
// @li < 0, the following errors:
// @li KErrDndNoRecord, there was no Nth RR.
// @li KErrDndCache, the message is corrupt and should be discarded
// @li Never returns 0 (KErrNone)!
*/
TInt TDndRR::FindRR(TInt aOffset, TInt aNumRR, EDnsQType aType, EDnsQClass aClass, TInt aNext)
{
TInt count = 0;
aNext++; // Looking for this! (internally numbered from 1..n)
for (TInt any = 0;; any = 1)
{
TInt offset = aOffset; // Start from beginning of RR's
for (TInt j = 0; j < aNumRR; j++)
{
iName = offset;
offset = iBuf.GetRR(iName, iType, iClass, iTTL, iRd, iRdLength);
if (offset < 0)
// ..if the above Resource() access fails (-1), then the stored cache
// record must be corrupt, indicate so to the caller...
return KErrDndCache;
if (EDnsQType(iType) == aType && EDnsQClass(iClass) == aClass)
// on first pass (any == 0), only count exact matches
count += any ? 0 : 1;
else
// on second pass (any == 1), only count non-matches (CNAMES, etc.)
count += any ? 1 : 0;
if (count == aNext)
{
// Found the target RR
return offset;
}
}
// exit, if second pass completed!
if (any != 0)
break;
}
//
// No matching entry located
//
return KErrDndNoRecord;
}
/**
// The RR's are numbered from 0 to aNumRR-1.
// @param aOffset from the start of the message to the first RR to be tested
// @param aNumRR the number of the RR's
// @param aType the query type
// @param aClass the query class (EDnsQClass_ANY matches any class)
// @retval aStart the first RR to test [0..aNumRR-1]
// @returns
// @li > 0, Nth resource entry located (value is offset to first octet following this RR)
// @li < 0, the following errors:
// @li KErrDndNoRecord, there was no such RR
// @li KErrDndCache, the message is corrupt and should be discarded
// @li Never returns 0 (KErrNone)!
*/
TInt TDndRR::LocateRR(TInt aOffset, TInt aNumRR, EDnsQType aType, EDnsQClass aClass, TInt aStart)
{
for (TInt j = 0; j < aNumRR; j++)
{
iName = aOffset;
aOffset = iBuf.GetRR(iName, iType, iClass, iTTL, iRd, iRdLength);
if (aOffset < 0)
// ..if the above Resource() access fails (-1), then the stored cache
// record must be corrupt, indicate so to the caller...
return KErrDndCache;
if (j >= aStart && EDnsQType(iType) == aType && (EDnsQClass(aClass) == EDnsQClass_ANY || EDnsQClass(iClass) == aClass))
{
// The object has been located
return aOffset;
}
}
//
// No matching entry located
//
return KErrDndNoRecord;
}
#ifdef LLMNR_ENABLED
/**
// Append a new RR
//
// This will append a new complete RR (with empty RDATA, RDLENGTH = 0)
// to the message buffer.
//
// @param aName
// content of the NAME field
// @param aCompression
// if non-zero, the name portion indicated by this
// offset will be logically conctenated into the
// NAME field.
// @returns
// @li KErrNone, on success
// @li KErrDndNameTooBig, when RR does not fit into the buffer
*/
TInt TDndRROut::Append(const TDesC8 &aName, TInt aCompression)
{
// Append the NAME
TInt ret = iBuf.AppendName(aName, aCompression);
if (ret != KErrNone)
return ret;
// Available space after fixed portion of the RR (after RDLENGTH)
// has been appended.
const TInt room = iBuf.MaxLength() - iBuf.Length() - 10;
if (room < 0)
return KErrDndNameTooBig; // Oops.. cannot fit RR into message (a bit dubious error code)
// Append the TYPE
iBuf.Append((TChar)(iType / 0x100));
iBuf.Append((TChar)(iType % 0x100));
// Append the CLASS
iBuf.Append((TChar)(iClass / 0x100));
iBuf.Append((TChar)(iClass % 0x100));
// Append the TTL
iBuf.Append((TChar)(iTTL / 0x1000000));
iBuf.Append((TChar)((iTTL / 0x10000) % 0x100));
iBuf.Append((TChar)((iTTL / 0x100) % 0x100));
iBuf.Append((TChar)(iTTL % 0x100));
// Append the RDLENGTH (placeholder)
iBuf.Append((TChar)(0));
iBuf.Append((TChar)(0));
iRd = iBuf.Length();
return KErrNone;
}
/**
// Extend the RR with address RDATA
//
// This will append address RDATA the Resource Record.
// Only types AAAA or A are supported, and the length
// and format of the RDATA is selected accordingly.
//
// @param aAddr specifies the address to be appended
//
// @returns
// @li KErrNone, on success
// @li KErrDndNameTooBig, when RR does not fit into the buffer
// @li KErrDndNotImplemented, if RR type is not A or AAAA.
*/
TInt TDndRROut::AppendRData(const TInetAddr &aAddr) const
{
const TInt room = iBuf.MaxLength() - iBuf.Length();
if (iType == EDnsType_A)
{
if (room < 4)
return KErrDndNameTooBig; // Oops.. cannot fit RR into message (a bit dubious error code)
TUint32 address = aAddr.Address();
iBuf.Append((TChar)(address >> 24));
iBuf.Append((TChar)(address >> 16));
iBuf.Append((TChar)(address >> 8));
iBuf.Append((TChar)address);
}
else if(iType == EDnsType_AAAA)
{
// assumes aAddr is in KAfInet6 format!!!
const TIp6Addr &address = aAddr.Ip6Address();
if (room < (TInt)sizeof(address.u.iAddr8))
return KErrDndNameTooBig; // Oops.. cannot fit RR into message (a bit dubious error code)
iBuf.Append(address.u.iAddr8, sizeof(address.u.iAddr8));
}
else
return KErrDndNotImplemented;
AppendRData();
return KErrNone;
}
/**
// Extend the RR with domain name
//
// This will append domain name RDATA the Resource Record.
//
// @param aName
// the domain name to be added
// @param aCompression
// if non-zero, the name portion indicated by this
// offset will be logically conctenated into aName.
//
// @returns
// @li KErrNone, on success
// @li KErrDndNameTooBig, when RR does not fit into the buffer
*/
TInt TDndRROut::AppendRData(const TDesC8 &aName, TInt aCompression) const
{
TInt ret = iBuf.AppendName(aName, aCompression);
AppendRData();
return ret;
}
/**
// Extend the RR with any binary data
//
// This will append binary data into the Resource Record.
// (just a convenience method, which provides length checking
// and implicitly calls AppendRData())
//
// @param aRData the data to be appended
//
// @returns
// @li KErrNone, on success
// @li KErrDndNameTooBig, when RR does not fit into the buffer
*/
TInt TDndRROut::AppendRData(const TDesC8 &aRData) const
{
const TInt room = iBuf.MaxLength() - iBuf.Length();
if (room < aRData.Length())
return KErrDndNameTooBig;
iBuf.Append(aRData);
AppendRData();
return KErrNone;
}
/**
// Update the RDLENGTH to match the current appended data
//
// Assume Recource Record is complete and compute
// the total amount RDATA that has been appended.
//
// This can be called any number of times.
*/
void TDndRROut::AppendRData() const
{
// Append(..) must have been called before this!
__ASSERT_DEBUG(iRd > 1, User::Panic(_L("DEBUG"), 0));
// Patch in the RD lenght
const TInt length = iBuf.Length() - iRd;
iBuf[iRd-2] = (TUint8)(length / 0x100);
iBuf[iRd-1] = (TUint8)(length % 0x100);
}
#endif