Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Beginning of dhcpclient service
Browse files Browse the repository at this point in the history
  • Loading branch information
byteduck committed Mar 20, 2024
1 parent 7465be6 commit 71c0059
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 0 deletions.
1 change: 1 addition & 0 deletions services/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
ADD_COMPILE_OPTIONS(-O2)
ADD_SUBDIRECTORY(dhcpclient/)
ADD_SUBDIRECTORY(init/)
ADD_SUBDIRECTORY(pond/)
ADD_SUBDIRECTORY(quack/)
4 changes: 4 additions & 0 deletions services/dhcpclient/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
SET(SOURCES main.cpp Client.cpp DHCP.cpp)

MAKE_PROGRAM(dhcpclient)
TARGET_LINK_LIBRARIES(dhcpclient libduck)
184 changes: 184 additions & 0 deletions services/dhcpclient/Client.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright © 2016-2024 Byteduck */

#include "Client.h"
#include "DHCP.h"
#include <unistd.h>
#include <ifaddrs.h>
#include <libduck/Log.h>

using namespace Duck;

ResultRet<Ptr<Client>> Client::make() {
// Get interfaces
std::vector<Interface> interfaces;
struct ifaddrs* addrs;
if (getifaddrs(&addrs) < 0)
return Result(errno);
struct ifaddrs* cur_addr = addrs;
while (cur_addr) {
if (cur_addr->ifa_addr->sa_family != AF_INET || cur_addr->ifa_macaddr->sa_family != AF_MACADDR)
goto next;

interfaces.push_back(Interface {
.name = cur_addr->ifa_name,
.addr = { *((sockaddr_in*) cur_addr->ifa_addr) },
.hwaddr = { *((sockaddr_mac*) cur_addr->ifa_macaddr) }
});

next:
cur_addr = cur_addr->ifa_next;
}
freeifaddrs(addrs);

if (interfaces.empty())
return Result("No interfaces to use");

// Open socket
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (fd == -1)
return Result(errno);

// Bind to port 68
sockaddr_in addr = IPv4Address(0).as_sockaddr(68);
if (bind(fd, (sockaddr*) &addr, sizeof(addr)) < 0) {
close(fd);
return Result(errno);
}

return Ptr<Client>(new Client(fd, interfaces));
}

Client::Client(int socket, std::vector<Interface> interfaces):
m_socket(socket),
m_interfaces(std::move(interfaces))
{
}

void Client::loop() {
for (auto& interface : m_interfaces)
discover(interface);

DHCPPacket buf;
while (true) {
auto nread = recv(m_socket, &buf.raw_packet(), sizeof(RawDHCPPacket), 0);
if (nread < 0) {
Duck::Log::errf("Error reading packet: {}", strerror(errno));
break;
}

if (nread != sizeof(RawDHCPPacket)) {
Duck::Log::errf("Received packet of invalid size: {}", nread);
continue;
}

if (!buf.has_valid_cookie()) {
Duck::Log::errf("Received packet with invalid magic cookie");
continue;
}

auto type = buf.get_option<uint8_t>(MessageType);
if (!type.has_value()) {
Duck::Log::errf("Received packet without message type");
continue;
}

Result res = Result::SUCCESS;
switch (type.value()) {
case Ack:
res = do_ack(buf);
break;

case Offer:
Duck::Log::warnf("Received offer, can't handle this yet");
break;

case Nak:
Duck::Log::warnf("Received nak, can't handle this yet");
break;

case Decline:
Duck::Log::warnf("Was declined DHCP request from {}", buf.raw_packet().siaddr);
break;

default:
break;
}

if (res.is_error())
Duck::Log::errf("{}", res);
}
}

