Create a DHCP server and client for the emulator

In order to have the system image be flexible about what networks it can
run in it needs to have a DHCP client running on eth0. The emulator
already has a DHCP server and will hand out the same address, gateway
and DNS servers we had already hardcoded so the configuration will
remain the same for user networking. When bridging networks the
hardcoded settings won't work. In this situation the DHCP client will
pick up the configuration from the bridged network and set everything up
so that the system image can work. The repo currently contains a version
of dhcpcd which is also a DHCP client but it doesn't work when running
inside the router namespace because it attempts to read information out
of /sys that is not there when run by execns. This is because execns
does not remount /sys as this is not allowed by the SELinux
configuration on Android (the configuration won't even allow giving the
permission to execns). Additionally dhcpcd would not correctly configure
the system properties for DNS that RIL uses to give access to the
cellular network.

The DHCP server is intended to run on the wireless interface in the
router namespace. Previously the WiFi solution used dnsmasq for this
purpose but it was too inflexible to work on bridged networks. The DHCP
server needs to forward the DNS servers from the outside network to the
WiFi client and dnsmasq could not do this on a bridged network where it
was impossible to know the DNS server address(es) beforehand. The DHCP
server implementation here simply picks up the DNS servers from the
system properties set by the DHCP client. If we are to support multiple
virtual access points we need to have even more control of the DHCP
server in the router namespace since it will have to be aware of the
multiple network interfaces created by hostapd and make sure to hand out
leases that matches the configuration of each of those interfaces.

