singrdk/base/Services/NetStack/Protocol/DnsFmt.cs

917 lines
34 KiB
C#

///////////////////////////////////////////////////////////////////////////////
//
// Microsoft Research Singularity
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// File: DnsFmt.cs
//
// Note:
//
// Constants taken from http://www.iana.org/assignments/dns-parameters
//
// Caveat Emptor:
//
// This implementation only supports message compression for messages
// received and not for outbound messages. (RFC1035 section 4.1.4)
//
using System;
using System.Collections;
using System.Text;
using System.Diagnostics;
using System.Net.IP;
using Drivers.Net;
using Microsoft.Contracts;
namespace NetStack.Protocols.Dns
{
public enum Class : ushort
{
// 0 Reserved // [IANA]
Internet = 1, // [RFC1035]
// 2 Unassigned // [IANA]
Chaos = 3, // [RFC1035]
Hesiod = 4, // [RFC1035]
// 5-253 Unassigned // [IANA]
None = 254, // [RFC2136]
Any = 255 // [RFC1035]
// 256-65534 Unassigned // [IANA]
// 65535 Reserved // [IANA]
}
public enum Type : ushort
{
A = 1, // a host address [RFC1035]
NS = 2, // an authoritative name server [RFC1035]
MD = 3, // a mail destination (Obsolete - use MX) [RFC1035]
MF = 4, // a mail forwarder (Obsolete - use MX) [RFC1035]
CNAME = 5, // the canonical name for an alias [RFC1035]
SOA = 6, // marks the start of a zone of authority [RFC1035]
MB = 7, // a mailbox domain name (EXPERIMENTAL) [RFC1035]
MG = 8, // a mail group member (EXPERIMENTAL) [RFC1035]
MR = 9, // a mail rename domain name (EXPERIMENTAL)[RFC1035]
NULL = 10, // a null RR (EXPERIMENTAL) [RFC1035]
WKS = 11, // a well known service description [RFC1035]
PTR = 12, // a domain name pointer [RFC1035]
HINFO = 13, // host information [RFC1035]
MINFO = 14, // mailbox or mail list information [RFC1035]
MX = 15, // mail exchange [RFC1035]
TXT = 16, // text strings [RFC1035]
RP = 17, // for Responsible Person [RFC1183]
AFSDB = 18, // for AFS Data Base location [RFC1183]
X25 = 19, // for X.25 PSDN address [RFC1183]
ISDN = 20, // for ISDN address [RFC1183]
RT = 21, // for Route Through [RFC1183]
NSAP = 22, // for NSAP address, NSAP style A record [RFC1706]
NSAPPTR = 23,
SIG = 24, // for security signature [RFC2931]
KEY = 25, // for security key [RFC2535]
PX = 26, // X.400 mail mapping information [RFC2163]
GPOS = 27, // Geographical Position [RFC1712]
AAAA = 28, // IP6 Address [Thomson]
LOC = 29, // Location Information [Vixie]
NXT = 30, // Next Domain - OBSOLETE [RFC2535, RFC3755]
EID = 31, // Endpoint Identifier [Patton]
NIMLOC = 32, // Nimrod Locator [Patton]
SRV = 33, // Server Selection [RFC2782]
ATMA = 34, // ATM Address [Dobrowski]
NAPTR = 35, // Naming Authority Pointer [RFC2168, RFC2915]
KX = 36, // Key Exchanger [RFC2230]
CERT = 37, // CERT [RFC2538]
A6 = 38, // A6 [RFC2874]
DNAME = 39, // DNAME [RFC2672]
SINK = 40, // SINK [Eastlake]
OPT = 41, // OPT [RFC2671]
APL = 42, // APL [RFC3123]
DS = 43, // Delegation Signer [RFC3658]
SSHFP = 44, // SSH Key Fingerprint [RFC-ietf-secsh-dns-05.txt]
RRSIG = 46, // RRSIG [RFC3755]
NSEC = 47, // NSEC [RFC3755]
DNSKEY = 48, // DNSKEY [RFC3755]
UINFO = 100, // [IANA-Reserved]
UID = 101, // [IANA-Reserved]
GID = 102, // [IANA-Reserved]
UNSPEC = 103, // [IANA-Reserved]
TKEY = 249, // Transaction Key [RFC2930]
TSIG = 250, // Transaction Signature [RFC2845]
IXFR = 251, // incremental transfer [RFC1995]
AXFR = 252, // transfer of an entire zone [RFC1035]
MAILB = 253, // mailbox-related RRs (MB, MG or MR) [RFC1035]
MAILA = 254, // mail agent RRs (Obsolete - see MX) [RFC1035]
ALL = 255, // A request for all records [RFC1035]
}
public enum OpCode : byte
{
Query = 0, // [RFC1035]
// IQUERY = 1 // OpCode Retired [RFC3425]
Status = 2, // [RFC1035]
// 3 // reserved [IANA]
Notify = 4, // [RFC1996]
Update = 5, // [RFC2136]
// 6-15 available for assignment
}
public enum RCode : ushort
{
NoError = 0, // No Error [RFC1035]
FormErr = 1, // Format Error [RFC1035]
ServFail = 2, // Server Failure [RFC1035]
NXDomain = 3, // Non-Existent Domain [RFC1035]
NotImp = 4, // Not Implemented [RFC1035]
Refused = 5, // Query Refused [RFC1035]
YXDomain = 6, // Name Exists when it should not [RFC2136]
YXRRSet = 7, // RR Set Exists when it should not [RFC2136]
NXRRSet = 8, // RR Set that should exist does not [RFC2136]
NotAuth = 9, // Server Not Authoritative for zone [RFC2136]
NotZone = 10, // Name not contained in zone [RFC2136]
Max4BitCode = NotZone,
// 11-15, Available for assignment
BADVERS = 16, // Bad OPT Version [RFC2671]
BADSIG = 16, // TSIG Signature Failure [RFC2845]
BADKEY = 17, // Key not recognized [RFC2845]
BADTIME = 18, // Signature out of time window [RFC2845]
BADMODE = 19, // Bad TKEY Mode [RFC2930]
BADNAME = 20, // Duplicate key name [RFC2930]
BADALG = 21, // Algorithm not supported [RFC2930]
// 22-3840 available for assignment
// 0x0016-0x0F00
// 3841-4095 Private Use
// 0x0F01-0x0FFF
// 4096-65535 available for assignment
// 0x1000-0xFFFF
}
public enum Flags : ushort
{
NONE = 0,
QR = 0x8000, // Response (1) vs Query (0)
AA = 0x0400, // Authoritative (1) vs Non-authoritative (0)
TC = 0x0200, // Truncated
RD = 0x0100, // Recursion Desired
RA = 0x0080, // Recursion Available
MBZ = 0x0040, // Must-Be-Zero
AD = 0x0020, // Authenticated data (DNSSEC)
CD = 0x0010, // Checking Disabled (DNSSEC)
ALL = 0x87f0, // All bit-field bits
}
/// <summary>
/// Convert between textual and binary DNS names.
/// </summary>
///
/// <remarks>
/// <para>
/// RFC1035 describes how names should be packed in DNS
/// queries and responses. The method is described as:
/// </para>
/// <para>
/// Domain names in messages are expressed in terms of a
/// sequence of labels. Each label is represented as a one
/// octet length field followed by that number of octets.
/// Since every domain name ends with the null label of the
/// root, a domain name is terminated by a length byte of
/// zero. The high order two bits of every length octet
/// must be zero, and the remaining six bits of the length
/// field limit the label to 63 octets or less.
/// </para>
/// </remarks>
public class LabelEncoding
{
private static ASCIIEncoding ascii = new ASCIIEncoding();
private LabelEncoding() {}
static public byte[] GetBytes(string! s)
{
byte[] result = new byte[GetByteCount(s)];
PutBytes(s, result, 0);
return result;
}
public static int PutBytes(string! input,
byte[]! bytes,
int byteIndex)
{
if (input.Length == 0)
{
bytes[byteIndex] = 0;
return 1;
}
ascii.GetBytes(input, 0, input.Length, bytes, byteIndex + 1);
int outIndex = byteIndex;
int finalIndex = byteIndex + input.Length + 1;
bytes[finalIndex] = 0;
for (int i = byteIndex + 1; i <= finalIndex; i++)
{
if (bytes[i] == (byte)'.' || bytes[i] == 0)
{
int blockLength = i - outIndex - 1;
if (blockLength > Format.MaxLabelLength)
throw new ArgumentException();
bytes[outIndex] = (byte)(i - outIndex - 1);
outIndex = i++;
}
}
return finalIndex + 1 - byteIndex;
}
static public int GetByteCount(string! s)
{
return (s.Length > 0) ? (2 + s.Length) : 1;
}
/// <summary>
/// Convert name from sequence of bytes to string.
/// </summary>
///
/// <returns>
/// Returns number of bytes consumed, or 0 if input is invalid.
///</returns>
public static int GetString(byte[]! bytes,
int byteIndex,
out string output)
{
int startIndex = byteIndex;
if (bytes[byteIndex] == 0)
goto fail;
// Validation and copy to mutable buffer pass
while (bytes[byteIndex] != 0)
{
if (byteIndex - startIndex - 1 > Format.MaxNameLength)
goto fail;
int offset = (int) bytes[byteIndex];
if ((offset & 0xc0) != 0)
goto fail;
int newByteIndex = byteIndex + offset + 1;
if (newByteIndex > bytes.Length)
goto fail;
byteIndex = newByteIndex;
}
// Copy data into a local buffer. At first glance,
// you might realize the unpacking could be done in
// place. However, because of RFC1035 name
// compression some other block may point to the raw
// data in the packet and expect it to be in packed
// format.
byte [] workBytes = new byte [byteIndex + 1 - startIndex];
Array.Copy(bytes, startIndex, workBytes, 0, workBytes.Length);
// Modify length bytes to '.' chars
int workIndex = 0;
while (workBytes[workIndex] != 0)
{
byte offset = workBytes[workIndex];
workBytes[workIndex] = (byte)'.';
workIndex += (int)offset + 1;
}
Debug.Assert(workIndex + 1 == workBytes.Length);
output = ascii.GetString(workBytes, 1, workIndex - 1);
return workBytes.Length;
fail:
output = "";
return 0;
}
}
public class InvalidDnsFormatException : SystemException
{
public InvalidDnsFormatException(String message)
: base(message)
{
}
public InvalidDnsFormatException(String message,
Exception innerException)
: base(message, innerException)
{
}
public InvalidDnsFormatException(String format, params object [] args)
: base (String.Format(format, args))
{
}
}
public class Query
{
public readonly string Name;
public readonly Type Type;
public readonly Class Class;
public Query(string queryName, Type queryType, Class queryClass)
{
if (queryName == null)
throw new ArgumentNullException("queryName");
this.Name = queryName;
this.Type = queryType;
this.Class = queryClass;
}
public int Size
{
// Packed name size plus 2 bytes Type, 2 bytes Class
get { return LabelEncoding.GetByteCount(Name) + 4; }
}
public int Write(byte[]! buffer, ref int offset)
{
offset += LabelEncoding.PutBytes(Name, buffer, offset);
offset += NetworkBitConverter.PutBytes((ushort)Type,
buffer, offset);
offset += NetworkBitConverter.PutBytes((ushort)Class,
buffer, offset);
return offset;
}
public static Query! Parse(byte []! buffer, ref int offset)
{
string name;
int used = LabelEncoding.GetString(buffer, offset, out name);
if (used == 0)
throw new InvalidDnsFormatException("name");
offset += used;
ushort qType = NetworkBitConverter.ToUInt16(buffer, offset);
offset += 2;
ushort qClass = NetworkBitConverter.ToUInt16(buffer, offset);
offset += 2;
return new Query(name, (Type) qType, (Class) qClass);
}
public static bool operator == (Query lhs, Query rhs)
{
if (lhs == null && rhs == null) return true;
if (lhs == null) return false;
if (rhs == null) return false;
return (lhs.Name == rhs.Name &&
lhs.Type == rhs.Type &&
lhs.Class == rhs.Class);
}
public static bool operator != (Query lhs, Query rhs)
{
return !(lhs == rhs);
}
public override bool Equals(Object other)
{
Query query = other as Query;
return ((Object.ReferenceEquals(query, null) == false) &&
(this == query));
}
public override int GetHashCode()
{
return this.Name.GetHashCode() ^ ((int)Type << 16) ^ (int)Class;
}
}
public class ResourceRecord
{
public readonly string Name;
public readonly Type Type;
public readonly Class Class;
public readonly uint TtlSeconds;
public readonly byte[] RData;
public ResourceRecord(string name,
Type recordType,
Class recordClass,
uint ttlSeconds,
byte[] rdata)
{
if (name == null)
throw new ArgumentNullException("name");
this.Name = name;
this.Type = recordType;
this.Class = recordClass;
this.TtlSeconds = ttlSeconds;
this.RData = rdata;
}
public int Size
{
get {
int baseLength = LabelEncoding.GetByteCount(Name) + 10;
if (RData == null)
return baseLength;
return baseLength + RData.Length;
}
}
public int Write(byte []! buffer, ref int offset)
{
offset += LabelEncoding.PutBytes(Name, buffer, offset);
offset += NetworkBitConverter.PutBytes((ushort) Type,
buffer, offset);
offset += NetworkBitConverter.PutBytes((ushort) Class,
buffer, offset);
offset += NetworkBitConverter.PutBytes(TtlSeconds, buffer, offset);
if (RData == null)
{
offset += NetworkBitConverter.PutBytes((ushort)0,
buffer, offset);
}
else
{
offset += NetworkBitConverter.PutBytes((ushort)RData.Length,
buffer, offset);
RData.CopyTo(buffer, offset);
offset += RData.Length;
}
return offset;
}
public static ResourceRecord Parse(byte []! buffer, ref int offset)
{
string name;
if ((buffer[offset] & 0xc0) == 0xc0)
{
// Detected compression. Name is at offset in
// lower 14 bits of next 2 bytes.
int start = NetworkBitConverter.ToInt16(buffer, offset);
if (LabelEncoding.GetString(buffer, start & 0x3fff,
out name) == 0)
throw new InvalidDnsFormatException("name");
offset += 2;
}
else
{
int used = LabelEncoding.GetString(buffer, offset, out name);
if (used == 0)
throw new InvalidDnsFormatException("name");
offset += used;
}
ushort rType = NetworkBitConverter.ToUInt16(buffer, offset);
offset += 2;
ushort rClass = NetworkBitConverter.ToUInt16(buffer, offset);
offset += 2;
uint ttlSeconds = NetworkBitConverter.ToUInt32(buffer, offset);
offset += 4;
ushort rdataLength = NetworkBitConverter.ToUInt16(buffer, offset);
offset += 2;
byte [] rdata = new byte[rdataLength];
Array.Copy(buffer, offset, rdata, 0, rdataLength);
offset += rdataLength;
return new ResourceRecord(name, (Type) rType, (Class) rClass,
ttlSeconds, rdata);
}
public static bool operator == (ResourceRecord lhs, ResourceRecord rhs)
{
if (lhs == null && rhs == null) return true;
if (lhs == null) return false;
if (rhs == null) return false;
if (lhs.Name != rhs.Name || lhs.Type != rhs.Type ||
lhs.Class != rhs.Class || lhs.TtlSeconds != rhs.TtlSeconds)
return false;
if (lhs.RData == null)
return rhs.RData == null;
if (rhs.RData == null)
return false;
if (lhs.RData.Length != rhs.RData.Length)
return false;
for (int i = 0; i < lhs.RData.Length; i++)
if (lhs.RData[i] != rhs.RData[i])
return false;
return true;
}
public static bool operator != (ResourceRecord lhs, ResourceRecord rhs)
{
return !(lhs == rhs);
}
public override bool Equals(Object other)
{
ResourceRecord rr = other as ResourceRecord;
return ((Object.ReferenceEquals(rr, null) == false) &&
(rr == this));
}
public override int GetHashCode()
{
int hashCode = this.Name.GetHashCode();
hashCode ^= (int)Type << 16;
hashCode ^= (int)Class;
if (RData != null)
{
hashCode ^= RData.Length;
for (int i = 0; i < RData.Length; i ++)
{
hashCode = hashCode ^ (hashCode << 1);
hashCode += (int)RData[i];
}
}
return hashCode;
}
}
public class Format
{
public const ushort ServerPort = 53;
public const int MaxLabelLength = 63;
public const int MinLabelLength = 1;
public const int MaxNameLength = 255;
public const int MaxUdpMessageLength = 512;
private const int OpCodeRoll = 11;
private const int OpCodeMask = 0x0f;
private const int RCodeMask = 0x0f;
private ushort id;
private Flags flags;
private OpCode opCode;
private RCode rCode;
private ArrayList! queries;
private ArrayList! answerRRs;
private ArrayList! authorityRRs;
private ArrayList! additionalRRs;
private static ushort nextId = 0;
public Format(ushort id, Flags flags, OpCode opCode, RCode rCode)
{
this.id = id;
this.queries = new ArrayList();
this.answerRRs = new ArrayList();
this.authorityRRs = new ArrayList();
this.additionalRRs = new ArrayList();
this.SetFlags(flags);
this.SetOpCode(opCode);
this.SetRCode(rCode);
}
public Format()
: this (++nextId, Flags.NONE, OpCode.Query, RCode.NoError)
{
}
public Flags GetFlags()
{
return flags;
}
[Delayed]
public void SetFlags(Flags newFlags)
{
if ((newFlags & Flags.ALL) != newFlags)
throw new ArgumentException("Invalid flags present");
flags = newFlags;
}
public OpCode GetOpCode()
{
return opCode;
}
[Delayed]
public void SetOpCode(OpCode newOpCode)
{
switch (newOpCode)
{
case OpCode.Query: break;
case OpCode.Status: break;
case OpCode.Notify: break;
case OpCode.Update: break;
default:
throw new ArgumentException("Invalid OpCode");
}
opCode = newOpCode;
}
public RCode GetRCode()
{
return rCode;
}
[Delayed]
public void SetRCode(RCode newRCode)
{
if (newRCode > RCode.Max4BitCode)
throw new ArgumentException("Invalid RCode");
rCode = newRCode;
}
public ArrayList! Queries
{
get { return queries; }
}
public ArrayList! AnswerRRs
{
get { return answerRRs; }
}
public ArrayList! AuthorityRRs
{
get { return authorityRRs; }
}
public ArrayList! AdditionalRRs
{
get { return additionalRRs; }
}
public int Size
{
get
{
int total = 12; // Fixed component
foreach (Query! q in queries)
total += q.Size;
foreach (ResourceRecord! r in answerRRs)
total += r.Size;
foreach (ResourceRecord! r in authorityRRs)
total += r.Size;
foreach (ResourceRecord! r in additionalRRs)
total += r.Size;
return total;
}
}
private void Write(byte[]! buffer, ushort value, ref int offset)
{
buffer[offset++] = (byte)((int)value >> 8);
buffer[offset++] = (byte)((int)value & 0xff);
}
public int Write(byte[]! buffer, ref int offset)
{
Debug.Assert((((int)opCode << OpCodeRoll) & (int)Flags.ALL) == 0);
Debug.Assert((((int)rCode) & (int)Flags.ALL) == 0);
Write(buffer, id, ref offset);
int word2 = ((int)opCode << OpCodeRoll) | (int)flags | (int)rCode;
Write(buffer, (ushort) word2, ref offset);
Write(buffer, (ushort) queries.Count, ref offset);
Write(buffer, (ushort) answerRRs.Count, ref offset);
Write(buffer, (ushort) authorityRRs.Count, ref offset);
Write(buffer, (ushort) additionalRRs.Count, ref offset);
foreach (Query! q in queries)
q.Write(buffer, ref offset);
foreach (ResourceRecord! r in answerRRs)
r.Write(buffer, ref offset);
foreach (ResourceRecord! r in authorityRRs)
r.Write(buffer, ref offset);
foreach (ResourceRecord! r in additionalRRs)
r.Write(buffer, ref offset);
return offset;
}
public static Format Parse(byte []! buffer, ref int offset)
{
//
// First extract enough information to instantiate a Dns.Format
//
ushort id = NetworkBitConverter.ToUInt16(buffer, offset);
offset += 2;
ushort flagsAndCodes = NetworkBitConverter.ToUInt16(buffer,
offset);
offset += 2;
Flags flags = (Flags)((int)flagsAndCodes & (int)Flags.ALL);
OpCode opCode =
(OpCode)(((int)flagsAndCodes >> OpCodeRoll) & OpCodeMask);
RCode rCode = (RCode)((int)flagsAndCodes & RCodeMask);
//
// Instantiate Dns.Format
//
Format format = new Format(id, flags, opCode, rCode);
//
// Extract query and record counts and then instantiate
// Query and ResourceRecord objects
//
ushort totalQueries = NetworkBitConverter.ToUInt16(buffer,
offset);
offset += 2;
ushort totalAnswerRRs = NetworkBitConverter.ToUInt16(buffer,
offset);
offset += 2;
ushort totalAuthorityRRs = NetworkBitConverter.ToUInt16(buffer,
offset);
offset += 2;
ushort totalAdditionalRRs = NetworkBitConverter.ToUInt16(buffer,
offset);
offset += 2;
for (ushort i = 0; i < totalQueries; i++)
{
Query query = Query.Parse(buffer, ref offset);
format.queries.Add(query);
}
// XXX Parsing stops here when a query fails.
//
// When a query fails, servers sometimes
// indicate resource records are present, but the
// data that we'd expect to be a resource record
// does not match the format defined in
// RFC1035. Details may exist in another RFC (???).
if (rCode != RCode.NoError)
return format;
for (ushort i = 0; i < totalAnswerRRs; i++)
{
ResourceRecord rr = ResourceRecord.Parse(buffer, ref offset);
format.answerRRs.Add(rr);
}
for (ushort i = 0; i < totalAuthorityRRs; i++)
{
ResourceRecord rr = ResourceRecord.Parse(buffer, ref offset);
format.authorityRRs.Add(rr);
}
for (ushort i = 0; i < totalAdditionalRRs; i++)
{
ResourceRecord rr = ResourceRecord.Parse(buffer, ref offset);
format.additionalRRs.Add(rr);
}
return format;
}
private static bool ListsEqual(ArrayList lhs, ArrayList rhs)
{
if (lhs == null && rhs == null)
return true;
if (lhs == null)
return false;
if (rhs == null)
return false;
if (lhs.Count != rhs.Count)
return false;
for (int i = 0; i < lhs.Count; i++)
if (Object.Equals(lhs[i],rhs[i]) == false)
return false;
return true;
}
public static bool operator == (Format lhs, Format rhs)
{
if (lhs == null && rhs == null) return true;
if (lhs == null) return false;
if (rhs == null) return false;
if (lhs.id != rhs.id || lhs.flags != rhs.flags ||
lhs.opCode != rhs.opCode || lhs.rCode != rhs.rCode)
return false;
return (ListsEqual(lhs.queries, rhs.queries) &&
ListsEqual(lhs.answerRRs, rhs.answerRRs) &&
ListsEqual(lhs.additionalRRs, rhs.additionalRRs) &&
ListsEqual(lhs.authorityRRs, rhs.authorityRRs));
}
public static bool operator != (Format lhs, Format rhs)
{
return !(lhs == rhs);
}
public override bool Equals(Object other)
{
Format format = other as Format;
return ((Object.ReferenceEquals(format, null) == false)
&& (format == this));
}
private static int GetListHashCode(ArrayList list, int nullHash)
{
if (list == null)
return nullHash;
int hashCode = ~nullHash;
foreach (object! o in list)
{
hashCode = hashCode ^ o.GetHashCode();
hashCode = hashCode ^ (hashCode << 1);
}
return hashCode;
}
public override int GetHashCode()
{
int hashCode = (int)id << 16;
hashCode += ((int)opCode << OpCodeRoll) | (int)flags | (int)rCode;
hashCode ^= GetListHashCode(queries, 0x01233210);
hashCode ^= GetListHashCode(answerRRs, 0x17017017);
hashCode ^= GetListHashCode(authorityRRs, 0x22223333);
hashCode ^= GetListHashCode(additionalRRs, 0x41414141);
return hashCode;
}
private static bool IsSafeLabel(string! name, int begin, int end)
{
Debug.Assert(end > begin);
if (end - begin > Format.MaxLabelLength ||
end - begin < Format.MinLabelLength)
return false;
// The description in RFC 1035 section 2.1 prefers
// first char to be [A-z], however this would rule out
// entries in the IN-ADDR.ARPA domain used to lookup
// hostnames from IP addresses.
if (Char.IsLetterOrDigit(name[begin]) == false)
return false;
// Last char [A-z0-9]
int last = end - 1;
if (Char.IsLetterOrDigit(name[last]) == false)
return false;
for (int i = begin + 1; i < last; i++)
{
// Middle Chars [A-z0-9-]
if (Char.IsLetterOrDigit(name[i]) == false &&
name[i] != '-')
return false;
}
return true;
}
public static bool IsSafeDnsName(string! name)
{
//
// Grammar from RFC1035:
//
// <domain> ::= <subdomain> | " "
//
// <subdomain> ::= <label> | <subdomain> "." <label>
//
// <label> ::= <letter> [ [ <ldh-str> ] <let-dig> ]
//
// <ldh-str> ::= <let-dig-hyp> | <let-dig-hyp> <ldh-str>
//
// <let-dig-hyp> ::= <let-dig> | "-"
//
// <let-dig> ::= <letter> | <digit>
//
if (name.Length > Format.MaxNameLength)
return false;
int labelStart = 0;
do
{
int labelEnd = name.IndexOf('.', labelStart);
if (labelEnd < 0)
{
return IsSafeLabel(name, labelStart, name.Length);
}
if (IsSafeLabel(name, labelStart, labelEnd) == false)
{
return false;
}
labelStart = labelEnd + 1;
} while (labelStart < name.Length);
return false;
}
}
}