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

Commit 71c0059

Browse files
committed
Beginning of dhcpclient service
1 parent 7465be6 commit 71c0059

File tree

7 files changed

+395
-0
lines changed

7 files changed

+395
-0
lines changed

services/CMakeLists.txt

+1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
ADD_COMPILE_OPTIONS(-O2)
2+
ADD_SUBDIRECTORY(dhcpclient/)
23
ADD_SUBDIRECTORY(init/)
34
ADD_SUBDIRECTORY(pond/)
45
ADD_SUBDIRECTORY(quack/)

services/dhcpclient/CMakeLists.txt

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
SET(SOURCES main.cpp Client.cpp DHCP.cpp)
2+
3+
MAKE_PROGRAM(dhcpclient)
4+
TARGET_LINK_LIBRARIES(dhcpclient libduck)

services/dhcpclient/Client.cpp

+184
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later */
2+
/* Copyright © 2016-2024 Byteduck */
3+
4+
#include "Client.h"
5+
#include "DHCP.h"
6+
#include <unistd.h>
7+
#include <ifaddrs.h>
8+
#include <libduck/Log.h>
9+
10+
using namespace Duck;
11+
12+
ResultRet<Ptr<Client>> Client::make() {
13+
// Get interfaces
14+
std::vector<Interface> interfaces;
15+
struct ifaddrs* addrs;
16+
if (getifaddrs(&addrs) < 0)
17+
return Result(errno);
18+
struct ifaddrs* cur_addr = addrs;
19+
while (cur_addr) {
20+
if (cur_addr->ifa_addr->sa_family != AF_INET || cur_addr->ifa_macaddr->sa_family != AF_MACADDR)
21+
goto next;
22+
23+
interfaces.push_back(Interface {
24+
.name = cur_addr->ifa_name,
25+
.addr = { *((sockaddr_in*) cur_addr->ifa_addr) },
26+
.hwaddr = { *((sockaddr_mac*) cur_addr->ifa_macaddr) }
27+
});
28+
29+
next:
30+
cur_addr = cur_addr->ifa_next;
31+
}
32+
freeifaddrs(addrs);
33+
34+
if (interfaces.empty())
35+
return Result("No interfaces to use");
36+
37+
// Open socket
38+
int fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
39+
if (fd == -1)
40+
return Result(errno);
41+
42+
// Bind to port 68
43+
sockaddr_in addr = IPv4Address(0).as_sockaddr(68);
44+
if (bind(fd, (sockaddr*) &addr, sizeof(addr)) < 0) {
45+
close(fd);
46+
return Result(errno);
47+
}
48+
49+
return Ptr<Client>(new Client(fd, interfaces));
50+
}
51+
52+
Client::Client(int socket, std::vector<Interface> interfaces):
53+
m_socket(socket),
54+
m_interfaces(std::move(interfaces))
55+
{
56+
}
57+
58+
void Client::loop() {
59+
for (auto& interface : m_interfaces)
60+
discover(interface);
61+
62+
DHCPPacket buf;
63+
while (true) {
64+
auto nread = recv(m_socket, &buf.raw_packet(), sizeof(RawDHCPPacket), 0);
65+
if (nread < 0) {
66+
Duck::Log::errf("Error reading packet: {}", strerror(errno));
67+
break;
68+
}
69+
70+
if (nread != sizeof(RawDHCPPacket)) {
71+
Duck::Log::errf("Received packet of invalid size: {}", nread);
72+
continue;
73+
}
74+
75+
if (!buf.has_valid_cookie()) {
76+
Duck::Log::errf("Received packet with invalid magic cookie");
77+
continue;
78+
}
79+
80+
auto type = buf.get_option<uint8_t>(MessageType);
81+
if (!type.has_value()) {
82+
Duck::Log::errf("Received packet without message type");
83+
continue;
84+
}
85+
86+
Result res = Result::SUCCESS;
87+
switch (type.value()) {
88+
case Ack:
89+
res = do_ack(buf);
90+
break;
91+
92+
case Offer:
93+
Duck::Log::warnf("Received offer, can't handle this yet");
94+
break;
95+
96+
case Nak:
97+
Duck::Log::warnf("Received nak, can't handle this yet");
98+
break;
99+
100+
case Decline:
101+
Duck::Log::warnf("Was declined DHCP request from {}", buf.raw_packet().siaddr);
102+
break;
103+
104+
default:
105+
break;
106+
}
107+
108+
if (res.is_error())
109+
Duck::Log::errf("{}", res);
110+
}
111+
}
112+
113+
Result Client::discover(const Interface& interface) {
114+
int txid = rand();
115+
m_transactions[txid] = {.iface = interface, .id = txid};
116+
117+
DHCPPacket packet;
118+
packet.raw_packet().op = DHCPOp::BootPRequest;
119+
packet.raw_packet().htype = 1;
120+
packet.raw_packet().hlen = sizeof(MACAddress);
121+
packet.raw_packet().xid = txid;
122+
packet.raw_packet().flags = 0x1;
123+
packet.raw_packet().ciaddr = interface.addr;
124+
for (int i = 0; i < 6; i++)
125+
packet.raw_packet().chaddr[i] = interface.hwaddr[i];
126+
packet.raw_packet().secs = 5000;
127+
128+
uint8_t msgtype = DHCPMsgType::Discover;
129+
packet.add_option(MessageType, 1, &msgtype);
130+
packet.add_option(End, 0, nullptr);
131+
132+
return send_packet(interface, packet.raw_packet());
133+
}
134+
135+
Result Client::send_packet(const Interface& interface, const RawDHCPPacket& packet) {
136+
const int sockid = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
137+
if (sockid < 0)
138+
return errno;
139+
140+
// Bind to interface
141+
if (setsockopt(sockid, SOL_SOCKET, SO_BINDTODEVICE, interface.name.c_str(), interface.name.length() + 1) < 0) {
142+
close(sockid);
143+
return errno;
144+
}
145+
146+
// Set allow broadcast
147+
const int allow = 1;
148+
if (setsockopt(sockid, SOL_SOCKET, SO_BROADCAST, &allow, sizeof(int)) < 0) {
149+
close(sockid);
150+
return errno;
151+
}
152+
153+
sockaddr_in addr = IPv4Address(255, 255, 255, 255).as_sockaddr(67);
154+
auto res = sendto(sockid, &packet, sizeof(packet), 0, (struct sockaddr*) &addr, sizeof(addr));
155+
close(sockid);
156+
if (res < 0)
157+
return errno;
158+
159+
return Result::SUCCESS;
160+
}
161+
162+
Duck::Result Client::do_ack(const DHCPPacket& packet) {
163+
Duck::Log::dbgf("Received ack from {}", packet.raw_packet().siaddr);
164+
165+
auto tx = m_transactions.find(packet.raw_packet().xid);
166+
if (tx == m_transactions.end())
167+
return {"Couldn't handle ack: No such transaction"};
168+
169+
auto subnet = packet.get_option<IPv4Address>(SubnetMask);
170+
if (!subnet.has_value())
171+
return {"Couldn't handle ack: Wasn't given a subnet mask"};
172+
173+
auto gateway = packet.get_option<IPv4Address>(Router);
174+
175+
return setup_interface(tx->second.iface, packet.raw_packet().yiaddr, subnet.value(), gateway);
176+
}
177+
178+
Duck::Result Client::setup_interface(const Client::Interface& interface, const IPv4Address& addr, const IPv4Address& subnet, const std::optional<IPv4Address>& gateway) {
179+
const int sockid = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
180+
if (sockid < 0)
181+
return errno;
182+
183+
// TODO
184+
}

