Serenity Operating System
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(), ¶meter_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}