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 
33 DhcpClient::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");
53  NetworkClient::Result result;
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.
94  result = m_client->bindSocket(m_socket, 0, ClientPort);
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 
118  m_transactionId++;
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];
219  DhcpClient::Header *hdr = (DhcpClient::Header *) &pkt;
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);
279  DhcpClient::Header *hdr = (DhcpClient::Header *) &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
306  if (readBe32(&hdr->transactionId) != m_transactionId)
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 }
DhcpClient::Router
@ Router
Definition: DhcpClient.h:105
DhcpClient::ServerIdentifier
@ ServerIdentifier
Definition: DhcpClient.h:109
NetworkClient::Result
Result
Result codes.
Definition: NetworkClient.h:99
DhcpClient::DomainNameServer
@ DomainNameServer
Definition: DhcpClient.h:106
ArgumentContainer::get
const char * get(const char *name) const
Get argument by name.
Definition: ArgumentContainer.cpp:49
NetworkClient::Success
@ Success
Definition: NetworkClient.h:101
DhcpClient::MagicValue
static const u32 MagicValue
Magic number value for the packet header.
Definition: DhcpClient.h:50
DhcpClient::DhcpClient
DhcpClient(int argc, char **argv)
Class constructor.
Definition: DhcpClient.cpp:33
BufferedFile
Provides a buffered abstract interface to a file.
Definition: BufferedFile.h:34
MemoryBlock::copy
static Size copy(void *dest, const void *src, Size count)
Copy memory from one place to another.
Definition: MemoryBlock.cpp:36
DhcpClient::DhcpMessageType
@ DhcpMessageType
Definition: DhcpClient.h:108
DhcpClient::Header::transactionId
u32 transactionId
Definition: DhcpClient.h:64
readBe32
const u32 readBe32(const void *data)
Read 32-bit big endian integer.
Definition: ByteOrder.h:384
DhcpClient::exec
virtual Result exec()
Execute the application event loop.
Definition: DhcpClient.cpp:106
DhcpClient::Discover
@ Discover
Definition: DhcpClient.h:90
NetworkClient::initialize
Result initialize()
Perform initialization.
Definition: NetworkClient.cpp:35
NetworkClient::createSocket
Result createSocket(const SocketType type, int *socket)
Create new socket.
Definition: NetworkClient.cpp:80
errno
C int errno
The lvalue errno is used by many functions to return error values.
DhcpClient::m_transactionId
u32 m_transactionId
Transaction ID of the current request.
Definition: DhcpClient.h:272
DhcpClient::Header::yourAddress
u32 yourAddress
Definition: DhcpClient.h:68
MemoryBlock::set
static void * set(void *dest, int ch, unsigned count)
Fill memory with a constant byte.
Definition: MemoryBlock.cpp:25
string.h
DhcpClient::Ack
@ Ack
Definition: DhcpClient.h:94
DhcpClient::Header
Protocol packet header.
Definition: DhcpClient.h:58
String
Abstraction of strings.
Definition: String.h:41
DhcpClient::BootResponse
@ BootResponse
Definition: DhcpClient.h:82
DhcpClient::sendBootRequest
Result sendBootRequest(const DhcpClient::MessageType messageType, const IPV4::Address &ipAddr, const IPV4::Address &ipServer, const IPV4::Address &ipGateway) const
Send DHCP boot request.
Definition: DhcpClient.cpp:213
DhcpClient::m_socket
int m_socket
UDP socket.
Definition: DhcpClient.h:266
Application::NotFound
@ NotFound
Definition: Application.h:56
BufferedFile::Result
Result
Result codes.
Definition: BufferedFile.h:41
POSIXApplication
POSIX-compatible application.
Definition: POSIXApplication.h:35
DhcpClient::Header::hardwareType
u8 hardwareType
Definition: DhcpClient.h:61
Application::Success
@ Success
Definition: Application.h:55
MemoryBlock.h
MemoryBlock::compare
static bool compare(const void *p1, const void *p2, const Size count)
Compare memory.
Definition: MemoryBlock.cpp:72
Application::arguments
const ArgumentContainer & arguments() const
Get program arguments.
Definition: Application.cpp:112
DhcpClient::ParameterRequestList
@ ParameterRequestList
Definition: DhcpClient.h:110
ArgumentParser::setDescription
void setDescription(const String &desc)
Set program description.
Definition: ArgumentParser.cpp:95
DhcpClient::ClientPort
static const u16 ClientPort
Client UDP port.
Definition: DhcpClient.h:44
BufferedFile::write
Result write(const void *data, const Size size) const
Write the file (unbuffered)
Definition: BufferedFile.cpp:99
DhcpClient::ReceiveTimeoutMs
static const Size ReceiveTimeoutMs
Timeout in milliseconds to wait for packet receive.
Definition: DhcpClient.h:53
DhcpClient::Offer
@ Offer
Definition: DhcpClient.h:91
sockaddr
Defines a socket address and port pair.
Definition: socket.h:35
Log.h
BufferedFile::buffer
const void * buffer() const
Get file buffer.
Definition: BufferedFile.cpp:48
DhcpClient::offer
Result offer(IPV4::Address &ipAddr, IPV4::Address &ipServer, IPV4::Address &ipGateway) const
Receive DHCP Offer message.
Definition: DhcpClient.cpp:188
recvfrom
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
Application::IOError
@ IOError
Definition: Application.h:57
BufferedFile::read
Result read()
Read the file (buffered)
Definition: BufferedFile.cpp:58
DhcpClient::initialize
virtual Result initialize()
Initialize the application.
Definition: DhcpClient.cpp:50
DEBUG
#define DEBUG(msg)
Output a debug message to standard output.
Definition: Log.h:89
Ethernet::Address
Ethernet network address.
Definition: Ethernet.h:52
DhcpClient::Header::hardwareLength
u8 hardwareLength
Definition: DhcpClient.h:62
Application::InvalidArgument
@ InvalidArgument
Definition: Application.h:58
DhcpClient::Options
Options
DHCP options.
Definition: DhcpClient.h:102
DhcpClient::EndMark
@ EndMark
Definition: DhcpClient.h:111
strerror
char * strerror(int errnum)
The strerror function maps the number in errnum to a message string.
Definition: strerror.cpp:20
DhcpClient::BootRequest
@ BootRequest
Definition: DhcpClient.h:81
ByteOrder.h
Size
unsigned int Size
Any sane size indicator cannot go negative.
Definition: Types.h:128
sendto
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
DhcpClient::m_client
NetworkClient * m_client
Network client.
Definition: DhcpClient.h:263
BufferedFile.h
Application::Result
Result
Result codes.
Definition: Application.h:53
DhcpClient::m_etherAddress
Ethernet::Address m_etherAddress
Host ethernet address.
Definition: DhcpClient.h:269
DhcpClient::SubnetMask
@ SubnetMask
Definition: DhcpClient.h:104
DhcpClient::Request
@ Request
Definition: DhcpClient.h:92
DhcpClient::receiveBootResponse
Result receiveBootResponse(const DhcpClient::MessageType messageType, IPV4::Address &ipAddr, IPV4::Address &ipServer, IPV4::Address &ipGateway) const
Receive DHCP boot response.
Definition: DhcpClient.cpp:272
IPV4::Address
u32 Address
IP-address.
Definition: IPV4.h:47
NetworkClient
Networking Client implementation.
Definition: NetworkClient.h:44
DhcpClient::Header::clientHardware
u8 clientHardware[16]
Definition: DhcpClient.h:71
DhcpClient::setIpAddress
Result setIpAddress(const char *device, const IPV4::Address ipAddr) const
Set IP address on a device.
Definition: DhcpClient.cpp:159
DhcpClient::udpReceive
Result udpReceive(void *packet, Size &size) const
Receive UDP packet.
Definition: DhcpClient.cpp:398
NetworkClient::bindSocket
Result bindSocket(const int sock, const IPV4::Address addr=0, const u16 port=0)
Bind socket to address/port.
Definition: NetworkClient.cpp:123
ArgumentParser::registerPositional
Result registerPositional(const char *name, const char *description, Size count=1)
Register a positional argument.
Definition: ArgumentParser.cpp:119
sockaddr::addr
u32 addr
Definition: socket.h:37
DhcpClient::request
Result request(const IPV4::Address &ipAddr, const IPV4::Address &ipServer, const IPV4::Address &ipGateway) const
Send DHCP Request message.
Definition: DhcpClient.cpp:197
IPV4::toString
static const String toString(const Address address)
Convert address to string.
Definition: IPV4.cpp:82
Application::parser
ArgumentParser & parser()
Get program arguments parser.
Definition: Application.cpp:102
writeBe32
void writeBe32(void *data, const u32 input)
Write 32-bit big endian integer.
Definition: ByteOrder.h:459
DhcpClient::Header::magic
u32 magic
Definition: DhcpClient.h:73
ERROR
#define ERROR(msg)
Output an error message.
Definition: Log.h:61
u8
unsigned char u8
Unsigned 8-bit number.
Definition: Types.h:59
BufferedFile::Success
@ Success
Definition: BufferedFile.h:43
socket.h
DhcpClient::RequestedIP
@ RequestedIP
Definition: DhcpClient.h:107
DhcpClient::MessageType
MessageType
DHCP message types.
Definition: DhcpClient.h:88
DhcpClient::MaximumRetries
static const Size MaximumRetries
Maximum number of retries to receive an IP address.
Definition: DhcpClient.h:47
DhcpClient.h
DhcpClient::acknowledge
Result acknowledge(IPV4::Address &ipAddr, IPV4::Address &ipServer, IPV4::Address &ipGateway) const
Receive DHCP Acknowledge message.
Definition: DhcpClient.cpp:205
DhcpClient::Header::operation
u8 operation
Definition: DhcpClient.h:60
NetworkClient.h
DhcpClient::ServerPort
static const u16 ServerPort
Server UDP port.
Definition: DhcpClient.h:41
NetworkClient::waitSocket
Result waitSocket(const NetworkClient::SocketType type, const int sock, const Size msecTimeout)
Wait until the given socket has data to receive.
Definition: NetworkClient.cpp:131
DhcpClient::~DhcpClient
virtual ~DhcpClient()
Class destructor.
Definition: DhcpClient.cpp:45
ZERO
#define ZERO
Zero value.
Definition: Macros.h:43
DhcpClient::udpSend
Result udpSend(const void *packet, const Size size) const
Send UDP broadcast packet.
Definition: DhcpClient.cpp:376
NetworkClient::UDP
@ UDP
Definition: NetworkClient.h:93
NetworkSocket.h
errno.h
DhcpClient::discover
Result discover(const IPV4::Address &ipAddr, const IPV4::Address &ipServer, const IPV4::Address &ipGateway) const
Send DHCP Discover message.
Definition: DhcpClient.cpp:180