mirror of
https://github.com/fergalmoran/DnsServer.git
synced 2025-12-28 20:40:20 +00:00
494 lines
16 KiB
C#
494 lines
16 KiB
C#
/*
|
|
Technitium DNS Server
|
|
Copyright (C) 2020 Shreyas Zare (shreyas@technitium.com)
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
using DnsServerCore.Dhcp.Options;
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Net;
|
|
using System.Net.Sockets;
|
|
using System.Text;
|
|
using TechnitiumLibrary.IO;
|
|
|
|
namespace DnsServerCore.Dhcp
|
|
{
|
|
enum DhcpMessageOpCode : byte
|
|
{
|
|
BootRequest = 1,
|
|
BootReply = 2
|
|
}
|
|
|
|
enum DhcpMessageHardwareAddressType : byte
|
|
{
|
|
Ethernet = 1
|
|
}
|
|
|
|
enum DhcpMessageFlags : ushort
|
|
{
|
|
None = 0,
|
|
Broadcast = 0x8000
|
|
}
|
|
|
|
class DhcpMessage
|
|
{
|
|
#region variables
|
|
|
|
const uint MAGIC_COOKIE = 0x63538263; //in reverse format
|
|
|
|
readonly DhcpMessageOpCode _op;
|
|
readonly DhcpMessageHardwareAddressType _htype;
|
|
readonly byte _hlen;
|
|
readonly byte _hops;
|
|
|
|
readonly byte[] _xid;
|
|
|
|
readonly byte[] _secs;
|
|
readonly DhcpMessageFlags _flags;
|
|
|
|
readonly IPAddress _ciaddr;
|
|
readonly IPAddress _yiaddr;
|
|
readonly IPAddress _siaddr;
|
|
readonly IPAddress _giaddr;
|
|
|
|
readonly byte[] _chaddr;
|
|
readonly byte[] _sname;
|
|
readonly byte[] _file;
|
|
|
|
readonly IReadOnlyCollection<DhcpOption> _options;
|
|
|
|
readonly byte[] _clientHardwareAddress;
|
|
readonly string _serverHostName;
|
|
readonly string _bootFileName;
|
|
|
|
OptionOverloadOption _optionOverload;
|
|
|
|
DhcpMessageTypeOption _dhcpMessageType;
|
|
VendorClassIdentifierOption _vendorClassIdentifier;
|
|
ClientIdentifierOption _clientIdentifier;
|
|
HostNameOption _hostName;
|
|
ClientFullyQualifiedDomainNameOption _clientFullyQualifiedDomainName;
|
|
ParameterRequestListOption _parameterRequestList;
|
|
MaximumDhcpMessageSizeOption _maximumDhcpMessageSize;
|
|
ServerIdentifierOption _serverIdentifier;
|
|
RequestedIpAddressOption _requestedIpAddress;
|
|
|
|
#endregion
|
|
|
|
#region constructor
|
|
|
|
public DhcpMessage(DhcpMessageOpCode op, DhcpMessageHardwareAddressType hardwareAddressType, byte[] xid, byte[] secs, DhcpMessageFlags flags, IPAddress ciaddr, IPAddress yiaddr, IPAddress siaddr, IPAddress giaddr, byte[] clientHardwareAddress, string sname, string file, IReadOnlyCollection<DhcpOption> options)
|
|
{
|
|
if (ciaddr.AddressFamily != AddressFamily.InterNetwork)
|
|
throw new ArgumentException("Address family not supported.", nameof(ciaddr));
|
|
|
|
if (yiaddr.AddressFamily != AddressFamily.InterNetwork)
|
|
throw new ArgumentException("Address family not supported.", nameof(yiaddr));
|
|
|
|
if (siaddr.AddressFamily != AddressFamily.InterNetwork)
|
|
throw new ArgumentException("Address family not supported.", nameof(siaddr));
|
|
|
|
if (giaddr.AddressFamily != AddressFamily.InterNetwork)
|
|
throw new ArgumentException("Address family not supported.", nameof(giaddr));
|
|
|
|
if (clientHardwareAddress == null)
|
|
throw new ArgumentNullException(nameof(clientHardwareAddress));
|
|
|
|
if (clientHardwareAddress.Length > 16)
|
|
throw new ArgumentException("Client hardware address cannot exceed 16 bytes.", nameof(clientHardwareAddress));
|
|
|
|
if (xid.Length != 4)
|
|
throw new ArgumentException("Transaction ID must be 4 bytes.", nameof(xid));
|
|
|
|
if (secs.Length != 2)
|
|
throw new ArgumentException("Seconds elapsed must be 2 bytes.", nameof(secs));
|
|
|
|
_op = op;
|
|
_htype = hardwareAddressType;
|
|
_hlen = Convert.ToByte(clientHardwareAddress.Length);
|
|
_hops = 0;
|
|
|
|
_xid = xid;
|
|
|
|
_secs = secs;
|
|
_flags = flags;
|
|
|
|
_ciaddr = ciaddr;
|
|
_yiaddr = yiaddr;
|
|
_siaddr = siaddr;
|
|
_giaddr = giaddr;
|
|
|
|
_clientHardwareAddress = clientHardwareAddress;
|
|
_chaddr = new byte[16];
|
|
Buffer.BlockCopy(_clientHardwareAddress, 0, _chaddr, 0, _clientHardwareAddress.Length);
|
|
|
|
_sname = new byte[64];
|
|
if (sname != null)
|
|
{
|
|
_serverHostName = sname;
|
|
|
|
byte[] buffer = Encoding.ASCII.GetBytes(sname);
|
|
if (buffer.Length >= 64)
|
|
throw new ArgumentException("Server host name cannot exceed 63 bytes.", nameof(sname));
|
|
|
|
Buffer.BlockCopy(buffer, 0, _sname, 0, buffer.Length);
|
|
}
|
|
|
|
_file = new byte[128];
|
|
if (file != null)
|
|
{
|
|
_bootFileName = file;
|
|
|
|
byte[] buffer = Encoding.ASCII.GetBytes(file);
|
|
if (buffer.Length >= 128)
|
|
throw new ArgumentException("Boot file name cannot exceed 127 bytes.", nameof(file));
|
|
|
|
Buffer.BlockCopy(buffer, 0, _file, 0, buffer.Length);
|
|
}
|
|
|
|
_options = options;
|
|
|
|
foreach (DhcpOption option in _options)
|
|
{
|
|
if (option.Code == DhcpOptionCode.ServerIdentifier)
|
|
{
|
|
_serverIdentifier = option as ServerIdentifierOption;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
public DhcpMessage(Stream s)
|
|
{
|
|
byte[] buffer = new byte[4];
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
_op = (DhcpMessageOpCode)buffer[0];
|
|
_htype = (DhcpMessageHardwareAddressType)buffer[1];
|
|
_hlen = buffer[2];
|
|
_hops = buffer[3];
|
|
|
|
_xid = s.ReadBytes(4);
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
_secs = new byte[2];
|
|
Buffer.BlockCopy(buffer, 0, _secs, 0, 2);
|
|
Array.Reverse(buffer);
|
|
_flags = (DhcpMessageFlags)BitConverter.ToUInt16(buffer, 0);
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
_ciaddr = new IPAddress(buffer);
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
_yiaddr = new IPAddress(buffer);
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
_siaddr = new IPAddress(buffer);
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
_giaddr = new IPAddress(buffer);
|
|
|
|
_chaddr = s.ReadBytes(16);
|
|
_clientHardwareAddress = new byte[_hlen];
|
|
Buffer.BlockCopy(_chaddr, 0, _clientHardwareAddress, 0, _hlen);
|
|
|
|
_sname = s.ReadBytes(64);
|
|
_file = s.ReadBytes(128);
|
|
|
|
//read options
|
|
List<DhcpOption> options = new List<DhcpOption>();
|
|
_options = options;
|
|
|
|
s.ReadBytes(buffer, 0, 4);
|
|
uint magicCookie = BitConverter.ToUInt32(buffer, 0);
|
|
|
|
if (magicCookie == MAGIC_COOKIE)
|
|
{
|
|
ParseOptions(s, options);
|
|
|
|
if ((_optionOverload != null) && _optionOverload.Value.HasFlag(OptionOverloadValue.FileFieldUsed))
|
|
{
|
|
using (MemoryStream mS = new MemoryStream(_file))
|
|
{
|
|
ParseOptions(mS, options);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < _file.Length; i++)
|
|
{
|
|
if (_file[i] == 0)
|
|
{
|
|
if (i == 0)
|
|
break;
|
|
|
|
_bootFileName = Encoding.ASCII.GetString(_file, 0, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_optionOverload != null) && _optionOverload.Value.HasFlag(OptionOverloadValue.SnameFieldUsed))
|
|
{
|
|
using (MemoryStream mS = new MemoryStream(_sname))
|
|
{
|
|
ParseOptions(mS, options);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for (int i = 0; i < _sname.Length; i++)
|
|
{
|
|
if (_sname[i] == 0)
|
|
{
|
|
if (i == 0)
|
|
break;
|
|
|
|
_serverHostName = Encoding.ASCII.GetString(_sname, 0, i);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
//parse all option values
|
|
foreach (DhcpOption option in options)
|
|
option.ParseOptionValue();
|
|
}
|
|
|
|
if (_clientIdentifier == null)
|
|
_clientIdentifier = new ClientIdentifierOption((byte)_htype, _clientHardwareAddress);
|
|
|
|
if (_maximumDhcpMessageSize != null)
|
|
_maximumDhcpMessageSize = new MaximumDhcpMessageSizeOption(576);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region static
|
|
|
|
public static DhcpMessage CreateReply(DhcpMessage request, IPAddress yiaddr, IPAddress siaddr, string sname, string file, IReadOnlyCollection<DhcpOption> options)
|
|
{
|
|
return new DhcpMessage(DhcpMessageOpCode.BootReply, request.HardwareAddressType, request.TransactionId, request.SecondsElapsed, request.Flags, request.ClientIpAddress, yiaddr, siaddr, request.RelayAgentIpAddress, request.ClientHardwareAddress, sname, file, options);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region private
|
|
|
|
private void ParseOptions(Stream s, List<DhcpOption> options)
|
|
{
|
|
while (true)
|
|
{
|
|
DhcpOption option = DhcpOption.Parse(s);
|
|
if (option.Code == DhcpOptionCode.End)
|
|
break;
|
|
|
|
if (option.Code == DhcpOptionCode.Pad)
|
|
continue;
|
|
|
|
bool optionExists = false;
|
|
|
|
foreach (DhcpOption existingOption in options)
|
|
{
|
|
if (existingOption.Code == option.Code)
|
|
{
|
|
//option already exists so append current option value into existing option
|
|
existingOption.AppendOptionValue(option);
|
|
optionExists = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (optionExists)
|
|
continue;
|
|
|
|
//add option to list
|
|
options.Add(option);
|
|
|
|
switch (option.Code)
|
|
{
|
|
case DhcpOptionCode.DhcpMessageType:
|
|
_dhcpMessageType = option as DhcpMessageTypeOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.VendorClassIdentifier:
|
|
_vendorClassIdentifier = option as VendorClassIdentifierOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.ClientIdentifier:
|
|
_clientIdentifier = option as ClientIdentifierOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.HostName:
|
|
_hostName = option as HostNameOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.ClientFullyQualifiedDomainName:
|
|
_clientFullyQualifiedDomainName = option as ClientFullyQualifiedDomainNameOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.ParameterRequestList:
|
|
_parameterRequestList = option as ParameterRequestListOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.MaximumDhcpMessageSize:
|
|
_maximumDhcpMessageSize = option as MaximumDhcpMessageSizeOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.ServerIdentifier:
|
|
_serverIdentifier = option as ServerIdentifierOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.RequestedIpAddress:
|
|
_requestedIpAddress = option as RequestedIpAddressOption;
|
|
break;
|
|
|
|
case DhcpOptionCode.OptionOverload:
|
|
_optionOverload = option as OptionOverloadOption;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region public
|
|
|
|
public void WriteTo(Stream s)
|
|
{
|
|
s.WriteByte((byte)_op);
|
|
s.WriteByte((byte)_htype);
|
|
s.WriteByte(_hlen);
|
|
s.WriteByte(_hops);
|
|
|
|
s.Write(_xid);
|
|
|
|
s.Write(_secs);
|
|
byte[] buffer = BitConverter.GetBytes((ushort)_flags);
|
|
Array.Reverse(buffer);
|
|
s.Write(buffer);
|
|
|
|
s.Write(_ciaddr.GetAddressBytes());
|
|
s.Write(_yiaddr.GetAddressBytes());
|
|
s.Write(_siaddr.GetAddressBytes());
|
|
s.Write(_giaddr.GetAddressBytes());
|
|
|
|
s.Write(_chaddr);
|
|
s.Write(_sname);
|
|
s.Write(_file);
|
|
|
|
//write options
|
|
s.Write(BitConverter.GetBytes(MAGIC_COOKIE));
|
|
|
|
foreach (DhcpOption option in _options)
|
|
option.WriteTo(s);
|
|
}
|
|
|
|
public string GetClientFullIdentifier()
|
|
{
|
|
string hardwareAddress = BitConverter.ToString(_clientHardwareAddress);
|
|
|
|
if (_clientFullyQualifiedDomainName != null)
|
|
return _clientFullyQualifiedDomainName.DomainName + " [" + hardwareAddress + "]";
|
|
|
|
if (_hostName != null)
|
|
return _hostName.HostName + " [" + hardwareAddress + "]";
|
|
|
|
return "[" + hardwareAddress + "]";
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region properties
|
|
|
|
public DhcpMessageOpCode OpCode
|
|
{ get { return _op; } }
|
|
|
|
public DhcpMessageHardwareAddressType HardwareAddressType
|
|
{ get { return _htype; } }
|
|
|
|
public byte HardwareAddressLength
|
|
{ get { return _hlen; } }
|
|
|
|
public byte Hops
|
|
{ get { return _hops; } }
|
|
|
|
public byte[] TransactionId
|
|
{ get { return _xid; } }
|
|
|
|
public byte[] SecondsElapsed
|
|
{ get { return _secs; } }
|
|
|
|
public DhcpMessageFlags Flags
|
|
{ get { return _flags; } }
|
|
|
|
public IPAddress ClientIpAddress
|
|
{ get { return _ciaddr; } }
|
|
|
|
public IPAddress YourClientIpAddress
|
|
{ get { return _yiaddr; } }
|
|
|
|
public IPAddress NextServerIpAddress
|
|
{ get { return _siaddr; } }
|
|
|
|
public IPAddress RelayAgentIpAddress
|
|
{ get { return _giaddr; } }
|
|
|
|
public byte[] ClientHardwareAddress
|
|
{ get { return _clientHardwareAddress; } }
|
|
|
|
public string ServerHostName
|
|
{ get { return _serverHostName; } }
|
|
|
|
public string BootFileName
|
|
{ get { return _bootFileName; } }
|
|
|
|
public IReadOnlyCollection<DhcpOption> Options
|
|
{ get { return _options; } }
|
|
|
|
public DhcpMessageTypeOption DhcpMessageType
|
|
{ get { return _dhcpMessageType; } }
|
|
|
|
public VendorClassIdentifierOption VendorClassIdentifier
|
|
{ get { return _vendorClassIdentifier; } }
|
|
|
|
public ClientIdentifierOption ClientIdentifier
|
|
{ get { return _clientIdentifier; } }
|
|
|
|
public HostNameOption HostName
|
|
{ get { return _hostName; } }
|
|
|
|
public ClientFullyQualifiedDomainNameOption ClientFullyQualifiedDomainName
|
|
{ get { return _clientFullyQualifiedDomainName; } }
|
|
|
|
public ParameterRequestListOption ParameterRequestList
|
|
{ get { return _parameterRequestList; } }
|
|
|
|
public MaximumDhcpMessageSizeOption MaximumDhcpMessageSize
|
|
{ get { return _maximumDhcpMessageSize; } }
|
|
|
|
public ServerIdentifierOption ServerIdentifier
|
|
{ get { return _serverIdentifier; } }
|
|
|
|
public RequestedIpAddressOption RequestedIpAddress
|
|
{ get { return _requestedIpAddress; } }
|
|
|
|
#endregion
|
|
}
|
|
}
|