DhcpMessage.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2020 Shreyas Zare (shreyas@technitium.com)
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. using DnsServerCore.Dhcp.Options;
  16. using System;
  17. using System.Collections.Generic;
  18. using System.IO;
  19. using System.Net;
  20. using System.Net.Sockets;
  21. using System.Text;
  22. using TechnitiumLibrary.IO;
  23. namespace DnsServerCore.Dhcp
  24. {
  25. enum DhcpMessageOpCode : byte
  26. {
  27. BootRequest = 1,
  28. BootReply = 2
  29. }
  30. enum DhcpMessageHardwareAddressType : byte
  31. {
  32. Ethernet = 1
  33. }
  34. enum DhcpMessageFlags : ushort
  35. {
  36. None = 0,
  37. Broadcast = 0x8000
  38. }
  39. class DhcpMessage
  40. {
  41. #region variables
  42. const uint MAGIC_COOKIE = 0x63538263; //in reverse format
  43. readonly DhcpMessageOpCode _op;
  44. readonly DhcpMessageHardwareAddressType _htype;
  45. readonly byte _hlen;
  46. readonly byte _hops;
  47. readonly byte[] _xid;
  48. readonly byte[] _secs;
  49. readonly DhcpMessageFlags _flags;
  50. readonly IPAddress _ciaddr;
  51. readonly IPAddress _yiaddr;
  52. readonly IPAddress _siaddr;
  53. readonly IPAddress _giaddr;
  54. readonly byte[] _chaddr;
  55. readonly byte[] _sname;
  56. readonly byte[] _file;
  57. readonly IReadOnlyCollection<DhcpOption> _options;
  58. readonly byte[] _clientHardwareAddress;
  59. readonly string _serverHostName;
  60. readonly string _bootFileName;
  61. OptionOverloadOption _optionOverload;
  62. DhcpMessageTypeOption _dhcpMessageType;
  63. VendorClassIdentifierOption _vendorClassIdentifier;
  64. ClientIdentifierOption _clientIdentifier;
  65. HostNameOption _hostName;
  66. ClientFullyQualifiedDomainNameOption _clientFullyQualifiedDomainName;
  67. ParameterRequestListOption _parameterRequestList;
  68. MaximumDhcpMessageSizeOption _maximumDhcpMessageSize;
  69. ServerIdentifierOption _serverIdentifier;
  70. RequestedIpAddressOption _requestedIpAddress;
  71. #endregion
  72. #region constructor
  73. 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)
  74. {
  75. if (ciaddr.AddressFamily != AddressFamily.InterNetwork)
  76. throw new ArgumentException("Address family not supported.", nameof(ciaddr));
  77. if (yiaddr.AddressFamily != AddressFamily.InterNetwork)
  78. throw new ArgumentException("Address family not supported.", nameof(yiaddr));
  79. if (siaddr.AddressFamily != AddressFamily.InterNetwork)
  80. throw new ArgumentException("Address family not supported.", nameof(siaddr));
  81. if (giaddr.AddressFamily != AddressFamily.InterNetwork)
  82. throw new ArgumentException("Address family not supported.", nameof(giaddr));
  83. if (clientHardwareAddress == null)
  84. throw new ArgumentNullException(nameof(clientHardwareAddress));
  85. if (clientHardwareAddress.Length > 16)
  86. throw new ArgumentException("Client hardware address cannot exceed 16 bytes.", nameof(clientHardwareAddress));
  87. if (xid.Length != 4)
  88. throw new ArgumentException("Transaction ID must be 4 bytes.", nameof(xid));
  89. if (secs.Length != 2)
  90. throw new ArgumentException("Seconds elapsed must be 2 bytes.", nameof(secs));
  91. _op = op;
  92. _htype = hardwareAddressType;
  93. _hlen = Convert.ToByte(clientHardwareAddress.Length);
  94. _hops = 0;
  95. _xid = xid;
  96. _secs = secs;
  97. _flags = flags;
  98. _ciaddr = ciaddr;
  99. _yiaddr = yiaddr;
  100. _siaddr = siaddr;
  101. _giaddr = giaddr;
  102. _clientHardwareAddress = clientHardwareAddress;
  103. _chaddr = new byte[16];
  104. Buffer.BlockCopy(_clientHardwareAddress, 0, _chaddr, 0, _clientHardwareAddress.Length);
  105. _sname = new byte[64];
  106. if (sname != null)
  107. {
  108. _serverHostName = sname;
  109. byte[] buffer = Encoding.ASCII.GetBytes(sname);
  110. if (buffer.Length >= 64)
  111. throw new ArgumentException("Server host name cannot exceed 63 bytes.", nameof(sname));
  112. Buffer.BlockCopy(buffer, 0, _sname, 0, buffer.Length);
  113. }
  114. _file = new byte[128];
  115. if (file != null)
  116. {
  117. _bootFileName = file;
  118. byte[] buffer = Encoding.ASCII.GetBytes(file);
  119. if (buffer.Length >= 128)
  120. throw new ArgumentException("Boot file name cannot exceed 127 bytes.", nameof(file));
  121. Buffer.BlockCopy(buffer, 0, _file, 0, buffer.Length);
  122. }
  123. _options = options;
  124. foreach (DhcpOption option in _options)
  125. {
  126. if (option.Code == DhcpOptionCode.ServerIdentifier)
  127. {
  128. _serverIdentifier = option as ServerIdentifierOption;
  129. break;
  130. }
  131. }
  132. }
  133. public DhcpMessage(Stream s)
  134. {
  135. byte[] buffer = new byte[4];
  136. s.ReadBytes(buffer, 0, 4);
  137. _op = (DhcpMessageOpCode)buffer[0];
  138. _htype = (DhcpMessageHardwareAddressType)buffer[1];
  139. _hlen = buffer[2];
  140. _hops = buffer[3];
  141. _xid = s.ReadBytes(4);
  142. s.ReadBytes(buffer, 0, 4);
  143. _secs = new byte[2];
  144. Buffer.BlockCopy(buffer, 0, _secs, 0, 2);
  145. Array.Reverse(buffer);
  146. _flags = (DhcpMessageFlags)BitConverter.ToUInt16(buffer, 0);
  147. s.ReadBytes(buffer, 0, 4);
  148. _ciaddr = new IPAddress(buffer);
  149. s.ReadBytes(buffer, 0, 4);
  150. _yiaddr = new IPAddress(buffer);
  151. s.ReadBytes(buffer, 0, 4);
  152. _siaddr = new IPAddress(buffer);
  153. s.ReadBytes(buffer, 0, 4);
  154. _giaddr = new IPAddress(buffer);
  155. _chaddr = s.ReadBytes(16);
  156. _clientHardwareAddress = new byte[_hlen];
  157. Buffer.BlockCopy(_chaddr, 0, _clientHardwareAddress, 0, _hlen);
  158. _sname = s.ReadBytes(64);
  159. _file = s.ReadBytes(128);
  160. //read options
  161. List<DhcpOption> options = new List<DhcpOption>();
  162. _options = options;
  163. s.ReadBytes(buffer, 0, 4);
  164. uint magicCookie = BitConverter.ToUInt32(buffer, 0);
  165. if (magicCookie == MAGIC_COOKIE)
  166. {
  167. ParseOptions(s, options);
  168. if ((_optionOverload != null) && _optionOverload.Value.HasFlag(OptionOverloadValue.FileFieldUsed))
  169. {
  170. using (MemoryStream mS = new MemoryStream(_file))
  171. {
  172. ParseOptions(mS, options);
  173. }
  174. }
  175. else
  176. {
  177. for (int i = 0; i < _file.Length; i++)
  178. {
  179. if (_file[i] == 0)
  180. {
  181. if (i == 0)
  182. break;
  183. _bootFileName = Encoding.ASCII.GetString(_file, 0, i);
  184. break;
  185. }
  186. }
  187. }
  188. if ((_optionOverload != null) && _optionOverload.Value.HasFlag(OptionOverloadValue.SnameFieldUsed))
  189. {
  190. using (MemoryStream mS = new MemoryStream(_sname))
  191. {
  192. ParseOptions(mS, options);
  193. }
  194. }
  195. else
  196. {
  197. for (int i = 0; i < _sname.Length; i++)
  198. {
  199. if (_sname[i] == 0)
  200. {
  201. if (i == 0)
  202. break;
  203. _serverHostName = Encoding.ASCII.GetString(_sname, 0, i);
  204. break;
  205. }
  206. }
  207. }
  208. //parse all option values
  209. foreach (DhcpOption option in options)
  210. option.ParseOptionValue();
  211. }
  212. if (_clientIdentifier == null)
  213. _clientIdentifier = new ClientIdentifierOption((byte)_htype, _clientHardwareAddress);
  214. if (_maximumDhcpMessageSize != null)
  215. _maximumDhcpMessageSize = new MaximumDhcpMessageSizeOption(576);
  216. }
  217. #endregion
  218. #region static
  219. public static DhcpMessage CreateReply(DhcpMessage request, IPAddress yiaddr, IPAddress siaddr, string sname, string file, IReadOnlyCollection<DhcpOption> options)
  220. {
  221. return new DhcpMessage(DhcpMessageOpCode.BootReply, request.HardwareAddressType, request.TransactionId, request.SecondsElapsed, request.Flags, request.ClientIpAddress, yiaddr, siaddr, request.RelayAgentIpAddress, request.ClientHardwareAddress, sname, file, options);
  222. }
  223. #endregion
  224. #region private
  225. private void ParseOptions(Stream s, List<DhcpOption> options)
  226. {
  227. while (true)
  228. {
  229. DhcpOption option = DhcpOption.Parse(s);
  230. if (option.Code == DhcpOptionCode.End)
  231. break;
  232. if (option.Code == DhcpOptionCode.Pad)
  233. continue;
  234. bool optionExists = false;
  235. foreach (DhcpOption existingOption in options)
  236. {
  237. if (existingOption.Code == option.Code)
  238. {
  239. //option already exists so append current option value into existing option
  240. existingOption.AppendOptionValue(option);
  241. optionExists = true;
  242. break;
  243. }
  244. }
  245. if (optionExists)
  246. continue;
  247. //add option to list
  248. options.Add(option);
  249. switch (option.Code)
  250. {
  251. case DhcpOptionCode.DhcpMessageType:
  252. _dhcpMessageType = option as DhcpMessageTypeOption;
  253. break;
  254. case DhcpOptionCode.VendorClassIdentifier:
  255. _vendorClassIdentifier = option as VendorClassIdentifierOption;
  256. break;
  257. case DhcpOptionCode.ClientIdentifier:
  258. _clientIdentifier = option as ClientIdentifierOption;
  259. break;
  260. case DhcpOptionCode.HostName:
  261. _hostName = option as HostNameOption;
  262. break;
  263. case DhcpOptionCode.ClientFullyQualifiedDomainName:
  264. _clientFullyQualifiedDomainName = option as ClientFullyQualifiedDomainNameOption;
  265. break;
  266. case DhcpOptionCode.ParameterRequestList:
  267. _parameterRequestList = option as ParameterRequestListOption;
  268. break;
  269. case DhcpOptionCode.MaximumDhcpMessageSize:
  270. _maximumDhcpMessageSize = option as MaximumDhcpMessageSizeOption;
  271. break;
  272. case DhcpOptionCode.ServerIdentifier:
  273. _serverIdentifier = option as ServerIdentifierOption;
  274. break;
  275. case DhcpOptionCode.RequestedIpAddress:
  276. _requestedIpAddress = option as RequestedIpAddressOption;
  277. break;
  278. case DhcpOptionCode.OptionOverload:
  279. _optionOverload = option as OptionOverloadOption;
  280. break;
  281. }
  282. }
  283. }
  284. #endregion
  285. #region public
  286. public void WriteTo(Stream s)
  287. {
  288. s.WriteByte((byte)_op);
  289. s.WriteByte((byte)_htype);
  290. s.WriteByte(_hlen);
  291. s.WriteByte(_hops);
  292. s.Write(_xid);
  293. s.Write(_secs);
  294. byte[] buffer = BitConverter.GetBytes((ushort)_flags);
  295. Array.Reverse(buffer);
  296. s.Write(buffer);
  297. s.Write(_ciaddr.GetAddressBytes());
  298. s.Write(_yiaddr.GetAddressBytes());
  299. s.Write(_siaddr.GetAddressBytes());
  300. s.Write(_giaddr.GetAddressBytes());
  301. s.Write(_chaddr);
  302. s.Write(_sname);
  303. s.Write(_file);
  304. //write options
  305. s.Write(BitConverter.GetBytes(MAGIC_COOKIE));
  306. foreach (DhcpOption option in _options)
  307. option.WriteTo(s);
  308. }
  309. public string GetClientFullIdentifier()
  310. {
  311. string hardwareAddress = BitConverter.ToString(_clientHardwareAddress);
  312. if (_clientFullyQualifiedDomainName != null)
  313. return _clientFullyQualifiedDomainName.DomainName + " [" + hardwareAddress + "]";
  314. if (_hostName != null)
  315. return _hostName.HostName + " [" + hardwareAddress + "]";
  316. return "[" + hardwareAddress + "]";
  317. }
  318. #endregion
  319. #region properties
  320. public DhcpMessageOpCode OpCode
  321. { get { return _op; } }
  322. public DhcpMessageHardwareAddressType HardwareAddressType
  323. { get { return _htype; } }
  324. public byte HardwareAddressLength
  325. { get { return _hlen; } }
  326. public byte Hops
  327. { get { return _hops; } }
  328. public byte[] TransactionId
  329. { get { return _xid; } }
  330. public byte[] SecondsElapsed
  331. { get { return _secs; } }
  332. public DhcpMessageFlags Flags
  333. { get { return _flags; } }
  334. public IPAddress ClientIpAddress
  335. { get { return _ciaddr; } }
  336. public IPAddress YourClientIpAddress
  337. { get { return _yiaddr; } }
  338. public IPAddress NextServerIpAddress
  339. { get { return _siaddr; } }
  340. public IPAddress RelayAgentIpAddress
  341. { get { return _giaddr; } }
  342. public byte[] ClientHardwareAddress
  343. { get { return _clientHardwareAddress; } }
  344. public string ServerHostName
  345. { get { return _serverHostName; } }
  346. public string BootFileName
  347. { get { return _bootFileName; } }
  348. public IReadOnlyCollection<DhcpOption> Options
  349. { get { return _options; } }
  350. public DhcpMessageTypeOption DhcpMessageType
  351. { get { return _dhcpMessageType; } }
  352. public VendorClassIdentifierOption VendorClassIdentifier
  353. { get { return _vendorClassIdentifier; } }
  354. public ClientIdentifierOption ClientIdentifier
  355. { get { return _clientIdentifier; } }
  356. public HostNameOption HostName
  357. { get { return _hostName; } }
  358. public ClientFullyQualifiedDomainNameOption ClientFullyQualifiedDomainName
  359. { get { return _clientFullyQualifiedDomainName; } }
  360. public ParameterRequestListOption ParameterRequestList
  361. { get { return _parameterRequestList; } }
  362. public MaximumDhcpMessageSizeOption MaximumDhcpMessageSize
  363. { get { return _maximumDhcpMessageSize; } }
  364. public ServerIdentifierOption ServerIdentifier
  365. { get { return _serverIdentifier; } }
  366. public RequestedIpAddressOption RequestedIpAddress
  367. { get { return _requestedIpAddress; } }
  368. #endregion
  369. }
  370. }