Result Client::discover(const Interface& interface) {
int txid = rand();
m_transactions[txid] = {.iface = interface, .id = txid};

DHCPPacket packet;
packet.raw_packet().op = DHCPOp::BootPRequest;
packet.raw_packet().htype = 1;
packet.raw_packet().hlen = sizeof(MACAddress);
packet.raw_packet().xid = txid;
packet.raw_packet().flags = 0x1;
packet.raw_packet().ciaddr = interface.addr;
for (int i = 0; i < 6; i++)
packet.raw_packet().chaddr[i] = interface.hwaddr[i];
packet.raw_packet().secs = 5000;

uint8_t msgtype = DHCPMsgType::Discover;
packet.add_option(MessageType, 1, &msgtype);
packet.add_option(End, 0, nullptr);

return send_packet(interface, packet.raw_packet());
}

Result Client::send_packet(const Interface& interface, const RawDHCPPacket& packet) {
const int sockid = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockid < 0)
return errno;

// Bind to interface
if (setsockopt(sockid, SOL_SOCKET, SO_BINDTODEVICE, interface.name.c_str(), interface.name.length() + 1) < 0) {
close(sockid);
return errno;
}

// Set allow broadcast
const int allow = 1;
if (setsockopt(sockid, SOL_SOCKET, SO_BROADCAST, &allow, sizeof(int)) < 0) {
close(sockid);
return errno;
}

sockaddr_in addr = IPv4Address(255, 255, 255, 255).as_sockaddr(67);
auto res = sendto(sockid, &packet, sizeof(packet), 0, (struct sockaddr*) &addr, sizeof(addr));
close(sockid);
if (res < 0)
return errno;

return Result::SUCCESS;
}

Duck::Result Client::do_ack(const DHCPPacket& packet) {
Duck::Log::dbgf("Received ack from {}", packet.raw_packet().siaddr);

auto tx = m_transactions.find(packet.raw_packet().xid);
if (tx == m_transactions.end())
return {"Couldn't handle ack: No such transaction"};

auto subnet = packet.get_option<IPv4Address>(SubnetMask);
if (!subnet.has_value())
return {"Couldn't handle ack: Wasn't given a subnet mask"};

auto gateway = packet.get_option<IPv4Address>(Router);

return setup_interface(tx->second.iface, packet.raw_packet().yiaddr, subnet.value(), gateway);
}

Duck::Result Client::setup_interface(const Client::Interface& interface, const IPv4Address& addr, const IPv4Address& subnet, const std::optional<IPv4Address>& gateway) {
const int sockid = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockid < 0)
return errno;

// TODO
}
44 changes: 44 additions & 0 deletions services/dhcpclient/Client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright © 2016-2024 Byteduck */

#pragma once

#include <string>
#include <vector>
#include "DHCP.h"
#include <libduck/Result.h>
#include <libduck/Object.h>
#include <sys/socket.h>
#include <map>

class Client {
public:
static Duck::ResultRet<Duck::Ptr<Client>> make();

void loop();

private:
struct Interface {
std::string name;
IPv4Address addr;
MACAddress hwaddr;
};

struct Transaction {
Interface iface;
int id;
};

Client(int socket, std::vector<Interface> interfaces);

Duck::Result discover(const Interface& interface);
Duck::Result send_packet(const Interface& interface, const RawDHCPPacket& packet);

Duck::Result do_ack(const DHCPPacket& packet);

Duck::Result setup_interface(const Interface& interface, const IPv4Address& addr, const IPv4Address& subnet, const std::optional<IPv4Address>& gateway);

std::vector<Interface> m_interfaces;
int m_socket;
std::map<int, Transaction> m_transactions;
};
43 changes: 43 additions & 0 deletions services/dhcpclient/DHCP.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright © 2016-2024 Byteduck */

#include "DHCP.h"
#include <string.h>

DHCPPacket::DHCPPacket() {
// Magic cookie
m_packet.options[0] = 0x63;
m_packet.options[1] = 0x82;
m_packet.options[2] = 0x53;
m_packet.options[3] = 0x63;
}

bool DHCPPacket::add_option(DHCPOption option, uint8_t size, const void* data) {
if (m_options_offset + sizeof(uint8_t) * 2 + size > BOOTP_OPTS_MAXLEN)
return false;
m_packet.options[m_options_offset++] = option;
m_packet.options[m_options_offset++] = size;
if (data && size) {
memcpy(&m_packet.options, data, size);
m_options_offset += size;
}
return true;
}