services/dhcpclient/Client.h

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later */
2+
/* Copyright © 2016-2024 Byteduck */
3+
4+
#pragma once
5+
6+
#include <string>
7+
#include <vector>
8+
#include "DHCP.h"
9+
#include <libduck/Result.h>
10+
#include <libduck/Object.h>
11+
#include <sys/socket.h>
12+
#include <map>
13+
14+
class Client {
15+
public:
16+
static Duck::ResultRet<Duck::Ptr<Client>> make();
17+
18+
void loop();
19+
20+
private:
21+
struct Interface {
22+
std::string name;
23+
IPv4Address addr;
24+
MACAddress hwaddr;
25+
};
26+
27+
struct Transaction {
28+
Interface iface;
29+
int id;
30+
};
31+
32+
Client(int socket, std::vector<Interface> interfaces);
33+
34+
Duck::Result discover(const Interface& interface);
35+
Duck::Result send_packet(const Interface& interface, const RawDHCPPacket& packet);
36+
37+
Duck::Result do_ack(const DHCPPacket& packet);
38+
39+
Duck::Result setup_interface(const Interface& interface, const IPv4Address& addr, const IPv4Address& subnet, const std::optional<IPv4Address>& gateway);
40+
41+
std::vector<Interface> m_interfaces;
42+
int m_socket;
43+
std::map<int, Transaction> m_transactions;
44+
};

