• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2017, 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 "dhcpclient.h"
18 #include "dhcp.h"
19 #include "interface.h"
20 #include "log.h"
21 
22 #include <arpa/inet.h>
23 #include <errno.h>
24 #include <linux/if_ether.h>
25 #include <poll.h>
26 #include <unistd.h>
27 
28 #include <cutils/properties.h>
29 
30 #include <inttypes.h>
31 
32 // The initial retry timeout for DHCP is 4000 milliseconds
33 static const uint32_t kInitialTimeout = 4000;
34 // The maximum retry timeout for DHCP is 64000 milliseconds
35 static const uint32_t kMaxTimeout = 64000;
36 // A specific value that indicates that no timeout should happen and that
37 // the state machine should immediately transition to the next state
38 static const uint32_t kNoTimeout = 0;
39 
40 // Enable debug messages
41 static const bool kDebug = false;
42 
43 // The number of milliseconds that the timeout should vary (up or down) from the
44 // base timeout. DHCP requires a -1 to +1 second variation in timeouts.
45 static const int kTimeoutSpan = 1000;
46 
addrToStr(in_addr_t address)47 static std::string addrToStr(in_addr_t address) {
48     struct in_addr addr = { address };
49     char buffer[64];
50     return inet_ntop(AF_INET, &addr, buffer, sizeof(buffer));
51 }
52 
DhcpClient()53 DhcpClient::DhcpClient()
54     : mRandomEngine(std::random_device()()),
55       mRandomDistribution(-kTimeoutSpan, kTimeoutSpan),
56       mState(State::Init),
57       mNextTimeout(kInitialTimeout),
58       mFuzzNextTimeout(true) {
59 }
60 
init(const char * interfaceName)61 Result DhcpClient::init(const char* interfaceName) {
62     Result res = mInterface.init(interfaceName);
63     if (!res) {
64         return res;
65     }
66 
67     res = mRouter.init();
68     if (!res) {
69         return res;
70     }
71 
72     res = mSocket.open(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
73     if (!res) {
74         return res;
75     }
76 
77     res = mSocket.bindRaw(mInterface.getIndex());
78     if (!res) {
79         return res;
80     }
81     return Result::success();
82 }
83 
run()84 Result DhcpClient::run() {
85     // Block all signals while we're running. This way we don't have to deal
86     // with things like EINTR. waitAndReceive then uses ppoll to set the
87     // original mask while polling. This way polling can be interrupted but
88     // socket writing, reading and ioctl remain interrupt free. If a signal
89     // arrives while we're blocking it it will be placed in the signal queue
90     // and handled once ppoll sets the original mask. This way no signals are
91     // lost.
92     sigset_t blockMask, originalMask;
93     int status = ::sigfillset(&blockMask);
94     if (status != 0) {
95         return Result::error("Unable to fill signal set: %s", strerror(errno));
96     }
97     status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask);
98     if (status != 0) {
99         return Result::error("Unable to set signal mask: %s", strerror(errno));
100     }
101 
102     for (;;) {
103         // Before waiting, polling or receiving we check the current state and
104         // see what we should do next. This may result in polling but could
105         // also lead to instant state changes without any polling. The new state
106         // will then be evaluated instead, most likely leading to polling.
107         switch (mState) {
108             case State::Init:
109                 // The starting state. This is the state the client is in when
110                 // it first starts. It's also the state that the client returns
111                 // to when things go wrong in other states.
112                 setNextState(State::Selecting);
113                 break;
114             case State::Selecting:
115                 // In the selecting state the client attempts to find DHCP
116                 // servers on the network. The client remains in this state
117                 // until a suitable server responds.
118                 sendDhcpDiscover();
119                 increaseTimeout();
120                 break;
121             case State::Requesting:
122                 // In the requesting state the client has found a suitable
123                 // server. The next step is to send a request directly to that
124                 // server.
125                 if (mNextTimeout >= kMaxTimeout) {
126                     // We've tried to request a bunch of times, start over
127                     setNextState(State::Init);
128                 } else {
129                     sendDhcpRequest(mServerAddress);
130                     increaseTimeout();
131                 }
132                 break;
133             case State::Bound:
134                 // The client enters the bound state when the server has
135                 // accepted and acknowledged a request and given us a lease. At
136                 // this point the client will wait until the lease is close to
137                 // expiring and then it will try to renew the lease.
138                 if (mT1.expired()) {
139                     // Lease expired, renew lease
140                     setNextState(State::Renewing);
141                 } else {
142                     // Spurious wake-up, continue waiting. Do not fuzz the
143                     // timeout with a random offset. Doing so can cause wakeups
144                     // before the timer has expired causing unnecessary
145                     // processing. Even worse it can cause the timer to expire
146                     // after the lease has ended.
147                     mNextTimeout = mT1.remainingMillis();
148                     mFuzzNextTimeout = false;
149                 }
150                 break;
151             case State::Renewing:
152                 // In the renewing state the client is sending a request for the
153                 // same address it had was previously bound to. If the second
154                 // timer expires when in this state the client will attempt to
155                 // do a full rebind.
156                 if (mT2.expired()) {
157                     // Timeout while renewing, move to rebinding
158                     setNextState(State::Rebinding);
159                 } else {
160                     sendDhcpRequest(mServerAddress);
161                     increaseTimeout();
162                 }
163                 break;
164             case State::Rebinding:
165                 // The client was unable to renew the lease and moved to the
166                 // rebinding state. In this state the client sends a request for
167                 // the same address it had before to the broadcast address. This
168                 // means that any DHCP server on the network is free to respond.
169                 // After attempting this a few times the client will give up and
170                 // move to the Init state to try to find a new DHCP server.
171                 if (mNextTimeout >= kMaxTimeout) {
172                     // We've tried to rebind a bunch of times, start over
173                     setNextState(State::Init);
174                 } else {
175                     // Broadcast a request
176                     sendDhcpRequest(INADDR_BROADCAST);
177                     increaseTimeout();
178                 }
179                 break;
180             default:
181                 break;
182         }
183         // The proper action for the current state has been taken, perform any
184         // polling and/or waiting needed.
185         waitAndReceive(originalMask);
186     }
187 
188     return Result::error("Client terminated unexpectedly");
189 }
190 
stateToStr(State state)191 const char* DhcpClient::stateToStr(State state) {
192     switch (state) {
193         case State::Init:
194             return "Init";
195         case State::Selecting:
196             return "Selecting";
197         case State::Requesting:
198             return "Requesting";
199         case State::Bound:
200             return "Bound";
201         case State::Renewing:
202             return "Renewing";
203         case State::Rebinding:
204             return "Rebinding";
205     }
206     return "<unknown>";
207 }
208 
waitAndReceive(const sigset_t & pollSignalMask)209 void DhcpClient::waitAndReceive(const sigset_t& pollSignalMask) {
210     if (mNextTimeout == kNoTimeout) {
211         // If there is no timeout the state machine has indicated that it wants
212         // an immediate transition to another state. Do nothing.
213         return;
214     }
215 
216     struct pollfd fds;
217     fds.fd = mSocket.get();
218     fds.events = POLLIN;
219 
220     uint32_t timeout = calculateTimeoutMillis();
221     for (;;) {
222         uint64_t startedAt = now();
223 
224         struct timespec ts;
225         ts.tv_sec = timeout / 1000;
226         ts.tv_nsec = (timeout - ts.tv_sec * 1000) * 1000000;
227 
228         // Poll for any incoming traffic with the calculated timeout. While
229         // polling the original signal mask is set so that the polling can be
230         // interrupted.
231         int res = ::ppoll(&fds, 1, &ts, &pollSignalMask);
232         if (res == 0) {
233             // Timeout, return to let the caller evaluate
234             return;
235         } else if (res > 0) {
236             // Something to read
237             Message msg;
238             if (receiveDhcpMessage(&msg)) {
239                 // We received a DHCP message, check if it's of interest
240                 uint8_t msgType = msg.type();
241                 switch (mState) {
242                     case State::Selecting:
243                         if (msgType == DHCPOFFER) {
244                             // Received an offer, move to the Requesting state
245                             // to request it.
246                             mServerAddress = msg.serverId();
247                             mRequestAddress = msg.dhcpData.yiaddr;
248                             setNextState(State::Requesting);
249                             return;
250                         }
251                         break;
252                     case State::Requesting:
253                     case State::Renewing:
254                     case State::Rebinding:
255                         // All of these states have sent a DHCP request and are
256                         // now waiting for an ACK so the behavior is the same.
257                         if (msgType == DHCPACK) {
258                             // Request approved
259                             if (configureDhcp(msg)) {
260                                 // Successfully configured DHCP, move to Bound
261                                 setNextState(State::Bound);
262                                 return;
263                             }
264                             // Unable to configure DHCP, keep sending requests.
265                             // This may not fix the issue but eventually it will
266                             // allow for a full timeout which will lead to a
267                             // move to the Init state. This might still not fix
268                             // the issue but at least the client keeps trying.
269                         } else if (msgType == DHCPNAK) {
270                             // Request denied, halt network and start over
271                             haltNetwork();
272                             setNextState(State::Init);
273                             return;
274                         }
275                         break;
276                     default:
277                         // For the other states the client is not expecting any
278                         // network messages so we ignore those messages.
279                         break;
280                 }
281             }
282         } else {
283             // An error occurred in polling, don't do anything here. The client
284             // should keep going anyway to try to acquire a lease in the future
285             // if things start working again.
286         }
287         // If we reach this point we received something that's not a DHCP,
288         // message, we timed out, or an error occurred. Go again with whatever
289         // time remains.
290         uint64_t currentTime = now();
291         uint64_t end = startedAt + timeout;
292         if (currentTime >= end) {
293             // We're done anyway, return and let caller evaluate
294             return;
295         }
296         // Wait whatever the remaining time is
297         timeout = end - currentTime;
298     }
299 }
300 
configureDhcp(const Message & msg)301 bool DhcpClient::configureDhcp(const Message& msg) {
302     uint8_t optLength = 0;
303 
304     size_t optsSize = msg.optionsSize();
305     if (optsSize < 4) {
306         // Message is too small
307         if (kDebug) ALOGD("Opts size too small %d", static_cast<int>(optsSize));
308         return false;
309     }
310 
311     const uint8_t* options = msg.dhcpData.options;
312 
313     memset(&mDhcpInfo, 0, sizeof(mDhcpInfo));
314 
315     // Inspect all options in the message to try to find the ones we want
316     for (size_t i = 4; i + 1 < optsSize; ) {
317         uint8_t optCode = options[i];
318         uint8_t optLength = options[i + 1];
319         if (optCode == OPT_END) {
320             break;
321         }
322 
323         if (options + optLength + i >= msg.end()) {
324             // Invalid option length, drop it
325             if (kDebug) ALOGD("Invalid opt length %d for opt %d",
326                               static_cast<int>(optLength),
327                               static_cast<int>(optCode));
328             return false;
329         }
330         const uint8_t* opt = options + i + 2;
331         switch (optCode) {
332             case OPT_LEASE_TIME:
333                 if (optLength == 4) {
334                     mDhcpInfo.leaseTime =
335                         ntohl(*reinterpret_cast<const uint32_t*>(opt));
336                 }
337                 break;
338             case OPT_T1:
339                 if (optLength == 4) {
340                     mDhcpInfo.t1 =
341                         ntohl(*reinterpret_cast<const uint32_t*>(opt));
342                 }
343                 break;
344             case OPT_T2:
345                 if (optLength == 4) {
346                     mDhcpInfo.t2 =
347                         ntohl(*reinterpret_cast<const uint32_t*>(opt));
348                 }
349                 break;
350             case OPT_SUBNET_MASK:
351                 if (optLength == 4) {
352                     mDhcpInfo.subnetMask =
353                         *reinterpret_cast<const in_addr_t*>(opt);
354                 }
355                 break;
356             case OPT_GATEWAY:
357                 if (optLength >= 4) {
358                     mDhcpInfo.gateway =
359                         *reinterpret_cast<const in_addr_t*>(opt);
360                 }
361                 break;
362             case OPT_MTU:
363                 if (optLength == 2) {
364                     mDhcpInfo.mtu =
365                         ntohs(*reinterpret_cast<const uint16_t*>(opt));
366                 }
367                 break;
368             case OPT_DNS:
369                 if (optLength >= 4) {
370                     mDhcpInfo.dns[0] =
371                         *reinterpret_cast<const in_addr_t*>(opt);
372                 }
373                 if (optLength >= 8) {
374                     mDhcpInfo.dns[1] =
375                         *reinterpret_cast<const in_addr_t*>(opt + 4);
376                 }
377                 if (optLength >= 12) {
378                     mDhcpInfo.dns[2] =
379                         *reinterpret_cast<const in_addr_t*>(opt + 8);
380                 }
381                 if (optLength >= 16) {
382                     mDhcpInfo.dns[3] =
383                         *reinterpret_cast<const in_addr_t*>(opt + 12);
384                 }
385             case OPT_SERVER_ID:
386                 if (optLength == 4) {
387                     mDhcpInfo.serverId =
388                         *reinterpret_cast<const in_addr_t*>(opt);
389                 }
390             default:
391                 break;
392         }
393         i += 2 + optLength;
394     }
395     mDhcpInfo.offeredAddress = msg.dhcpData.yiaddr;
396 
397     if (mDhcpInfo.leaseTime == 0) {
398         // We didn't get a lease time, ignore this offer
399         return false;
400     }
401     // If there is no T1 or T2 timer given then we create an estimate as
402     // suggested for servers in RFC 2131.
403     uint32_t t1 = mDhcpInfo.t1, t2 = mDhcpInfo.t2;
404     mT1.expireSeconds(t1 > 0 ? t1 : (mDhcpInfo.leaseTime / 2));
405     mT2.expireSeconds(t2 > 0 ? t2 : ((mDhcpInfo.leaseTime * 7) / 8));
406 
407     Result res = mInterface.bringUp();
408     if (!res) {
409         ALOGE("Could not configure DHCP: %s", res.c_str());
410         return false;
411     }
412 
413     if (mDhcpInfo.mtu != 0) {
414         res = mInterface.setMtu(mDhcpInfo.mtu);
415         if (!res) {
416             // Consider this non-fatal, the system will not perform at its best
417             // but should still work.
418             ALOGE("Could not configure DHCP: %s", res.c_str());
419         }
420     }
421 
422     res = mInterface.setAddress(mDhcpInfo.offeredAddress);
423     if (!res) {
424         ALOGE("Could not configure DHCP: %s", res.c_str());
425         return false;
426     }
427 
428     res = mInterface.setSubnetMask(mDhcpInfo.subnetMask);
429     if (!res) {
430         ALOGE("Could not configure DHCP: %s", res.c_str());
431         return false;
432     }
433 
434     res = mRouter.setDefaultGateway(mDhcpInfo.gateway, mInterface.getIndex());
435     if (!res) {
436         ALOGE("Could not configure DHCP: %s", res.c_str());
437         return false;
438     }
439     char propName[64];
440     snprintf(propName, sizeof(propName), "net.%s.gw",
441              mInterface.getName().c_str());
442     property_set(propName, addrToStr(mDhcpInfo.gateway).c_str());
443 
444     int numDnsEntries = sizeof(mDhcpInfo.dns) / sizeof(mDhcpInfo.dns[0]);
445     for (int i = 0; i < numDnsEntries; ++i) {
446         snprintf(propName, sizeof(propName), "net.%s.dns%d",
447                  mInterface.getName().c_str(), i + 1);
448         if (mDhcpInfo.dns[i] != 0) {
449             property_set(propName, addrToStr(mDhcpInfo.dns[i]).c_str());
450         } else {
451             // Clear out any previous value here in case it was set
452             property_set(propName, "");
453         }
454     }
455 
456     return true;
457 }
458 
haltNetwork()459 void DhcpClient::haltNetwork() {
460     Result res = mInterface.setAddress(0);
461     if (!res) {
462         ALOGE("Could not halt network: %s", res.c_str());
463     }
464     res = mInterface.bringDown();
465     if (!res) {
466         ALOGE("Could not halt network: %s", res.c_str());
467     }
468 }
469 
receiveDhcpMessage(Message * msg)470 bool DhcpClient::receiveDhcpMessage(Message* msg) {
471     bool isValid = false;
472     Result res = mSocket.receiveRawUdp(PORT_BOOTP_CLIENT, msg, &isValid);
473     if (!res) {
474         if (kDebug) ALOGD("Discarding message: %s", res.c_str());
475         return false;
476     }
477 
478     return isValid &&
479            msg->isValidDhcpMessage(OP_BOOTREPLY, mLastMsg.dhcpData.xid);
480 }
481 
calculateTimeoutMillis()482 uint32_t DhcpClient::calculateTimeoutMillis() {
483     if (!mFuzzNextTimeout) {
484         return mNextTimeout;
485     }
486     int adjustment = mRandomDistribution(mRandomEngine);
487     if (adjustment < 0 && static_cast<uint32_t>(-adjustment) > mNextTimeout) {
488         // Underflow, return a timeout of zero milliseconds
489         return 0;
490     }
491     return mNextTimeout + adjustment;
492 }
493 
increaseTimeout()494 void DhcpClient::increaseTimeout() {
495     if (mNextTimeout == kNoTimeout) {
496         mNextTimeout = kInitialTimeout;
497     } else {
498         if (mNextTimeout < kMaxTimeout) {
499             mNextTimeout *= 2;
500         }
501         if (mNextTimeout > kMaxTimeout) {
502             mNextTimeout = kMaxTimeout;
503         }
504     }
505 }
506 
setNextState(State state)507 void DhcpClient::setNextState(State state) {
508     if (kDebug) ALOGD("Moving from state %s to %s",
509                       stateToStr(mState), stateToStr(state));
510     mState = state;
511     mNextTimeout = kNoTimeout;
512     mFuzzNextTimeout = true;
513 }
514 
sendDhcpRequest(in_addr_t destination)515 void DhcpClient::sendDhcpRequest(in_addr_t destination) {
516     if (kDebug) ALOGD("Sending DHCPREQUEST");
517     mLastMsg = Message::request(mInterface.getMacAddress(),
518                                 mRequestAddress,
519                                 destination);
520     sendMessage(mLastMsg);
521 }
522 
sendDhcpDiscover()523 void DhcpClient::sendDhcpDiscover() {
524     if (kDebug) ALOGD("Sending DHCPDISCOVER");
525     mLastMsg = Message::discover(mInterface.getMacAddress());
526     sendMessage(mLastMsg);
527 }
528 
sendMessage(const Message & message)529 void DhcpClient::sendMessage(const Message& message) {
530     Result res = mSocket.sendRawUdp(INADDR_ANY,
531                                     PORT_BOOTP_CLIENT,
532                                     INADDR_BROADCAST,
533                                     PORT_BOOTP_SERVER,
534                                     mInterface.getIndex(),
535                                     message);
536     if (!res) {
537         ALOGE("Unable to send message: %s", res.c_str());
538     }
539 }
540 
541