bool DHCPPacket::has_valid_cookie() const {
return m_packet.options[0] == 0x63 && m_packet.options[1] == 0x82 && m_packet.options[2] == 0x53 && m_packet.options[3] == 0x63;
}

bool DHCPPacket::get_option(DHCPOption option, size_t size, void* ptr) const {
size_t offset = 4;
while (offset < BOOTP_OPTS_MAXLEN) {
if (!m_packet.options[offset] || !m_packet.options[offset + 1])
return false;
if (m_packet.options[offset] == option && m_packet.options[offset + 1] == size) {
memcpy(ptr, &m_packet.options[offset + 2], size);
return true;
}
offset += m_packet.options[offset + 1] + 2;
}
return false;
}
109 changes: 109 additions & 0 deletions services/dhcpclient/DHCP.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
/* SPDX-License-Identifier: GPL-3.0-or-later */
/* Copyright © 2016-2024 Byteduck */

#pragma once
#include <kernel/api/endian.h>
#include <kernel/api/ipv4.h>
#include <kernel/api/net.h>
#include <optional>

enum DHCPMsgType {
Discover = 1,
Offer = 2,
Request = 3,
Decline = 4,
Ack = 5,
Nak = 6,
Release = 7
};

enum DHCPOp {
BootPRequest = 1,
BootPReply = 2
};

enum DHCPOption {
Pad = 0,
SubnetMask = 1,
TimeOffset = 2,
Router = 3,
TimeServer = 4,
NameServer = 5,
DomainNameServer = 6,
LogServer = 7,
CookieServer = 8,
LPRServer = 9,
ImpressServer = 10,
ResourceLocationServer = 11,
Hostname = 12,
BootFileSize = 13,
MeritDumpFile = 14,
DomainName = 15,
SwapServer = 16,
RootPath = 17,
ExtensionsPath = 18,
RequestedIP = 50,
IPLeaseTime = 51,
OptionOverload = 52,
MessageType = 53,
ServerID = 54,
ParameterRequestList = 55,
Message = 56,
MaximumMessageSize = 57,
RenewalTime = 58,
RebindingTime = 59,
VendorClass = 60,
ClientID = 61,
TFTPServerName = 66,
BootfileName = 67,
End = 255
};

#define BOOTP_OPTS_MAXLEN 312

struct RawDHCPPacket {
uint8_t op {0};
uint8_t htype {0};
uint8_t hlen {0};
uint8_t hop {0};
BigEndian<uint32_t> xid {0};
BigEndian<uint16_t> secs {0};
BigEndian<uint16_t> flags {0};
IPv4Address ciaddr {0};
IPv4Address yiaddr {0};
IPv4Address siaddr {0};
IPv4Address giaddr {0};
uint8_t chaddr[16] { 0 };
uint8_t sname[64] { 0 };
uint8_t file[128] { 0 };
uint8_t options[BOOTP_OPTS_MAXLEN] { 0 };

inline const MACAddress& mac() const { return *((const MACAddress*) &chaddr); }
inline void set_mac(const MACAddress& mac) const { *((MACAddress*) &chaddr) = mac; }
} __attribute__((packed));

class DHCPPacket {
public:
DHCPPacket();
explicit DHCPPacket(const RawDHCPPacket& packet): m_packet(packet) {}

[[nodiscard]] RawDHCPPacket& raw_packet() { return m_packet; }
[[nodiscard]] const RawDHCPPacket& raw_packet() const { return m_packet; }
bool add_option(DHCPOption option, uint8_t size, const void* data);

template<typename T>
std::optional<T> get_option(DHCPOption option) const {
T ret;
if (get_option(option, sizeof(T), &ret))
return ret;
return std::nullopt;
}

[[nodiscard]] bool has_valid_cookie() const;

private:
bool get_option(DHCPOption option, size_t size, void* ptr) const;

RawDHCPPacket m_packet;
size_t m_options_offset = 4;
};
Loading

0 comments on commit 71c0059

Please sign in to comment.