services/dhcpclient/DHCP.cpp

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later */
2+
/* Copyright © 2016-2024 Byteduck */
3+
4+
#include "DHCP.h"
5+
#include <string.h>
6+
7+
DHCPPacket::DHCPPacket() {
8+
// Magic cookie
9+
m_packet.options[0] = 0x63;
10+
m_packet.options[1] = 0x82;
11+
m_packet.options[2] = 0x53;
12+
m_packet.options[3] = 0x63;
13+
}
14+
15+
bool DHCPPacket::add_option(DHCPOption option, uint8_t size, const void* data) {
16+
if (m_options_offset + sizeof(uint8_t) * 2 + size > BOOTP_OPTS_MAXLEN)
17+
return false;
18+
m_packet.options[m_options_offset++] = option;
19+
m_packet.options[m_options_offset++] = size;
20+
if (data && size) {
21+
memcpy(&m_packet.options, data, size);
22+
m_options_offset += size;
23+
}
24+
return true;
25+
}
26+
27+
bool DHCPPacket::has_valid_cookie() const {
28+
return m_packet.options[0] == 0x63 && m_packet.options[1] == 0x82 && m_packet.options[2] == 0x53 && m_packet.options[3] == 0x63;
29+
}
30+
31+
bool DHCPPacket::get_option(DHCPOption option, size_t size, void* ptr) const {
32+
size_t offset = 4;
33+
while (offset < BOOTP_OPTS_MAXLEN) {
34+
if (!m_packet.options[offset] || !m_packet.options[offset + 1])
35+
return false;
36+
if (m_packet.options[offset] == option && m_packet.options[offset + 1] == size) {
37+
memcpy(ptr, &m_packet.options[offset + 2], size);
38+
return true;
39+
}
40+
offset += m_packet.options[offset + 1] + 2;
41+
}
42+
return false;
43+
}

services/dhcpclient/DHCP.h

+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/* SPDX-License-Identifier: GPL-3.0-or-later */
2+
/* Copyright © 2016-2024 Byteduck */
3+
4+
#pragma once
5+
#include <kernel/api/endian.h>
6+
#include <kernel/api/ipv4.h>
7+
#include <kernel/api/net.h>
8+
#include <optional>
9+
10+
enum DHCPMsgType {
11+
Discover = 1,
12+
Offer = 2,
13+
Request = 3,
14+
Decline = 4,
15+
Ack = 5,
16+
Nak = 6,
17+
Release = 7
18+
};
19+
20+
enum DHCPOp {
21+
BootPRequest = 1,
22+
BootPReply = 2
23+
};
24+
25+
enum DHCPOption {
26+
Pad = 0,
27+
SubnetMask = 1,
28+
TimeOffset = 2,
29+
Router = 3,
30+
TimeServer = 4,
31+
NameServer = 5,
32+
DomainNameServer = 6,
33+
LogServer = 7,
34+
CookieServer = 8,
35+
LPRServer = 9,
36+
ImpressServer = 10,
37+
ResourceLocationServer = 11,
38+
Hostname = 12,
39+
BootFileSize = 13,
40+
MeritDumpFile = 14,
41+
DomainName = 15,
42+
SwapServer = 16,
43+
RootPath = 17,
44+
ExtensionsPath = 18,
45+
RequestedIP = 50,
46+
IPLeaseTime = 51,
47+
OptionOverload = 52,
48+
MessageType = 53,
49+
ServerID = 54,
50+
ParameterRequestList = 55,
51+
Message = 56,
52+
MaximumMessageSize = 57,
53+
RenewalTime = 58,
54+
RebindingTime = 59,
55+
VendorClass = 60,
56+
ClientID = 61,
57+
TFTPServerName = 66,
58+
BootfileName = 67,
59+
End = 255
60+
};
61+
62+
#define BOOTP_OPTS_MAXLEN 312
63+
64+
struct RawDHCPPacket {
65+
uint8_t op {0};
66+
uint8_t htype {0};
67+
uint8_t hlen {0};
68+
uint8_t hop {0};
69+
BigEndian<uint32_t> xid {0};
70+
BigEndian<uint16_t> secs {0};
71+
BigEndian<uint16_t> flags {0};
72+
IPv4Address ciaddr {0};
73+
IPv4Address yiaddr {0};
74+
IPv4Address siaddr {0};
75+
IPv4Address giaddr {0};
76+
uint8_t chaddr[16] { 0 };
77+
uint8_t sname[64] { 0 };
78+
uint8_t file[128] { 0 };
79+
uint8_t options[BOOTP_OPTS_MAXLEN] { 0 };
80+
81+
inline const MACAddress& mac() const { return *((const MACAddress*) &chaddr); }
82+
inline void set_mac(const MACAddress& mac) const { *((MACAddress*) &chaddr) = mac; }
83+
} __attribute__((packed));
84+
85+
class DHCPPacket {
86+
public:
87+
DHCPPacket();
88+
explicit DHCPPacket(const RawDHCPPacket& packet): m_packet(packet) {}
89+
90+
[[nodiscard]] RawDHCPPacket& raw_packet() { return m_packet; }
91+
[[nodiscard]] const RawDHCPPacket& raw_packet() const { return m_packet; }
92+
bool add_option(DHCPOption option, uint8_t size, const void* data);
93+
94+
template<typename T>
95+
std::optional<T> get_option(DHCPOption option) const {
96+
T ret;
97+
if (get_option(option, sizeof(T), &ret))
98+
return ret;
99+
return std::nullopt;
100+
}
101+
102+
[[nodiscard]] bool has_valid_cookie() const;
103+
104+
private:
105+
bool get_option(DHCPOption option, size_t size, void* ptr) const;
106+
107+
RawDHCPPacket m_packet;
108+
size_t m_options_offset = 4;
109+
};

0 commit comments

Comments
 (0)