24#ifndef JOIN_FABRIC_DHCP_HPP
25#define JOIN_FABRIC_DHCP_HPP
38#include <unordered_map>
39#include <system_error>
45#include <net/ethernet.h>
46#include <netinet/udp.h>
47#include <netinet/ip.h>
56 template <
class Protocol>
57 class BasicDhcp :
public EventHandler
60 using Socket =
typename Protocol::Socket;
74 :
_buffer (
std::make_unique<char[]> (Protocol::maxMsgSize))
79 if (::if_nametoindex (
_interface.c_str ()) == 0)
81 throw std::system_error (errno, std::system_category (),
"dhcp interface lookup failed");
86 throw std::system_error (lastError,
"dhcp socket setup failed");
156 uint32_t destination;
170 throw std::system_error (lastError,
"dhcp reactor registration failed");
189 static uint16_t
udpChecksum (
const Frame& frame,
const char* payload,
size_t size)
noexcept
192 pseudo.source = frame.ip.saddr;
193 pseudo.destination = frame.ip.daddr;
194 pseudo.protocol = IPPROTO_UDP;
195 pseudo.length = frame.udp.len;
197 uint16_t sum =
join::checksum (
reinterpret_cast<const uint16_t*
> (&pseudo),
sizeof (pseudo));
198 sum =
join::checksum (
reinterpret_cast<const uint16_t*
> (&frame.udp), sizeof (frame.udp),
199 static_cast<uint16_t
> (~sum));
200 sum =
join::checksum (
reinterpret_cast<const uint16_t*
> (payload), size,
static_cast<uint16_t
> (~sum));
202 return sum ? sum : 0xffff;
217 if (size > Protocol::maxMsgSize)
228 const uint16_t datagram =
static_cast<uint16_t
> (
sizeof (frame.udp) + size);
230 frame.udp.source = htons (boot ? Protocol::clientPort : Protocol::serverPort);
231 frame.udp.dest = htons (boot ? Protocol::serverPort : Protocol::clientPort);
232 frame.udp.len = htons (datagram);
234 frame.ip.version = IPVERSION;
235 frame.ip.ihl =
sizeof (frame.ip) >> 2;
236 frame.ip.tos = IPTOS_CLASS_CS6 | IPTOS_ECN_NOT_ECT;
237 frame.ip.tot_len = htons (
static_cast<uint16_t
> (
sizeof (frame.ip) + datagram));
238 frame.ip.frag_off = htons (IP_DF);
239 frame.ip.ttl = IPDEFTTL;
240 frame.ip.protocol = IPPROTO_UDP;
241 ::memcpy (&frame.ip.saddr, source.addr (), sizeof (frame.ip.saddr));
242 ::memcpy (&frame.ip.daddr, destination.addr (), sizeof (frame.ip.daddr));
243 frame.ip.check =
join::checksum (
reinterpret_cast<const uint16_t*
> (&frame.ip), sizeof (frame.ip));
247 ::memcpy (frame.eth.h_dest, to.addr (), ETH_ALEN);
249 frame.eth.h_proto = htons (ETH_P_IP);
252 iov[0].iov_base = &frame;
253 iov[0].iov_len =
sizeof (frame);
254 iov[1].iov_base =
const_cast<char*
> (data);
255 iov[1].iov_len = size;
257 struct msghdr message = {};
258 message.msg_iov = iov;
259 message.msg_iovlen = 2;
261 if (::sendmsg (
_socket.handle (), &message, 0) == -1)
264 lastError = std::error_code (errno, std::generic_category ());
285 iov[0].iov_base = &frame;
286 iov[0].iov_len =
sizeof (frame);
287 iov[1].iov_base = data;
288 iov[1].iov_len = maxSize;
290 struct msghdr message = {};
291 message.msg_iov = iov;
292 message.msg_iovlen = 2;
294 ssize_t size = ::recvmsg (
_socket.handle (), &message, 0);
298 lastError = std::error_code (errno, std::generic_category ());
303 if (message.msg_flags & MSG_TRUNC)
316 if ((frame.eth.h_proto != htons (ETH_P_IP)) || (frame.ip.version != IPVERSION) ||
317 (frame.ip.ihl != (sizeof (frame.ip) >> 2)) || (frame.ip.protocol != IPPROTO_UDP))
322 struct iphdr header = frame.ip;
323 const uint16_t check = header.check;
326 if (check !=
join::checksum (
reinterpret_cast<const uint16_t*
> (&header),
sizeof (header)))
331 if (((frame.udp.source != htons (Protocol::serverPort)) ||
332 (frame.udp.dest != htons (Protocol::clientPort))) &&
333 ((frame.udp.source != htons (Protocol::clientPort)) ||
334 (frame.udp.dest != htons (Protocol::serverPort))))
339 const size_t datagram = ntohs (frame.udp.len);
340 const size_t available =
static_cast<size_t> (size) -
sizeof (frame.eth) -
sizeof (frame.ip);
342 if ((datagram <
sizeof (frame.udp)) || (datagram > available))
347 const size_t payloadSize = datagram -
sizeof (frame.udp);
354 if (frame.udp.check !=
udpChecksum (probe, data, payloadSize))
360 from =
MacAddress (frame.eth.h_source, ETH_ALEN);
363 return static_cast<ssize_t
> (payloadSize);
388 template <
class Protocol>
412 , _hostname (hostname)
418 "dhcp maximum message size is too small");
449 if (!_hostname.empty ())
472 std::chrono::milliseconds timeout = std::chrono::seconds (1)) noexcept
476 lastError = std::make_error_code (std::errc::resource_deadlock_would_occur);
480 char payload[Protocol::maxMsgSize];
491 PendingRequest pending;
492 pending.answer = &answer;
494 if (!_pending.emplace (
request.id, &pending).second)
502 if (this->
send (payload,
static_cast<size_t> (size),
request.dest,
request.client, destination) == -1)
510 bool answered = pending.cond.
timedWait (lock, timeout, [&pending] {
532 if (message !=
nullptr)
541 if (*type != expected)
569 std::chrono::milliseconds timeout = std::chrono::seconds (1)) noexcept
575 if (!wants.isWildcard ())
592 std::chrono::milliseconds timeout = std::chrono::seconds (1)) noexcept
612 std::chrono::milliseconds timeout = std::chrono::seconds (1)) noexcept
616 lastError = std::make_error_code (std::errc::resource_deadlock_would_occur);
643 std::chrono::milliseconds timeout = std::chrono::seconds (1)) noexcept
661 std::chrono::milliseconds timeout = std::chrono::seconds (1)) noexcept
665 lastError = std::make_error_code (std::errc::resource_deadlock_would_occur);
681 char payload[Protocol::maxMsgSize];
688 return this->
send (payload,
static_cast<size_t> (size), out.
dest, client, server);
705 if (!message.empty ())
710 char payload[Protocol::maxMsgSize];
726 void onReadable ([[maybe_unused]]
int fd)
override final
729 ssize_t size = this->
receive (this->
_buffer.get (), Protocol::maxMsgSize, from, to);
736 if ((this->
_message.
deserialize (packet, this->_buffer.get (),
static_cast<size_t> (size)) == -1) ||
745 ScopedLock<Mutex> lock (_syncMutex);
747 auto it = _pending.find (packet.id);
748 if ((it != _pending.end ()) && !it->second->done)
750 *it->second->answer = std::move (packet);
751 it->second->done =
true;
752 it->second->cond.signal ();
759 struct PendingRequest
765 DhcpPacket* answer =
nullptr;
772 static const ByteList _defaultParams;
775 std::unordered_map<uint32_t, PendingRequest*> _pending;
778 mutable Mutex _syncMutex;
784 const std::string _hostname;
787 const uint16_t _maxSize;
790 template <
class Protocol>
798 template <
class Protocol>
799 class BasicDhcpServer final :
protected BasicDhcp<Protocol>
863 int nak (
const DhcpPacket& request,
const std::string& message = {})
noexcept
867 if (!message.empty ())
885 busy = _calling || _listener;
888 _listener = std::move (cb);
925 void onReadable ([[maybe_unused]]
int fd)
override final
928 ssize_t size = this->
receive (this->
_buffer.get (), Protocol::maxMsgSize, from, to);
935 if ((this->
_message.
deserialize (packet, this->_buffer.get (),
static_cast<size_t> (size)) == -1) ||
959 _listener = std::move (listener);
973 int reply (
const DhcpPacket& request, uint8_t type,
const IpAddress&
address,
974 const DhcpOption& options)
noexcept
977 if (server.isWildcard ())
979 lastError = std::make_error_code (std::errc::address_not_available);
986 out.flags = request.
flags;
991 out.client = request.
client;
999 out.options.insert (options.
begin (), options.
end ());
1001 const bool broadcast =
1009 char payload[Protocol::maxMsgSize];
1016 return this->
send (payload,
static_cast<size_t> (size), out.dest, server,
1024 bool _calling =
false;
MacAddress get(const IpAddress &ip, std::chrono::duration< Rep, Period > timeout)
get the MAC address for the given IP address using netlink neighbor cache or ARP request.
Definition arp.hpp:102
DHCP client.
Definition dhcp_protocol.hpp:37
int exchange(DhcpPacket &request, DhcpPacket &answer, const IpAddress &destination, uint8_t expected, std::chrono::milliseconds timeout=std::chrono::seconds(1)) noexcept
send a message and wait for its answer.
Definition dhcp.hpp:471
int inform(DhcpPacket &ack, const IpAddress &client, std::chrono::milliseconds timeout=std::chrono::seconds(1)) noexcept
ask a server for the parameters of an externally configured address.
Definition dhcp.hpp:642
int release(const IpAddress &client, const IpAddress &server, std::chrono::milliseconds timeout=std::chrono::seconds(1)) noexcept
give a lease back to the server holding it.
Definition dhcp.hpp:660
~BasicDhcpClient()
stop receiving messages and destroy the instance.
Definition dhcp.hpp:427
int decline(const IpAddress &address, const IpAddress &server, const std::string &message={}) noexcept
tell the server that the address it offered is already in use.
Definition dhcp.hpp:698
BasicDhcpClient()=delete
create the BasicDhcpClient instance.
int discover(DhcpPacket &offer, const IpAddress &wants=IpAddress::ipv4Wildcard, std::chrono::milliseconds timeout=std::chrono::seconds(1)) noexcept
broadcast a DISCOVER message and wait for an OFFER.
Definition dhcp.hpp:568
DhcpPacket compose(uint8_t type) const
build a message carrying what every message a client sends has in common.
Definition dhcp.hpp:437
int request(DhcpPacket &ack, const IpAddress &wants, const IpAddress &server, std::chrono::milliseconds timeout=std::chrono::seconds(1)) noexcept
broadcast a REQUEST message and wait for an ACK.
Definition dhcp.hpp:591
std::string reason() const
get the reason the server gave for the last refusal.
Definition dhcp.hpp:554
BasicDhcpClient(const std::string &interface, uint16_t maxSize=0, const std::string &hostname={}, Reactor &reactor=ReactorThread::reactor())
create the instance bound to the given interface.
Definition dhcp.hpp:409
int renew(DhcpPacket &ack, const IpAddress &client, const IpAddress &server, std::chrono::milliseconds timeout=std::chrono::seconds(1)) noexcept
send a REQUEST message to the server holding the lease and wait for an ACK.
Definition dhcp.hpp:611
Function< void(DhcpMessage::MessageType type, const DhcpPacket &request)> RequestNotify
request notification callback.
Definition dhcp.hpp:806
BasicDhcpServer()=delete
create the BasicDhcpServer instance.
int nak(const DhcpPacket &request, const std::string &message={}) noexcept
refuse a REQUEST message with a NAK.
Definition dhcp.hpp:863
int offer(const DhcpPacket &request, const IpAddress &address, const DhcpOption &options={}) noexcept
answer a DISCOVER message with an OFFER.
Definition dhcp.hpp:840
int setRequestListener(RequestNotify cb) noexcept
set the callback called on every request received, whatever its message type.
Definition dhcp.hpp:880
int unsetRequestListener() noexcept
unset the request callback, it is no longer called once this returns.
Definition dhcp.hpp:910
~BasicDhcpServer()
stop receiving messages and destroy the instance.
Definition dhcp.hpp:828
BasicDhcpServer(const std::string &interface, Reactor &reactor=ReactorThread::reactor())
create the instance bound to the given interface.
Definition dhcp.hpp:819
int ack(const DhcpPacket &request, const IpAddress &address, const DhcpOption &options={}) noexcept
answer a REQUEST message with an ACK.
Definition dhcp.hpp:852
carries DHCP messages over a packet socket.
Definition dhcp_protocol.hpp:34
struct __attribute__((packed, aligned(2))) Frame
link, internet and transport headers a DHCP message is framed with.
Definition dhcp.hpp:143
Reactor & _reactor
event loop reactor.
Definition dhcp.hpp:382
const std::string & interface() const noexcept
get the name of the interface the instance is bound to.
Definition dhcp.hpp:125
void detach() noexcept
unregister from the reactor, to be called by the final class before destruction starts.
Definition dhcp.hpp:177
BasicDhcp & operator=(const BasicDhcp &other)=delete
assign instance by copy.
std::unique_ptr< char[]> _buffer
receive buffer.
Definition dhcp.hpp:373
BasicDhcp(BasicDhcp &&other)=delete
create instance by move.
int send(const char *data, size_t size, const MacAddress &to, const IpAddress &source, const IpAddress &destination) noexcept
frame a message and write it on the wire.
Definition dhcp.hpp:214
BasicDhcp(const std::string &interface, Reactor &reactor=ReactorThread::reactor())
create the instance bound to the given interface.
Definition dhcp.hpp:73
typename Protocol::Socket Socket
Definition dhcp.hpp:60
const std::string _interface
interface name.
Definition dhcp.hpp:376
BasicDhcp(const BasicDhcp &other)=delete
create instance by copy.
BasicDhcp()=delete
create the BasicDhcp instance.
const MacAddress _hardware
hardware address of the interface.
Definition dhcp.hpp:379
ssize_t receive(char *data, size_t maxSize, MacAddress &from, MacAddress &to) noexcept
read a message that passed the link, internet and transport checks.
Definition dhcp.hpp:280
void attach()
register with the reactor, to be called by the final class once fully constructed.
Definition dhcp.hpp:166
static uint16_t udpChecksum(const Frame &frame, const char *payload, size_t size) noexcept
compute the checksum of a UDP datagram, RFC 768.
Definition dhcp.hpp:189
DhcpMessage _message
DHCP message codec.
Definition dhcp.hpp:370
virtual ~BasicDhcp()=default
destroy the instance.
const MacAddress & hardware() const noexcept
get the hardware address of the interface the instance is bound to.
Definition dhcp.hpp:134
Socket _socket
underlying socket.
Definition dhcp.hpp:367
bool timedWait(Lock &lock, std::chrono::duration< Rep, Period > timeout)
wait on a condition until timeout expire.
Definition condition.hpp:121
DHCP message codec.
Definition dhcp_message.hpp:88
@ BootReply
Definition dhcp_message.hpp:111
@ BootRequest
Definition dhcp_message.hpp:110
ssize_t serialize(const DhcpPacket &packet, char *data, size_t maxSize) const
serialize a DHCP message into a buffer.
Definition dhcp_message.hpp:138
MessageType
DHCP message types, RFC 2132 section 9.6.
Definition dhcp_message.hpp:94
@ Request
Definition dhcp_message.hpp:97
@ Nak
Definition dhcp_message.hpp:100
@ Discover
Definition dhcp_message.hpp:95
@ Ack
Definition dhcp_message.hpp:99
@ Decline
Definition dhcp_message.hpp:98
@ Offer
Definition dhcp_message.hpp:96
@ Inform
Definition dhcp_message.hpp:102
@ Release
Definition dhcp_message.hpp:101
@ BroadcastFlag
Definition dhcp_message.hpp:119
int deserialize(DhcpPacket &packet, const char *data, size_t size) const
deserialize a DHCP message from a buffer.
Definition dhcp_message.hpp:202
static constexpr size_t headerSize
size of the fixed part of a DHCP message, magic cookie included.
Definition dhcp_message.hpp:291
DHCP option list.
Definition dhcp_option.hpp:64
static bool isValid(uint8_t code, T &&value)
check that a value can be carried by the given option.
Definition dhcp_option.hpp:413
@ Router
Definition dhcp_option.hpp:74
@ Message
Definition dhcp_option.hpp:127
@ ServerIdentifier
Definition dhcp_option.hpp:125
@ BroadcastAddress
Definition dhcp_option.hpp:99
@ DomainNameServer
Definition dhcp_option.hpp:77
@ ParameterRequestList
Definition dhcp_option.hpp:126
@ DomainName
Definition dhcp_option.hpp:86
@ SubnetMask
Definition dhcp_option.hpp:72
@ ClientIdentifier
Definition dhcp_option.hpp:132
@ MaximumDhcpMessageSize
Definition dhcp_option.hpp:128
@ RequestedIpAddress
Definition dhcp_option.hpp:121
@ DhcpMessageType
Definition dhcp_option.hpp:124
@ HostName
Definition dhcp_option.hpp:83
@ InterfaceMtu
Definition dhcp_option.hpp:97
const_iterator end() const
get an iterator to the element following the last element of the list.
Definition dhcp_option.hpp:294
bool insert(uint8_t code, T &&value)
add an option to the list, converting the value to the type the option carries.
Definition dhcp_option.hpp:351
const T * getIf(uint8_t code) const
get the value identified by code if it is present and holds the requested type.
Definition dhcp_option.hpp:246
const_iterator begin() const
get an iterator to the first element of the list.
Definition dhcp_option.hpp:276
IPv6, IPv4 address class.
Definition ip_address.hpp:52
static const IpAddress ipv4Wildcard
wildcard IPv4 address.
Definition ip_address.hpp:370
static const IpAddress ipv4Broadcast
broadcast IPv4 address.
Definition ip_address.hpp:373
static IpAddress ipv4Address(const std::string &interface)
get the specified interface IPv4 address.
Definition ip_address.cpp:1526
bool isWildcard() const
check if IP address is a wildcard address.
Definition ip_address.cpp:1295
MAC address class.
Definition mac_address.hpp:47
static const MacAddress broadcast
broadcast MAC address.
Definition mac_address.hpp:310
bool isWildcard() const
check if MAC address is a wildcard address.
Definition mac_address.cpp:182
const uint8_t * addr() const
get the internal MAC address array address.
Definition mac_address.cpp:164
static Reactor & reactor()
get the global Reactor instance.
Definition reactor.cpp:612
Reactor class.
Definition reactor.hpp:159
bool isReactorThread() const noexcept
check if the calling thread is the reactor thread.
Definition reactor.cpp:337
int delHandler(int fd, bool sync=true) noexcept
delete handler from reactor.
Definition reactor.cpp:156
int addHandler(int fd, EventHandler *handler, bool wantRead=true, bool wantWrite=false, bool sync=true) noexcept
add handler to reactor.
Definition reactor.cpp:101
int invoke(InvokeHandler *fn, bool sync=true) noexcept
invoke a function from the reactor thread.
Definition reactor.cpp:199
class owning a mutex for the duration of a scoped block.
Definition mutex.hpp:249
Definition acceptor.hpp:32
uint16_t checksum(const uint16_t *data, size_t len, uint16_t current=0) noexcept
get standard 1s complement checksum.
Definition utils.hpp:319
Type randomize()
create a random number.
Definition utils.hpp:402
std::error_code make_error_code(join::Errc code) noexcept
Create an std::error_code object.
Definition error.cpp:195
std::vector< uint8_t > ByteList
list of bytes.
Definition dhcp_option.hpp:48
DHCP message.
Definition dhcp_message.hpp:46
uint16_t flags
message flags.
Definition dhcp_message.hpp:75
MacAddress hardware
client hardware address, carried by the message itself.
Definition dhcp_message.hpp:54
IpAddress client
client IP address, set when the client already owns a lease.
Definition dhcp_message.hpp:57
uint8_t op
operation code.
Definition dhcp_message.hpp:78
DhcpOption options
message options.
Definition dhcp_message.hpp:81
MacAddress dest
link layer destination address, set by the transport.
Definition dhcp_message.hpp:51
IpAddress gateway
IP address of the relay agent the message went through.
Definition dhcp_message.hpp:66
uint32_t id
transaction identifier.
Definition dhcp_message.hpp:69
IpAddress address
Definition tcp_acceptor_test.cpp:35