DhcpMessage.cs 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500
  1. /*
  2. Technitium DNS Server
  3. Copyright (C) 2024 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. ClientIdentifierOption _clientHardwareIdentifier;
  66. HostNameOption _hostName;
  67. ClientFullyQualifiedDomainNameOption _clientFullyQualifiedDomainName;
  68. ParameterRequestListOption _parameterRequestList;
  69. MaximumDhcpMessageSizeOption _maximumDhcpMessageSize;
  70. ServerIdentifierOption _serverIdentifier;
  71. RequestedIpAddressOption _requestedIpAddress;
  72. #endregion
  73. #region constructor
  74. 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)
  75. {
  76. if (ciaddr.AddressFamily != AddressFamily.InterNetwork)
  77. throw new ArgumentException("Address family not supported.", nameof(ciaddr));
  78. if (yiaddr.AddressFamily != AddressFamily.InterNetwork)
  79. throw new ArgumentException("Address family not supported.", nameof(yiaddr));
  80. if (siaddr.AddressFamily != AddressFamily.InterNetwork)
  81. throw new ArgumentException("Address family not supported.", nameof(siaddr));
  82. if (giaddr.AddressFamily != AddressFamily.InterNetwork)
  83. throw new ArgumentException("Address family not supported.", nameof(giaddr));
  84. ArgumentNullException.ThrowIfNull(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. Span<byte> buffer = stackalloc byte[4];
  136. s.ReadExactly(buffer);
  137. _op = (DhcpMessageOpCode)buffer[0];
  138. _htype = (DhcpMessageHardwareAddressType)buffer[1];
  139. _hlen = buffer[2];
  140. _hops = buffer[3];
  141. _xid = s.ReadExactly(4);
  142. s.ReadExactly(buffer);
  143. _secs = new byte[2];
  144. buffer.Slice(0, 2).CopyTo(_secs);
  145. buffer.Reverse();
  146. _flags = (DhcpMessageFlags)BitConverter.ToUInt16(buffer);
  147. s.ReadExactly(buffer);
  148. _ciaddr = new IPAddress(buffer);
  149. s.ReadExactly(buffer);
  150. _yiaddr = new IPAddress(buffer);
  151. s.ReadExactly(buffer);
  152. _siaddr = new IPAddress(buffer);
  153. s.ReadExactly(buffer);
  154. _giaddr = new IPAddress(buffer);
  155. _chaddr = s.ReadExactly(16);
  156. _clientHardwareAddress = new byte[_hlen];
  157. Buffer.BlockCopy(_chaddr, 0, _clientHardwareAddress, 0, _hlen);
  158. _sname = s.ReadExactly(64);
  159. _file = s.ReadExactly(128);
  160. //read options
  161. List<DhcpOption> options = new List<DhcpOption>();
  162. _options = options;
  163. s.ReadExactly(buffer);
  164. uint magicCookie = BitConverter.ToUInt32(buffer);
  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 (_maximumDhcpMessageSize != null)
  213. _maximumDhcpMessageSize = new MaximumDhcpMessageSizeOption(576);
  214. }
  215. #endregion
  216. #region static
  217. public static DhcpMessage CreateReply(DhcpMessage request, IPAddress yiaddr, IPAddress siaddr, string sname, string file, IReadOnlyCollection<DhcpOption> options)
  218. {
  219. return new DhcpMessage(DhcpMessageOpCode.BootReply, request.HardwareAddressType, request.TransactionId, request.SecondsElapsed, request.Flags, request.ClientIpAddress, yiaddr, siaddr, request.RelayAgentIpAddress, request.ClientHardwareAddress, sname, file, options);
  220. }
  221. #endregion
  222. #region private
  223. private void ParseOptions(Stream s, List<DhcpOption> options)
  224. {
  225. while (true)
  226. {
  227. DhcpOption option = DhcpOption.Parse(s);
  228. if (option.Code == DhcpOptionCode.End)
  229. break;
  230. if (option.Code == DhcpOptionCode.Pad)
  231. continue;
  232. bool optionExists = false;
  233. foreach (DhcpOption existingOption in options)
  234. {
  235. if (existingOption.Code == option.Code)
  236. {
  237. //option already exists so append current option value into existing option
  238. existingOption.AppendOptionValue(option);
  239. optionExists = true;
  240. break;
  241. }
  242. }
  243. if (optionExists)
  244. continue;
  245. //add option to list
  246. options.Add(option);
  247. switch (option.Code)
  248. {
  249. case DhcpOptionCode.DhcpMessageType:
  250. _dhcpMessageType = option as DhcpMessageTypeOption;
  251. break;
  252. case DhcpOptionCode.VendorClassIdentifier:
  253. _vendorClassIdentifier = option as VendorClassIdentifierOption;
  254. break;
  255. case DhcpOptionCode.ClientIdentifier:
  256. _clientIdentifier = option as ClientIdentifierOption;
  257. break;
  258. case DhcpOptionCode.HostName:
  259. _hostName = option as HostNameOption;
  260. break;
  261. case DhcpOptionCode.ClientFullyQualifiedDomainName:
  262. _clientFullyQualifiedDomainName = option as ClientFullyQualifiedDomainNameOption;
  263. break;
  264. case DhcpOptionCode.ParameterRequestList:
  265. _parameterRequestList = option as ParameterRequestListOption;
  266. break;
  267. case DhcpOptionCode.MaximumDhcpMessageSize:
  268. _maximumDhcpMessageSize = option as MaximumDhcpMessageSizeOption;
  269. break;
  270. case DhcpOptionCode.ServerIdentifier:
  271. _serverIdentifier = option as ServerIdentifierOption;
  272. break;
  273. case DhcpOptionCode.RequestedIpAddress:
  274. _requestedIpAddress = option as RequestedIpAddressOption;
  275. break;
  276. case DhcpOptionCode.OptionOverload:
  277. _optionOverload = option as OptionOverloadOption;
  278. break;
  279. }
  280. }
  281. }
  282. #endregion
  283. #region public
  284. public void WriteTo(Stream s)
  285. {
  286. s.WriteByte((byte)_op);
  287. s.WriteByte((byte)_htype);
  288. s.WriteByte(_hlen);
  289. s.WriteByte(_hops);
  290. s.Write(_xid);
  291. s.Write(_secs);
  292. byte[] buffer = BitConverter.GetBytes((ushort)_flags);
  293. Array.Reverse(buffer);
  294. s.Write(buffer);
  295. s.Write(_ciaddr.GetAddressBytes());
  296. s.Write(_yiaddr.GetAddressBytes());
  297. s.Write(_siaddr.GetAddressBytes());
  298. s.Write(_giaddr.GetAddressBytes());
  299. s.Write(_chaddr);
  300. s.Write(_sname);
  301. s.Write(_file);
  302. //write options
  303. s.Write(BitConverter.GetBytes(MAGIC_COOKIE));
  304. foreach (DhcpOption option in _options)
  305. option.WriteTo(s);
  306. }
  307. public ClientIdentifierOption GetClientIdentifier(bool ignoreClientIdentifierOption)
  308. {
  309. if (ignoreClientIdentifierOption || (_clientIdentifier is null))
  310. {
  311. if (_clientHardwareIdentifier is null)
  312. _clientHardwareIdentifier = new ClientIdentifierOption((byte)_htype, _clientHardwareAddress);
  313. return _clientHardwareIdentifier;
  314. }
  315. return _clientIdentifier;
  316. }
  317. public string GetClientFullIdentifier()
  318. {
  319. string hardwareAddress = BitConverter.ToString(_clientHardwareAddress);
  320. if (_clientFullyQualifiedDomainName != null)
  321. return _clientFullyQualifiedDomainName.DomainName + " [" + hardwareAddress + "]";
  322. if (_hostName != null)
  323. return _hostName.HostName + " [" + hardwareAddress + "]";
  324. return "[" + hardwareAddress + "]";
  325. }
  326. #endregion
  327. #region properties
  328. public DhcpMessageOpCode OpCode
  329. { get { return _op; } }
  330. public DhcpMessageHardwareAddressType HardwareAddressType
  331. { get { return _htype; } }
  332. public byte HardwareAddressLength
  333. { get { return _hlen; } }
  334. public byte Hops
  335. { get { return _hops; } }
  336. public byte[] TransactionId
  337. { get { return _xid; } }
  338. public byte[] SecondsElapsed
  339. { get { return _secs; } }
  340. public DhcpMessageFlags Flags
  341. { get { return _flags; } }
  342. public IPAddress ClientIpAddress
  343. { get { return _ciaddr; } }
  344. public IPAddress YourClientIpAddress
  345. { get { return _yiaddr; } }
  346. public IPAddress NextServerIpAddress
  347. { get { return _siaddr; } }
  348. public IPAddress RelayAgentIpAddress
  349. { get { return _giaddr; } }
  350. public byte[] ClientHardwareAddress
  351. { get { return _clientHardwareAddress; } }
  352. public string ServerHostName
  353. { get { return _serverHostName; } }
  354. public string BootFileName
  355. { get { return _bootFileName; } }
  356. public IReadOnlyCollection<DhcpOption> Options
  357. { get { return _options; } }
  358. public DhcpMessageTypeOption DhcpMessageType
  359. { get { return _dhcpMessageType; } }
  360. public VendorClassIdentifierOption VendorClassIdentifier
  361. { get { return _vendorClassIdentifier; } }
  362. public HostNameOption HostName
  363. { get { return _hostName; } }
  364. public ClientFullyQualifiedDomainNameOption ClientFullyQualifiedDomainName
  365. { get { return _clientFullyQualifiedDomainName; } }
  366. public ParameterRequestListOption ParameterRequestList
  367. { get { return _parameterRequestList; } }
  368. public MaximumDhcpMessageSizeOption MaximumDhcpMessageSize
  369. { get { return _maximumDhcpMessageSize; } }
  370. public ServerIdentifierOption ServerIdentifier
  371. { get { return _serverIdentifier; } }
  372. public RequestedIpAddressOption RequestedIpAddress
  373. { get { return _requestedIpAddress; } }
  374. #endregion
  375. }
  376. }