1 //
2 // Copyright (C) 2015 The Android Open Source Project
3 //
4 // Licensed under the Apache License, Version 2.0 (the "License");
5 // you may not use this file except in compliance with the License.
6 // You may obtain a copy of the License at
7 //
8 // http://www.apache.org/licenses/LICENSE-2.0
9 //
10 // Unless required by applicable law or agreed to in writing, software
11 // distributed under the License is distributed on an "AS IS" BASIS,
12 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 // See the License for the specific language governing permissions and
14 // limitations under the License.
15 //
16
17 #include "dhcp_client/dhcp_message.h"
18
19 #include <net/if.h>
20 #include <net/if_arp.h>
21 #include <netinet/in.h>
22
23 #include <memory>
24 #include <set>
25 #include <string>
26 #include <utility>
27 #include <vector>
28
29 #include <base/logging.h>
30
31 #include "dhcp_client/dhcp_options.h"
32 #include "dhcp_client/dhcp_options_writer.h"
33
34 using shill::ByteString;
35
36 namespace dhcp_client {
37
38 namespace {
39 const int kClientHardwareAddressLength = 16;
40 const int kServerNameLength = 64;
41 const int kBootFileLength = 128;
42 const uint32_t kMagicCookie = 0x63825363;
43 const size_t kDHCPMessageMaxLength = 548;
44 const size_t kDHCPMessageMinLength = 236;
45 const uint8_t kDHCPMessageBootRequest = 1;
46 const uint8_t kDHCPMessageBootReply = 2;
47
48 // Follow the naming in rfc2131 for this struct.
49 struct __attribute__((__packed__)) RawDHCPMessage {
50 uint8_t op;
51 uint8_t htype;
52 uint8_t hlen;
53 uint8_t hops;
54 uint32_t xid;
55 uint16_t secs;
56 uint16_t flags;
57 uint32_t ciaddr;
58 uint32_t yiaddr;
59 uint32_t siaddr;
60 uint32_t giaddr;
61 uint8_t chaddr[kClientHardwareAddressLength];
62 uint8_t sname[kServerNameLength];
63 uint8_t file[kBootFileLength];
64 uint32_t cookie;
65 uint8_t options[kDHCPOptionLength];
66 };
67 } // namespace
68
DHCPMessage()69 DHCPMessage::DHCPMessage()
70 : requested_ip_address_(0),
71 lease_time_(0),
72 message_type_(0),
73 server_identifier_(0),
74 renewal_time_(0),
75 rebinding_time_(0) {
76 options_map_.insert(std::make_pair(kDHCPOptionMessageType,
77 ParserContext(new UInt8Parser(), &message_type_)));
78 options_map_.insert(std::make_pair(kDHCPOptionLeaseTime,
79 ParserContext(new UInt32Parser(), &lease_time_)));
80 options_map_.insert(std::make_pair(kDHCPOptionMessage,
81 ParserContext(new StringParser(), &error_message_)));
82 options_map_.insert(std::make_pair(kDHCPOptionSubnetMask,
83 ParserContext(new UInt32Parser(), &subnet_mask_)));
84 options_map_.insert(std::make_pair(kDHCPOptionServerIdentifier,
85 ParserContext(new UInt32Parser(), &server_identifier_)));
86 options_map_.insert(std::make_pair(kDHCPOptionRenewalTime,
87 ParserContext(new UInt32Parser(), &renewal_time_)));
88 options_map_.insert(std::make_pair(kDHCPOptionRebindingTime,
89 ParserContext(new UInt32Parser(), &rebinding_time_)));
90 options_map_.insert(std::make_pair(kDHCPOptionDNSServer,
91 ParserContext(new UInt32ListParser(), &dns_server_)));
92 options_map_.insert(std::make_pair(kDHCPOptionRouter,
93 ParserContext(new UInt32ListParser(), &router_)));
94 options_map_.insert(std::make_pair(kDHCPOptionDomainName,
95 ParserContext(new StringParser(), &domain_name_)));
96 options_map_.insert(std::make_pair(kDHCPOptionVendorSpecificInformation,
97 ParserContext(new ByteArrayParser(), &vendor_specific_info_)));
98 }
99
~DHCPMessage()100 DHCPMessage::~DHCPMessage() {}
101
InitFromBuffer(const unsigned char * buffer,size_t length,DHCPMessage * message)102 bool DHCPMessage::InitFromBuffer(const unsigned char* buffer,
103 size_t length,
104 DHCPMessage* message) {
105 if (buffer == NULL) {
106 LOG(ERROR) << "Invalid buffer address";
107 return false;
108 }
109 if (length < kDHCPMessageMinLength || length > kDHCPMessageMaxLength) {
110 LOG(ERROR) << "Invalid DHCP message length";
111 return false;
112 }
113 const RawDHCPMessage* raw_message
114 = reinterpret_cast<const RawDHCPMessage*>(buffer);
115 size_t options_length = reinterpret_cast<const unsigned char*>(raw_message) +
116 length - reinterpret_cast<const unsigned char*>(raw_message->options) + 1;
117 message->opcode_ = raw_message->op;
118 message->hardware_address_type_ = raw_message->htype;
119 message->hardware_address_length_ = raw_message->hlen;
120 if (message->hardware_address_length_ > kClientHardwareAddressLength) {
121 LOG(ERROR) << "Invalid hardware address length";
122 return false;
123 }
124 message->relay_hops_ = raw_message->hops;
125 message->transaction_id_ = ntohl(raw_message->xid);
126 message->seconds_ = ntohs(raw_message->secs);
127 message->flags_ = ntohs(raw_message->flags);
128 message->client_ip_address_ = ntohl(raw_message->ciaddr);
129 message->your_ip_address_ = ntohl(raw_message->yiaddr);
130 message->next_server_ip_address_ = ntohl(raw_message->siaddr);
131 message->agent_ip_address_ = ntohl(raw_message->giaddr);
132 message->cookie_ = ntohl(raw_message->cookie);
133 message->client_hardware_address_ = ByteString(
134 reinterpret_cast<const char*>(raw_message->chaddr),
135 message->hardware_address_length_);
136 message->servername_.assign(reinterpret_cast<const char*>(raw_message->sname),
137 kServerNameLength);
138 message->bootfile_.assign(reinterpret_cast<const char*>(raw_message->file),
139 kBootFileLength);
140 // Validate the DHCP Message
141 if (!message->IsValid()) {
142 return false;
143 }
144 if (!message->ParseDHCPOptions(raw_message->options, options_length)) {
145 LOG(ERROR) << "Failed to parse DHCP options";
146 return false;
147 }
148 return true;
149 }
150
ParseDHCPOptions(const uint8_t * options,size_t options_length)151 bool DHCPMessage::ParseDHCPOptions(const uint8_t* options,
152 size_t options_length) {
153 // DHCP options are in TLV format.
154 // T: tag, L: length, V: value(data)
155 // RFC 1497, RFC 1533, RFC 2132
156 const uint8_t* ptr = options;
157 const uint8_t* end_ptr = options + options_length;
158 std::set<uint8_t> options_set;
159 while (ptr < end_ptr) {
160 uint8_t option_code = *ptr++;
161 int option_code_int = static_cast<int>(option_code);
162 if (option_code == kDHCPOptionPad) {
163 continue;
164 } else if (option_code == kDHCPOptionEnd) {
165 // We reach the end of the option field.
166 // Validate the options before we return.
167 return ContainsValidOptions(options_set);
168 }
169 if (ptr >= end_ptr) {
170 LOG(ERROR) << "Failed to decode dhcp options, no option length field"
171 " for option: " << option_code_int;
172 return false;
173 }
174 uint8_t option_length = *ptr++;
175 if (ptr + option_length >= end_ptr) {
176 LOG(ERROR) << "Failed to decode dhcp options, invalid option length field"
177 " for option: " << option_code_int;
178 return false;
179 }
180 if (options_set.find(option_code) != options_set.end()) {
181 LOG(ERROR) << "Found repeated DHCP option: " << option_code_int;
182 return false;
183 }
184 // Here we find a valid DHCP option.
185 auto it = options_map_.find(option_code);
186 if (it != options_map_.end()) {
187 ParserContext* context = &(it->second);
188 if (!context->parser->GetOption(ptr, option_length, context->output)) {
189 return false;
190 }
191 options_set.insert(option_code);
192 } else {
193 DLOG(INFO) << "Ignore DHCP option: " << option_code_int;
194 }
195 // Move to next tag.
196 ptr += option_length;
197 }
198 // Reach the end of message without seeing kDHCPOptionEnd.
199 LOG(ERROR) << "Broken DHCP options without END tag.";
200 return false;
201 }
202
ContainsValidOptions(const std::set<uint8_t> & options_set)203 bool DHCPMessage::ContainsValidOptions(const std::set<uint8_t>& options_set) {
204 // A DHCP message must contain option 53: DHCP Message Type.
205 if (options_set.find(kDHCPOptionMessageType) == options_set.end()) {
206 LOG(ERROR) << "Faied to find option 53: DHCP Message Type.";
207 return false;
208 }
209 if (message_type_ != kDHCPMessageTypeOffer &&
210 message_type_ != kDHCPMessageTypeAck &&
211 message_type_ != kDHCPMessageTypeNak) {
212 LOG(ERROR) << "Invalid DHCP Message Type: "
213 << static_cast<int>(message_type_);
214 return false;
215 }
216 // A DHCP Offer message must contain option 51: IP Address Lease Time.
217 if (message_type_ == kDHCPMessageTypeOffer) {
218 if (options_set.find(kDHCPOptionLeaseTime) == options_set.end()) {
219 LOG(ERROR) << "Faied to find option 51: IP Address Lease Time";
220 return false;
221 }
222 }
223 // A message from DHCP server must contain option 54: Server Identifier.
224 if (options_set.find(kDHCPOptionServerIdentifier) == options_set.end()) {
225 LOG(ERROR) << "Faied to find option 54: Server Identifier.";
226 return false;
227 }
228 return true;
229 }
230
IsValid()231 bool DHCPMessage::IsValid() {
232 if (opcode_ != kDHCPMessageBootReply) {
233 LOG(ERROR) << "Invalid DHCP message op code";
234 return false;
235 }
236 if (hardware_address_type_ != ARPHRD_ETHER) {
237 LOG(ERROR) << "DHCP message device family id does not match";
238 return false;
239 }
240 if (hardware_address_length_ != IFHWADDRLEN) {
241 LOG(ERROR) <<
242 "DHCP message device hardware address length does not match";
243 return false;
244 }
245 // We have nothing to do with the 'hops' field.
246
247 // The reply message from server should have the same xid we cached in client.
248 // DHCP state machine will take charge of this checking.
249
250 // According to RFC 2131, all secs field in reply messages should be 0.
251 if (seconds_) {
252 LOG(ERROR) << "Invalid DHCP message secs";
253 return false;
254 }
255
256 // Check broadcast flags.
257 // It should be 0 because we do not request broadcast reply.
258 if (flags_) {
259 LOG(ERROR) << "Invalid DHCP message flags";
260 return false;
261 }
262
263 // We need to ensure the message contains the correct client hardware address.
264 // DHCP state machine will take charge of this checking.
265
266 // We do not use the bootfile field.
267 if (cookie_ != kMagicCookie) {
268 LOG(ERROR) << "DHCP message cookie does not match";
269 return false;
270 }
271 return true;
272 }
273
Serialize(ByteString * data) const274 bool DHCPMessage::Serialize(ByteString* data) const {
275 RawDHCPMessage raw_message;
276 raw_message.op = opcode_;
277 raw_message.htype = hardware_address_type_;
278 raw_message.hlen = hardware_address_length_;
279 raw_message.hops = relay_hops_;
280 raw_message.xid = htonl(transaction_id_);
281 raw_message.secs = htons(seconds_);
282 raw_message.flags = htons(flags_);
283 raw_message.ciaddr = htonl(client_ip_address_);
284 raw_message.yiaddr = htonl(your_ip_address_);
285 raw_message.siaddr = htonl(next_server_ip_address_);
286 raw_message.giaddr = htonl(agent_ip_address_);
287 raw_message.cookie = htonl(cookie_);
288 memcpy(raw_message.chaddr,
289 client_hardware_address_.GetConstData(),
290 hardware_address_length_);
291 if (servername_.length() >= kServerNameLength) {
292 LOG(ERROR) << "Invalid server name length: " << servername_.length();
293 return false;
294 }
295 memcpy(raw_message.sname,
296 servername_.c_str(),
297 servername_.length());
298 raw_message.sname[servername_.length()] = 0;
299 if (bootfile_.length() >= kBootFileLength) {
300 LOG(ERROR) << "Invalid boot file length: " << bootfile_.length();
301 return false;
302 }
303 memcpy(raw_message.file,
304 bootfile_.c_str(),
305 bootfile_.length());
306 raw_message.file[bootfile_.length()] = 0;
307 data->Append(ByteString(reinterpret_cast<const char*>(&raw_message),
308 sizeof(raw_message) - kDHCPOptionLength));
309 // Append DHCP options to the message.
310 DHCPOptionsWriter* options_writer = DHCPOptionsWriter::GetInstance();
311 if (options_writer->WriteUInt8Option(data,
312 kDHCPOptionMessageType,
313 message_type_) == -1) {
314 LOG(ERROR) << "Failed to write message type option";
315 return false;
316 }
317 if (requested_ip_address_ != 0) {
318 if (options_writer->WriteUInt32Option(data,
319 kDHCPOptionRequestedIPAddr,
320 requested_ip_address_) == -1) {
321 LOG(ERROR) << "Failed to write requested ip address option";
322 return false;
323 }
324 }
325 if (lease_time_ != 0) {
326 if (options_writer->WriteUInt32Option(data,
327 kDHCPOptionLeaseTime,
328 lease_time_) == -1) {
329 LOG(ERROR) << "Failed to write lease time option";
330 return false;
331 }
332 }
333 if (server_identifier_ != 0) {
334 if (options_writer->WriteUInt32Option(data,
335 kDHCPOptionServerIdentifier,
336 server_identifier_) == -1) {
337 LOG(ERROR) << "Failed to write server identifier option";
338 return false;
339 }
340 }
341 if (error_message_.size() != 0) {
342 if (options_writer->WriteStringOption(data,
343 kDHCPOptionMessage,
344 error_message_) == -1) {
345 LOG(ERROR) << "Failed to write error message option";
346 return false;
347 }
348 }
349 if (parameter_request_list_.size() != 0) {
350 if (options_writer->WriteUInt8ListOption(data,
351 kDHCPOptionParameterRequestList,
352 parameter_request_list_) == -1) {
353 LOG(ERROR) << "Failed to write parameter request list";
354 return false;
355 }
356 }
357 // TODO(nywang): Append other options.
358 // Append end tag.
359 if (options_writer->WriteEndTag(data) == -1) {
360 LOG(ERROR) << "Failed to write DHCP options end tag";
361 return false;
362 }
363 // Ensure we do not exceed the maximum length.
364 if (data->GetLength() > kDHCPMessageMaxLength) {
365 LOG(ERROR) << "DHCP message length exceeds the limit";
366 return false;
367 }
368 return true;
369 }
370
ComputeChecksum(const uint8_t * data,size_t len)371 uint16_t DHCPMessage::ComputeChecksum(const uint8_t* data, size_t len) {
372 uint32_t sum = 0;
373
374 while (len > 1) {
375 sum += static_cast<uint32_t>(data[0]) << 8 | static_cast<uint32_t>(data[1]);
376 data += 2;
377 len -= 2;
378 }
379 if (len == 1) {
380 sum += static_cast<uint32_t>(*data) << 8;
381 }
382 sum = (sum >> 16) + (sum & 0xffff);
383 sum += (sum >> 16);
384
385 return ~static_cast<uint16_t>(sum);
386 }
387
SetClientIdentifier(const ByteString & client_identifier)388 void DHCPMessage::SetClientIdentifier(
389 const ByteString& client_identifier) {
390 client_identifier_ = client_identifier;
391 }
392
SetClientIPAddress(uint32_t client_ip_address)393 void DHCPMessage::SetClientIPAddress(uint32_t client_ip_address) {
394 client_ip_address_ = client_ip_address;
395 }
396
SetClientHardwareAddress(const ByteString & client_hardware_address)397 void DHCPMessage::SetClientHardwareAddress(
398 const ByteString& client_hardware_address) {
399 client_hardware_address_ = client_hardware_address;
400 }
401
SetErrorMessage(const std::string & error_message)402 void DHCPMessage::SetErrorMessage(const std::string& error_message) {
403 error_message_ = error_message;
404 }
405
SetLeaseTime(uint32_t lease_time)406 void DHCPMessage::SetLeaseTime(uint32_t lease_time) {
407 lease_time_ = lease_time;
408 }
409
SetMessageType(uint8_t message_type)410 void DHCPMessage::SetMessageType(uint8_t message_type) {
411 message_type_ = message_type;
412 }
413
SetParameterRequestList(const std::vector<uint8_t> & parameter_request_list)414 void DHCPMessage::SetParameterRequestList(
415 const std::vector<uint8_t>& parameter_request_list) {
416 parameter_request_list_ = parameter_request_list;
417 }
418
SetRequestedIpAddress(uint32_t requested_ip_address)419 void DHCPMessage::SetRequestedIpAddress(uint32_t requested_ip_address) {
420 requested_ip_address_ = requested_ip_address;
421 }
422
SetServerIdentifier(uint32_t server_identifier)423 void DHCPMessage::SetServerIdentifier(uint32_t server_identifier) {
424 server_identifier_ = server_identifier;
425 }
426
SetTransactionID(uint32_t transaction_id)427 void DHCPMessage::SetTransactionID(uint32_t transaction_id) {
428 transaction_id_ = transaction_id;
429 }
430
SetVendorSpecificInfo(const shill::ByteString & vendor_specific_info)431 void DHCPMessage::SetVendorSpecificInfo(
432 const shill::ByteString& vendor_specific_info) {
433 vendor_specific_info_ = vendor_specific_info;
434 }
435
InitRequest(DHCPMessage * message)436 void DHCPMessage::InitRequest(DHCPMessage* message) {
437 message->opcode_ = kDHCPMessageBootRequest;
438 message->hardware_address_type_ = ARPHRD_ETHER;
439 message->hardware_address_length_ = IFHWADDRLEN;
440 message->relay_hops_ = 0;
441 // Seconds since DHCP process started.
442 // 0 is also valid according to RFC 2131.
443 message->seconds_ = 0;
444 // Only firewire (IEEE 1394) and InfiniBand interfaces
445 // require broadcast flag.
446 message->flags_ = 0;
447 // Should be zero in client's messages.
448 message->your_ip_address_ = 0;
449 // Should be zero in client's messages.
450 message->next_server_ip_address_ = 0;
451 // Should be zero in client's messages.
452 message->agent_ip_address_ = 0;
453 message->cookie_ = kMagicCookie;
454 }
455
456 } // namespace dhcp_client
457