mirror of
https://github.com/SerenityOS/serenity.git
synced 2025-01-23 18:02:05 -05:00
Servers: Add a new DHCP client
This adds a DHCPClient...Server that leases IPv4s.
This commit is contained in:
parent
77191d82dc
commit
3e8cf79efa
8 changed files with 840 additions and 1 deletions
|
@ -20,6 +20,11 @@ Priority=low
|
|||
KeepAlive=1
|
||||
User=lookup
|
||||
|
||||
[DHCPClient]
|
||||
Priority=high
|
||||
KeepAlive=1
|
||||
User=root
|
||||
|
||||
[NotificationServer]
|
||||
Socket=/tmp/portal/notify
|
||||
SocketPermissions=660
|
||||
|
|
|
@ -51,7 +51,7 @@ mknod mnt/dev/zero c 1 5
|
|||
mknod mnt/dev/full c 1 7
|
||||
mknod mnt/dev/debuglog c 1 18
|
||||
# random, is failing (randomly) on fuse-ext2 on macos :)
|
||||
chmod 666 mnt/dev/random || true
|
||||
chmod 666 mnt/dev/random || true
|
||||
chmod 666 mnt/dev/null
|
||||
chmod 666 mnt/dev/zero
|
||||
chmod 666 mnt/dev/full
|
||||
|
@ -156,6 +156,7 @@ cp ../DevTools/Inspector/Inspector mnt/bin/Inspector
|
|||
cp ../DevTools/ProfileViewer/ProfileViewer mnt/bin/ProfileViewer
|
||||
cp ../Games/Minesweeper/Minesweeper mnt/bin/Minesweeper
|
||||
cp ../Games/Snake/Snake mnt/bin/Snake
|
||||
cp ../Servers/DHCPClient/DHCPClient mnt/bin/DHCPClient
|
||||
cp ../Servers/LookupServer/LookupServer mnt/bin/LookupServer
|
||||
cp ../Servers/SystemServer/SystemServer mnt/bin/SystemServer
|
||||
cp ../Servers/WindowServer/WindowServer mnt/bin/WindowServer
|
||||
|
|
59
Servers/DHCPClient/DHCPv4.cpp
Normal file
59
Servers/DHCPClient/DHCPv4.cpp
Normal file
|
@ -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;
|
||||
}
|
300
Servers/DHCPClient/DHCPv4.h
Normal file
300
Servers/DHCPClient/DHCPv4.h
Normal file
|
@ -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 };
|
||||
};
|
283
Servers/DHCPClient/DHCPv4Client.cpp
Normal file
283
Servers/DHCPClient/DHCPv4Client.cpp
Normal file
|
@ -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";
|
||||
return;
|
||||
}
|
||||
|
||||
if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, iface.m_ifname.characters(), IFNAMSIZ) < 0) {
|
||||
dbg() << "ERROR from 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";
|
||||
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)";
|
||||
}
|
||||
|
||||
// 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(SIOCSIFADDR)";
|
||||
}
|
||||
|
||||
// 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)";
|
||||
}
|
||||
}
|
||||
|
||||
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::construct(
|
||||
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_single_shot(true);
|
||||
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::construct(
|
||||
10000, [this, iface = InterfaceDescriptor { iface }] {
|
||||
dhcp_discover(iface);
|
||||
},
|
||||
this)
|
||||
->set_single_shot(true);
|
||||
}
|
||||
|
||||
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 discover request
|
||||
send(iface, dhcp_packet, this);
|
||||
transaction.accepted_offer = true;
|
||||
}
|
80
Servers/DHCPClient/DHCPv4Client.h
Normal file
80
Servers/DHCPClient/DHCPv4Client.h
Normal file
|
@ -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&);
|
||||
};
|
10
Servers/DHCPClient/Makefile
Normal file
10
Servers/DHCPClient/Makefile
Normal file
|
@ -0,0 +1,10 @@
|
|||
OBJS = \
|
||||
DHCPv4.o \
|
||||
DHCPv4Client.o \
|
||||
main.o
|
||||
|
||||
PROGRAM = DHCPClient
|
||||
|
||||
LIB_DEPS = Core
|
||||
|
||||
include ../../Makefile.common
|
101
Servers/DHCPClient/main.cpp
Normal file
101
Servers/DHCPClient/main.cpp
Normal file
|
@ -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();
|
||||
}
|
Loading…
Add table
Reference in a new issue