BUG: 74514143
Test: Build emulator image and manually verify that WiFi is working
Change-Id: I1bff8968ff2af93f01a9509b9557aaa13b0a0874
(cherry picked from commit fdc59b88391cedc4829c590b87c2df466fa7fd33)
(cherry picked from commit 623df714384bd8b86c030f82cb7f301dbd025d1c)
(cherry picked from commit 724322528f159e1a475127d01443ffa5995af881)
diff --git a/dhcp/Android.mk b/dhcp/Android.mk
new file mode 100644
index 0000000..fb00329
--- /dev/null
+++ b/dhcp/Android.mk
@@ -0,0 +1,20 @@
+#
+# Copyright 2017 The Android Open-Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/dhcp/client/Android.mk b/dhcp/client/Android.mk
new file mode 100644
index 0000000..11a874c
--- /dev/null
+++ b/dhcp/client/Android.mk
@@ -0,0 +1,24 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	dhcpclient.cpp \
+	interface.cpp \
+	main.cpp \
+	router.cpp \
+	timer.cpp \
+	../common/message.cpp \
+	../common/socket.cpp \
+
+
+LOCAL_CPPFLAGS += -Werror
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/../common
+LOCAL_SHARED_LIBRARIES := libcutils liblog
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := dhcpclient
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/dhcp/client/dhcpclient.cpp b/dhcp/client/dhcpclient.cpp
new file mode 100644
index 0000000..20dd143
--- /dev/null
+++ b/dhcp/client/dhcpclient.cpp
@@ -0,0 +1,540 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dhcpclient.h"
+#include "dhcp.h"
+#include "interface.h"
+#include "log.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/if_ether.h>
+#include <poll.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+
+#include <inttypes.h>
+
+// The initial retry timeout for DHCP is 4000 milliseconds
+static const uint32_t kInitialTimeout = 4000;
+// The maximum retry timeout for DHCP is 64000 milliseconds
+static const uint32_t kMaxTimeout = 64000;
+// A specific value that indicates that no timeout should happen and that
+// the state machine should immediately transition to the next state
+static const uint32_t kNoTimeout = 0;
+
+// Enable debug messages
+static const bool kDebug = false;
+
+// The number of milliseconds that the timeout should vary (up or down) from the
+// base timeout. DHCP requires a -1 to +1 second variation in timeouts.
+static const int kTimeoutSpan = 1000;
+
+static std::string addrToStr(in_addr_t address) {
+    struct in_addr addr = { address };
+    char buffer[64];
+    return inet_ntop(AF_INET, &addr, buffer, sizeof(buffer));
+}
+
+DhcpClient::DhcpClient()
+    : mRandomEngine(std::random_device()()),
+      mRandomDistribution(-kTimeoutSpan, kTimeoutSpan),
+      mState(State::Init),
+      mNextTimeout(kInitialTimeout),
+      mFuzzNextTimeout(true) {
+}
+
+Result DhcpClient::init(const char* interfaceName) {
+    Result res = mInterface.init(interfaceName);
+    if (!res) {
+        return res;
+    }
+
+    res = mRouter.init();
+    if (!res) {
+        return res;
+    }
+
+    res = mSocket.open(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+    if (!res) {
+        return res;
+    }
+
+    res = mSocket.bindRaw(mInterface.getIndex());
+    if (!res) {
+        return res;
+    }
+    return Result::success();
+}
+
+Result DhcpClient::run() {
+    // Block all signals while we're running. This way we don't have to deal
+    // with things like EINTR. waitAndReceive then uses ppoll to set the
+    // original mask while polling. This way polling can be interrupted but
+    // socket writing, reading and ioctl remain interrupt free. If a signal
+    // arrives while we're blocking it it will be placed in the signal queue
+    // and handled once ppoll sets the original mask. This way no signals are
+    // lost.
+    sigset_t blockMask, originalMask;
+    int status = ::sigfillset(&blockMask);
+    if (status != 0) {
+        return Result::error("Unable to fill signal set: %s", strerror(errno));
+    }
+    status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask);
+    if (status != 0) {
+        return Result::error("Unable to set signal mask: %s", strerror(errno));
+    }
+
+    for (;;) {
+        // Before waiting, polling or receiving we check the current state and
+        // see what we should do next. This may result in polling but could
+        // also lead to instant state changes without any polling. The new state
+        // will then be evaluated instead, most likely leading to polling.
+        switch (mState) {
+            case State::Init:
+                // The starting state. This is the state the client is in when
+                // it first starts. It's also the state that the client returns
+                // to when things go wrong in other states.
+                setNextState(State::Selecting);
+                break;
+            case State::Selecting:
+                // In the selecting state the client attempts to find DHCP
+                // servers on the network. The client remains in this state
+                // until a suitable server responds.
+                sendDhcpDiscover();
+                increaseTimeout();
+                break;
+            case State::Requesting:
+                // In the requesting state the client has found a suitable
+                // server. The next step is to send a request directly to that
+                // server.
+                if (mNextTimeout >= kMaxTimeout) {
+                    // We've tried to request a bunch of times, start over
+                    setNextState(State::Init);
+                } else {
+                    sendDhcpRequest(mServerAddress);
+                    increaseTimeout();
+                }
+                break;
+            case State::Bound:
+                // The client enters the bound state when the server has
+                // accepted and acknowledged a request and given us a lease. At
+                // this point the client will wait until the lease is close to
+                // expiring and then it will try to renew the lease.
+                if (mT1.expired()) {
+                    // Lease expired, renew lease
+                    setNextState(State::Renewing);
+                } else {
+                    // Spurious wake-up, continue waiting. Do not fuzz the
+                    // timeout with a random offset. Doing so can cause wakeups
+                    // before the timer has expired causing unnecessary
+                    // processing. Even worse it can cause the timer to expire
+                    // after the lease has ended.
+                    mNextTimeout = mT1.remainingMillis();
+                    mFuzzNextTimeout = false;
+                }
+                break;
+            case State::Renewing:
+                // In the renewing state the client is sending a request for the
+                // same address it had was previously bound to. If the second
+                // timer expires when in this state the client will attempt to
+                // do a full rebind.
+                if (mT2.expired()) {
+                    // Timeout while renewing, move to rebinding
+                    setNextState(State::Rebinding);
+                } else {
+                    sendDhcpRequest(mServerAddress);
+                    increaseTimeout();
+                }
+                break;
+            case State::Rebinding:
+                // The client was unable to renew the lease and moved to the
+                // rebinding state. In this state the client sends a request for
+                // the same address it had before to the broadcast address. This
+                // means that any DHCP server on the network is free to respond.
+                // After attempting this a few times the client will give up and
+                // move to the Init state to try to find a new DHCP server.
+                if (mNextTimeout >= kMaxTimeout) {
+                    // We've tried to rebind a bunch of times, start over
+                    setNextState(State::Init);
+                } else {
+                    // Broadcast a request
+                    sendDhcpRequest(INADDR_BROADCAST);
+                    increaseTimeout();
+                }
+                break;
+            default:
+                break;
+        }
+        // The proper action for the current state has been taken, perform any
+        // polling and/or waiting needed.
+        waitAndReceive(originalMask);
+    }
+
+    return Result::error("Client terminated unexpectedly");
+}
+
+const char* DhcpClient::stateToStr(State state) {
+    switch (state) {
+        case State::Init:
+            return "Init";
+        case State::Selecting:
+            return "Selecting";
+        case State::Requesting:
+            return "Requesting";
+        case State::Bound:
+            return "Bound";
+        case State::Renewing:
+            return "Renewing";
+        case State::Rebinding:
+            return "Rebinding";
+    }
+    return "<unknown>";
+}
+
+void DhcpClient::waitAndReceive(const sigset_t& pollSignalMask) {
+    if (mNextTimeout == kNoTimeout) {
+        // If there is no timeout the state machine has indicated that it wants
+        // an immediate transition to another state. Do nothing.
+        return;
+    }
+
+    struct pollfd fds;
+    fds.fd = mSocket.get();
+    fds.events = POLLIN;
+
+    uint32_t timeout = calculateTimeoutMillis();
+    for (;;) {
+        uint64_t startedAt = now();
+
+        struct timespec ts;
+        ts.tv_sec = timeout / 1000;
+        ts.tv_nsec = (timeout - ts.tv_sec * 1000) * 1000000;
+
+        // Poll for any incoming traffic with the calculated timeout. While
+        // polling the original signal mask is set so that the polling can be
+        // interrupted.
+        int res = ::ppoll(&fds, 1, &ts, &pollSignalMask);
+        if (res == 0) {
+            // Timeout, return to let the caller evaluate
+            return;
+        } else if (res > 0) {
+            // Something to read
+            Message msg;
+            if (receiveDhcpMessage(&msg)) {
+                // We received a DHCP message, check if it's of interest
+                uint8_t msgType = msg.type();
+                switch (mState) {
+                    case State::Selecting:
+                        if (msgType == DHCPOFFER) {
+                            // Received an offer, move to the Requesting state
+                            // to request it.
+                            mServerAddress = msg.serverId();
+                            mRequestAddress = msg.dhcpData.yiaddr;
+                            setNextState(State::Requesting);
+                            return;
+                        }
+                        break;
+                    case State::Requesting:
+                    case State::Renewing:
+                    case State::Rebinding:
+                        // All of these states have sent a DHCP request and are
+                        // now waiting for an ACK so the behavior is the same.
+                        if (msgType == DHCPACK) {
+                            // Request approved
+                            if (configureDhcp(msg)) {
+                                // Successfully configured DHCP, move to Bound
+                                setNextState(State::Bound);
+                                return;
+                            }
+                            // Unable to configure DHCP, keep sending requests.
+                            // This may not fix the issue but eventually it will
+                            // allow for a full timeout which will lead to a
+                            // move to the Init state. This might still not fix
+                            // the issue but at least the client keeps trying.
+                        } else if (msgType == DHCPNAK) {
+                            // Request denied, halt network and start over
+                            haltNetwork();
+                            setNextState(State::Init);
+                            return;
+                        } 
+                        break;
+                    default:
+                        // For the other states the client is not expecting any
+                        // network messages so we ignore those messages.
+                        break;
+                }
+            }
+        } else {
+            // An error occurred in polling, don't do anything here. The client
+            // should keep going anyway to try to acquire a lease in the future
+            // if things start working again.
+        }
+        // If we reach this point we received something that's not a DHCP,
+        // message, we timed out, or an error occurred. Go again with whatever
+        // time remains.
+        uint64_t currentTime = now();
+        uint64_t end = startedAt + timeout;
+        if (currentTime >= end) {
+            // We're done anyway, return and let caller evaluate
+            return;
+        }
+        // Wait whatever the remaining time is
+        timeout = end - currentTime;
+    }
+}
+
+bool DhcpClient::configureDhcp(const Message& msg) {
+    uint8_t optLength = 0;
+
+    size_t optsSize = msg.optionsSize();
+    if (optsSize < 4) {
+        // Message is too small
+        if (kDebug) ALOGD("Opts size too small %d", static_cast<int>(optsSize));
+        return false;
+    }
+
+    const uint8_t* options = msg.dhcpData.options;
+
+    memset(&mDhcpInfo, 0, sizeof(mDhcpInfo));
+
+    // Inspect all options in the message to try to find the ones we want
+    for (size_t i = 4; i + 1 < optsSize; ) {
+        uint8_t optCode = options[i];
+        uint8_t optLength = options[i + 1];
+        if (optCode == OPT_END) {
+            break;
+        }
+
+        if (options + optLength + i >= msg.end()) {
+            // Invalid option length, drop it
+            if (kDebug) ALOGD("Invalid opt length %d for opt %d",
+                              static_cast<int>(optLength),
+                              static_cast<int>(optCode));
+            return false;
+        }
+        const uint8_t* opt = options + i + 2;
+        switch (optCode) {
+            case OPT_LEASE_TIME:
+                if (optLength == 4) {
+                    mDhcpInfo.leaseTime =
+                        ntohl(*reinterpret_cast<const uint32_t*>(opt));
+                }
+                break;
+            case OPT_T1:
+                if (optLength == 4) {
+                    mDhcpInfo.t1 =
+                        ntohl(*reinterpret_cast<const uint32_t*>(opt));
+                }
+                break;
+            case OPT_T2:
+                if (optLength == 4) {
+                    mDhcpInfo.t2 =
+                        ntohl(*reinterpret_cast<const uint32_t*>(opt));
+                }
+                break;
+            case OPT_SUBNET_MASK:
+                if (optLength == 4) {
+                    mDhcpInfo.subnetMask =
+                        *reinterpret_cast<const in_addr_t*>(opt);
+                }
+                break;
+            case OPT_GATEWAY:
+                if (optLength >= 4) {
+                    mDhcpInfo.gateway =
+                        *reinterpret_cast<const in_addr_t*>(opt);
+                }
+                break;
+            case OPT_MTU:
+                if (optLength == 2) {
+                    mDhcpInfo.mtu =
+                        ntohs(*reinterpret_cast<const uint16_t*>(opt));
+                }
+                break;
+            case OPT_DNS:
+                if (optLength >= 4) {
+                    mDhcpInfo.dns[0] =
+                        *reinterpret_cast<const in_addr_t*>(opt);
+                }
+                if (optLength >= 8) {
+                    mDhcpInfo.dns[1] =
+                        *reinterpret_cast<const in_addr_t*>(opt + 4);
+                }
+                if (optLength >= 12) {
+                    mDhcpInfo.dns[2] =
+                        *reinterpret_cast<const in_addr_t*>(opt + 8);
+                }
+                if (optLength >= 16) {
+                    mDhcpInfo.dns[3] =
+                        *reinterpret_cast<const in_addr_t*>(opt + 12);
+                }
+            case OPT_SERVER_ID:
+                if (optLength == 4) {
+                    mDhcpInfo.serverId =
+                        *reinterpret_cast<const in_addr_t*>(opt);
+                }
+            default:
+                break;
+        }
+        i += 2 + optLength;
+    }
+    mDhcpInfo.offeredAddress = msg.dhcpData.yiaddr;
+
+    if (mDhcpInfo.leaseTime == 0) {
+        // We didn't get a lease time, ignore this offer
+        return false;
+    }
+    // If there is no T1 or T2 timer given then we create an estimate as
+    // suggested for servers in RFC 2131.
+    uint32_t t1 = mDhcpInfo.t1, t2 = mDhcpInfo.t2;
+    mT1.expireSeconds(t1 > 0 ? t1 : (mDhcpInfo.leaseTime / 2));
+    mT2.expireSeconds(t2 > 0 ? t2 : ((mDhcpInfo.leaseTime * 7) / 8));
+
+    Result res = mInterface.bringUp();
+    if (!res) {
+        ALOGE("Could not configure DHCP: %s", res.c_str());
+        return false;
+    }
+
+    if (mDhcpInfo.mtu != 0) {
+        res = mInterface.setMtu(mDhcpInfo.mtu);
+        if (!res) {
+            // Consider this non-fatal, the system will not perform at its best
+            // but should still work.
+            ALOGE("Could not configure DHCP: %s", res.c_str());
+        }
+    }
+
+    res = mInterface.setAddress(mDhcpInfo.offeredAddress);
+    if (!res) {
+        ALOGE("Could not configure DHCP: %s", res.c_str());
+        return false;
+    }
+
+    res = mInterface.setSubnetMask(mDhcpInfo.subnetMask);
+    if (!res) {
+        ALOGE("Could not configure DHCP: %s", res.c_str());
+        return false;
+    }
+
+    res = mRouter.setDefaultGateway(mDhcpInfo.gateway, mInterface.getIndex());
+    if (!res) {
+        ALOGE("Could not configure DHCP: %s", res.c_str());
+        return false;
+    }
+    char propName[64];
+    snprintf(propName, sizeof(propName), "net.%s.gw",
+             mInterface.getName().c_str());
+    property_set(propName, addrToStr(mDhcpInfo.gateway).c_str());
+
+    int numDnsEntries = sizeof(mDhcpInfo.dns) / sizeof(mDhcpInfo.dns[0]);
+    for (int i = 0; i < numDnsEntries; ++i) {
+        snprintf(propName, sizeof(propName), "net.%s.dns%d",
+                 mInterface.getName().c_str(), i + 1);
+        if (mDhcpInfo.dns[i] != 0) {
+            property_set(propName, addrToStr(mDhcpInfo.dns[i]).c_str());
+        } else {
+            // Clear out any previous value here in case it was set
+            property_set(propName, "");
+        }
+    }
+
+    return true;
+}
+
+void DhcpClient::haltNetwork() {
+    Result res = mInterface.setAddress(0);
+    if (!res) {
+        ALOGE("Could not halt network: %s", res.c_str());
+    }
+    res = mInterface.bringDown();
+    if (!res) {
+        ALOGE("Could not halt network: %s", res.c_str());
+    }
+}
+
+bool DhcpClient::receiveDhcpMessage(Message* msg) {
+    bool isValid = false;
+    Result res = mSocket.receiveRawUdp(PORT_BOOTP_CLIENT, msg, &isValid);
+    if (!res) {
+        if (kDebug) ALOGD("Discarding message: %s", res.c_str());
+        return false;
+    }
+
+    return isValid &&
+           msg->isValidDhcpMessage(OP_BOOTREPLY, mLastMsg.dhcpData.xid);
+}
+
+uint32_t DhcpClient::calculateTimeoutMillis() {
+    if (!mFuzzNextTimeout) {
+        return mNextTimeout;
+    }
+    int adjustment = mRandomDistribution(mRandomEngine);
+    if (adjustment < 0 && static_cast<uint32_t>(-adjustment) > mNextTimeout) {
+        // Underflow, return a timeout of zero milliseconds
+        return 0;
+    }
+    return mNextTimeout + adjustment;
+}
+
+void DhcpClient::increaseTimeout() {
+    if (mNextTimeout == kNoTimeout) {
+        mNextTimeout = kInitialTimeout;
+    } else {
+        if (mNextTimeout < kMaxTimeout) {
+            mNextTimeout *= 2;
+        }
+        if (mNextTimeout > kMaxTimeout) {
+            mNextTimeout = kMaxTimeout;
+        }
+    }
+}
+
+void DhcpClient::setNextState(State state) {
+    if (kDebug) ALOGD("Moving from state %s to %s",
+                      stateToStr(mState), stateToStr(state));
+    mState = state;
+    mNextTimeout = kNoTimeout;
+    mFuzzNextTimeout = true;
+}
+
+void DhcpClient::sendDhcpRequest(in_addr_t destination) {
+    if (kDebug) ALOGD("Sending DHCPREQUEST");
+    mLastMsg = Message::request(mInterface.getMacAddress(),
+                                mRequestAddress,
+                                destination);
+    sendMessage(mLastMsg);
+}
+
+void DhcpClient::sendDhcpDiscover() {
+    if (kDebug) ALOGD("Sending DHCPDISCOVER");
+    mLastMsg = Message::discover(mInterface.getMacAddress());
+    sendMessage(mLastMsg);
+}
+
+void DhcpClient::sendMessage(const Message& message) {
+    Result res = mSocket.sendRawUdp(INADDR_ANY,
+                                    PORT_BOOTP_CLIENT,
+                                    INADDR_BROADCAST,
+                                    PORT_BOOTP_SERVER,
+                                    mInterface.getIndex(),
+                                    message);
+    if (!res) {
+        ALOGE("Unable to send message: %s", res.c_str());
+    }
+}
+
diff --git a/dhcp/client/dhcpclient.h b/dhcp/client/dhcpclient.h
new file mode 100644
index 0000000..718f4c9
--- /dev/null
+++ b/dhcp/client/dhcpclient.h
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "interface.h"
+#include "message.h"
+#include "result.h"
+#include "router.h"
+#include "socket.h"
+#include "timer.h"
+
+#include <netinet/in.h>
+#include <stdint.h>
+
+#include <random>
+
+
+class DhcpClient {
+public:
+    DhcpClient();
+
+    // Initialize the DHCP client to listen on |interfaceName|.
+    Result init(const char* interfaceName);
+    Result run();
+private:
+    enum class State {
+        Init,
+        Selecting,
+        Requesting,
+        Bound,
+        Renewing,
+        Rebinding
+    };
+    const char* stateToStr(State state);
+
+    // Wait for any pending timeouts
+    void waitAndReceive(const sigset_t& pollSignalMask);
+    // Create a varying timeout (+- 1 second) based on the next timeout.
+    uint32_t calculateTimeoutMillis();
+    // Increase the next timeout in a manner that's compliant with the DHCP RFC.
+    void increaseTimeout();
+    // Move to |state|, the next poll timeout will be zero and the new
+    // state will be immediately evaluated.
+    void setNextState(State state);
+    // Configure network interface based on the DHCP configuration in |msg|.
+    bool configureDhcp(const Message& msg);
+    // Halt network operations on the network interface for when configuration
+    // is not possible and the protocol demands it.
+    void haltNetwork();
+    // Receive a message on the socket and populate |msg| with the received
+    // data. If the message is a valid DHCP message the method returns true. If
+    // it's not valid false is returned.
+    bool receiveDhcpMessage(Message* msg);
+
+    void sendDhcpDiscover();
+    void sendDhcpRequest(in_addr_t destination);
+    void sendMessage(const Message& message);
+    Result send(in_addr_t source, in_addr_t destination,
+                uint16_t sourcePort, uint16_t destinationPort,
+                const uint8_t* data, size_t size);
+
+    std::mt19937 mRandomEngine; // Mersenne Twister RNG
+    std::uniform_int_distribution<int> mRandomDistribution;
+
+    struct DhcpInfo {
+        uint32_t t1;
+        uint32_t t2;
+        uint32_t leaseTime;
+        uint16_t mtu;
+        in_addr_t dns[4];
+        in_addr_t gateway;
+        in_addr_t subnetMask;
+        in_addr_t serverId;
+        in_addr_t offeredAddress;
+    } mDhcpInfo;
+
+    Router mRouter;
+    Interface mInterface;
+    Message mLastMsg;
+    Timer mT1, mT2;
+    Socket mSocket;
+    State mState;
+    uint32_t mNextTimeout;
+    bool mFuzzNextTimeout;
+
+    in_addr_t mRequestAddress; // Address we'd like to use in requests
+    in_addr_t mServerAddress;  // Server to send request to
+};
+
diff --git a/dhcp/client/interface.cpp b/dhcp/client/interface.cpp
new file mode 100644
index 0000000..805fa52
--- /dev/null
+++ b/dhcp/client/interface.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "interface.h"
+
+#include <errno.h>
+#include <linux/if.h>
+#include <linux/if_ether.h>
+#include <linux/route.h>
+#include <string.h>
+#include <unistd.h>
+
+Interface::Interface() : mSocketFd(-1) {
+}
+
+Interface::~Interface() {
+    if (mSocketFd != -1) {
+        close(mSocketFd);
+        mSocketFd = -1;
+    }
+}
+
+Result Interface::init(const char* interfaceName) {
+    mInterfaceName = interfaceName;
+
+    if (mSocketFd != -1) {
+        return Result::error("Interface initialized more than once");
+    }
+
+    mSocketFd = ::socket(AF_INET, SOCK_DGRAM | SOCK_CLOEXEC, IPPROTO_IP);
+    if (mSocketFd == -1) {
+        return Result::error("Failed to create interface socket for '%s': %s",
+                             interfaceName, strerror(errno));
+    }
+
+    Result res = populateIndex();
+    if (!res) {
+        return res;
+    }
+
+    res = populateMacAddress();
+    if (!res) {
+        return res;
+    }
+
+    res = bringUp();
+    if (!res) {
+        return res;
+    }
+
+    res = setAddress(0);
+    if (!res) {
+        return res;
+    }
+
+    return Result::success();
+}
+
+Result Interface::bringUp() {
+    return setInterfaceUp(true);
+}
+
+Result Interface::bringDown() {
+    return setInterfaceUp(false);
+}
+
+Result Interface::setMtu(uint16_t mtu) {
+    struct ifreq request = createRequest();
+
+    strncpy(request.ifr_name, mInterfaceName.c_str(), sizeof(request.ifr_name));
+    request.ifr_mtu = mtu;
+    int status = ::ioctl(mSocketFd, SIOCSIFMTU, &request);
+    if (status != 0) {
+        return Result::error("Failed to set interface MTU %u for '%s': %s",
+                             static_cast<unsigned int>(mtu),
+                             mInterfaceName.c_str(),
+                             strerror(errno));
+    }
+
+    return Result::success();
+}
+
+Result Interface::setAddress(in_addr_t address) {
+    struct ifreq request = createRequest();
+
+    auto requestAddr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr);
+    requestAddr->sin_family = AF_INET;
+    requestAddr->sin_port = 0;
+    requestAddr->sin_addr.s_addr = address;
+
+    int status = ::ioctl(mSocketFd, SIOCSIFADDR, &request);
+    if (status != 0) {
+        return Result::error("Failed to set interface address for '%s': %s",
+                             mInterfaceName.c_str(), strerror(errno));
+    }
+
+    return Result::success();
+}
+
+Result Interface::setSubnetMask(in_addr_t subnetMask) {
+    struct ifreq request = createRequest();
+
+    auto addr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr);
+    addr->sin_family = AF_INET;
+    addr->sin_port = 0;
+    addr->sin_addr.s_addr = subnetMask;
+
+    int status = ::ioctl(mSocketFd, SIOCSIFNETMASK, &request);
+    if (status != 0) {
+        return Result::error("Failed to set subnet mask for '%s': %s",
+                             mInterfaceName.c_str(), strerror(errno));
+    }
+
+    return Result::success();
+}
+
+struct ifreq Interface::createRequest() const {
+    struct ifreq request;
+    memset(&request, 0, sizeof(request));
+    strncpy(request.ifr_name, mInterfaceName.c_str(), sizeof(request.ifr_name));
+    request.ifr_name[sizeof(request.ifr_name) - 1] = '\0';
+
+    return request;
+}
+
+Result Interface::populateIndex() {
+    struct ifreq request = createRequest();
+
+    int status = ::ioctl(mSocketFd, SIOCGIFINDEX, &request);
+    if (status != 0) {
+        return Result::error("Failed to get interface index for '%s': %s",
+                             mInterfaceName.c_str(), strerror(errno));
+    }
+    mIndex = request.ifr_ifindex;
+    return Result::success();
+}
+
+Result Interface::populateMacAddress() {
+    struct ifreq request = createRequest();
+
+    int status = ::ioctl(mSocketFd, SIOCGIFHWADDR, &request);
+    if (status != 0) {
+        return Result::error("Failed to get MAC address for '%s': %s",
+                             mInterfaceName.c_str(), strerror(errno));
+    }
+    memcpy(mMacAddress, &request.ifr_hwaddr.sa_data, ETH_ALEN);
+    return Result::success();
+}
+
+Result Interface::setInterfaceUp(bool shouldBeUp) {
+    struct ifreq request = createRequest();
+
+    int status = ::ioctl(mSocketFd, SIOCGIFFLAGS, &request);
+    if (status != 0) {
+        return Result::error("Failed to get interface flags for '%s': %s",
+                             mInterfaceName.c_str(), strerror(errno));
+    }
+
+    bool isUp = (request.ifr_flags & IFF_UP) != 0;
+    if (isUp != shouldBeUp) {
+        // Toggle the up flag
+        request.ifr_flags ^= IFF_UP;
+    } else {
+        // Interface is already in desired state, do nothing
+        return Result::success();
+    }
+
+    status = ::ioctl(mSocketFd, SIOCSIFFLAGS, &request);
+    if (status != 0) {
+        return Result::error("Failed to set interface flags for '%s': %s",
+                             mInterfaceName.c_str(), strerror(errno));
+    }
+
+    return Result::success();
+}
+
diff --git a/dhcp/client/interface.h b/dhcp/client/interface.h
new file mode 100644
index 0000000..b7f549a
--- /dev/null
+++ b/dhcp/client/interface.h
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include "result.h"
+
+#include <linux/if_ether.h>
+#include <netinet/in.h>
+
+#include <string>
+
+// A class representing a network interface. The class provides useful
+// functionality to configure and query the network interface.
+class Interface {
+public:
+    Interface();
+    ~Interface();
+    Result init(const char* interfaceName);
+
+    // Returns the interface index indicated by the system
+    unsigned int getIndex() const { return mIndex; }
+    // Get the MAC address of the interface
+    const uint8_t (&getMacAddress() const)[ETH_ALEN] { return mMacAddress; }
+    // Get the name of the interface
+    const std::string& getName() const { return mInterfaceName; }
+
+    Result bringUp();
+    Result bringDown();
+    Result setMtu(uint16_t mtu);
+    Result setAddress(in_addr_t address);
+    Result setSubnetMask(in_addr_t subnetMask);
+
+private:
+    struct ifreq createRequest() const;
+    Result populateIndex();
+    Result populateMacAddress();
+    Result setInterfaceUp(bool shouldBeUp);
+
+    std::string mInterfaceName;
+    int mSocketFd;
+    unsigned int mIndex;
+    uint8_t mMacAddress[ETH_ALEN];
+};
+
diff --git a/dhcp/client/log.h b/dhcp/client/log.h
new file mode 100644
index 0000000..c34da01
--- /dev/null
+++ b/dhcp/client/log.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#define LOG_TAG "dhcpclient"
+#include <cutils/log.h>
+
diff --git a/dhcp/client/main.cpp b/dhcp/client/main.cpp
new file mode 100644
index 0000000..f4a3ea9
--- /dev/null
+++ b/dhcp/client/main.cpp
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dhcpclient.h"
+#include "log.h"
+
+static void usage(const char* program) {
+    ALOGE("Usage: %s -i <interface>", program);
+}
+
+int main(int argc, char* argv[]) {
+    if (argc < 3) {
+        usage(argv[0]);
+        return 1;
+    }
+    const char* interfaceName = nullptr;
+
+    for (int i = 1; i < argc; ++i) {
+        if (strcmp(argv[i], "-i") == 0) {
+            if (i + 1 < argc) {
+                interfaceName = argv[++i];
+            } else {
+                ALOGE("ERROR: -i parameter needs an argument");
+                usage(argv[0]);
+                return 1;
+            }
+        } else {
+            ALOGE("ERROR: unknown parameters %s", argv[i]);
+            usage(argv[0]);
+            return 1;
+        }
+    }
+    if (interfaceName == nullptr) {
+        ALOGE("ERROR: No interface specified");
+        usage(argv[0]);
+        return 1;
+    }
+
+    DhcpClient client;
+    Result res = client.init(interfaceName);
+    if (!res) {
+        ALOGE("Failed to initialize DHCP client: %s\n", res.c_str());
+        return 1;
+    }
+
+    res = client.run();
+    if (!res) {
+        ALOGE("DHCP client failed: %s\n", res.c_str());
+        return 1;
+    }
+    // This is weird and shouldn't happen, the client should run forever.
+    return 0;
+}
+
diff --git a/dhcp/client/router.cpp b/dhcp/client/router.cpp
new file mode 100644
index 0000000..9b9f17d
--- /dev/null
+++ b/dhcp/client/router.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "router.h"
+
+#include <linux/rtnetlink.h>
+
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+
+template<class Request>
+static void addRouterAttribute(Request& r,
+                               int type,
+                               const void* data,
+                               size_t size) {
+    // Calculate the offset into the character buffer where the RTA data lives
+    // We use offsetof on the buffer to get it. This avoids undefined behavior
+    // by casting the buffer (which is safe because it's char) instead of the
+    // Request struct.(which is undefined because of aliasing)
+    size_t offset = NLMSG_ALIGN(r.hdr.nlmsg_len) - offsetof(Request, buf);
+    auto attr = reinterpret_cast<struct rtattr*>(r.buf + offset);
+    attr->rta_type = type;
+    attr->rta_len = RTA_LENGTH(size);
+    memcpy(RTA_DATA(attr), data, size);
+
+    // Update the message length to include the router attribute.
+    r.hdr.nlmsg_len = NLMSG_ALIGN(r.hdr.nlmsg_len) + RTA_ALIGN(attr->rta_len);
+}
+
+Router::Router() : mSocketFd(-1) {
+}
+
+Router::~Router() {
+    if (mSocketFd != -1) {
+        ::close(mSocketFd);
+        mSocketFd = -1;
+    }
+}
+
+Result Router::init() {
+    // Create a netlink socket to the router
+    mSocketFd = ::socket(AF_NETLINK, SOCK_RAW, NETLINK_ROUTE);
+    if (mSocketFd == -1) {
+        return Result::error(strerror(errno));
+    }
+    return Result::success();
+}
+
+Result Router::setDefaultGateway(in_addr_t gateway, unsigned int ifaceIndex) {
+    struct Request {
+        struct nlmsghdr hdr;
+        struct rtmsg msg;
+        char buf[256];
+    } request;
+
+    memset(&request, 0, sizeof(request));
+
+    // Set up a request to create a new route
+    request.hdr.nlmsg_len = NLMSG_LENGTH(sizeof(request.msg));
+    request.hdr.nlmsg_type = RTM_NEWROUTE;
+    request.hdr.nlmsg_flags = NLM_F_REQUEST | NLM_F_CREATE;
+
+    request.msg.rtm_family = AF_INET;
+    request.msg.rtm_dst_len = 0;
+    request.msg.rtm_table = RT_TABLE_MAIN;
+    request.msg.rtm_protocol = RTPROT_BOOT;
+    request.msg.rtm_scope = RT_SCOPE_UNIVERSE;
+    request.msg.rtm_type = RTN_UNICAST;
+
+    addRouterAttribute(request, RTA_GATEWAY, &gateway, sizeof(gateway));
+    addRouterAttribute(request, RTA_OIF, &ifaceIndex, sizeof(ifaceIndex));
+
+    return sendNetlinkMessage(&request, request.hdr.nlmsg_len);
+}
+
+Result Router::sendNetlinkMessage(const void* data, size_t size) {
+    struct sockaddr_nl nlAddress;
+    memset(&nlAddress, 0, sizeof(nlAddress));
+    nlAddress.nl_family = AF_NETLINK;
+
+    int res = ::sendto(mSocketFd, data, size, 0,
+                       reinterpret_cast<sockaddr*>(&nlAddress),
+                       sizeof(nlAddress));
+    if (res == -1) {
+        return Result::error("Unable to send on netlink socket: %s",
+                             strerror(errno));
+    }
+    return Result::success();
+}
+
diff --git a/dhcp/client/router.h b/dhcp/client/router.h
new file mode 100644
index 0000000..1ab6654
--- /dev/null
+++ b/dhcp/client/router.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stdint.h>
+
+#include <netinet/in.h>
+
+#include "result.h"
+
+class Router {
+public:
+    Router();
+    ~Router();
+    // Initialize the router, this has to be called before any other methods can
+    // be called. It only needs to be called once.
+    Result init();
+
+    // Set the default route to |gateway| on the interface specified by
+    // |interfaceIndex|. If the default route is already set up with the same
+    // configuration then nothing is done. If another default route exists it
+    // will be removed and replaced by the new one. If no default route exists
+    // a route will be created with the given parameters.
+    Result setDefaultGateway(in_addr_t gateway, unsigned int interfaceIndex);
+private:
+    Result sendNetlinkMessage(const void* data, size_t size);
+
+    // Netlink socket for setting up neighbors and routes
+    int mSocketFd;
+};
+
diff --git a/dhcp/client/timer.cpp b/dhcp/client/timer.cpp
new file mode 100644
index 0000000..5f81322
--- /dev/null
+++ b/dhcp/client/timer.cpp
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "timer.h"
+
+#include <time.h>
+
+uint64_t now() {
+    struct timespec time = { 0, 0 };
+    clock_gettime(CLOCK_MONOTONIC, &time);
+    return static_cast<uint64_t>(time.tv_sec) * 1000u +
+           static_cast<uint64_t>(time.tv_nsec / 1000000u);
+}
+
+Timer::Timer() : mExpires(0) {
+}
+
+void Timer::expireSeconds(uint64_t seconds) {
+    mExpires = now() + seconds * 1000u;
+}
+
+bool Timer::expired() const {
+    return now() >= mExpires;
+}
+
+uint64_t Timer::remainingMillis() const {
+    uint64_t current = now();
+    if (current > mExpires) {
+        return 0;
+    }
+    return mExpires - current;
+}
+
diff --git a/dhcp/client/timer.h b/dhcp/client/timer.h
new file mode 100644
index 0000000..7ae01f9
--- /dev/null
+++ b/dhcp/client/timer.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+// Return the current timestamp from a monotonic clock in milliseconds.
+uint64_t now();
+
+class Timer {
+public:
+    // Create a timer, initially the timer is already expired.
+    Timer();
+
+    // Set the timer to expire in |seconds| seconds.
+    void expireSeconds(uint64_t seconds);
+
+    // Return true if the timer has expired.
+    bool expired() const;
+    // Get the remaining time on the timer in milliseconds.
+    uint64_t remainingMillis() const;
+
+private:
+    uint64_t mExpires;
+};
+
diff --git a/dhcp/common/dhcp.h b/dhcp/common/dhcp.h
new file mode 100644
index 0000000..beb388f
--- /dev/null
+++ b/dhcp/common/dhcp.h
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+// Ports
+#define PORT_BOOTP_SERVER 67
+#define PORT_BOOTP_CLIENT 68
+
+// Operations
+#define OP_BOOTREQUEST 1
+#define OP_BOOTREPLY   2
+
+// Flags
+#define FLAGS_BROADCAST 0x8000
+
+// Hardware address types
+#define HTYPE_ETHER    1
+
+// The first four bytes of options are a cookie to indicate that the payload are
+// DHCP options as opposed to some other BOOTP extension.
+#define OPT_COOKIE1          0x63
+#define OPT_COOKIE2          0x82
+#define OPT_COOKIE3          0x53
+#define OPT_COOKIE4          0x63
+
+// BOOTP/DHCP options - see RFC 2132
+#define OPT_PAD              0
+
+#define OPT_SUBNET_MASK      1     // 4 <ipaddr>
+#define OPT_TIME_OFFSET      2     // 4 <seconds>
+#define OPT_GATEWAY          3     // 4*n <ipaddr> * n
+#define OPT_DNS              6     // 4*n <ipaddr> * n
+#define OPT_DOMAIN_NAME      15    // n <domainnamestring>
+#define OPT_MTU              26    // 2 <mtu>
+#define OPT_BROADCAST_ADDR   28    // 4 <ipaddr>
+
+#define OPT_REQUESTED_IP     50    // 4 <ipaddr>
+#define OPT_LEASE_TIME       51    // 4 <seconds>
+#define OPT_MESSAGE_TYPE     53    // 1 <msgtype>
+#define OPT_SERVER_ID        54    // 4 <ipaddr>
+#define OPT_PARAMETER_LIST   55    // n <optcode> * n
+#define OPT_MESSAGE          56    // n <errorstring>
+#define OPT_T1               58    // 4 <renewal time value>
+#define OPT_T2               59    // 4 <rebinding time value>
+#define OPT_CLASS_ID         60    // n <opaque>
+#define OPT_CLIENT_ID        61    // n <opaque>
+#define OPT_END              255
+
+// DHCP message types
+#define DHCPDISCOVER         1
+#define DHCPOFFER            2
+#define DHCPREQUEST          3
+#define DHCPDECLINE          4
+#define DHCPACK              5
+#define DHCPNAK              6
+#define DHCPRELEASE          7
+#define DHCPINFORM           8
+
+
diff --git a/dhcp/common/message.cpp b/dhcp/common/message.cpp
new file mode 100644
index 0000000..42b1593
--- /dev/null
+++ b/dhcp/common/message.cpp
@@ -0,0 +1,314 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "message.h"
+#include "dhcp.h"
+
+#include <string.h>
+
+#include <vector>
+
+static uint32_t sNextTransactionId = 1;
+
+static const ptrdiff_t kOptionOffset = 7;
+
+// The default lease time in seconds
+static const uint32_t kDefaultLeaseTime = 10 * 60;
+
+// The parameters that the client would like to receive from the server
+static const uint8_t kRequestParameters[] = { OPT_SUBNET_MASK,
+                                              OPT_GATEWAY,
+                                              OPT_DNS,
+                                              OPT_BROADCAST_ADDR,
+                                              OPT_LEASE_TIME,
+                                              OPT_T1,
+                                              OPT_T2,
+                                              OPT_MTU };
+
+Message::Message() {
+    memset(&dhcpData, 0, sizeof(dhcpData));
+    mSize = 0;
+}
+
+Message::Message(const uint8_t* data, size_t size) {
+    if (size <= sizeof(dhcpData)) {
+        memcpy(&dhcpData, data, size);
+        mSize = size;
+    } else {
+        memset(&dhcpData, 0, sizeof(dhcpData));
+        mSize = 0;
+    }
+}
+
+Message Message::discover(const uint8_t (&sourceMac)[ETH_ALEN]) {
+    Message message(OP_BOOTREQUEST,
+                    sourceMac,
+                    static_cast<uint8_t>(DHCPDISCOVER));
+
+    message.addOption(OPT_PARAMETER_LIST, kRequestParameters);
+    message.endOptions();
+
+    return message;
+}
+
+Message Message::request(const uint8_t (&sourceMac)[ETH_ALEN],
+                         in_addr_t requestAddress,
+                         in_addr_t serverAddress) {
+
+    Message message(OP_BOOTREQUEST,
+                    sourceMac,
+                    static_cast<uint8_t>(DHCPREQUEST));
+
+    message.addOption(OPT_PARAMETER_LIST, kRequestParameters);
+    message.addOption(OPT_REQUESTED_IP, requestAddress);
+    message.addOption(OPT_SERVER_ID, serverAddress);
+    message.endOptions();
+
+    return message;
+}
+
+Message Message::offer(const Message& sourceMessage,
+                       in_addr_t serverAddress,
+                       in_addr_t offeredAddress,
+                       in_addr_t offeredNetmask,
+                       in_addr_t offeredGateway,
+                       const in_addr_t* offeredDnsServers,
+                       size_t numOfferedDnsServers) {
+
+    uint8_t macAddress[ETH_ALEN];
+    memcpy(macAddress, sourceMessage.dhcpData.chaddr, sizeof(macAddress));
+    Message message(OP_BOOTREPLY, macAddress, static_cast<uint8_t>(DHCPOFFER));
+
+    message.dhcpData.xid = sourceMessage.dhcpData.xid;
+    message.dhcpData.flags = sourceMessage.dhcpData.flags;
+    message.dhcpData.yiaddr = offeredAddress;
+    message.dhcpData.giaddr = sourceMessage.dhcpData.giaddr;
+
+    message.addOption(OPT_SERVER_ID, serverAddress);
+    message.addOption(OPT_LEASE_TIME, kDefaultLeaseTime);
+    message.addOption(OPT_SUBNET_MASK, offeredNetmask);
+    message.addOption(OPT_GATEWAY, offeredGateway);
+    message.addOption(OPT_DNS,
+                      offeredDnsServers,
+                      numOfferedDnsServers * sizeof(in_addr_t));
+
+    message.endOptions();
+
+    return message;
+}
+
+Message Message::ack(const Message& sourceMessage,
+                     in_addr_t serverAddress,
+                     in_addr_t offeredAddress,
+                     in_addr_t offeredNetmask,
+                     in_addr_t offeredGateway,
+                     const in_addr_t* offeredDnsServers,
+                     size_t numOfferedDnsServers) {
+    uint8_t macAddress[ETH_ALEN];
+    memcpy(macAddress, sourceMessage.dhcpData.chaddr, sizeof(macAddress));
+    Message message(OP_BOOTREPLY, macAddress, static_cast<uint8_t>(DHCPACK));
+
+    message.dhcpData.xid = sourceMessage.dhcpData.xid;
+    message.dhcpData.flags = sourceMessage.dhcpData.flags;
+    message.dhcpData.yiaddr = offeredAddress;
+    message.dhcpData.giaddr = sourceMessage.dhcpData.giaddr;
+
+    message.addOption(OPT_SERVER_ID, serverAddress);
+    message.addOption(OPT_LEASE_TIME, kDefaultLeaseTime);
+    message.addOption(OPT_SUBNET_MASK, offeredNetmask);
+    message.addOption(OPT_GATEWAY, offeredGateway);
+    message.addOption(OPT_DNS,
+                      offeredDnsServers,
+                      numOfferedDnsServers * sizeof(in_addr_t));
+
+    message.endOptions();
+
+    return message;
+}
+
+Message Message::nack(const Message& sourceMessage, in_addr_t serverAddress) {
+    uint8_t macAddress[ETH_ALEN];
+    memcpy(macAddress, sourceMessage.dhcpData.chaddr, sizeof(macAddress));
+    Message message(OP_BOOTREPLY, macAddress, static_cast<uint8_t>(DHCPNAK));
+
+    message.dhcpData.xid = sourceMessage.dhcpData.xid;
+    message.dhcpData.flags = sourceMessage.dhcpData.flags;
+    message.dhcpData.giaddr = sourceMessage.dhcpData.giaddr;
+
+    message.addOption(OPT_SERVER_ID, serverAddress);
+    message.endOptions();
+
+    return message;
+}
+
+bool Message::isValidDhcpMessage(uint8_t expectedOp,
+                                 uint32_t expectedXid) const {
+    if (!isValidDhcpMessage(expectedOp)) {
+        return false;
+    }
+    // Only look for message with a matching transaction ID
+    if (dhcpData.xid != expectedXid) {
+        return false;
+    }
+    return true;
+}
+
+bool Message::isValidDhcpMessage(uint8_t expectedOp) const {
+    // Require that there is at least enough options for the DHCP cookie
+    if (dhcpData.options + 4 > end()) {
+        return false;
+    }
+
+    if (dhcpData.op != expectedOp) {
+        return false;
+    }
+    if (dhcpData.htype != HTYPE_ETHER) {
+        return false;
+    }
+    if (dhcpData.hlen != ETH_ALEN) {
+        return false;
+    }
+
+    // Need to have the correct cookie in the options
+    if (dhcpData.options[0] != OPT_COOKIE1) {
+        return false;
+    }
+    if (dhcpData.options[1] != OPT_COOKIE2) {
+        return false;
+    }
+    if (dhcpData.options[2] != OPT_COOKIE3) {
+        return false;
+    }
+    if (dhcpData.options[3] != OPT_COOKIE4) {
+        return false;
+    }
+
+    return true;
+}
+
+size_t Message::optionsSize() const {
+    auto options = reinterpret_cast<const uint8_t*>(&dhcpData.options);
+    const uint8_t* msgEnd = end();
+    if (msgEnd <= options) {
+        return 0;
+    }
+    return msgEnd - options;
+}
+
+uint8_t Message::type() const {
+    uint8_t length = 0;
+    const uint8_t* opt = getOption(OPT_MESSAGE_TYPE, &length);
+    if (opt && length == 1) {
+        return *opt;
+    }
+    return 0;
+}
+
+in_addr_t Message::serverId() const {
+    uint8_t length = 0;
+    const uint8_t* opt = getOption(OPT_SERVER_ID, &length);
+    if (opt && length == 4) {
+        return *reinterpret_cast<const in_addr_t*>(opt);
+    }
+    return 0;
+}
+
+in_addr_t Message::requestedIp() const {
+    uint8_t length = 0;
+    const uint8_t* opt = getOption(OPT_REQUESTED_IP, &length);
+    if (opt && length == 4) {
+        return *reinterpret_cast<const in_addr_t*>(opt);
+    }
+    return 0;
+}
+
+Message::Message(uint8_t operation,
+                 const uint8_t (&macAddress)[ETH_ALEN],
+                 uint8_t type) {
+    memset(&dhcpData, 0, sizeof(dhcpData));
+
+    dhcpData.op = operation;
+    dhcpData.htype = HTYPE_ETHER;
+    dhcpData.hlen = ETH_ALEN;
+    dhcpData.hops = 0;
+
+    dhcpData.flags = htons(FLAGS_BROADCAST);
+
+    dhcpData.xid = htonl(sNextTransactionId++);
+
+    memcpy(dhcpData.chaddr, macAddress, ETH_ALEN);
+
+    uint8_t* opts = dhcpData.options;
+
+    *opts++ = OPT_COOKIE1;
+    *opts++ = OPT_COOKIE2;
+    *opts++ = OPT_COOKIE3;
+    *opts++ = OPT_COOKIE4;
+
+    *opts++ = OPT_MESSAGE_TYPE;
+    *opts++ = 1;
+    *opts++ = type;
+
+    updateSize(opts);
+}
+
+void Message::addOption(uint8_t type, const void* data, uint8_t size) {
+    uint8_t* opts = nextOption();
+
+    *opts++ = type;
+    *opts++ = size;
+    memcpy(opts, data, size);
+    opts += size;
+
+    updateSize(opts);
+}
+
+void Message::endOptions() {
+    uint8_t* opts = nextOption();
+
+    *opts++ = OPT_END;
+
+    updateSize(opts);
+}
+
+const uint8_t* Message::getOption(uint8_t expectedOptCode,
+                                  uint8_t* length) const {
+    size_t optsSize = optionsSize();
+    for (size_t i = 4; i + 2 < optsSize; ) {
+        uint8_t optCode = dhcpData.options[i];
+        uint8_t optLen = dhcpData.options[i + 1];
+        const uint8_t* opt = dhcpData.options + i + 2;
+
+        if (optCode == OPT_END) {
+            return nullptr;
+        }
+        if (optCode == expectedOptCode) {
+            *length = optLen;
+            return opt;
+        }
+        i += 2 + optLen;
+    }
+    return nullptr;
+}
+
+uint8_t* Message::nextOption() {
+    return reinterpret_cast<uint8_t*>(&dhcpData) + size();
+}
+
+void Message::updateSize(uint8_t* optionsEnd) {
+    mSize = optionsEnd - reinterpret_cast<uint8_t*>(&dhcpData);
+}
+
diff --git a/dhcp/common/message.h b/dhcp/common/message.h
new file mode 100644
index 0000000..84029cc
--- /dev/null
+++ b/dhcp/common/message.h
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <linux/if_ether.h>
+#include <netinet/in.h>
+#include <stddef.h>
+#include <string.h>
+
+#include <initializer_list>
+
+class Message {
+public:
+    Message();
+    Message(const uint8_t* data, size_t size);
+    static Message discover(const uint8_t (&sourceMac)[ETH_ALEN]);
+    static Message request(const uint8_t (&sourceMac)[ETH_ALEN],
+                           in_addr_t requestAddress,
+                           in_addr_t serverAddress);
+    static Message offer(const Message& sourceMessage,
+                         in_addr_t serverAddress,
+                         in_addr_t offeredAddress,
+                         in_addr_t offeredNetmask,
+                         in_addr_t offeredGateway,
+                         const in_addr_t* offeredDnsServers,
+                         size_t numOfferedDnsServers);
+    static Message ack(const Message& sourceMessage,
+                       in_addr_t serverAddress,
+                       in_addr_t offeredAddress,
+                       in_addr_t offeredNetmask,
+                       in_addr_t offeredGateway,
+                       const in_addr_t* offeredDnsServers,
+                       size_t numOfferedDnsServers);
+    static Message nack(const Message& sourceMessage, in_addr_t serverAddress);
+
+    // Ensure that the data in the message represent a valid DHCP message
+    bool isValidDhcpMessage(uint8_t expectedOp) const;
+    // Ensure that the data in the message represent a valid DHCP message and
+    // has a xid (transaction ID) that matches |expectedXid|.
+    bool isValidDhcpMessage(uint8_t expectedOp, uint32_t expectedXid) const;
+
+    const uint8_t* data() const {
+        return reinterpret_cast<const uint8_t*>(&dhcpData);
+    }
+    uint8_t* data() {
+        return reinterpret_cast<uint8_t*>(&dhcpData);
+    }
+    const uint8_t* end() const { return data() + mSize; }
+
+    size_t optionsSize() const;
+    size_t size() const { return mSize; }
+    void setSize(size_t size) { mSize = size; }
+    size_t capacity() const { return sizeof(dhcpData); }
+
+    // Get the DHCP message type
+    uint8_t type() const;
+    // Get the DHCP server ID
+    in_addr_t serverId() const;
+    // Get the requested IP
+    in_addr_t requestedIp() const;
+
+    struct Dhcp {
+        uint8_t op;           /* BOOTREQUEST / BOOTREPLY    */
+        uint8_t htype;        /* hw addr type               */
+        uint8_t hlen;         /* hw addr len                */
+        uint8_t hops;         /* client set to 0            */
+
+        uint32_t xid;         /* transaction id             */
+
+        uint16_t secs;        /* seconds since start of acq */
+        uint16_t flags;
+
+        uint32_t ciaddr;      /* client IP addr             */
+        uint32_t yiaddr;      /* your (client) IP addr      */
+        uint32_t siaddr;      /* ip addr of next server     */
+                              /* (DHCPOFFER and DHCPACK)    */
+        uint32_t giaddr;      /* relay agent IP addr        */
+
+        uint8_t chaddr[16];  /* client hw addr             */
+        char sname[64];      /* asciiz server hostname     */
+        char file[128];      /* asciiz boot file name      */
+
+        uint8_t options[1024];  /* optional parameters        */
+    }  dhcpData;
+private:
+    Message(uint8_t operation,
+            const uint8_t (&macAddress)[ETH_ALEN],
+            uint8_t type);
+
+    void addOption(uint8_t type, const void* data, uint8_t size);
+    template<typename T>
+    void addOption(uint8_t type, T data) {
+        static_assert(sizeof(T) <= 255, "The size of data is too large");
+        addOption(type, &data, sizeof(data));
+    }
+    template<typename T, size_t N>
+    void addOption(uint8_t type, T (&items)[N]) {
+        static_assert(sizeof(T) * N <= 255,
+                      "The size of data is too large");
+        uint8_t* opts = nextOption();
+        *opts++ = type;
+        *opts++ = sizeof(T) * N;
+        for (const T& item : items) {
+            memcpy(opts, &item, sizeof(item));
+            opts += sizeof(item);
+        }
+        updateSize(opts);
+    }
+    void endOptions();
+
+    const uint8_t* getOption(uint8_t optCode, uint8_t* length) const;
+    uint8_t* nextOption();
+    void updateSize(uint8_t* optionsEnd);
+    size_t mSize;
+};
+
+static_assert(offsetof(Message::Dhcp, htype) == sizeof(Message::Dhcp::op),
+              "Invalid packing for DHCP message struct");
diff --git a/dhcp/common/result.h b/dhcp/common/result.h
new file mode 100644
index 0000000..5087e14
--- /dev/null
+++ b/dhcp/common/result.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include <string>
+
+class Result {
+public:
+    static Result success() {
+        return Result(true);
+    }
+    // Construct a result indicating an error.
+    static Result error(std::string message) {
+        return Result(message);
+    }
+    static Result error(const char* format, ...) {
+        char buffer[1024];
+        va_list args;
+        va_start(args, format);
+        vsnprintf(buffer, sizeof(buffer), format, args);
+        va_end(args);
+        buffer[sizeof(buffer) - 1] = '\0';
+        return Result(std::string(buffer));
+    }
+
+    bool isSuccess() const { return mSuccess; }
+    bool operator!() const { return !mSuccess; }
+
+    const char* c_str() const { return mMessage.c_str(); }
+private:
+    explicit Result(bool success) : mSuccess(success) { }
+    explicit Result(std::string message)
+        : mMessage(message), mSuccess(false) {
+    }
+    std::string mMessage;
+    bool mSuccess;
+};
+
diff --git a/dhcp/common/socket.cpp b/dhcp/common/socket.cpp
new file mode 100644
index 0000000..96a2f2a
--- /dev/null
+++ b/dhcp/common/socket.cpp
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "socket.h"
+
+#include "message.h"
+#include "utils.h"
+
+#include <errno.h>
+#include <linux/if_packet.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/uio.h>
+#include <unistd.h>
+
+// Combine the checksum of |buffer| with |size| bytes with |checksum|. This is
+// used for checksum calculations for IP and UDP.
+static uint32_t addChecksum(const uint8_t* buffer,
+                            size_t size,
+                            uint32_t checksum) {
+    const uint16_t* data = reinterpret_cast<const uint16_t*>(buffer);
+    while (size > 1) {
+        checksum += *data++;
+        size -= 2;
+    }
+    if (size > 0) {
+        // Odd size, add the last byte
+        checksum += *reinterpret_cast<const uint8_t*>(data);
+    }
+    // msw is the most significant word, the upper 16 bits of the checksum
+    for (uint32_t msw = checksum >> 16; msw != 0; msw = checksum >> 16) {
+        checksum = (checksum & 0xFFFF) + msw;
+    }
+    return checksum;
+}
+
+// Convenienct template function for checksum calculation
+template<typename T>
+static uint32_t addChecksum(const T& data, uint32_t checksum) {
+    return addChecksum(reinterpret_cast<const uint8_t*>(&data), sizeof(T), checksum);
+}
+
+// Finalize the IP or UDP |checksum| by inverting and truncating it.
+static uint32_t finishChecksum(uint32_t checksum) {
+    return ~checksum & 0xFFFF;
+}
+
+Socket::Socket() : mSocketFd(-1) {
+}
+
+Socket::~Socket() {
+    if (mSocketFd != -1) {
+        ::close(mSocketFd);
+        mSocketFd = -1;
+    }
+}
+
+
+Result Socket::open(int domain, int type, int protocol) {
+    if (mSocketFd != -1) {
+        return Result::error("Socket already open");
+    }
+    mSocketFd = ::socket(domain, type, protocol);
+    if (mSocketFd == -1) {
+        return Result::error("Failed to open socket: %s", strerror(errno));
+    }
+    return Result::success();
+}
+
+Result Socket::bind(const void* sockaddr, size_t sockaddrLength) {
+    if (mSocketFd == -1) {
+        return Result::error("Socket not open");
+    }
+
+    int status = ::bind(mSocketFd,
+                        reinterpret_cast<const struct sockaddr*>(sockaddr),
+                        sockaddrLength);
+    if (status != 0) {
+        return Result::error("Unable to bind raw socket: %s", strerror(errno));
+    }
+
+    return Result::success();
+}
+
+Result Socket::bindIp(in_addr_t address, uint16_t port) {
+    struct sockaddr_in sockaddr;
+    memset(&sockaddr, 0, sizeof(sockaddr));
+    sockaddr.sin_family = AF_INET;
+    sockaddr.sin_port = htons(port);
+    sockaddr.sin_addr.s_addr = address;
+
+    return bind(&sockaddr, sizeof(sockaddr));
+}
+
+Result Socket::bindRaw(unsigned int interfaceIndex) {
+    struct sockaddr_ll sockaddr;
+    memset(&sockaddr, 0, sizeof(sockaddr));
+    sockaddr.sll_family = AF_PACKET;
+    sockaddr.sll_protocol = htons(ETH_P_IP);
+    sockaddr.sll_ifindex = interfaceIndex;
+
+    return bind(&sockaddr, sizeof(sockaddr));
+}
+
+Result Socket::sendOnInterface(unsigned int interfaceIndex,
+                               in_addr_t destinationAddress,
+                               uint16_t destinationPort,
+                               const Message& message) {
+    if (mSocketFd == -1) {
+        return Result::error("Socket not open");
+    }
+
+    char controlData[CMSG_SPACE(sizeof(struct in_pktinfo))] = { 0 };
+    struct sockaddr_in addr;
+    memset(&addr, 0, sizeof(addr));
+    addr.sin_family = AF_INET;
+    addr.sin_port = htons(destinationPort);
+    addr.sin_addr.s_addr = destinationAddress;
+
+    struct msghdr header;
+    memset(&header, 0, sizeof(header));
+    struct iovec iov;
+    // The struct member is non-const since it's used for receiving but it's
+    // safe to cast away const for sending.
+    iov.iov_base = const_cast<uint8_t*>(message.data());
+    iov.iov_len = message.size();
+    header.msg_name = &addr;
+    header.msg_namelen = sizeof(addr);
+    header.msg_iov = &iov;
+    header.msg_iovlen = 1;
+    header.msg_control = &controlData;
+    header.msg_controllen = sizeof(controlData);
+
+    struct cmsghdr* controlHeader = CMSG_FIRSTHDR(&header);
+    controlHeader->cmsg_level = IPPROTO_IP;
+    controlHeader->cmsg_type = IP_PKTINFO;
+    controlHeader->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo));
+    auto packetInfo =
+        reinterpret_cast<struct in_pktinfo*>(CMSG_DATA(controlHeader));
+    memset(packetInfo, 0, sizeof(*packetInfo));
+    packetInfo->ipi_ifindex = interfaceIndex;
+
+    ssize_t status = ::sendmsg(mSocketFd, &header, 0);
+    if (status <= 0) {
+        return Result::error("Failed to send packet: %s", strerror(errno));
+    }
+    return Result::success();
+}
+
+Result Socket::sendRawUdp(in_addr_t source,
+                          uint16_t sourcePort,
+                          in_addr_t destination,
+                          uint16_t destinationPort,
+                          unsigned int interfaceIndex,
+                          const Message& message) {
+    struct iphdr ip;
+    struct udphdr udp;
+
+    ip.version = IPVERSION;
+    ip.ihl = sizeof(ip) >> 2;
+    ip.tos = 0;
+    ip.tot_len = htons(sizeof(ip) + sizeof(udp) + message.size());
+    ip.id = 0;
+    ip.frag_off = 0;
+    ip.ttl = IPDEFTTL;
+    ip.protocol = IPPROTO_UDP;
+    ip.check = 0;
+    ip.saddr = source;
+    ip.daddr = destination;
+    ip.check = finishChecksum(addChecksum(ip, 0));
+
+    udp.source = htons(sourcePort);
+    udp.dest = htons(destinationPort);
+    udp.len = htons(sizeof(udp) + message.size());
+    udp.check = 0;
+
+    uint32_t udpChecksum = 0;
+    udpChecksum = addChecksum(ip.saddr, udpChecksum);
+    udpChecksum = addChecksum(ip.daddr, udpChecksum);
+    udpChecksum = addChecksum(htons(IPPROTO_UDP), udpChecksum);
+    udpChecksum = addChecksum(udp.len, udpChecksum);
+    udpChecksum = addChecksum(udp, udpChecksum);
+    udpChecksum = addChecksum(message.data(), message.size(), udpChecksum);
+    udp.check = finishChecksum(udpChecksum);
+
+    struct iovec iov[3];
+
+    iov[0].iov_base = static_cast<void*>(&ip);
+    iov[0].iov_len = sizeof(ip);
+    iov[1].iov_base = static_cast<void*>(&udp);
+    iov[1].iov_len = sizeof(udp);
+    // sendmsg requires these to be non-const but for sending won't modify them
+    iov[2].iov_base = static_cast<void*>(const_cast<uint8_t*>(message.data()));
+    iov[2].iov_len = message.size();
+
+    struct sockaddr_ll dest;
+    memset(&dest, 0, sizeof(dest));
+    dest.sll_family = AF_PACKET;
+    dest.sll_protocol = htons(ETH_P_IP);
+    dest.sll_ifindex = interfaceIndex;
+    dest.sll_halen = ETH_ALEN;
+    memset(dest.sll_addr, 0xFF, ETH_ALEN);
+
+    struct msghdr header;
+    memset(&header, 0, sizeof(header));
+    header.msg_name = &dest;
+    header.msg_namelen = sizeof(dest);
+    header.msg_iov = iov;
+    header.msg_iovlen = sizeof(iov) / sizeof(iov[0]);
+
+    ssize_t res = ::sendmsg(mSocketFd, &header, 0);
+    if (res == -1) {
+        return Result::error("Failed to send message: %s", strerror(errno));
+    }
+    return Result::success();
+}
+
+Result Socket::receiveFromInterface(Message* message,
+                                    unsigned int* interfaceIndex) {
+    char controlData[CMSG_SPACE(sizeof(struct in_pktinfo))];
+    struct msghdr header;
+    memset(&header, 0, sizeof(header));
+    struct iovec iov;
+    iov.iov_base = message->data();
+    iov.iov_len = message->capacity();
+    header.msg_iov = &iov;
+    header.msg_iovlen = 1;
+    header.msg_control = &controlData;
+    header.msg_controllen = sizeof(controlData);
+
+    ssize_t bytesRead = ::recvmsg(mSocketFd, &header, 0);
+    if (bytesRead < 0) {
+        return Result::error("Error receiving on socket: %s", strerror(errno));
+    }
+    message->setSize(static_cast<size_t>(bytesRead));
+    if (header.msg_controllen >= sizeof(struct cmsghdr)) {
+        for (struct cmsghdr* ctrl = CMSG_FIRSTHDR(&header);
+             ctrl;
+             ctrl = CMSG_NXTHDR(&header, ctrl)) {
+            if (ctrl->cmsg_level == SOL_IP &&
+                ctrl->cmsg_type == IP_PKTINFO) {
+                auto packetInfo =
+                    reinterpret_cast<struct in_pktinfo*>(CMSG_DATA(ctrl));
+                *interfaceIndex = packetInfo->ipi_ifindex;
+            }
+        }
+    }
+    return Result::success();
+}
+
+Result Socket::receiveRawUdp(uint16_t expectedPort,
+                             Message* message,
+                             bool* isValid) {
+    struct iphdr ip;
+    struct udphdr udp;
+
+    struct iovec iov[3];
+    iov[0].iov_base = &ip;
+    iov[0].iov_len = sizeof(ip);
+    iov[1].iov_base = &udp;
+    iov[1].iov_len = sizeof(udp);
+    iov[2].iov_base = message->data();
+    iov[2].iov_len = message->capacity();
+
+    ssize_t bytesRead = ::readv(mSocketFd, iov, 3);
+    if (bytesRead < 0) {
+        return Result::error("Unable to read from socket: %s", strerror(errno));
+    }
+    if (static_cast<size_t>(bytesRead) < sizeof(ip) + sizeof(udp)) {
+        // Not enough bytes to even cover IP and UDP headers
+        *isValid = false;
+        return Result::success();
+    }
+    if (ip.version != IPVERSION) *isValid = false;
+    if (ip.ihl != (sizeof(ip) >> 2)) *isValid = false;
+    if (ip.protocol != IPPROTO_UDP) *isValid = false;
+    if (udp.dest != htons(expectedPort)) *isValid = false;
+
+    *isValid = true;
+    message->setSize(bytesRead - sizeof(ip) - sizeof(udp));
+    return Result::success();
+}
+
+Result Socket::enableOption(int level, int optionName) {
+    if (mSocketFd == -1) {
+        return Result::error("Socket not open");
+    }
+
+    int enabled = 1;
+    int status = ::setsockopt(mSocketFd,
+                              level,
+                              optionName,
+                              &enabled,
+                              sizeof(enabled));
+    if (status == -1) {
+        return Result::error("Failed to set socket option: %s",
+                             strerror(errno));
+    }
+    return Result::success();
+}
diff --git a/dhcp/common/socket.h b/dhcp/common/socket.h
new file mode 100644
index 0000000..0c9483c
--- /dev/null
+++ b/dhcp/common/socket.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "result.h"
+
+#include <arpa/inet.h>
+
+class Message;
+
+class Socket {
+public:
+    Socket();
+    Socket(const Socket&) = delete;
+    ~Socket();
+
+    Socket& operator=(const Socket&) = delete;
+
+    int get() const { return mSocketFd; }
+    // Open a socket, |domain|, |type| and |protocol| are as described in the
+    // man pages for socket.
+    Result open(int domain, int type, int protocol);
+    // Bind to a generic |sockaddr| of size |sockaddrLength|
+    Result bind(const void* sockaddr, size_t sockaddrLength);
+    // Bind to an IP |address| and |port|
+    Result bindIp(in_addr_t address, uint16_t port);
+    // Bind a raw socket to the interface with index |interfaceIndex|.
+    Result bindRaw(unsigned int interfaceIndex);
+    // Send data in |message| on an IP socket to
+    // |destinationAddress|:|destinationPort|, the message will egress on the
+    // interface specified by |interfaceIndex|
+    Result sendOnInterface(unsigned int interfaceIndex,
+                           in_addr_t destinationAddress,
+                           uint16_t destinationPort,
+                           const Message& message);
+    // Send |message| as a UDP datagram on a raw socket. The source address of
+    // the message will be |source|:|sourcePort| and the destination will be
+    // |destination|:|destinationPort|. The message will be sent on the
+    // interface indicated by |interfaceIndex|.
+    Result sendRawUdp(in_addr_t source,
+                      uint16_t sourcePort,
+                      in_addr_t destination,
+                      uint16_t destinationPort,
+                      unsigned int interfaceIndex,
+                      const Message& message);
+    // Receive data on the socket and indicate which interface the data was
+    // received on in |interfaceIndex|. The received data is placed in |message|
+    Result receiveFromInterface(Message* message, unsigned int* interfaceIndex);
+    // Receive UDP data on a raw socket. Expect that the protocol in the IP
+    // header is UDP and that the port in the UDP header is |expectedPort|. If
+    // the received data is valid then |isValid| will be set to true, otherwise
+    // false. The validity check includes the expected values as well as basic
+    // size requirements to fit the expected protocol headers.  The method will
+    // only return an error result if the actual receiving fails.
+    Result receiveRawUdp(uint16_t expectedPort,
+                         Message* message,
+                         bool* isValid);
+    // Enable |optionName| on option |level|. These values are the same as used
+    // in setsockopt calls.
+    Result enableOption(int level, int optionName);
+private:
+    int mSocketFd;
+};
+
diff --git a/dhcp/common/utils.cpp b/dhcp/common/utils.cpp
new file mode 100644
index 0000000..e4a37f3
--- /dev/null
+++ b/dhcp/common/utils.cpp
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "utils.h"
+
+std::string addrToStr(in_addr_t address) {
+    char buffer[INET_ADDRSTRLEN];
+    if (::inet_ntop(AF_INET, &address, buffer, sizeof(buffer)) == nullptr) {
+        return "[unknown]";
+    }
+    return buffer;
+}
+
diff --git a/dhcp/common/utils.h b/dhcp/common/utils.h
new file mode 100644
index 0000000..5f4b971
--- /dev/null
+++ b/dhcp/common/utils.h
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <arpa/inet.h>
+
+#include <string>
+
+std::string addrToStr(in_addr_t address);
+
diff --git a/dhcp/server/Android.mk b/dhcp/server/Android.mk
new file mode 100644
index 0000000..6d99bf4
--- /dev/null
+++ b/dhcp/server/Android.mk
@@ -0,0 +1,22 @@
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+	dhcpserver.cpp \
+	main.cpp \
+	../common/message.cpp \
+	../common/socket.cpp \
+	../common/utils.cpp \
+
+
+LOCAL_CPPFLAGS += -Werror
+LOCAL_C_INCLUDES += $(LOCAL_PATH)/../common
+LOCAL_SHARED_LIBRARIES := libcutils liblog
+LOCAL_MODULE_TAGS := debug
+LOCAL_MODULE := dhcpserver
+
+LOCAL_MODULE_CLASS := EXECUTABLES
+
+include $(BUILD_EXECUTABLE)
+
diff --git a/dhcp/server/dhcpserver.cpp b/dhcp/server/dhcpserver.cpp
new file mode 100644
index 0000000..33fb61a
--- /dev/null
+++ b/dhcp/server/dhcpserver.cpp
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dhcpserver.h"
+
+#include "dhcp.h"
+#include "log.h"
+#include "message.h"
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <linux/sockios.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <poll.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <cutils/properties.h>
+
+static const int kMaxDnsServers = 4;
+
+DhcpServer::DhcpServer(in_addr_t dhcpRangeStart,
+                       in_addr_t dhcpRangeEnd,
+                       in_addr_t netmask,
+                       in_addr_t gateway,
+                       unsigned int excludeInterface) :
+    mNextAddressOffset(0),
+    mDhcpRangeStart(dhcpRangeStart),
+    mDhcpRangeEnd(dhcpRangeEnd),
+    mNetmask(netmask),
+    mGateway(gateway),
+    mExcludeInterface(excludeInterface)
+{
+}
+
+Result DhcpServer::init() {
+    Result res = mSocket.open(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
+    if (!res) {
+        return res;
+    }
+    res = mSocket.enableOption(SOL_IP, IP_PKTINFO);
+    if (!res) {
+        return res;
+    }
+    res = mSocket.enableOption(SOL_SOCKET, SO_BROADCAST);
+    if (!res) {
+        return res;
+    }
+
+    res = mSocket.bindIp(INADDR_ANY, PORT_BOOTP_SERVER);
+    if (!res) {
+        return res;
+    }
+
+    return Result::success();
+}
+
+Result DhcpServer::run() {
+    // Block all signals while we're running. This way we don't have to deal
+    // with things like EINTR. We then uses ppoll to set the original mask while
+    // polling. This way polling can be interrupted but socket writing, reading
+    // and ioctl remain interrupt free. If a signal arrives while we're blocking
+    // it it will be placed in the signal queue and handled once ppoll sets the
+    // original mask. This way no signals are lost.
+    sigset_t blockMask, originalMask;
+    int status = ::sigfillset(&blockMask);
+    if (status != 0) {
+        return Result::error("Unable to fill signal set: %s", strerror(errno));
+    }
+    status = ::sigprocmask(SIG_SETMASK, &blockMask, &originalMask);
+    if (status != 0) {
+        return Result::error("Unable to set signal mask: %s", strerror(errno));
+    }
+
+    struct pollfd fds;
+    fds.fd = mSocket.get();
+    fds.events = POLLIN;
+    Message message;
+    while ((status = ::ppoll(&fds, 1, nullptr, &originalMask)) >= 0) {
+        if (status == 0) {
+            // Timeout
+            continue;
+        }
+
+        unsigned int interfaceIndex = 0;
+        Result res = mSocket.receiveFromInterface(&message,
+                                                  &interfaceIndex);
+        if (!res) {
+            ALOGE("Failed to recieve on socket: %s", res.c_str());
+            continue;
+        }
+        if (interfaceIndex == 0 || mExcludeInterface == interfaceIndex) {
+            // Received packet on unknown or unwanted interface, drop it
+            continue;
+        }
+        if (!message.isValidDhcpMessage(OP_BOOTREQUEST)) {
+            // Not a DHCP request, drop it
+            continue;
+        }
+        switch (message.type()) {
+            case DHCPDISCOVER:
+                // Someone is trying to find us, let them know we exist
+                sendDhcpOffer(message, interfaceIndex);
+                break;
+            case DHCPREQUEST:
+                // Someone wants a lease based on an offer
+                if (isValidDhcpRequest(message, interfaceIndex)) {
+                    // The request matches our offer, acknowledge it
+                    sendAck(message, interfaceIndex);
+                } else {
+                    // Request for something other than we offered, denied
+                    sendNack(message, interfaceIndex);
+                }
+                break;
+        }
+    }
+    // Polling failed, exit
+    return Result::error("Polling failed: %s", strerror(errno));
+}
+
+Result DhcpServer::sendMessage(unsigned int interfaceIndex,
+                               in_addr_t /*sourceAddress*/,
+                               const Message& message) {
+    return mSocket.sendOnInterface(interfaceIndex,
+                                   INADDR_BROADCAST,
+                                   PORT_BOOTP_CLIENT,
+                                   message);
+}
+
+void DhcpServer::sendDhcpOffer(const Message& message,
+                               unsigned int interfaceIndex ) {
+    updateDnsServers();
+    in_addr_t offerAddress;
+    Result res = getOfferAddress(interfaceIndex,
+                                 message.dhcpData.chaddr,
+                                 &offerAddress);
+    if (!res) {
+        ALOGE("Failed to get address for offer: %s", res.c_str());
+        return;
+    }
+    in_addr_t serverAddress;
+    res = getInterfaceAddress(interfaceIndex, &serverAddress);
+    if (!res) {
+        ALOGE("Failed to get address for interface %u: %s",
+              interfaceIndex, res.c_str());
+        return;
+    }
+
+    Message offer = Message::offer(message,
+                                   serverAddress,
+                                   offerAddress,
+                                   mNetmask,
+                                   mGateway,
+                                   mDnsServers.data(),
+                                   mDnsServers.size());
+    res = sendMessage(interfaceIndex, serverAddress, offer);
+    if (!res) {
+        ALOGE("Failed to send DHCP offer: %s", res.c_str());
+    }
+}
+
+void DhcpServer::sendAck(const Message& message, unsigned int interfaceIndex) {
+    updateDnsServers();
+    in_addr_t offerAddress, serverAddress;
+    Result res = getOfferAddress(interfaceIndex,
+                                 message.dhcpData.chaddr,
+                                 &offerAddress);
+    if (!res) {
+        ALOGE("Failed to get address for offer: %s", res.c_str());
+        return;
+    }
+    res = getInterfaceAddress(interfaceIndex, &serverAddress);
+    if (!res) {
+        ALOGE("Failed to get address for interface %u: %s",
+              interfaceIndex, res.c_str());
+        return;
+    }
+    Message ack = Message::ack(message,
+                               serverAddress,
+                               offerAddress,
+                               mNetmask,
+                               mGateway,
+                               mDnsServers.data(),
+                               mDnsServers.size());
+    res = sendMessage(interfaceIndex, serverAddress, ack);
+    if (!res) {
+        ALOGE("Failed to send DHCP ack: %s", res.c_str());
+    }
+}
+
+void DhcpServer::sendNack(const Message& message, unsigned int interfaceIndex) {
+    in_addr_t serverAddress;
+    Result res = getInterfaceAddress(interfaceIndex, &serverAddress);
+    if (!res) {
+        ALOGE("Failed to get address for interface %u: %s",
+              interfaceIndex, res.c_str());
+        return;
+    }
+    Message nack = Message::nack(message, serverAddress);
+    res = sendMessage(interfaceIndex, serverAddress, nack);
+    if (!res) {
+        ALOGE("Failed to send DHCP nack: %s", res.c_str());
+    }
+}
+
+bool DhcpServer::isValidDhcpRequest(const Message& message,
+                                    unsigned int interfaceIndex) {
+    in_addr_t offerAddress;
+    Result res = getOfferAddress(interfaceIndex,
+                                 message.dhcpData.chaddr,
+                                 &offerAddress);
+    if (!res) {
+        ALOGE("Failed to get address for offer: %s", res.c_str());
+        return false;
+    }
+    if (message.requestedIp() != offerAddress) {
+        ALOGE("Client requested a different IP address from the offered one");
+        return false;
+    }
+    return true;
+}
+
+void DhcpServer::updateDnsServers() {
+    char key[64];
+    char value[PROPERTY_VALUE_MAX];
+    mDnsServers.clear();
+    for (int i = 1; i <= kMaxDnsServers; ++i) {
+        snprintf(key, sizeof(key), "net.eth0.dns%d", i);
+        if (property_get(key, value, nullptr) > 0) {
+            struct in_addr address;
+            if (::inet_pton(AF_INET, value, &address) > 0) {
+                mDnsServers.push_back(address.s_addr);
+            }
+        }
+    }
+}
+
+Result DhcpServer::getInterfaceAddress(unsigned int interfaceIndex,
+                                       in_addr_t* address) {
+    char interfaceName[IF_NAMESIZE + 1];
+    if (if_indextoname(interfaceIndex, interfaceName) == nullptr) {
+        return Result::error("Failed to get interface name for index %u: %s",
+                             interfaceIndex, strerror(errno));
+    }
+    struct ifreq request;
+    memset(&request, 0, sizeof(request));
+    request.ifr_addr.sa_family = AF_INET;
+    strncpy(request.ifr_name, interfaceName, IFNAMSIZ - 1);
+
+    if (::ioctl(mSocket.get(), SIOCGIFADDR, &request) == -1) {
+        return Result::error("Failed to get address for interface %s: %s",
+                             interfaceName, strerror(errno));
+    }
+
+    auto inAddr = reinterpret_cast<struct sockaddr_in*>(&request.ifr_addr);
+    *address = inAddr->sin_addr.s_addr;
+
+    return Result::success();
+}
+
+Result DhcpServer::getOfferAddress(unsigned int interfaceIndex,
+                                   const uint8_t* macAddress,
+                                   in_addr_t* address) {
+    Lease key(interfaceIndex, macAddress);
+
+    // Find or create entry, if it's created it will be zero and we update it
+    in_addr_t& value = mLeases[key];
+    if (value == 0) {
+        // Addresses are stored in network byte order so when doing math on them
+        // they have to be converted to host byte order
+        in_addr_t nextAddress = ntohl(mDhcpRangeStart) + mNextAddressOffset;
+        uint8_t lastAddressByte = nextAddress & 0xFF;
+        while (lastAddressByte == 0xFF || lastAddressByte == 0) {
+            // The address ends in .255 or .0 which means it's a broadcast or
+            // network address respectively. Increase it further to avoid this.
+            ++nextAddress;
+            ++mNextAddressOffset;
+        }
+        if (nextAddress <= ntohl(mDhcpRangeEnd)) {
+            // And then converted back again
+            value = htonl(nextAddress);
+            ++mNextAddressOffset;
+        } else {
+            // Ran out of addresses
+            return Result::error("DHCP server is out of addresses");
+        }
+    }
+    *address = value;
+    return Result::success();
+}
+
diff --git a/dhcp/server/dhcpserver.h b/dhcp/server/dhcpserver.h
new file mode 100644
index 0000000..c5e1007
--- /dev/null
+++ b/dhcp/server/dhcpserver.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include "lease.h"
+#include "result.h"
+#include "socket.h"
+
+#include <netinet/in.h>
+#include <stdint.h>
+
+#include <unordered_map>
+#include <vector>
+
+class Message;
+
+class DhcpServer {
+public:
+    // Construct a DHCP server with the given parameters. Ignore any requests
+    // and discoveries coming on the network interface identified by
+    // |excludeInterface|.
+    DhcpServer(in_addr_t dhcpRangeStart,
+               in_addr_t dhcpRangeEnd,
+               in_addr_t netmask,
+               in_addr_t gateway,
+               unsigned int excludeInterface);
+
+    Result init();
+    Result run();
+
+private:
+    Result sendMessage(unsigned int interfaceIndex,
+                       in_addr_t sourceAddress,
+                       const Message& message);
+
+    void sendDhcpOffer(const Message& message, unsigned int interfaceIndex);
+    void sendAck(const Message& message, unsigned int interfaceIndex);
+    void sendNack(const Message& message, unsigned int interfaceIndex);
+
+    bool isValidDhcpRequest(const Message& message,
+                            unsigned int interfaceIndex);
+    void updateDnsServers();
+    Result getInterfaceAddress(unsigned int interfaceIndex,
+                               in_addr_t* address);
+    Result getOfferAddress(unsigned int interfaceIndex,
+                           const uint8_t* macAddress,
+                           in_addr_t* address);
+
+    Socket mSocket;
+    // This is the next address offset. This will be added to whatever the base
+    // address of the DHCP address range is. For each new MAC address seen this
+    // value will increase by one.
+    in_addr_t mNextAddressOffset;
+    in_addr_t mDhcpRangeStart;
+    in_addr_t mDhcpRangeEnd;
+    in_addr_t mNetmask;
+    in_addr_t mGateway;
+    std::vector<in_addr_t> mDnsServers;
+    // Map a lease to an IP address for that lease
+    std::unordered_map<Lease, in_addr_t> mLeases;
+    unsigned int mExcludeInterface;
+};
+
diff --git a/dhcp/server/lease.h b/dhcp/server/lease.h
new file mode 100644
index 0000000..bad5614
--- /dev/null
+++ b/dhcp/server/lease.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <linux/if_ether.h>
+#include <stdint.h>
+
+#include <functional>
+
+// A lease consists of both the interface index and the MAC address. This
+// way the server can run on many different interfaces that have the same
+// client MAC address without giving out the same IP address. The reason
+// this is useful is because we might have several virtual interfaces, one
+// for each access point, that all have the same endpoint on the other side.
+// This endpoint would then have the same MAC address and always get the
+// same address. But for routing purposes it's useful to give it different
+// addresses depending on the server side interface. That way the routing
+// table can be set up so that packets are forwarded to the correct access
+// point interface based on IP address.
+struct Lease {
+    Lease(unsigned int interfaceIndex, const uint8_t* macAddress) {
+        InterfaceIndex = interfaceIndex;
+        memcpy(MacAddress, macAddress, sizeof(MacAddress));
+    }
+    unsigned int InterfaceIndex;
+    uint8_t MacAddress[ETH_ALEN];
+};
+
+template<class T>
+inline void hash_combine(size_t& seed, const T& value) {
+    std::hash<T> hasher;
+    seed ^= hasher(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
+}
+
+namespace std {
+template<> struct hash<Lease> {
+    size_t operator()(const Lease& lease) const {
+        size_t seed = 0;
+        hash_combine(seed, lease.InterfaceIndex);
+        // Treat the first 4 bytes as an uint32_t to save some computation
+        hash_combine(seed, *reinterpret_cast<const uint32_t*>(lease.MacAddress));
+        // And the remaining 2 bytes as an uint16_t
+        hash_combine(seed,
+                     *reinterpret_cast<const uint16_t*>(lease.MacAddress + 4));
+        return seed;
+    }
+};
+}
+
+inline bool operator==(const Lease& left, const Lease& right) {
+    return left.InterfaceIndex == right.InterfaceIndex &&
+        memcmp(left.MacAddress, right.MacAddress, sizeof(left.MacAddress)) == 0;
+}
diff --git a/dhcp/server/log.h b/dhcp/server/log.h
new file mode 100644
index 0000000..bb1094f
--- /dev/null
+++ b/dhcp/server/log.h
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#pragma once
+
+#define LOG_TAG "dhcpserver"
+#include <cutils/log.h>
+
diff --git a/dhcp/server/main.cpp b/dhcp/server/main.cpp
new file mode 100644
index 0000000..eecafc1
--- /dev/null
+++ b/dhcp/server/main.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2017, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "dhcpserver.h"
+
+#include "log.h"
+
+#include <arpa/inet.h>
+#include <net/if.h>
+
+static void usage(const char* program) {
+    ALOGE("Usage: %s -i <interface> -r <", program);
+}
+
+int main(int argc, char* argv[]) {
+    in_addr_t rangeStart = 0;
+    in_addr_t rangeEnd = 0;
+    in_addr_t gateway = 0;
+    in_addr_t netmask = 0;
+    char* excludeInterfaceName = nullptr;
+    unsigned int excludeInterfaceIndex = 0;
+    for (int i = 1; i < argc; ++i) {
+        if (strcmp("--range", argv[i]) == 0) {
+            if (i + 1 >= argc) {
+                ALOGE("ERROR: Missing argument to --range parameter");
+                usage(argv[0]);
+                return 1;
+            }
+            char* divider = strchr(argv[i + 1], ',');
+            if (divider != nullptr) {
+                *divider = '\0';
+                struct in_addr address;
+                if (inet_pton(AF_INET, argv[i + 1], &address) > 0) {
+                    rangeStart = address.s_addr;
+                } else {
+                    ALOGE("ERROR: Invalid start address '%s'", argv[i + 1]);
+                    usage(argv[0]);
+                    return 1;
+                }
+                char* next = divider + 1;
+                if (inet_pton(AF_INET, next, &address) > 0) {
+                    rangeEnd = address.s_addr;
+                } else {
+                    ALOGE("ERROR: Invalid end address '%s'", next);
+                    usage(argv[0]);
+                    return 1;
+                }
+            } else {
+                ALOGE("ERROR: Invalid --range parameter '%s'", argv[i + 1]);
+                usage(argv[0]);
+                return 1;
+            }
+            ++i;
+        } else if (strcmp("--gateway", argv[i]) == 0) {
+            if (i + 1 >= argc) {
+                ALOGE("ERROR: Missing argument to --gateway parameter");
+                usage(argv[0]);
+                return 1;
+            }
+            struct in_addr address;
+            if (inet_pton(AF_INET, argv[i + 1], &address) > 0) {
+                gateway = address.s_addr;
+            } else {
+                ALOGE("ERROR: Invalid gateway '%s'", argv[i + 1]);
+                usage(argv[0]);
+                return 1;
+            }
+        } else if (strcmp("--netmask", argv[i]) == 0) {
+            if (i + 1 >= argc) {
+                ALOGE("ERROR: Missing argument to --netmask parameter");
+                usage(argv[0]);
+                return 1;
+            }
+            struct in_addr address;
+            if (inet_pton(AF_INET, argv[i + 1], &address) > 0) {
+                netmask = address.s_addr;
+            } else {
+                ALOGE("ERROR: Invalid netmask '%s'", argv[i + 1]);
+                usage(argv[0]);
+                return 1;
+            }
+        } else if (strcmp("--exclude-interface", argv[i]) == 0) {
+            if (i + 1 >= argc) {
+                ALOGE("ERROR: Missing argument to "
+                     "--exclude-interfaces parameter");
+                usage(argv[0]);
+                return 1;
+            }
+            excludeInterfaceName = argv[i + 1];
+            excludeInterfaceIndex = if_nametoindex(excludeInterfaceName);
+            if (excludeInterfaceIndex == 0) {
+                ALOGE("ERROR: Invalid argument '%s' to --exclude-interface",
+                     argv[i + 1]);
+                usage(argv[0]);
+                return 1;
+            }
+        }
+    }
+
+    if (rangeStart == 0 || rangeEnd == 0) {
+        ALOGE("ERROR: Missing or invalid --range argument");
+        usage(argv[0]);
+        return 1;
+    }
+    if (gateway == 0) {
+        ALOGE("ERROR: Missing or invalid --gateway argument");
+        usage(argv[0]);
+        return 1;
+    }
+    if (netmask == 0) {
+        ALOGE("ERROR: Missing or invalid --netmask argument");
+        usage(argv[0]);
+        return 1;
+    }
+
+    DhcpServer server(rangeStart,
+                      rangeEnd,
+                      netmask,
+                      gateway,
+                      excludeInterfaceIndex);
+    Result res = server.init();
+    if (!res) {
+        ALOGE("Failed to initialize DHCP server: %s\n", res.c_str());
+        return 1;
+    }
+
+    res = server.run();
+    if (!res) {
+        ALOGE("DHCP server failed: %s\n", res.c_str());
+        return 1;
+    }
+    // This is weird and shouldn't happen, the server should run forever.
+    return 0;
+}
+
+