From cf3b58fbe8f836c13e44d6152d78960aff6089ef Mon Sep 17 00:00:00 2001 From: Andreas Kling Date: Fri, 8 May 2020 21:57:44 +0200 Subject: 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. --- Services/DHCPClient/DHCPv4.cpp | 59 +++++++ Services/DHCPClient/DHCPv4.h | 300 +++++++++++++++++++++++++++++++++++ Services/DHCPClient/DHCPv4Client.cpp | 283 +++++++++++++++++++++++++++++++++ Services/DHCPClient/DHCPv4Client.h | 80 ++++++++++ Services/DHCPClient/Makefile | 10 ++ Services/DHCPClient/main.cpp | 101 ++++++++++++ 6 files changed, 833 insertions(+) create mode 100644 Services/DHCPClient/DHCPv4.cpp create mode 100644 Services/DHCPClient/DHCPv4.h create mode 100644 Services/DHCPClient/DHCPv4Client.cpp create mode 100644 Services/DHCPClient/DHCPv4Client.h create mode 100644 Services/DHCPClient/Makefile create mode 100644 Services/DHCPClient/main.cpp (limited to 'Services/DHCPClient') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 : public AK::GenericTraits { + static constexpr bool is_trivial() { return true; } + static unsigned hash(DHCPOption u) { return int_hash((u8)u); } +}; + +struct ParsedDHCPv4Options { + template + Optional 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 + Vector get_many(DHCPOption option_name, size_t max_number) const + { + Vector 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(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 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 m_op; + NetworkOrdered m_htype; + NetworkOrdered m_hlen; + NetworkOrdered m_hops; + NetworkOrdered m_xid; + NetworkOrdered m_secs; + NetworkOrdered 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 +#include +#include +#include +#include + +//#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(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 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(DHCPOption::IPAddressLeaseTime).value_or(0); + auto* transaction = const_cast(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(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(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(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(DHCPOption::SubnetMask).value(), options.get_many(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(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(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(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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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 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> m_ongoing_transactions; + Vector m_ifnames; + RefPtr 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 +#include +#include +#include +#include +#include +#include +#include +#include + +static u8 mac_part(const Vector& 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 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(); +} -- cgit v1.2.3