summaryrefslogtreecommitdiff
path: root/Services/DHCPClient
diff options
context:
space:
mode:
authorAndreas Kling <kling@serenityos.org>2020-05-08 21:57:44 +0200
committerAndreas Kling <kling@serenityos.org>2020-05-08 21:57:44 +0200
commitcf3b58fbe8f836c13e44d6152d78960aff6089ef (patch)
treedcc7664f0004ee9c495f9d948cfb16d12f8a70bf /Services/DHCPClient
parent042b1f68145ad3754fd98429b405c5c1a173d3fc (diff)
downloadserenity-cf3b58fbe8f836c13e44d6152d78960aff6089ef.zip
Services: Renamed from Servers
It didn't feel right to have a "DHCPClient" in a "Servers" directory. Rename this to Services to better reflect the type of programs we'll be putting in there.
Diffstat (limited to 'Services/DHCPClient')
-rw-r--r--Services/DHCPClient/DHCPv4.cpp59
-rw-r--r--Services/DHCPClient/DHCPv4.h300
-rw-r--r--Services/DHCPClient/DHCPv4Client.cpp283
-rw-r--r--Services/DHCPClient/DHCPv4Client.h80
-rw-r--r--Services/DHCPClient/Makefile10
-rw-r--r--Services/DHCPClient/main.cpp101
6 files changed, 833 insertions, 0 deletions
diff --git a/Services/DHCPClient/DHCPv4.cpp b/Services/DHCPClient/DHCPv4.cpp
new file mode 100644
index 0000000000..36430f4eec
--- /dev/null
+++ b/Services/DHCPClient/DHCPv4.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DHCPv4.h"
+
+//#define DHCPV4_DEBUG
+
+ParsedDHCPv4Options DHCPv4Packet::parse_options() const
+{
+ ParsedDHCPv4Options options;
+ for (size_t index = 4; index < DHCPV4_OPTION_FIELD_MAX_LENGTH; ++index) {
+ auto opt_name = *(const DHCPOption*)&m_options[index];
+ switch (opt_name) {
+ case DHCPOption::Pad:
+ continue;
+ case DHCPOption::End:
+ goto escape;
+ default:
+ ++index;
+ auto length = m_options[index];
+ if ((size_t)length > DHCPV4_OPTION_FIELD_MAX_LENGTH - index) {
+ dbg() << "Bogus option length " << length << " assuming forgotten END";
+ break;
+ }
+#ifdef DHCPV4_DEBUG
+ dbg() << "DHCP Option " << (u8)opt_name << " with length " << length;
+#endif
+ ++index;
+ options.options.set(opt_name, { length, &m_options[index] });
+ index += length - 1;
+ break;
+ }
+ }
+escape:;
+ return options;
+}
diff --git a/Services/DHCPClient/DHCPv4.h b/Services/DHCPClient/DHCPv4.h
new file mode 100644
index 0000000000..07c5787f8a
--- /dev/null
+++ b/Services/DHCPClient/DHCPv4.h
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <AK/Assertions.h>
+#include <AK/ByteBuffer.h>
+#include <AK/HashMap.h>
+#include <AK/IPv4Address.h>
+#include <AK/MACAddress.h>
+#include <AK/NetworkOrdered.h>
+#include <AK/StringBuilder.h>
+#include <AK/StringView.h>
+#include <AK/Traits.h>
+#include <AK/Types.h>
+#include <string.h>
+
+enum class DHCPv4Flags : u16 {
+ Broadcast = 1,
+ /* everything else is reserved and must be zero */
+};
+
+enum class DHCPv4Op : u8 {
+ BootRequest = 1,
+ BootReply = 2
+};
+
+enum class DHCPOption : u8 {
+ // BOOTP
+ Pad = 0,
+ SubnetMask,
+ TimeOffset,
+ Router,
+ TimeServer,
+ NameServer,
+ DomainNameServer,
+ LogServer,
+ CookieServer,
+ LPRServer,
+ ImpressServer,
+ ResourceLocationServer,
+ HostName,
+ BootFileSize,
+ MeritDumpFile,
+ DomainName,
+ SwapServer,
+ RootPath,
+ ExtensionsPath,
+ IPForwardingEnableDisable,
+ NonLocalSourceRoutingEnableDisable,
+ PolicyFilter,
+ MaximumDatagramReassemblySize,
+ DefaultIPTTL,
+ PathMTUAgingTimeout,
+ PathMTUPlateauTable,
+ InterfaceMTU,
+ AllSubnetsAreLocal,
+ BroadcastAddress,
+ PerformMaskDiscovery,
+ MaskSupplier,
+ PerformRouterDiscovery,
+ RouterSolicitationAddress,
+ StaticRoute,
+ TrailerEncapsulation,
+ ARPCacheTimeout,
+ EthernetEncapsulation,
+ TCPDefaultTTL,
+ TCPKeepaliveInterval,
+ TCPKeepaliveGarbage,
+ NetworkInformationServiceDomain,
+ NetworkInformationServers,
+ NetworkTimeProtocolServers,
+ VendorSpecificInformation,
+ NetBIOSOverTCPIPNameServer,
+ NetBIOSOverTCPIPDatagramDistributionServer,
+ NetBIOSOverTCPIPNodeType,
+ NetBIOSOverTCPIPScope,
+ XWindowSystemFontServer, // wow
+ XWindowSystemDisplayManager,
+ // DHCP
+ RequestedIPAddress = 50,
+ IPAddressLeaseTime,
+ OptionOverload,
+ DHCPMessageType,
+ ServerIdentifier,
+ ParameterRequestList,
+ Message,
+ MaximumDHCPMessageSize,
+ RenewalT1Time,
+ RenewalT2Time,
+ ClassIdentifier,
+ ClientIdentifier,
+ End = 255
+};
+
+enum class DHCPMessageType : u8 {
+ DHCPDiscover = 1,
+ DHCPOffer,
+ DHCPRequest,
+ DHCPDecline,
+ DHCPAck,
+ DHCPNak,
+ DHCPRelease,
+};
+
+template <>
+struct AK::Traits<DHCPOption> : public AK::GenericTraits<DHCPOption> {
+ static constexpr bool is_trivial() { return true; }
+ static unsigned hash(DHCPOption u) { return int_hash((u8)u); }
+};
+
+struct ParsedDHCPv4Options {
+ template <typename T>
+ Optional<const T> get(DHCPOption option_name) const
+ {
+ auto option = options.get(option_name);
+ if (!option.has_value()) {
+ return {};
+ }
+ auto& value = option.value();
+ if (value.length != sizeof(T))
+ return {};
+ return *(const T*)value.value;
+ }
+
+ template <typename T>
+ Vector<T> get_many(DHCPOption option_name, size_t max_number) const
+ {
+ Vector<T> values;
+
+ auto option = options.get(option_name);
+ if (!option.has_value()) {
+ return {};
+ }
+ auto& value = option.value();
+ if (value.length < sizeof(T))
+ return {};
+
+ for (size_t i = 0; i < max_number; ++i) {
+ auto offset = i * sizeof(T);
+ if (offset >= value.length)
+ break;
+ values.append(*(T*)((u8*)const_cast<void*>(value.value) + offset));
+ }
+
+ return values;
+ }
+
+ String to_string() const
+ {
+ StringBuilder builder;
+ builder.append("DHCP Options (");
+ builder.appendf("%d", options.size());
+ builder.append(" entries)\n");
+ for (auto& opt : options) {
+ builder.appendf("\toption %d (%d bytes):", (u8)opt.key, (u8)opt.value.length);
+ for (auto i = 0; i < opt.value.length; ++i)
+ builder.appendf(" %u ", ((const u8*)opt.value.value)[i]);
+ builder.append('\n');
+ }
+ return builder.build();
+ }
+
+ struct DHCPOptionValue {
+ u8 length;
+ const void* value;
+ };
+
+ HashMap<DHCPOption, DHCPOptionValue> options;
+};
+
+constexpr auto DHCPV4_OPTION_FIELD_MAX_LENGTH = 312;
+
+class [[gnu::packed]] DHCPv4Packet
+{
+public:
+ u8 op() const { return m_op; }
+ void set_op(DHCPv4Op op) { m_op = (u8)op; }
+
+ u8 htype() const { return m_htype; }
+ void set_htype(u8 htype) { m_htype = htype; }
+
+ u8 hlen() const { return m_hlen; }
+ void set_hlen(u8 hlen) { m_hlen = hlen; }
+
+ u8 hops() const { return m_hops; }
+ void set_hops(u8 hops) { m_hops = hops; }
+
+ u32 xid() const { return m_xid; }
+ void set_xid(u32 xid) { m_xid = xid; }
+
+ u16 secs() const { return m_secs; }
+ void set_secs(u16 secs) { m_secs = secs; }
+
+ u16 flags() const { return m_flags; }
+ void set_flags(DHCPv4Flags flags) { m_flags = (u16)flags; }
+
+ const IPv4Address& ciaddr() const { return m_ciaddr; }
+ const IPv4Address& yiaddr() const { return m_yiaddr; }
+ const IPv4Address& siaddr() const { return m_siaddr; }
+ const IPv4Address& giaddr() const { return m_giaddr; }
+
+ IPv4Address& ciaddr() { return m_ciaddr; }
+ IPv4Address& yiaddr() { return m_yiaddr; }
+ IPv4Address& siaddr() { return m_siaddr; }
+ IPv4Address& giaddr() { return m_giaddr; }
+
+ u8* options() { return m_options; }
+ ParsedDHCPv4Options parse_options() const;
+
+ const MACAddress& chaddr() const { return *(const MACAddress*)&m_chaddr[0]; }
+ void set_chaddr(const MACAddress& mac) { *(MACAddress*)&m_chaddr[0] = mac; }
+
+ StringView sname() const { return { (const char*)&m_sname[0] }; }
+ StringView file() const { return { (const char*)&m_file[0] }; }
+
+private:
+ NetworkOrdered<u8> m_op;
+ NetworkOrdered<u8> m_htype;
+ NetworkOrdered<u8> m_hlen;
+ NetworkOrdered<u8> m_hops;
+ NetworkOrdered<u32> m_xid;
+ NetworkOrdered<u16> m_secs;
+ NetworkOrdered<u16> m_flags;
+ IPv4Address m_ciaddr;
+ IPv4Address m_yiaddr;
+ IPv4Address m_siaddr;
+ IPv4Address m_giaddr;
+ u8 m_chaddr[16]; // 10 bytes of padding at the end
+ u8 m_sname[64] { 0 };
+ u8 m_file[128] { 0 };
+ u8 m_options[DHCPV4_OPTION_FIELD_MAX_LENGTH] { 0 }; // variable, less than 312 bytes
+};
+
+class DHCPv4PacketBuilder {
+public:
+ DHCPv4PacketBuilder()
+ : m_buffer(ByteBuffer::create_zeroed(sizeof(DHCPv4Packet)))
+ {
+ auto* options = peek().options();
+ // set the magic DHCP cookie value
+ options[0] = 99;
+ options[1] = 130;
+ options[2] = 83;
+ options[3] = 99;
+ }
+
+ void add_option(DHCPOption option, u8 length, const void* data)
+ {
+ ASSERT(m_can_add);
+ // we need enough space to fit the option value, its length, and its data
+ ASSERT(next_option_offset + length + 2 < DHCPV4_OPTION_FIELD_MAX_LENGTH);
+
+ auto* options = peek().options();
+ options[next_option_offset++] = (u8)option;
+ memcpy(options + next_option_offset, &length, 1);
+ next_option_offset++;
+ memcpy(options + next_option_offset, data, length);
+ next_option_offset += length;
+ }
+
+ void set_message_type(DHCPMessageType type) { add_option(DHCPOption::DHCPMessageType, 1, &type); }
+
+ DHCPv4Packet& peek() { return *(DHCPv4Packet*)m_buffer.data(); }
+ DHCPv4Packet& build()
+ {
+ add_option(DHCPOption::End, 0, nullptr);
+ m_can_add = false;
+ return *(DHCPv4Packet*)m_buffer.data();
+ }
+ size_t size() const { return m_buffer.size(); }
+
+private:
+ ByteBuffer m_buffer;
+ size_t next_option_offset { 4 };
+ bool m_can_add { true };
+};
diff --git a/Services/DHCPClient/DHCPv4Client.cpp b/Services/DHCPClient/DHCPv4Client.cpp
new file mode 100644
index 0000000000..918a20b93f
--- /dev/null
+++ b/Services/DHCPClient/DHCPv4Client.cpp
@@ -0,0 +1,283 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DHCPv4Client.h"
+#include <AK/ByteBuffer.h>
+#include <AK/Function.h>
+#include <LibCore/SocketAddress.h>
+#include <LibCore/Timer.h>
+#include <stdio.h>
+
+//#define DHCPV4CLIENT_DEBUG
+
+static void send(const InterfaceDescriptor& iface, const DHCPv4Packet& packet, Core::Object*)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+ if (fd < 0) {
+ dbg() << "ERROR: socket :: " << strerror(errno);
+ return;
+ }
+
+ if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface.m_ifname.characters(), IFNAMSIZ) < 0) {
+ dbg() << "ERROR: setsockopt(SO_BINDTODEVICE) :: " << strerror(errno);
+ return;
+ }
+
+ sockaddr_in dst;
+ memset(&dst, 0, sizeof(dst));
+ dst.sin_family = AF_INET;
+ dst.sin_port = htons(67);
+ dst.sin_addr.s_addr = IPv4Address { 255, 255, 255, 255 }.to_u32();
+ memset(&dst.sin_zero, 0, sizeof(dst.sin_zero));
+
+ auto rc = sendto(fd, &packet, sizeof(packet), 0, (sockaddr*)&dst, sizeof(dst));
+ if (rc < 0) {
+ dbg() << "sendto failed with " << strerror(errno);
+ // FIXME: what do we do here?
+ }
+}
+
+static void set_params(const InterfaceDescriptor& iface, const IPv4Address& ipv4_addr, const IPv4Address& netmask, const IPv4Address& gateway)
+{
+ int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (fd < 0) {
+ dbg() << "ERROR: socket :: " << strerror(errno);
+ return;
+ }
+
+ struct ifreq ifr;
+ memset(&ifr, 0, sizeof(ifr));
+ strncpy(ifr.ifr_name, iface.m_ifname.characters(), IFNAMSIZ);
+
+ // set the IP address
+ ifr.ifr_addr.sa_family = AF_INET;
+ ((sockaddr_in&)ifr.ifr_addr).sin_addr.s_addr = ipv4_addr.to_in_addr_t();
+
+ if (ioctl(fd, SIOCSIFADDR, &ifr) < 0) {
+ dbg() << "ERROR: ioctl(SIOCSIFADDR) :: " << strerror(errno);
+ }
+
+ // set the network mask
+ ((sockaddr_in&)ifr.ifr_netmask).sin_addr.s_addr = netmask.to_in_addr_t();
+
+ if (ioctl(fd, SIOCSIFNETMASK, &ifr) < 0) {
+ dbg() << "ERROR: ioctl(SIOCSIFNETMASK) :: " << strerror(errno);
+ }
+
+ // set the default gateway
+ struct rtentry rt;
+ memset(&rt, 0, sizeof(rt));
+
+ rt.rt_dev = const_cast<char*>(iface.m_ifname.characters());
+ rt.rt_gateway.sa_family = AF_INET;
+ ((sockaddr_in&)rt.rt_gateway).sin_addr.s_addr = gateway.to_in_addr_t();
+ rt.rt_flags = RTF_UP | RTF_GATEWAY;
+
+ if (ioctl(fd, SIOCADDRT, &rt) < 0) {
+ dbg() << "Error: ioctl(SIOCADDRT) :: " << strerror(errno);
+ }
+}
+
+DHCPv4Client::DHCPv4Client(Vector<InterfaceDescriptor> ifnames)
+ : m_ifnames(ifnames)
+{
+ m_server = Core::UDPServer::construct(this);
+ m_server->on_ready_to_receive = [this] {
+ auto buffer = m_server->receive(sizeof(DHCPv4Packet));
+ dbg() << "Received " << buffer.size() << " bytes";
+ if (buffer.size() != sizeof(DHCPv4Packet)) {
+ dbg() << "we expected " << sizeof(DHCPv4Packet) << " bytes, this is a bad packet";
+ return;
+ }
+ auto& packet = *(DHCPv4Packet*)buffer.data();
+ process_incoming(packet);
+ };
+
+ if (!m_server->bind({}, 68)) {
+ dbg() << "The server we just created somehow came already bound, refusing to continue";
+ ASSERT_NOT_REACHED();
+ }
+
+ for (auto& iface : m_ifnames)
+ dhcp_discover(iface);
+}
+
+DHCPv4Client::~DHCPv4Client()
+{
+}
+
+void DHCPv4Client::handle_offer(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
+{
+ dbg() << "We were offered " << packet.yiaddr().to_string() << " for " << options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(0);
+ auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
+ if (!transaction) {
+ dbg() << "we're not looking for " << packet.xid();
+ return;
+ }
+ if (transaction->has_ip)
+ return;
+ if (transaction->accepted_offer) {
+ // we've accepted someone's offer, but they haven't given us an ack
+ // TODO: maybe record this offer?
+ return;
+ }
+ // TAKE IT...
+ transaction->offered_lease_time = options.get<u32>(DHCPOption::IPAddressLeaseTime).value();
+ dhcp_request(*transaction, packet);
+}
+
+void DHCPv4Client::handle_ack(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
+{
+#ifdef DHCPV4CLIENT_DEBUG
+ dbg() << "The DHCP server handed us " << packet.yiaddr().to_string();
+ dbg() << "Here are the options: " << options.to_string();
+#endif
+ auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
+ if (!transaction) {
+ dbg() << "we're not looking for " << packet.xid();
+ return;
+ }
+ transaction->has_ip = true;
+ auto& interface = transaction->interface;
+ auto new_ip = packet.yiaddr();
+ auto lease_time = convert_between_host_and_network(options.get<u32>(DHCPOption::IPAddressLeaseTime).value_or(transaction->offered_lease_time));
+ // set a timer for the duration of the lease, we shall renew if needed
+ Core::Timer::create_single_shot(
+ lease_time * 1000,
+ [this, transaction, interface = InterfaceDescriptor { interface }, new_ip] {
+ transaction->accepted_offer = false;
+ transaction->has_ip = false;
+ dhcp_discover(interface, new_ip);
+ },
+ this);
+ set_params(transaction->interface, new_ip, options.get<IPv4Address>(DHCPOption::SubnetMask).value(), options.get_many<IPv4Address>(DHCPOption::Router, 1).first());
+}
+
+void DHCPv4Client::handle_nak(const DHCPv4Packet& packet, const ParsedDHCPv4Options& options)
+{
+ dbg() << "The DHCP server told us to go chase our own tail about " << packet.yiaddr().to_string();
+ dbg() << "Here are the options: " << options.to_string();
+ // make another request a bit later :shrug:
+ auto* transaction = const_cast<DHCPv4Transaction*>(m_ongoing_transactions.get(packet.xid()).value_or(nullptr));
+ if (!transaction) {
+ dbg() << "we're not looking for " << packet.xid();
+ return;
+ }
+ transaction->accepted_offer = false;
+ transaction->has_ip = false;
+ auto& iface = transaction->interface;
+ Core::Timer::create_single_shot(
+ 10000,
+ [this, iface = InterfaceDescriptor { iface }] {
+ dhcp_discover(iface);
+ },
+ this);
+}
+
+void DHCPv4Client::process_incoming(const DHCPv4Packet& packet)
+{
+ auto options = packet.parse_options();
+#ifdef DHCPV4CLIENT_DEBUG
+ dbg() << "Here are the options: " << options.to_string();
+#endif
+ auto value = options.get<DHCPMessageType>(DHCPOption::DHCPMessageType).value();
+ switch (value) {
+ case DHCPMessageType::DHCPOffer:
+ handle_offer(packet, options);
+ break;
+ case DHCPMessageType::DHCPAck:
+ handle_ack(packet, options);
+ break;
+ case DHCPMessageType::DHCPNak:
+ handle_nak(packet, options);
+ break;
+ case DHCPMessageType::DHCPDiscover:
+ case DHCPMessageType::DHCPRequest:
+ case DHCPMessageType::DHCPRelease:
+ // These are not for us
+ // we're just getting them because there are other people on our subnet
+ // broadcasting stuff
+ break;
+ case DHCPMessageType::DHCPDecline:
+ default:
+ dbg() << "I dunno what to do with this " << (u8)value;
+ ASSERT_NOT_REACHED();
+ break;
+ }
+}
+
+void DHCPv4Client::dhcp_discover(const InterfaceDescriptor& iface, IPv4Address previous)
+{
+ auto transaction_id = rand();
+#ifdef DHCPV4CLIENT_DEBUG
+ dbg() << "Trying to lease an IP for " << iface.m_ifname << " with ID " << transaction_id;
+ if (!previous.is_zero())
+ dbg() << "going to request the server to hand us " << previous.to_string();
+#endif
+ DHCPv4PacketBuilder builder;
+
+ DHCPv4Packet& packet = builder.peek();
+ packet.set_op(DHCPv4Op::BootRequest);
+ packet.set_htype(1); // 10mb ethernet
+ packet.set_hlen(sizeof(MACAddress));
+ packet.set_xid(transaction_id);
+ packet.set_flags(DHCPv4Flags::Broadcast);
+ packet.ciaddr() = previous;
+ packet.set_chaddr(iface.m_mac_address);
+ packet.set_secs(65535); // we lie
+
+ // set packet options
+ builder.set_message_type(DHCPMessageType::DHCPDiscover);
+ auto& dhcp_packet = builder.build();
+
+ // broadcast the discover request
+ send(iface, dhcp_packet, this);
+ m_ongoing_transactions.set(transaction_id, make<DHCPv4Transaction>(iface));
+}
+
+void DHCPv4Client::dhcp_request(DHCPv4Transaction& transaction, const DHCPv4Packet& offer)
+{
+ auto& iface = transaction.interface;
+ dbg() << "Leasing the IP " << offer.yiaddr().to_string() << " for adapter " << iface.m_ifname;
+ DHCPv4PacketBuilder builder;
+
+ DHCPv4Packet& packet = builder.peek();
+ packet.set_op(DHCPv4Op::BootRequest);
+ packet.set_htype(1); // 10mb ethernet
+ packet.set_hlen(sizeof(MACAddress));
+ packet.set_xid(offer.xid());
+ packet.set_flags(DHCPv4Flags::Broadcast);
+ packet.set_chaddr(iface.m_mac_address);
+ packet.set_secs(65535); // we lie
+
+ // set packet options
+ builder.set_message_type(DHCPMessageType::DHCPRequest);
+ auto& dhcp_packet = builder.build();
+
+ // broadcast the "request" request
+ send(iface, dhcp_packet, this);
+ transaction.accepted_offer = true;
+}
diff --git a/Services/DHCPClient/DHCPv4Client.h b/Services/DHCPClient/DHCPv4Client.h
new file mode 100644
index 0000000000..c45f08f0d1
--- /dev/null
+++ b/Services/DHCPClient/DHCPv4Client.h
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "DHCPv4.h"
+#include <AK/FlyString.h>
+#include <AK/HashMap.h>
+#include <AK/OwnPtr.h>
+#include <AK/Vector.h>
+#include <LibCore/UDPServer.h>
+#include <LibCore/UDPSocket.h>
+#include <net/if.h>
+#include <net/route.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+struct InterfaceDescriptor {
+ FlyString m_ifname;
+ MACAddress m_mac_address;
+};
+
+struct DHCPv4Transaction {
+ DHCPv4Transaction(InterfaceDescriptor ifname)
+ : interface(ifname)
+ {
+ }
+
+ InterfaceDescriptor interface;
+ bool accepted_offer { false };
+ bool has_ip { false };
+ u32 offered_lease_time { 0 };
+};
+
+class DHCPv4Client final : public Core::Object {
+ C_OBJECT(DHCPv4Client)
+
+public:
+ explicit DHCPv4Client(Vector<InterfaceDescriptor> ifnames);
+ virtual ~DHCPv4Client() override;
+
+ void dhcp_discover(const InterfaceDescriptor& ifname, IPv4Address previous = IPv4Address { 0, 0, 0, 0 });
+ void dhcp_request(DHCPv4Transaction& transaction, const DHCPv4Packet& packet);
+
+ void process_incoming(const DHCPv4Packet& packet);
+
+ bool id_is_registered(u32 id) { return m_ongoing_transactions.contains(id); }
+
+private:
+ HashMap<u32, OwnPtr<DHCPv4Transaction>> m_ongoing_transactions;
+ Vector<InterfaceDescriptor> m_ifnames;
+ RefPtr<Core::UDPServer> m_server;
+
+ void handle_offer(const DHCPv4Packet&, const ParsedDHCPv4Options&);
+ void handle_ack(const DHCPv4Packet&, const ParsedDHCPv4Options&);
+ void handle_nak(const DHCPv4Packet&, const ParsedDHCPv4Options&);
+};
diff --git a/Services/DHCPClient/Makefile b/Services/DHCPClient/Makefile
new file mode 100644
index 0000000000..ce7c27b86d
--- /dev/null
+++ b/Services/DHCPClient/Makefile
@@ -0,0 +1,10 @@
+OBJS = \
+ DHCPv4.o \
+ DHCPv4Client.o \
+ main.o
+
+PROGRAM = DHCPClient
+
+LIB_DEPS = Core
+
+include ../../Makefile.common
diff --git a/Services/DHCPClient/main.cpp b/Services/DHCPClient/main.cpp
new file mode 100644
index 0000000000..b65b63eb46
--- /dev/null
+++ b/Services/DHCPClient/main.cpp
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2020, The SerenityOS developers.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice, this
+ * list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "DHCPv4Client.h"
+#include <AK/JsonArray.h>
+#include <AK/JsonObject.h>
+#include <AK/String.h>
+#include <AK/Types.h>
+#include <LibCore/EventLoop.h>
+#include <LibCore/File.h>
+#include <LibCore/LocalServer.h>
+#include <stdio.h>
+#include <string.h>
+
+static u8 mac_part(const Vector<String>& parts, size_t index)
+{
+ auto chars = parts.at(index).characters();
+ return (chars[0] - '0') * 16 + (chars[1] - '0');
+}
+
+static MACAddress mac_from_string(const String& str)
+{
+ auto chunks = str.split(':');
+ ASSERT(chunks.size() == 6); // should we...worry about this?
+ return {
+ mac_part(chunks, 0), mac_part(chunks, 1), mac_part(chunks, 2),
+ mac_part(chunks, 3), mac_part(chunks, 4), mac_part(chunks, 5)
+ };
+}
+
+int main(int argc, char** argv)
+{
+ (void)argc;
+ (void)argv;
+
+ if (pledge("stdio unix inet cpath rpath fattr", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ Core::EventLoop event_loop;
+
+ if (unveil("/proc/net/", "r") < 0) {
+ perror("unveil");
+ return 1;
+ }
+
+ unveil(nullptr, nullptr);
+
+ auto file = Core::File::construct("/proc/net/adapters");
+ if (!file->open(Core::IODevice::ReadOnly)) {
+ fprintf(stderr, "Error: %s\n", file->error_string());
+ return 1;
+ }
+
+ auto file_contents = file->read_all();
+ auto json = JsonValue::from_string(file_contents).as_array();
+ Vector<InterfaceDescriptor> ifnames;
+ json.for_each([&ifnames](auto& value) {
+ auto if_object = value.as_object();
+
+ if (if_object.get("class_name").to_string() == "LoopbackAdapter")
+ return;
+
+ auto name = if_object.get("name").to_string();
+ auto mac = if_object.get("mac_address").to_string();
+ ifnames.append({ name, mac_from_string(mac) });
+ });
+
+ auto client = DHCPv4Client::construct(move(ifnames));
+
+ if (pledge("stdio inet", nullptr) < 0) {
+ perror("pledge");
+ return 1;
+ }
+
+ return event_loop.exec();
+}