DHCPv4Client.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400
  1. /*
  2. * Copyright (c) 2020-2022, the SerenityOS developers.
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "DHCPv4Client.h"
  7. #include <AK/Array.h>
  8. #include <AK/Debug.h>
  9. #include <AK/IPv4Address.h>
  10. #include <AK/JsonArray.h>
  11. #include <AK/JsonObject.h>
  12. #include <AK/JsonParser.h>
  13. #include <AK/Random.h>
  14. #include <AK/ScopeGuard.h>
  15. #include <AK/Try.h>
  16. #include <LibCore/File.h>
  17. #include <LibCore/Timer.h>
  18. #include <stdio.h>
  19. static u8 mac_part(Vector<DeprecatedString> const& parts, size_t index)
  20. {
  21. auto result = AK::StringUtils::convert_to_uint_from_hex(parts.at(index));
  22. VERIFY(result.has_value());
  23. return result.value();
  24. }
  25. static MACAddress mac_from_string(DeprecatedString const& str)
  26. {
  27. auto chunks = str.split(':');
  28. VERIFY(chunks.size() == 6); // should we...worry about this?
  29. return {
  30. mac_part(chunks, 0), mac_part(chunks, 1), mac_part(chunks, 2),
  31. mac_part(chunks, 3), mac_part(chunks, 4), mac_part(chunks, 5)
  32. };
  33. }
  34. static bool send(InterfaceDescriptor const& iface, DHCPv4Packet const& packet, Core::EventReceiver*)
  35. {
  36. int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
  37. if (fd < 0) {
  38. dbgln("ERROR: socket :: {}", strerror(errno));
  39. return false;
  40. }
  41. ScopeGuard socket_close_guard = [&] { close(fd); };
  42. if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface.ifname.characters(), IFNAMSIZ) < 0) {
  43. dbgln("ERROR: setsockopt(SO_BINDTODEVICE) :: {}", strerror(errno));
  44. return false;
  45. }
  46. sockaddr_in dst;
  47. memset(&dst, 0, sizeof(dst));
  48. dst.sin_family = AF_INET;
  49. dst.sin_port = htons(67);
  50. dst.sin_addr.s_addr = IPv4Address { 255, 255, 255, 255 }.to_u32();
  51. memset(&dst.sin_zero, 0, sizeof(dst.sin_zero));
  52. dbgln_if(DHCPV4CLIENT_DEBUG, "sendto({} bound to {}, ..., {} at {}) = ...?", fd, iface.ifname, dst.sin_addr.s_addr, dst.sin_port);
  53. auto rc = sendto(fd, &packet, sizeof(packet), 0, (sockaddr*)&dst, sizeof(dst));
  54. dbgln_if(DHCPV4CLIENT_DEBUG, "sendto({}) = {}", fd, rc);
  55. if (rc < 0) {
  56. dbgln("sendto failed with {}", strerror(errno));
  57. return false;
  58. }
  59. return true;
  60. }
  61. static void set_params(InterfaceDescriptor const& iface, IPv4Address const& ipv4_addr, IPv4Address const& netmask, Optional<IPv4Address> const& gateway)
  62. {
  63. int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
  64. if (fd < 0) {
  65. dbgln("ERROR: socket :: {}", strerror(errno));
  66. return;
  67. }
  68. struct ifreq ifr;
  69. memset(&ifr, 0, sizeof(ifr));
  70. bool fits = iface.ifname.copy_characters_to_buffer(ifr.ifr_name, IFNAMSIZ);
  71. if (!fits) {
  72. dbgln("Interface name doesn't fit into IFNAMSIZ!");
  73. return;
  74. }
  75. // set the IP address
  76. ifr.ifr_addr.sa_family = AF_INET;
  77. ((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = ipv4_addr.to_in_addr_t();
  78. if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) {
  79. dbgln("ERROR: ioctl(SIOCSIFADDR) :: {}", strerror(errno));
  80. }
  81. // set the network mask
  82. ((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = netmask.to_in_addr_t();
  83. if (ioctl(fd, SIOCSIFNETMASK, &ifr) < 0) {
  84. dbgln("ERROR: ioctl(SIOCSIFNETMASK) :: {}", strerror(errno));
  85. }
  86. if (!gateway.has_value())
  87. return;
  88. // set the default gateway
  89. struct rtentry rt;
  90. memset(&rt, 0, sizeof(rt));
  91. rt.rt_dev = const_cast<char*>(iface.ifname.characters());
  92. rt.rt_gateway.sa_family = AF_INET;
  93. ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.value().to_in_addr_t();
  94. rt.rt_flags = RTF_UP | RTF_GATEWAY;
  95. if (ioctl(fd, SIOCADDRT, &rt) < 0) {
  96. dbgln("Error: ioctl(SIOCADDRT) :: {}", strerror(errno));
  97. }
  98. }
  99. DHCPv4Client::DHCPv4Client(Vector<DeprecatedString> interfaces_with_dhcp_enabled)
  100. : m_interfaces_with_dhcp_enabled(move(interfaces_with_dhcp_enabled))
  101. {
  102. m_server = Core::UDPServer::construct(this);
  103. m_server->on_ready_to_receive = [this] {
  104. // TODO: we need to handle possible errors here somehow
  105. auto buffer = MUST(m_server->receive(sizeof(DHCPv4Packet)));
  106. dbgln_if(DHCPV4CLIENT_DEBUG, "Received {} bytes", buffer.size());
  107. if (buffer.size() < sizeof(DHCPv4Packet) - DHCPV4_OPTION_FIELD_MAX_LENGTH + 1 || buffer.size() > sizeof(DHCPv4Packet)) {
  108. dbgln("we expected {}-{} bytes, this is a bad packet", sizeof(DHCPv4Packet) - DHCPV4_OPTION_FIELD_MAX_LENGTH + 1, sizeof(DHCPv4Packet));
  109. return;
  110. }
  111. auto& packet = *(DHCPv4Packet*)buffer.data();
  112. process_incoming(packet);
  113. };
  114. if (!m_server->bind({}, 68)) {
  115. dbgln("The server we just created somehow came already bound, refusing to continue");
  116. VERIFY_NOT_REACHED();
  117. }
  118. m_check_timer = Core::Timer::create_repeating(
  119. 1000, [this] { try_discover_ifs(); }, this)
  120. .release_value_but_fixme_should_propagate_errors();
  121. m_check_timer->start();
  122. try_discover_ifs();
  123. }
  124. void DHCPv4Client::try_discover_ifs()
  125. {
  126. auto ifs_result = get_discoverable_interfaces();
  127. if (ifs_result.is_error())
  128. return;
  129. dbgln_if(DHCPV4CLIENT_DEBUG, "Interfaces with DHCP enabled: {}", m_interfaces_with_dhcp_enabled);
  130. bool sent_discover_request = false;
  131. Interfaces& ifs = ifs_result.value();
  132. for (auto& iface : ifs.ready) {
  133. dbgln_if(DHCPV4CLIENT_DEBUG, "Checking interface {} / {}", iface.ifname, iface.current_ip_address);
  134. if (!m_interfaces_with_dhcp_enabled.contains_slow(iface.ifname))
  135. continue;
  136. if (iface.current_ip_address != IPv4Address { 0, 0, 0, 0 })
  137. continue;
  138. dhcp_discover(iface);
  139. sent_discover_request = true;
  140. }
  141. if (sent_discover_request) {
  142. auto current_interval = m_check_timer->interval();
  143. if (current_interval < m_max_timer_backoff_interval)
  144. current_interval *= 1.9f;
  145. m_check_timer->set_interval(current_interval);
  146. } else {
  147. m_check_timer->set_interval(1000);
  148. }
  149. }
  150. ErrorOr<DHCPv4Client::Interfaces> DHCPv4Client::get_discoverable_interfaces()
  151. {
  152. auto file = TRY(Core::File::open("/sys/kernel/net/adapters"sv, Core::File::OpenMode::Read));
  153. auto file_contents = TRY(file->read_until_eof());
  154. auto json = JsonValue::from_string(file_contents);
  155. if (json.is_error() || !json.value().is_array()) {
  156. dbgln("Error: No network adapters available");
  157. return Error::from_string_literal("No network adapters available");
  158. }
  159. Vector<InterfaceDescriptor> ifnames_to_immediately_discover, ifnames_to_attempt_later;
  160. json.value().as_array().for_each([&ifnames_to_immediately_discover, &ifnames_to_attempt_later](auto& value) {
  161. auto if_object = value.as_object();
  162. if (if_object.get_deprecated_string("class_name"sv).value_or({}) == "LoopbackAdapter")
  163. return;
  164. auto name = if_object.get_deprecated_string("name"sv).value_or({});
  165. auto mac = if_object.get_deprecated_string("mac_address"sv).value_or({});
  166. auto is_up = if_object.get_bool("link_up"sv).value_or(false);
  167. auto ipv4_addr_maybe = IPv4Address::from_string(if_object.get_deprecated_string("ipv4_address"sv).value_or({}));
  168. auto ipv4_addr = ipv4_addr_maybe.has_value() ? ipv4_addr_maybe.value() : IPv4Address { 0, 0, 0, 0 };
  169. if (is_up) {
  170. dbgln_if(DHCPV4_DEBUG, "Found adapter '{}' with mac {}, and it was up!", name, mac);
  171. ifnames_to_immediately_discover.empend(name, mac_from_string(mac), ipv4_addr);
  172. } else {
  173. dbgln_if(DHCPV4_DEBUG, "Found adapter '{}' with mac {}, but it was down", name, mac);
  174. ifnames_to_attempt_later.empend(name, mac_from_string(mac), ipv4_addr);
  175. }
  176. });
  177. return Interfaces {
  178. move(ifnames_to_immediately_discover),
  179. move(ifnames_to_attempt_later)
  180. };
  181. }
  182. void DHCPv4Client::handle_offer(DHCPv4Packet const& packet, ParsedDHCPv4Options const& options)
  183. {
  184. dbgln("We were offered {} for {}", packet.yiaddr().to_deprecated_string(), options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(0));
  185. auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
  186. if (!transaction) {
  187. dbgln("we're not looking for {}", packet.xid());
  188. return;
  189. }
  190. if (transaction->has_ip)
  191. return;
  192. if (transaction->accepted_offer) {
  193. // we've accepted someone's offer, but they haven't given us an ack
  194. // TODO: maybe record this offer?
  195. return;
  196. }
  197. // TAKE IT...
  198. transaction->offered_lease_time = options.get<u32>(DHCPOption::IPAddressLeaseTime).value();
  199. dhcp_request(*transaction, packet);
  200. }
  201. void DHCPv4Client::handle_ack(DHCPv4Packet const& packet, ParsedDHCPv4Options const& options)
  202. {
  203. if constexpr (DHCPV4CLIENT_DEBUG) {
  204. dbgln("The DHCP server handed us {}", packet.yiaddr().to_deprecated_string());
  205. dbgln("Here are the options: {}", options.to_deprecated_string());
  206. }
  207. auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
  208. if (!transaction) {
  209. dbgln("we're not looking for {}", packet.xid());
  210. return;
  211. }
  212. transaction->has_ip = true;
  213. auto& interface = transaction->interface;
  214. auto new_ip = packet.yiaddr();
  215. interface.current_ip_address = new_ip;
  216. auto lease_time = AK::convert_between_host_and_network_endian(options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(transaction->offered_lease_time));
  217. // set a timer for the duration of the lease, we shall renew if needed
  218. (void)Core::Timer::create_single_shot(
  219. lease_time * 1000,
  220. [this, transaction, interface = InterfaceDescriptor { interface }] {
  221. transaction->accepted_offer = false;
  222. transaction->has_ip = false;
  223. dhcp_discover(interface);
  224. },
  225. this)
  226. .release_value_but_fixme_should_propagate_errors();
  227. Optional<IPv4Address> gateway;
  228. if (auto routers = options.get_many<IPv4Address>(DHCPOption::Router, 1); !routers.is_empty())
  229. gateway = routers.first();
  230. set_params(transaction->interface, new_ip, options.get<IPv4Address>(DHCPOption::SubnetMask).value(), gateway);
  231. }
  232. void DHCPv4Client::handle_nak(DHCPv4Packet const& packet, ParsedDHCPv4Options const& options)
  233. {
  234. dbgln("The DHCP server told us to go chase our own tail about {}", packet.yiaddr().to_deprecated_string());
  235. dbgln("Here are the options: {}", options.to_deprecated_string());
  236. // make another request a bit later :shrug:
  237. auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
  238. if (!transaction) {
  239. dbgln("we're not looking for {}", packet.xid());
  240. return;
  241. }
  242. transaction->accepted_offer = false;
  243. transaction->has_ip = false;
  244. auto& iface = transaction->interface;
  245. (void)Core::Timer::create_single_shot(
  246. 10000,
  247. [this, iface = InterfaceDescriptor { iface }] {
  248. dhcp_discover(iface);
  249. },
  250. this)
  251. .release_value_but_fixme_should_propagate_errors();
  252. }
  253. void DHCPv4Client::process_incoming(DHCPv4Packet const& packet)
  254. {
  255. auto options = packet.parse_options();
  256. dbgln_if(DHCPV4CLIENT_DEBUG, "Here are the options: {}", options.to_deprecated_string());
  257. auto value_or_error = options.get<DHCPMessageType>(DHCPOption::DHCPMessageType);
  258. if (!value_or_error.has_value())
  259. return;
  260. auto value = value_or_error.value();
  261. switch (value) {
  262. case DHCPMessageType::DHCPOffer:
  263. handle_offer(packet, options);
  264. break;
  265. case DHCPMessageType::DHCPAck:
  266. handle_ack(packet, options);
  267. break;
  268. case DHCPMessageType::DHCPNak:
  269. handle_nak(packet, options);
  270. break;
  271. case DHCPMessageType::DHCPDiscover:
  272. case DHCPMessageType::DHCPRequest:
  273. case DHCPMessageType::DHCPRelease:
  274. // These are not for us
  275. // we're just getting them because there are other people on our subnet
  276. // broadcasting stuff
  277. break;
  278. case DHCPMessageType::DHCPDecline:
  279. default:
  280. dbgln("I dunno what to do with this {}", (u8)value);
  281. VERIFY_NOT_REACHED();
  282. break;
  283. }
  284. }
  285. void DHCPv4Client::dhcp_discover(InterfaceDescriptor const& iface)
  286. {
  287. auto transaction_id = get_random<u32>();
  288. if constexpr (DHCPV4CLIENT_DEBUG) {
  289. dbgln("Trying to lease an IP for {} with ID {}", iface.ifname, transaction_id);
  290. if (!iface.current_ip_address.is_zero())
  291. dbgln("going to request the server to hand us {}", iface.current_ip_address.to_deprecated_string());
  292. }
  293. DHCPv4PacketBuilder builder;
  294. DHCPv4Packet& packet = builder.peek();
  295. packet.set_op(DHCPv4Op::BootRequest);
  296. packet.set_htype(1); // 10mb ethernet
  297. packet.set_hlen(sizeof(MACAddress));
  298. packet.set_xid(transaction_id);
  299. packet.set_flags(DHCPv4Flags::Broadcast);
  300. packet.ciaddr() = iface.current_ip_address;
  301. packet.set_chaddr(iface.mac_address);
  302. packet.set_secs(65535); // we lie
  303. // set packet options
  304. builder.set_message_type(DHCPMessageType::DHCPDiscover);
  305. auto& dhcp_packet = builder.build();
  306. // broadcast the discover request
  307. if (!send(iface, dhcp_packet, this))
  308. return;
  309. m_ongoing_transactions.set(transaction_id, make<DHCPv4Transaction>(iface));
  310. }
  311. void DHCPv4Client::dhcp_request(DHCPv4Transaction& transaction, DHCPv4Packet const& offer)
  312. {
  313. auto& iface = transaction.interface;
  314. dbgln("Leasing the IP {} for adapter {}", offer.yiaddr().to_deprecated_string(), iface.ifname);
  315. DHCPv4PacketBuilder builder;
  316. DHCPv4Packet& packet = builder.peek();
  317. packet.set_op(DHCPv4Op::BootRequest);
  318. packet.ciaddr() = iface.current_ip_address;
  319. packet.set_htype(1); // 10mb ethernet
  320. packet.set_hlen(sizeof(MACAddress));
  321. packet.set_xid(offer.xid());
  322. packet.set_flags(DHCPv4Flags::Broadcast);
  323. packet.set_chaddr(iface.mac_address);
  324. packet.set_secs(65535); // we lie
  325. // set packet options
  326. builder.set_message_type(DHCPMessageType::DHCPRequest);
  327. builder.add_option(DHCPOption::RequestedIPAddress, sizeof(IPv4Address), &offer.yiaddr());
  328. auto maybe_dhcp_server_ip = offer.parse_options().get<IPv4Address>(DHCPOption::ServerIdentifier);
  329. if (maybe_dhcp_server_ip.has_value())
  330. builder.add_option(DHCPOption::ServerIdentifier, sizeof(IPv4Address), &maybe_dhcp_server_ip.value());
  331. AK::Array<DHCPOption, 2> parameter_request_list = {
  332. DHCPOption::SubnetMask,
  333. DHCPOption::Router,
  334. };
  335. builder.add_option(DHCPOption::ParameterRequestList, parameter_request_list.size(), &parameter_request_list);
  336. auto& dhcp_packet = builder.build();
  337. // broadcast the "request" request
  338. if (!send(iface, dhcp_packet, this))
  339. return;
  340. transaction.accepted_offer = true;
  341. }