FreeNOS
DhcpClient.cpp
Go to the documentation of this file.
1/*
2 * Copyright (C) 2020 Niek Linnenbank
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 3 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#pragma clang optimize off
19#pragma GCC push_options
20#pragma GCC optimize ("O0")
21
22#include <Log.h>
23#include <ByteOrder.h>
24#include <MemoryBlock.h>
25#include <BufferedFile.h>
26#include <NetworkClient.h>
27#include <NetworkSocket.h>
28#include <errno.h>
29#include <string.h>
30#include <sys/socket.h>
31#include "DhcpClient.h"
32
33DhcpClient::DhcpClient(int argc, char **argv)
34 : POSIXApplication(argc, argv)
35 , m_client(ZERO)
36 , m_socket(0)
37 , m_transactionId(1)
38{
40
41 parser().setDescription("Dynamic Host Configuration Protocol (DHCP) client");
42 parser().registerPositional("DEVICE", "device name of network adapter");
43}
44
46{
47 delete m_client;
48}
49
51{
52 const char *device = arguments().get("DEVICE");
54
55 DEBUG("");
56
57 // Read ethernet device address
58 String ethFilePath;
59 ethFilePath << "/network/" << device << "/ethernet/address";
60 BufferedFile ethFile(*ethFilePath);
61
62 const BufferedFile::Result readResult = ethFile.read();
63 if (readResult != BufferedFile::Success)
64 {
65 ERROR("failed to read ethernet device address of " <<
66 device << ": result = " << (int) readResult);
67 return IOError;
68 }
70 DEBUG(device << " has address " << m_etherAddress);
71
72 // Create a network client
73 m_client = new NetworkClient(device);
74
75 // Initialize networking client
76 result = m_client->initialize();
77 if (result != NetworkClient::Success)
78 {
79 ERROR("failed to initialize network client for device "
80 << device << ": result = " << (int) result);
81 return IOError;
82 }
83
84 // Create an UDP socket
86 if (result != NetworkClient::Success)
87 {
88 ERROR("failed to create UDP socket on device " << device <<
89 ": result = " << (int) result);
90 return IOError;
91 }
92
93 // Bind to a local port.
95 if (result != NetworkClient::Success)
96 {
97 ERROR("failed to bind socket to UDP port " << ClientPort <<
98 " on device " << device << ": result = " << (int) result);
99 return IOError;
100 }
101
102 // Success
103 return Success;
104}
105
107{
108 DEBUG("");
109
110 // Keep retrying until we have an address
111 for (Size i = 0; i < MaximumRetries; i++)
112 {
113 IPV4::Address ipAddr, ipServer, ipGateway;
114 DhcpClient::Result result;
115
116 DEBUG("device = " << arguments().get("DEVICE") << " attempt = " << (i + 1));
117
119 ipAddr = ipServer = ipGateway = 0;
120
121 result = discover(ipAddr, ipServer, ipGateway);
122 if (result != DhcpClient::Success)
123 {
124 ERROR("failed to send discover: result = " << (int) result);
125 continue;
126 }
127
128 result = offer(ipAddr, ipServer, ipGateway);
129 if (result != DhcpClient::Success)
130 {
131 ERROR("failed to receive offer: result = " << (int) result);
132 continue;
133 }
134
135 result = request(ipAddr, ipServer, ipGateway);
136 if (result != DhcpClient::Success)
137 {
138 ERROR("failed to send request: result = " << (int) result);
139 continue;
140 }
141
142 result = acknowledge(ipAddr, ipServer, ipGateway);
143 if (result != DhcpClient::Success)
144 {
145 ERROR("failed to receive acknowledge: result = " << (int) result);
146 continue;
147 }
148
149 DEBUG("ipAddr = " << *IPV4::toString(ipAddr) <<
150 " ipServer = " << *IPV4::toString(ipServer) <<
151 " ipGateway = " << *IPV4::toString(ipGateway));
152
153 return setIpAddress(arguments().get("DEVICE"), ipAddr);
154 }
155
156 return NotFound;
157}
158
160 const IPV4::Address ipAddr) const
161{
162 DEBUG("device = " << device << " ipAddr = " << *IPV4::toString(ipAddr));
163
164 // Apply the IP address on the device
165 String ipFilePath;
166 ipFilePath << "/network/" << device << "/ipv4/address";
167 BufferedFile ipFile(*ipFilePath);
168
169 const BufferedFile::Result writeResult = ipFile.write(&ipAddr, sizeof(ipAddr));
170 if (writeResult != BufferedFile::Success)
171 {
172 ERROR("failed to set IPV4 address for device " <<
173 device << ": result = " << (int) writeResult);
174 return IOError;
175 }
176
177 return Success;
178}
179
181 const IPV4::Address & ipServer,
182 const IPV4::Address & ipGateway) const
183{
184 DEBUG("");
185 return sendBootRequest(Discover, ipAddr, ipServer, ipGateway);
186}
187
189 IPV4::Address & ipServer,
190 IPV4::Address & ipGateway) const
191{
192 DEBUG("");
193 return receiveBootResponse(Offer, ipAddr, ipServer, ipGateway);
194}
195
196
198 const IPV4::Address & ipServer,
199 const IPV4::Address & ipGateway) const
200{
201 DEBUG("");
202 return sendBootRequest(Request, ipAddr, ipServer, ipGateway);
203}
204
206 IPV4::Address & ipServer,
207 IPV4::Address & ipGateway) const
208{
209 DEBUG("");
210 return receiveBootResponse(Ack, ipAddr, ipServer, ipGateway);
211}
212
214 const IPV4::Address & ipAddr,
215 const IPV4::Address & ipServer,
216 const IPV4::Address & ipGateway) const
217{
218 u8 pkt[1024];
220
221 DEBUG("messageType = " << (int) messageType <<
222 " ipAddr = " << *IPV4::toString(ipAddr) <<
223 " ipServer = " << *IPV4::toString(ipServer) <<
224 " ipGateway = " << *IPV4::toString(ipGateway));
225
226 // Prepare request packet
227 MemoryBlock::set(&pkt, 0, sizeof(pkt));
228 hdr->operation = BootRequest;
229 hdr->hardwareType = 0x1;
230 hdr->hardwareLength = 0x6;
233 writeBe32(&hdr->magic, MagicValue);
234
235 // Add message type
236 u8 *opt = (u8 *) (hdr + 1);
237 *opt++ = DhcpMessageType;
238 *opt++ = sizeof(u8);
239 *opt++ = messageType;
240
241 // Add requested IP
242 if (ipAddr != ZERO)
243 {
244 *opt++ = RequestedIP;
245 *opt++ = sizeof(IPV4::Address);
246 writeBe32(opt, ipAddr);
247 opt += sizeof(IPV4::Address);
248 }
249
250 // Add parameter list
251 *opt++ = ParameterRequestList;
252 *opt++ = 2;
253 *opt++ = Router;
254 *opt++ = DomainNameServer;
255
256 // Add server IP
257 if (ipServer != ZERO)
258 {
259 *opt++ = ServerIdentifier;
260 *opt++ = sizeof(IPV4::Address);
261 writeBe32(opt, ipServer);
262 opt += sizeof(IPV4::Address);
263 }
264
265 // Close options
266 *opt++ = EndMark;
267
268 // Send the packet
269 return udpSend(&pkt, opt - &pkt[0]);
270}
271
273 IPV4::Address & ipAddr,
274 IPV4::Address & ipServer,
275 IPV4::Address & ipGateway) const
276{
277 u8 pkt[1024];
278 Size size = sizeof(pkt);
280
281 DEBUG("");
282
283 const DhcpClient::Result result = udpReceive(&pkt, size);
284 if (result != DhcpClient::Success)
285 {
286 ERROR("failed to receive UDP packet: result = " << (int) result);
287 return IOError;
288 }
289
290 // The packet must be targeted at our ethernet address
293 {
294 ERROR("invalid ethernet address: " << *(Ethernet::Address *) &hdr->clientHardware);
295 return InvalidArgument;
296 }
297
298 // The packet operation must be a DHCP response
299 if (hdr->operation != BootResponse)
300 {
301 ERROR("invalid operation: " << hdr->operation << " != " << (int) BootResponse);
302 return InvalidArgument;
303 }
304
305 // The packet must have the correct transaction number
307 {
308 ERROR("invalid transaction: " << readBe32(&hdr->transactionId) <<
309 " != " << m_transactionId);
310 return InvalidArgument;
311 }
312
313 // Parse the offer message
314 ipAddr = readBe32(&hdr->yourAddress);
315
316 // Parse options
317 for (u8 *option = (u8 *) (hdr + 1); option < &pkt[size - 2]; )
318 {
319 const Options optionValue = (const Options) *option++;
320 const Size optionLength = *option++;
321
322 if (option + optionLength > &pkt[size])
323 break;
324
325 switch (optionValue)
326 {
327 case Router:
328 {
329 ipGateway = readBe32(option);
330 break;
331 }
332
333 case SubnetMask:
334 case EndMark:
335 case DomainNameServer:
336 case RequestedIP:
337 break;
338
339 case DhcpMessageType:
340 {
341 if (messageType != (DhcpClient::MessageType) (*option))
342 {
343 ERROR("invalid message type: " << (int) (*option) << " != " << (int) messageType);
344 return InvalidArgument;
345 }
346 }
347
349 break;
350
351 case ServerIdentifier:
352 {
353 ipServer = readBe32(option);
354 break;
355 }
356
357 default:
358 DEBUG("ignored unsupported option " << (int) optionValue);
359 break;
360 }
361
362 option += optionLength;
363
364 if (optionValue == EndMark)
365 break;
366 }
367
368 DEBUG("messageType = " << (int) messageType <<
369 " ipAddr = " << *IPV4::toString(ipAddr) <<
370 " ipServer = " << *IPV4::toString(ipServer) <<
371 " ipGateway = " << *IPV4::toString(ipGateway));
372
373 return DhcpClient::Success;
374}
375
377 const Size size) const
378{
379 DEBUG("size = " << size);
380
381 // Prepare UDP broadcast datagram
382 struct sockaddr addr;
383 addr.addr = 0xffffffff;
384 addr.port = ServerPort;
385
386 // Send the packet
387 int result = ::sendto(m_socket, packet, size, 0,
388 &addr, sizeof(addr));
389 if (result <= 0)
390 {
391 ERROR("failed to send UDP datagram: " << strerror(errno));
392 return IOError;
393 }
394
395 return Success;
396}
397
399 Size & size) const
400{
401 DEBUG("");
402
403 struct sockaddr addr;
404
405 // Wait for a packet in the UDP socket
407 if (result != NetworkClient::Success)
408 {
409 ERROR("failed to wait for UDP socket " << m_socket << ": result = " << (int) result);
410 return IOError;
411 }
412
413 // Receive UDP datagram
414 int r = recvfrom(m_socket, packet, size, 0,
415 &addr, sizeof(addr));
416 if (r < 0)
417 {
418 ERROR("failed to receive UDP datagram: " << strerror(errno));
419 return IOError;
420 }
421
422 size = r;
423 DEBUG("received " << r << " bytes from " << *IPV4::toString(addr.addr) <<
424 " at port " << addr.port);
425
426 return Success;
427}
Result
Result codes.
Definition Application.h:54
const ArgumentContainer & arguments() const
Get program arguments.
ArgumentParser & parser()
Get program arguments parser.
const char * get(const char *name) const
Get argument by name.
void setDescription(const String &desc)
Set program description.
Result registerPositional(const char *name, const char *description, Size count=1)
Register a positional argument.
Provides a buffered abstract interface to a file.
Result
Result codes.
Result write(const void *data, const Size size) const
Write the file (unbuffered)
Result read()
Read the file (buffered)
const void * buffer() const
Get file buffer.
Result receiveBootResponse(const DhcpClient::MessageType messageType, IPV4::Address &ipAddr, IPV4::Address &ipServer, IPV4::Address &ipGateway) const
Receive DHCP boot response.
static const u16 ServerPort
Server UDP port.
Definition DhcpClient.h:41
u32 m_transactionId
Transaction ID of the current request.
Definition DhcpClient.h:272
Result acknowledge(IPV4::Address &ipAddr, IPV4::Address &ipServer, IPV4::Address &ipGateway) const
Receive DHCP Acknowledge message.
static const u16 ClientPort
Client UDP port.
Definition DhcpClient.h:44
static const u32 MagicValue
Magic number value for the packet header.
Definition DhcpClient.h:50
Result udpSend(const void *packet, const Size size) const
Send UDP broadcast packet.
virtual ~DhcpClient()
Class destructor.
Ethernet::Address m_etherAddress
Host ethernet address.
Definition DhcpClient.h:269
int m_socket
UDP socket.
Definition DhcpClient.h:266
Result udpReceive(void *packet, Size &size) const
Receive UDP packet.
MessageType
DHCP message types.
Definition DhcpClient.h:89
static const Size MaximumRetries
Maximum number of retries to receive an IP address.
Definition DhcpClient.h:47
Result offer(IPV4::Address &ipAddr, IPV4::Address &ipServer, IPV4::Address &ipGateway) const
Receive DHCP Offer message.
static const Size ReceiveTimeoutMs
Timeout in milliseconds to wait for packet receive.
Definition DhcpClient.h:53
Result request(const IPV4::Address &ipAddr, const IPV4::Address &ipServer, const IPV4::Address &ipGateway) const
Send DHCP Request message.
Result setIpAddress(const char *device, const IPV4::Address ipAddr) const
Set IP address on a device.
Result sendBootRequest(const DhcpClient::MessageType messageType, const IPV4::Address &ipAddr, const IPV4::Address &ipServer, const IPV4::Address &ipGateway) const
Send DHCP boot request.
DhcpClient(int argc, char **argv)
Class constructor.
Result discover(const IPV4::Address &ipAddr, const IPV4::Address &ipServer, const IPV4::Address &ipGateway) const
Send DHCP Discover message.
NetworkClient * m_client
Network client.
Definition DhcpClient.h:263
virtual Result exec()
Execute the application event loop.
virtual Result initialize()
Initialize the application.
Options
DHCP options.
Definition DhcpClient.h:103
@ ParameterRequestList
Definition DhcpClient.h:110
static const String toString(const Address address)
Convert address to string.
Definition IPV4.cpp:82
u32 Address
IP-address.
Definition IPV4.h:47
static void * set(void *dest, int ch, unsigned count)
Fill memory with a constant byte.
static Size copy(void *dest, const void *src, Size count)
Copy memory from one place to another.
static bool compare(const void *p1, const void *p2, const Size count)
Compare memory.
Networking Client implementation.
Result bindSocket(const int sock, const IPV4::Address addr=0, const u16 port=0)
Bind socket to address/port.
Result initialize()
Perform initialization.
Result waitSocket(const NetworkClient::SocketType type, const int sock, const Size msecTimeout)
Wait until the given socket has data to receive.
Result
Result codes.
Result createSocket(const SocketType type, int *socket)
Create new socket.
POSIX-compatible application.
Abstraction of strings.
Definition String.h:42
C char * strerror(int errnum)
The strerror function maps the number in errnum to a message string.
Definition strerror.cpp:20
C int errno
The lvalue errno is used by many functions to return error values.
C int sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *addr, socklen_t addrlen)
Send a single datagram to a remote host.
Definition sendto.cpp:25
C int recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *addr, socklen_t addrlen)
Receive a single datagram from a socket.
Definition recvfrom.cpp:25
void writeBe32(void *data, const u32 input)
Write 32-bit big endian integer.
Definition ByteOrder.h:459
#define ERROR(msg)
Output an error message.
Definition Log.h:61
const u32 readBe32(const void *data)
Read 32-bit big endian integer.
Definition ByteOrder.h:384
unsigned int Size
Any sane size indicator cannot go negative.
Definition Types.h:128
#define ZERO
Zero value.
Definition Macros.h:43
#define DEBUG(msg)
Output a debug message to standard output.
Definition Log.h:89
unsigned char u8
Unsigned 8-bit number.
Definition Types.h:59
Protocol packet header.
Definition DhcpClient.h:59
Ethernet network address.
Definition Ethernet.h:53
Defines a socket address and port pair.
Definition socket.h:36
u32 addr
Definition socket.h:37
u16 port
Definition socket.h:38