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