Implement DHCP client.
authorBen Pfaff <blp@nicira.com>
Tue, 8 Jul 2008 00:18:34 +0000 (17:18 -0700)
committerBen Pfaff <blp@nicira.com>
Fri, 18 Jul 2008 21:16:40 +0000 (14:16 -0700)
include/Makefile.am
include/dhcp-client.h [new file with mode: 0644]
include/dhcp.h [new file with mode: 0644]
include/vlog.h
lib/Makefile.am
lib/dhcp-client.c [new file with mode: 0644]
lib/dhcp.c [new file with mode: 0644]
tests/Makefile.am
tests/test-dhcp-client.c [new file with mode: 0644]

index e773ba0a6c00a9639519ab09572dfeec83085d19..c37f1426a4a1109aa93993e01bef23a3093fc3b3 100644 (file)
@@ -4,6 +4,8 @@ noinst_HEADERS = \
        compiler.h \
        csum.h \
        daemon.h \
+       dhcp-client.h \
+       dhcp.h \
        dynamic-string.h \
        dpif.h \
        fatal-signal.h \
diff --git a/include/dhcp-client.h b/include/dhcp-client.h
new file mode 100644 (file)
index 0000000..960e81e
--- /dev/null
@@ -0,0 +1,61 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef DHCP_CLIENT_H
+#define DHCP_CLIENT_H 1
+
+#include <stdbool.h>
+#include <stdint.h>
+
+struct dhclient;
+struct dhcp_msg;
+int dhclient_create(const char *netdev,
+                    void (*modify_request)(struct dhcp_msg *, void *aux),
+                    bool (*validate_offer)(const struct dhcp_msg *, void *aux),
+                    void *aux, struct dhclient **);
+void dhclient_destroy(struct dhclient *);
+
+void dhclient_init(struct dhclient *, uint32_t requested_ip);
+void dhclient_release(struct dhclient *);
+void dhclient_force_renew(struct dhclient *, int deadline);
+bool dhclient_is_bound(const struct dhclient *);
+bool dhclient_changed(struct dhclient *);
+
+uint32_t dhclient_get_ip(const struct dhclient *);
+uint32_t dhclient_get_netmask(const struct dhclient *);
+const struct dhcp_msg *dhclient_get_config(const struct dhclient *);
+
+void dhclient_run(struct dhclient *);
+void dhclient_wait(struct dhclient *);
+
+#endif /* dhcp-client.h */
diff --git a/include/dhcp.h b/include/dhcp.h
new file mode 100644 (file)
index 0000000..82f3d3e
--- /dev/null
@@ -0,0 +1,274 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#ifndef DHCP_H
+#define DHCP_H 1
+
+#include <stdint.h>
+#include "packets.h"
+#include "util.h"
+
+struct ds;
+struct buffer;
+
+/* Values for 'op' field. */
+#define DHCP_BOOTREQUEST        1        /* Message sent by DHCP client. */
+#define DHCP_BOOTREPLY          2        /* Message sent by DHCP server. */
+
+/* Bits in 'flags' field. */
+#define DHCP_FLAGS_BROADCAST    0x8000 /* Server must broadcast all replies. */
+#define DHCP_FLAGS_MBZ          0x7fff /* Must be zero. */
+
+/* First four bytes of 'options' field. */
+#define DHCP_OPTS_COOKIE 0x63825363
+
+#define DHCP_HEADER_LEN 236
+struct dhcp_header {
+    uint8_t op;                 /* DHCP_BOOTREQUEST or DHCP_BOOTREPLY. */
+    uint8_t htype;              /* ARP_HRD_ETHERNET (typically). */
+    uint8_t hlen;               /* ETH_ADDR_LEN (typically). */
+    uint8_t hops;               /* Hop count; set to 0 by client. */
+    uint32_t xid;               /* Transaction ID. */
+    uint16_t secs;              /* Since client started address acquisition. */
+    uint16_t flags;             /* DHCP_FLAGS_*. */
+    uint32_t ciaddr;            /* Client IP, if it has a lease for one. */
+    uint32_t yiaddr;            /* Client ("your") IP address. */
+    uint32_t siaddr;            /* Next server IP address. */
+    uint32_t giaddr;            /* Relay agent IP address. */
+    uint8_t chaddr[16];         /* Client hardware address. */
+    char sname[64];             /* Optional server host name. */
+    char file[128];             /* Boot file name. */
+    /* Followed by variable-length options field. */
+};
+BUILD_ASSERT_DECL(DHCP_HEADER_LEN == sizeof(struct dhcp_header));
+
+#define DHCP_ARGS                                                             \
+    DHCP_ARG(FIXED, 0)          /* Fixed-length option (PAD and END only). */ \
+    DHCP_ARG(IP, 4)             /* IP addresses. */                           \
+    DHCP_ARG(SECS, 4)           /* 32-bit duration in seconds. */             \
+    DHCP_ARG(STRING, 1)         /* NVT string, optionally null-terminated. */ \
+    DHCP_ARG(UINT8, 1)          /* 8-bit unsigned integer. */                 \
+    DHCP_ARG(UINT16, 2)         /* 16-bit unsigned integer. */                \
+    DHCP_ARG(UINT32, 4)         /* 32-bit unsigned integer. */                \
+    DHCP_ARG(BOOLEAN, 1)        /* Boolean octet (0 or 1). */
+
+/* DHCP option argument types. */
+enum dhcp_arg_type {
+#define DHCP_ARG(NAME, SIZE) DHCP_ARG_##NAME,
+    DHCP_ARGS
+#undef DHCP_ARG
+};
+
+#define DHCP_MSGS                                                             \
+    DHCP_MSG(DHCPDISCOVER, 1)   /* Client->server: What IPs are available? */ \
+    DHCP_MSG(DHCPOFFER, 2)      /* Server->client: This IP is available. */   \
+    DHCP_MSG(DHCPREQUEST, 3)    /* Client->server: I want that IP. */         \
+    DHCP_MSG(DHCPDECLINE, 4)    /* Client->server: That IP is in use!. */     \
+    DHCP_MSG(DHCPACK, 5)        /* Server->client: You can have that IP. */   \
+    DHCP_MSG(DHCPNAK, 6)        /* Server->client: You can't have that IP. */ \
+    DHCP_MSG(DHCPRELEASE, 7)    /* Client->server: I'm done with this IP. */  \
+    DHCP_MSG(DCHPINFORM, 8)     /* Client->server: I'm using this IP. */
+
+/* DHCP message type (this is the argument for the DHCP_MSG_TYPE option). */
+enum dhcp_msg_type {
+#define DHCP_MSG(NAME, VALUE) NAME = VALUE,
+    DHCP_MSGS
+#undef DHCP_MSG
+};
+const char *dhcp_type_name(enum dhcp_msg_type);
+
+/* DHCP allows for 256 standardized options and 256 vendor-specific options.
+ * We put them in a single array, with the standard options at the
+ * beginning. */
+#define DHCP_N_OPTIONS          512
+#define DHCP_VENDOR_OFS         256
+
+/* DHCP options. */
+#define DHCP_OPTS                                                       \
+    /*                                        arg   min  max         */ \
+    /*          name                    code  type  args args        */ \
+    DHCP_OPT(PAD,                       0, FIXED,   0, 0)               \
+    DHCP_OPT(END,                     255, FIXED,   0, 0)               \
+    DHCP_OPT(SUBNET_MASK,               1, IP,      1, 1)               \
+    DHCP_OPT(TIME_OFFSET,               2, SECS,    1, 1)               \
+    DHCP_OPT(ROUTER,                    3, IP,      1, SIZE_MAX)        \
+    /* Time Server Option is obsolete. */                               \
+    /* Name Server Option is obsolete. */                               \
+    DHCP_OPT(DNS_SERVER,                6, IP,      1, SIZE_MAX)        \
+    /* Log Server Option is obsolete. */                                \
+    /* Cookie Server Option is obsolete. */                             \
+    DHCP_OPT(LPR_SERVER,                9, IP,      1, SIZE_MAX)        \
+    /* Impress Server Option is obsolete. */                            \
+    /* Resource Location Server Option is obsolete. */                  \
+    DHCP_OPT(HOST_NAME,                12, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(BOOT_FILE_SIZE,           13, UINT16,  1, 1)               \
+    /* Merit Dump File option is obsolete. */                           \
+    DHCP_OPT(DOMAIN_NAME,              15, STRING,  1, SIZE_MAX)        \
+    /* Swap Server option is obsolete. */                               \
+    DHCP_OPT(ROOT_PATH,                17, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(EXTENSIONS_PATH,          18, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(IP_FORWARDING,            19, BOOLEAN, 1, 1)               \
+    DHCP_OPT(SOURCE_ROUTING,           20, BOOLEAN, 1, 1)               \
+    DHCP_OPT(POLICY_FILTER,            21, IP,      2, SIZE_MAX)        \
+    DHCP_OPT(MAX_DGRAM_REASSEMBLY,     22, UINT16,  1, 1)               \
+    DHCP_OPT(IP_TTL,                   23, UINT8,   1, 1)               \
+    DHCP_OPT(PATH_MTU_TIMEOUT,         24, SECS,    1, 1)               \
+    DHCP_OPT(PATH_MTU_PLATEAU,         25, UINT16,  2, SIZE_MAX)        \
+    DHCP_OPT(MTU,                      26, UINT16,  1, 1)               \
+    DHCP_OPT(ALL_SUBNETS_ARE_LOCAL,    27, BOOLEAN, 1, 1)               \
+    DHCP_OPT(BROADCAST_ADDRESS,        28, IP,      1, 1)               \
+    DHCP_OPT(PERFORM_MASK_DISCOVERY,   29, BOOLEAN, 1, 1)               \
+    DHCP_OPT(MASK_SUPPLIER,            30, BOOLEAN, 1, 1)               \
+    DHCP_OPT(PERFORM_ROUTER_DISCOVERY, 31, BOOLEAN, 1, 1)               \
+    DHCP_OPT(ROUTER_SOLICITATION,      32, IP,      1, 1)               \
+    DHCP_OPT(STATIC_ROUTE,             33, IP,      2, SIZE_MAX)        \
+    /* Trailer Encapsulation Option is obsolete. */                     \
+    DHCP_OPT(ARP_CACHE_TIMEOUT,        35, SECS,    1, 1)               \
+    DHCP_OPT(ETHERNET_ENCAPSULATION,   36, BOOLEAN, 1, 1)               \
+    DHCP_OPT(TCP_TTL,                  37, UINT8,   1, 1)               \
+    DHCP_OPT(TCP_KEEPALIVE_INTERVAL,   38, SECS,    1, 1)               \
+    DHCP_OPT(TCP_KEEPALIVE_GARBAGE,    39, BOOLEAN, 1, 1)               \
+    DHCP_OPT(NIS_DOMAIN,               40, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(NIS_SERVERS,              41, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(NTP_SERVERS,              42, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(VENDOR_SPECIFIC,          43, UINT8,   1, SIZE_MAX)        \
+    DHCP_OPT(NETBIOS_NS,               44, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(NETBIOS_DDS,              45, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(NETBIOS_NODE_TYPE,        46, UINT8,   1, 1)               \
+    DHCP_OPT(NETBIOS_SCOPE,            47, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(X_FONT_SERVER,            48, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(XDM,                      49, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(NISPLUS_DOMAIN,           64, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(NISPLUS_SERVERS,          65, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(MOBILE_IP_HOME_AGENT,     68, IP,      0, SIZE_MAX)        \
+    DHCP_OPT(SMTP_SERVER,              69, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(POP3_SERVER,              70, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(NNTP_SERVER,              71, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(WWW_SERVER,               72, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(FINGER_SERVER,            73, IP,      1, SIZE_MAX)        \
+    DHCP_OPT(IRC_SERVER,               74, IP,      1, SIZE_MAX)        \
+    /* StreetTalk Server Option is obsolete. */                         \
+    /* StreetTalk Directory Assistance Server Option is obsolete. */    \
+    DHCP_OPT(REQUESTED_IP,             50, IP,      1, 1)               \
+    DHCP_OPT(LEASE_TIME,               51, SECS,    1, 1)               \
+    DHCP_OPT(OPTION_OVERLOAD,          52, UINT8,   1, 1)               \
+    DHCP_OPT(TFTP_SERVER,              66, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(BOOTFILE_NAME,            67, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(DHCP_MSG_TYPE,            53, UINT8,   1, 1)               \
+    DHCP_OPT(SERVER_IDENTIFIER,        54, IP,      1, 1)               \
+    DHCP_OPT(PARAMETER_REQUEST_LIST,   55, UINT8,   1, SIZE_MAX)        \
+    DHCP_OPT(MESSAGE,                  56, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(MAX_DHCP_MSG_SIZE,        57, UINT16,  1, 1)               \
+    DHCP_OPT(T1,                       58, SECS,    1, 1)               \
+    DHCP_OPT(T2,                       59, SECS,    1, 1)               \
+    DHCP_OPT(VENDOR_CLASS,             60, STRING,  1, SIZE_MAX)        \
+    DHCP_OPT(CLIENT_ID,                61, UINT8,   2, SIZE_MAX)        \
+    DHCP_VNDOPT(OFP_CONTROLLER_VCONN,   1, STRING,  1, SIZE_MAX)
+
+/* Shorthand for defining vendor options (used above). */
+#define DHCP_VNDOPT(NAME, CODE, ARG, MIN, MAX) \
+    DHCP_OPT(NAME, (CODE) + DHCP_VENDOR_OFS, ARG, MIN, MAX)
+
+/* DHCP option codes. */
+enum {
+#define DHCP_OPT(NAME, VALUE, ARGTYPE, MIN_ARGS, MAX_ARGS) \
+    DHCP_CODE_##NAME = VALUE,
+DHCP_OPTS
+#undef DHCP_OPT
+};
+
+/* The contents of a DHCP option.
+ *
+ * DHCP options can (rarely) be present but lack content.  To represent such an
+ * option, 'n' is 0 and 'data' is non-null (but does not point to anything
+ * useful).  */
+struct dhcp_option {
+    size_t n;                   /* Number of bytes of data. */
+    void *data;                 /* Data. */
+};
+
+/* Abstracted DHCP protocol message, to make them easier to manipulate than
+ * through raw protocol buffers. */
+struct dhcp_msg {
+    /* For use by calling code. */
+    uint8_t op;                 /* DHCP_BOOTREQUEST or DHCP_BOOTREPLY. */
+    uint32_t xid;               /* Transaction ID. */
+    uint16_t secs;              /* Since client started address acquisition. */
+    uint16_t flags;             /* DHCP_FLAGS_*. */
+    uint32_t ciaddr;            /* Client IP, if it has a lease for one. */
+    uint32_t yiaddr;            /* Client ("your") IP address. */
+    uint32_t siaddr;            /* Next server IP address. */
+    uint32_t giaddr;            /* Relay agent IP address. */
+    uint8_t chaddr[ETH_ADDR_LEN]; /* Client hardware address. */
+    enum dhcp_msg_type type;    /* DHCP_CODE_DHCP_MSG_TYPE option argument. */
+    struct dhcp_option options[DHCP_N_OPTIONS]; /* Indexed by option code. */
+
+    /* For direct use only by dhcp_msg_*() functions. */
+    uint8_t *data;
+    size_t data_used, data_allocated;
+};
+
+void dhcp_msg_init(struct dhcp_msg *);
+void dhcp_msg_uninit(struct dhcp_msg *);
+void dhcp_msg_copy(struct dhcp_msg *, const struct dhcp_msg *);
+void dhcp_msg_put(struct dhcp_msg *, int code, const void *, size_t);
+void dhcp_msg_put_bool(struct dhcp_msg *, int code, bool);
+void dhcp_msg_put_secs(struct dhcp_msg *, int code, uint32_t);
+void dhcp_msg_put_ip(struct dhcp_msg *, int code, uint32_t);
+void dhcp_msg_put_string(struct dhcp_msg *, int code, const char *);
+void dhcp_msg_put_uint8(struct dhcp_msg *, int code, uint8_t);
+void dhcp_msg_put_uint8_array(struct dhcp_msg *, int code,
+                              const uint8_t[], size_t n);
+void dhcp_msg_put_uint16(struct dhcp_msg *, int code, uint16_t);
+void dhcp_msg_put_uint16_array(struct dhcp_msg *, int code,
+                               const uint16_t[], size_t n);
+const void *dhcp_msg_get(const struct dhcp_msg *, int code, size_t offset,
+                         size_t size);
+bool dhcp_msg_get_bool(const struct dhcp_msg *, int code,
+                       size_t offset, bool *);
+bool dhcp_msg_get_secs(const struct dhcp_msg *, int code,
+                       size_t offset, uint32_t *);
+bool dhcp_msg_get_ip(const struct dhcp_msg *, int code,
+                     size_t offset, uint32_t *);
+char *dhcp_msg_get_string(const struct dhcp_msg *, int code);
+bool dhcp_msg_get_uint8(const struct dhcp_msg *, int code,
+                        size_t offset, uint8_t *);
+bool dhcp_msg_get_uint16(const struct dhcp_msg *, int code,
+                         size_t offset, uint16_t *);
+const char *dhcp_option_to_string(const struct dhcp_option *, int code,
+                                  struct ds *);
+const char *dhcp_msg_to_string(const struct dhcp_msg *, struct ds *);
+int dhcp_parse(struct dhcp_msg *, const struct buffer *);
+void dhcp_assemble(const struct dhcp_msg *, struct buffer *);
+
+#endif /* dhcp.h */
index 068a62779760eb821fd537612227312d5d128df0..fe5b4322a5efc56482e4bf2d6fb1d85e0de8c828 100644 (file)
@@ -66,6 +66,8 @@ enum vlog_facility vlog_get_facility_val(const char *name);
         VLOG_MODULE(ctlpath)                    \
         VLOG_MODULE(daemon)                     \
         VLOG_MODULE(datapath)                   \
+        VLOG_MODULE(dhcp)                       \
+        VLOG_MODULE(dhcp_client)                \
         VLOG_MODULE(dpif)                       \
         VLOG_MODULE(dpctl)                      \
         VLOG_MODULE(fault)                      \
index cdc87ca4aa770952f94391a6d6e5846d63189d89..404d925ede558ee20baf7b6b3a39184550a82491 100644 (file)
@@ -7,6 +7,8 @@ libopenflow_la_SOURCES = \
        command-line.c \
        csum.c \
        daemon.c \
+       dhcp-client.c \
+       dhcp.c \
        dynamic-string.c \
        fatal-signal.c \
        fault.c \
diff --git a/lib/dhcp-client.c b/lib/dhcp-client.c
new file mode 100644 (file)
index 0000000..7fc621e
--- /dev/null
@@ -0,0 +1,868 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include "dhcp-client.h"
+#include <arpa/inet.h>
+#include <assert.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include "buffer.h"
+#include "csum.h"
+#include "dhcp.h"
+#include "dynamic-string.h"
+#include "flow.h"
+#include "netdev.h"
+#include "ofp-print.h"
+#include "poll-loop.h"
+
+#define THIS_MODULE VLM_dhcp_client
+#include "vlog.h"
+
+#define DHCLIENT_STATES                         \
+    DHCLIENT_STATE(INIT, 1 << 0)                \
+    DHCLIENT_STATE(INIT_REBOOT, 1 << 1)         \
+    DHCLIENT_STATE(REBOOTING, 1 << 2)           \
+    DHCLIENT_STATE(SELECTING, 1 << 3)           \
+    DHCLIENT_STATE(REQUESTING, 1 << 4)          \
+    DHCLIENT_STATE(BOUND, 1 << 5)               \
+    DHCLIENT_STATE(RENEWING, 1 << 6)            \
+    DHCLIENT_STATE(REBINDING, 1 << 7)           \
+    DHCLIENT_STATE(RELEASED, 1 << 8)
+enum dhclient_state {
+#define DHCLIENT_STATE(NAME, VALUE) S_##NAME = VALUE,
+    DHCLIENT_STATES
+#undef DHCLIENT_STATE
+};
+
+static const char *
+state_name(enum dhclient_state state)
+{
+    switch (state) {
+#define DHCLIENT_STATE(NAME, VALUE) case S_##NAME: return #NAME;
+        DHCLIENT_STATES
+#undef DHCLIENT_STATE
+    }
+    return "***ERROR***";
+}
+
+struct dhclient {
+    /* Configuration. */
+    struct netdev *netdev;
+
+    void (*modify_request)(struct dhcp_msg *, void *aux);
+    bool (*validate_offer)(const struct dhcp_msg *, void *aux);
+    void *aux;
+
+    /* DHCP state. */
+    enum dhclient_state state;
+    unsigned int state_entered; /* When we transitioned to this state. */
+    uint32_t xid;               /* In host byte order. */
+    uint32_t ipaddr, netmask, router;
+    uint32_t server_ip;
+    struct dhcp_msg *binding;
+    bool changed;
+
+    unsigned int retransmit, delay; /* Used by send_reliably(). */
+
+    uint16_t next_ip_id;
+
+    unsigned int init_delay;    /* Used by S_INIT. */
+
+    time_t lease_expiration;
+    unsigned int bound_timeout;
+    unsigned int renewing_timeout;
+    unsigned int rebinding_timeout;
+
+    /* Used by dhclient_run() and dhclient_wait() */
+    unsigned int min_timeout;
+    int received;
+
+    /* Set when we send out a DHCPDISCOVER message. */
+    uint32_t secs;
+
+    struct ds s;
+};
+
+/* Minimum acceptable lease time, in seconds. */
+#define MIN_ACCEPTABLE_LEASE 15
+
+static void state_transition(struct dhclient *, enum dhclient_state);
+static unsigned int elapsed_in_this_state(const struct dhclient *cli);
+static bool timeout(struct dhclient *, unsigned int secs);
+
+static void dhclient_msg_init(struct dhclient *, enum dhcp_msg_type,
+                              struct dhcp_msg *);
+static void send_reliably(struct dhclient *cli,
+                          void (*make_packet)(struct dhclient *,
+                                              struct dhcp_msg *));
+static bool do_receive_msg(struct dhclient *, struct dhcp_msg *);
+static void do_send_msg(struct dhclient *, const struct dhcp_msg *);
+static bool receive_ack(struct dhclient *);
+
+static unsigned int fuzz(unsigned int x, int max_fuzz);
+static unsigned int calc_t2(unsigned int lease);
+static unsigned int calc_t1(unsigned int lease, unsigned int t2);
+
+static unsigned int clamp(unsigned int x, unsigned int min, unsigned int max);
+static unsigned int sat_add(unsigned int x, unsigned int y);
+static unsigned int sat_sub(unsigned int x, unsigned int y);
+static unsigned int sat_mul(unsigned int x, unsigned int y);
+
+/* Creates a new DHCP client to configure the network device 'netdev_name'
+ * (e.g. "eth0").
+ *
+ * If 'modify_request' is non-null, then each DHCP message to discover or
+ * request an address will be passed to it (along with auxiliary data 'aux').
+ * It may then add any desired options to the message for transmission.
+ *
+ * If 'validate_offer' is non-null, then each DHCP message that offers an
+ * address will be passed to it (along with auxiliary data 'aux') for
+ * validation: if it returns true, the address will accepted; otherwise, it
+ * will be rejected.
+ *
+ * The DHCP client will not start advertising for an IP address until
+ * dhclient_init() is called.
+ *
+ * If successful, returns 0 and sets '*cli' to the new DHCP client.  Otherwise,
+ * returns a positive errno value and sets '*cli' to a null pointer. */
+int
+dhclient_create(const char *netdev_name,
+                void (*modify_request)(struct dhcp_msg *, void *aux),
+                bool (*validate_offer)(const struct dhcp_msg *, void *aux),
+                void *aux, struct dhclient **cli_)
+{
+    struct in_addr any = { INADDR_ANY };
+    struct dhclient *cli;
+    struct netdev *netdev;
+    int error;
+
+    *cli_ = NULL;
+
+    error = netdev_open(netdev_name, ETH_TYPE_IP, &netdev);
+    /* XXX install socket filter to catch only DHCP packets. */
+    if (error) {
+        VLOG_ERR("could not open %s network device: %s",
+                 netdev_name, strerror(error));
+        return error;
+    }
+
+    error = netdev_set_in4(netdev, any, any);
+    if (error) {
+        VLOG_ERR("could not remove IPv4 address from %s network device: %s",
+                 netdev_name, strerror(error));
+        netdev_close(netdev);
+        return error;
+    }
+
+    cli = xcalloc(1, sizeof *cli);
+    cli->modify_request = modify_request;
+    cli->validate_offer = validate_offer;
+    cli->aux = aux;
+    cli->netdev = netdev;
+    cli->state = S_RELEASED;
+    cli->state_entered = time(0);
+    cli->xid = random_uint32();
+    cli->ipaddr = 0;
+    cli->server_ip = 0;
+    cli->next_ip_id = random_uint32();
+    cli->retransmit = cli->delay = 0;
+    cli->min_timeout = 1;
+    ds_init(&cli->s);
+    cli->changed = true;
+    *cli_ = cli;
+    return 0;
+}
+
+/* Forces 'cli' into a (re)initialization state, in which no address is bound
+ * but the client is advertising to obtain one.  If 'requested_ip' is nonzero,
+ * then the client will attempt to re-bind to that IP address; otherwise, it
+ * will not ask for any particular address. */
+void
+dhclient_init(struct dhclient *cli, uint32_t requested_ip)
+{
+    state_transition(cli, requested_ip ? S_INIT_REBOOT : S_INIT);
+    cli->ipaddr = requested_ip;
+    cli->changed = true;
+    cli->min_timeout = 0;
+    cli->init_delay = 0;
+}
+
+/* Forces 'cli' to release its bound IP address (if any).  The client will not
+ * advertise for a new address until dhclient_init() is called again. */
+void
+dhclient_release(struct dhclient *cli)
+{
+    if (dhclient_is_bound(cli)) {
+        struct dhcp_msg msg;
+        dhclient_msg_init(cli, DHCPRELEASE, &msg);
+        msg.ciaddr = cli->ipaddr;
+        do_send_msg(cli, &msg);
+        dhcp_msg_uninit(&msg);
+    }
+    state_transition(cli, S_RELEASED);
+    cli->min_timeout = UINT_MAX;
+}
+
+static void
+do_force_renew(struct dhclient *cli, int deadline)
+{
+    time_t now = time(0);
+    unsigned int lease_left = sat_sub(cli->lease_expiration, now);
+    if (lease_left <= deadline) {
+        if (cli->state & (S_RENEWING | S_REBINDING)) {
+            return;
+        }
+        deadline = lease_left;
+    }
+    if (cli->state & (S_BOUND | S_RENEWING)) {
+        state_transition(cli, S_RENEWING);
+        cli->renewing_timeout = deadline * 3 / 4;
+        cli->rebinding_timeout = deadline * 1 / 4;
+    } else {
+        state_transition(cli, S_REBINDING);
+        cli->rebinding_timeout = deadline;
+    }
+    cli->min_timeout = 0;
+}
+
+/* Forces 'cli' to attempt to renew the lease its current IP address (if any)
+ * within 'deadline' seconds.  If the deadline is not met, then the client
+ * gives up its IP address binding and re-starts the DHCP process. */
+void
+dhclient_force_renew(struct dhclient *cli, int deadline)
+{
+    /* Drain the receive queue so that we know that any DHCPACK we process is
+     * freshly received. */
+    netdev_drain(cli->netdev);
+
+    switch (cli->state) {
+    case S_INIT:
+    case S_INIT_REBOOT:
+    case S_REBOOTING:
+    case S_SELECTING:
+    case S_REQUESTING:
+        break;
+
+    case S_BOUND:
+    case S_RENEWING:
+    case S_REBINDING:
+        do_force_renew(cli, deadline);
+        break;
+
+    case S_RELEASED:
+        dhclient_init(cli, 0);
+        break;
+    }
+}
+
+/* Returns true if 'cli' is bound to an IP address, false otherwise. */
+bool
+dhclient_is_bound(const struct dhclient *cli)
+{
+    return cli->state & (S_BOUND | S_RENEWING | S_REBINDING);
+}
+
+/* Returns true if 'cli' has changed from bound to unbound, or vice versa, at
+ * least once since the last time this function was called.  */
+bool
+dhclient_changed(struct dhclient *cli)
+{
+    bool changed = cli->changed;
+    cli->changed = 0;
+    return changed;
+}
+
+/* If 'cli' is bound to an IP address, returns that IP address; otherwise,
+ * returns 0. */
+uint32_t
+dhclient_get_ip(const struct dhclient *cli)
+{
+    return dhclient_is_bound(cli) ? cli->ipaddr : 0;
+}
+
+/* If 'cli' is bound to an IP address, returns the netmask for that IP address;
+ * otherwise, returns 0. */
+uint32_t
+dhclient_get_netmask(const struct dhclient *cli)
+{
+    return dhclient_is_bound(cli) ? cli->netmask : 0;
+}
+
+/* If 'cli' is bound to an IP address, returns the DHCP message that was
+ * received to obtain that IP address (so that the caller can obtain additional
+ * options from it).  Otherwise, returns a null pointer. */
+const struct dhcp_msg *
+dhclient_get_config(const struct dhclient *cli)
+{
+    return dhclient_is_bound(cli) ? cli->binding : NULL;
+}
+\f
+/* DHCP protocol. */
+
+static void
+make_dhcpdiscover(struct dhclient *cli, struct dhcp_msg *msg)
+{
+    cli->secs = elapsed_in_this_state(cli);
+    dhclient_msg_init(cli, DHCPDISCOVER, msg);
+    if (cli->ipaddr) {
+        dhcp_msg_put_ip(msg, DHCP_CODE_REQUESTED_IP, cli->ipaddr);
+    }
+}
+
+static void
+make_dhcprequest(struct dhclient *cli, struct dhcp_msg *msg)
+{
+    dhclient_msg_init(cli, DHCPREQUEST, msg);
+    msg->ciaddr = dhclient_get_ip(cli);
+    if (cli->state == S_REQUESTING) {
+        dhcp_msg_put_ip(msg, DHCP_CODE_SERVER_IDENTIFIER, cli->server_ip);
+    }
+    dhcp_msg_put_ip(msg, DHCP_CODE_REQUESTED_IP, cli->ipaddr);
+}
+
+static void
+do_init(struct dhclient *cli, enum dhclient_state next_state)
+{
+    if (!cli->init_delay) {
+        cli->init_delay = clamp(fuzz(2, 8), 1, 10);
+    }
+    if (timeout(cli, cli->init_delay)) {
+        state_transition(cli, next_state);
+    }
+}
+
+static void
+dhclient_run_INIT(struct dhclient *cli)
+{
+    do_init(cli, S_SELECTING);
+}
+
+static void
+dhclient_run_INIT_REBOOT(struct dhclient *cli)
+{
+    do_init(cli, S_REBOOTING);
+}
+
+static void
+dhclient_run_REBOOTING(struct dhclient *cli)
+{
+    send_reliably(cli, make_dhcprequest);
+    if (!receive_ack(cli) && timeout(cli, 60)) {
+        state_transition(cli, S_INIT);
+    }
+}
+
+static bool
+dhcp_receive(struct dhclient *cli, unsigned int msgs, struct dhcp_msg *msg)
+{
+    while (do_receive_msg(cli, msg)) {
+        if (msg->type < 0 || msg->type > 31 || !((1u << msg->type) & msgs)) {
+            VLOG_DBG("received unexpected %s in %s state: %s",
+                     dhcp_type_name(msg->type), state_name(cli->state),
+                     dhcp_msg_to_string(msg, &cli->s));
+        } else if (msg->xid != cli->xid) {
+            VLOG_DBG("ignoring %s with xid != %08"PRIx32" in %s state: %s",
+                     dhcp_type_name(msg->type), msg->xid,
+                     state_name(cli->state), dhcp_msg_to_string(msg, &cli->s));
+        } else {
+            return true;
+        }
+        dhcp_msg_uninit(msg);
+    }
+    return false;
+}
+
+static bool
+validate_offered_options(struct dhclient *cli, const struct dhcp_msg *msg)
+{
+    uint32_t lease, netmask;
+    if (!dhcp_msg_get_secs(msg, DHCP_CODE_LEASE_TIME, 0, &lease)) {
+        VLOG_WARN("%s lacks lease time: %s",
+                  dhcp_type_name(msg->type), dhcp_msg_to_string(msg, &cli->s));
+    } else if (!dhcp_msg_get_ip(msg, DHCP_CODE_SUBNET_MASK, 0, &netmask)) {
+        VLOG_WARN("%s lacks netmask: %s",
+                  dhcp_type_name(msg->type), dhcp_msg_to_string(msg, &cli->s));
+    } else if (lease < MIN_ACCEPTABLE_LEASE) {
+        VLOG_WARN("Ignoring %s with %"PRIu32"-second lease time: %s",
+                  dhcp_type_name(msg->type), lease,
+                  dhcp_msg_to_string(msg, &cli->s));
+    } else if (cli->validate_offer && !cli->validate_offer(msg, cli->aux)) {
+        VLOG_DBG("client validation hook refused offer: %s",
+                 dhcp_msg_to_string(msg, &cli->s));
+    } else {
+        return true;
+    }
+    return false;
+}
+
+static void
+dhclient_run_SELECTING(struct dhclient *cli)
+{
+    struct dhcp_msg msg;
+
+    send_reliably(cli, make_dhcpdiscover);
+    if (cli->server_ip && timeout(cli, 60)) {
+        cli->server_ip = 0;
+        state_transition(cli, S_INIT);
+    }
+    for (; dhcp_receive(cli, 1u << DHCPOFFER, &msg); dhcp_msg_uninit(&msg)) {
+        if (!validate_offered_options(cli, &msg)) {
+            continue;
+        }
+        if (!dhcp_msg_get_ip(&msg, DHCP_CODE_SERVER_IDENTIFIER,
+                             0, &cli->server_ip)) {
+            VLOG_WARN("DHCPOFFER lacks server identifier: %s",
+                      dhcp_msg_to_string(&msg, &cli->s));
+            continue;
+        }
+
+        VLOG_DBG("accepting DHCPOFFER: %s", dhcp_msg_to_string(&msg, &cli->s));
+        cli->ipaddr = msg.yiaddr;
+        state_transition(cli, S_REQUESTING);
+        break;
+    }
+}
+
+static bool
+receive_ack(struct dhclient *cli)
+{
+    struct dhcp_msg msg;
+
+    if (!dhcp_receive(cli, (1u << DHCPACK) | (1u << DHCPNAK), &msg)) {
+        return false;
+    } else if (msg.type == DHCPNAK) {
+        dhcp_msg_uninit(&msg);
+        state_transition(cli, S_INIT);
+        return true;
+    } else if (!validate_offered_options(cli, &msg)) {
+        dhcp_msg_uninit(&msg);
+        return false;
+    } else {
+        uint32_t lease = 0, t1 = 0, t2 = 0;
+
+        if (cli->binding) {
+            dhcp_msg_uninit(cli->binding);
+        } else {
+            cli->binding = xmalloc(sizeof *cli->binding);
+        }
+        dhcp_msg_copy(cli->binding, &msg);
+
+        dhcp_msg_get_secs(&msg, DHCP_CODE_LEASE_TIME, 0, &lease);
+        dhcp_msg_get_secs(&msg, DHCP_CODE_T1, 0, &t1);
+        dhcp_msg_get_secs(&msg, DHCP_CODE_T2, 0, &t2);
+        assert(lease >= MIN_ACCEPTABLE_LEASE);
+
+        if (!t2 || t2 >= lease) {
+            t2 = calc_t2(lease);
+        }
+        if (!t1 || t1 >= t2) {
+            t1 = calc_t1(lease, t2);
+        }
+
+        cli->lease_expiration = sat_add(time(0), lease);
+        cli->bound_timeout = t1;
+        cli->renewing_timeout = t2 - t1;
+        cli->rebinding_timeout = lease - t2;
+
+        cli->ipaddr = msg.yiaddr;
+        dhcp_msg_get_ip(&msg, DHCP_CODE_SUBNET_MASK, 0, &cli->netmask);
+        if (!dhcp_msg_get_ip(&msg, DHCP_CODE_ROUTER, 0, &cli->router)) {
+            cli->router = INADDR_ANY;
+        }
+        state_transition(cli, S_BOUND);
+        VLOG_DBG("Bound: %s", dhcp_msg_to_string(&msg, &cli->s));
+        return true;
+    }
+}
+
+static void
+dhclient_run_REQUESTING(struct dhclient *cli)
+{
+    send_reliably(cli, make_dhcprequest);
+    if (!receive_ack(cli) && timeout(cli, 60)) {
+        state_transition(cli, S_INIT);
+    }
+}
+
+static void
+dhclient_run_BOUND(struct dhclient *cli)
+{
+    if (timeout(cli, cli->bound_timeout)) {
+        state_transition(cli, S_RENEWING);
+    }
+}
+
+static void
+dhclient_run_RENEWING(struct dhclient *cli)
+{
+    send_reliably(cli, make_dhcprequest);
+    if (!receive_ack(cli) && timeout(cli, cli->renewing_timeout)) {
+        state_transition(cli, S_REBINDING);
+    }
+}
+
+static void
+dhclient_run_REBINDING(struct dhclient *cli)
+{
+    send_reliably(cli, make_dhcprequest);
+    if (!receive_ack(cli) && timeout(cli, cli->rebinding_timeout)) {
+        state_transition(cli, S_INIT);
+    }
+}
+
+static void
+dhclient_run_RELEASED(struct dhclient *cli UNUSED)
+{
+    /* Nothing to do. */
+}
+
+/* Processes the DHCP protocol for 'cli'. */
+void
+dhclient_run(struct dhclient *cli)
+{
+    int old_state;
+    do {
+        old_state = cli->state;
+        cli->min_timeout = UINT_MAX;
+        cli->received = 0;
+        switch (cli->state) {
+#define DHCLIENT_STATE(NAME, VALUE) \
+            case S_##NAME: dhclient_run_##NAME(cli); break;
+            DHCLIENT_STATES
+#undef DHCLIENT_STATE
+        default:
+            NOT_REACHED();
+        }
+    } while (cli->state != old_state);
+}
+
+/* Sets up poll timeouts to wake up the poll loop when 'cli' needs to do some
+ * work. */
+void
+dhclient_wait(struct dhclient *cli)
+{
+    if (cli->min_timeout != UINT_MAX) {
+        poll_timer_wait(sat_mul(cli->min_timeout, 1000));
+    }
+    /* Reset timeout to 1 second.  This will have no effect ordinarily, because
+     * dhclient_run() will typically set it back to a higher value.  If,
+     * however, the caller fails to call dhclient_run() before its next call to
+     * dhclient_wait() we won't potentially block forever. */
+    cli->min_timeout = 1;
+
+    if (cli->state & (S_SELECTING | S_REQUESTING | S_RENEWING | S_REBINDING)) {
+        netdev_recv_wait(cli->netdev);
+    }
+}
+
+static void
+state_transition(struct dhclient *cli, enum dhclient_state state)
+{
+    bool was_bound = dhclient_is_bound(cli);
+    bool am_bound;
+    VLOG_DBG("entering %s", state_name(state));
+    cli->state = state;
+    cli->state_entered = time(0);
+    cli->retransmit = cli->delay = 0;
+    am_bound = dhclient_is_bound(cli);
+    if (was_bound != am_bound) {
+        struct in_addr addr, mask;
+        int error;
+
+        cli->changed = true;
+        if (am_bound) {
+            VLOG_WARN("%s: binding to "IP_FMT"/"IP_FMT,
+                      netdev_get_name(cli->netdev),
+                      IP_ARGS(&cli->ipaddr), IP_ARGS(&cli->netmask));
+            addr.s_addr = cli->ipaddr;
+            mask.s_addr = cli->netmask;
+        } else {
+            VLOG_WARN("%s: unbinding IPv4 network address",
+                      netdev_get_name(cli->netdev));
+            addr.s_addr = mask.s_addr = INADDR_ANY;
+        }
+        error = netdev_set_in4(cli->netdev, addr, mask);
+        if (error) {
+            VLOG_ERR("could not set %s address "IP_FMT"/"IP_FMT": %s",
+                     netdev_get_name(cli->netdev),
+                     IP_ARGS(&addr.s_addr), IP_ARGS(&mask.s_addr),
+                     strerror(error));
+        }
+        if (am_bound && !error && cli->router) {
+            struct in_addr router = { cli->router };
+            error = netdev_add_router(cli->netdev, router);
+            VLOG_WARN("%s: configuring router "IP_FMT,
+                      netdev_get_name(cli->netdev), IP_ARGS(cli->router));
+            if (error) {
+                VLOG_ERR("failed to add default route to "IP_FMT" on %s: %s",
+                         IP_ARGS(&router), netdev_get_name(cli->netdev),
+                         strerror(error));
+            }
+        }
+        if (am_bound) {
+            assert(cli->binding != NULL);
+        } else {
+            dhcp_msg_uninit(cli->binding);
+            free(cli->binding);
+            cli->binding = NULL;
+        }
+    }
+    if (cli->state & (S_SELECTING | S_REQUESTING | S_REBOOTING)) {
+        netdev_drain(cli->netdev);
+    }
+}
+
+static void
+send_reliably(struct dhclient *cli,
+              void (*make_packet)(struct dhclient *, struct dhcp_msg *))
+{
+    if (timeout(cli, cli->retransmit)) {
+        struct dhcp_msg msg;
+        make_packet(cli, &msg);
+        if (cli->modify_request) {
+            cli->modify_request(&msg, cli->aux);
+        }
+        do_send_msg(cli, &msg);
+        cli->delay = MIN(64, MAX(4, cli->delay * 2));
+        cli->retransmit += fuzz(cli->delay, 1);
+        timeout(cli, cli->retransmit);
+        dhcp_msg_uninit(&msg);
+     }
+}
+
+static void
+dhclient_msg_init(struct dhclient *cli, enum dhcp_msg_type type,
+                  struct dhcp_msg *msg)
+{
+    dhcp_msg_init(msg);
+    msg->op = DHCP_BOOTREQUEST;
+    msg->xid = cli->xid;
+    msg->secs = cli->secs;
+    msg->type = type;
+    memcpy(msg->chaddr, netdev_get_etheraddr(cli->netdev), ETH_ADDR_LEN);
+    
+}
+
+static unsigned int
+elapsed_in_this_state(const struct dhclient *cli)
+{
+    return time(0) - cli->state_entered;
+}
+
+static bool
+timeout(struct dhclient *cli, unsigned int secs)
+{
+    cli->min_timeout = MIN(cli->min_timeout, secs);
+    return time(0) >= sat_add(cli->state_entered, secs);
+}
+
+static bool
+do_receive_msg(struct dhclient *cli, struct dhcp_msg *msg)
+{
+    struct buffer b;
+
+    buffer_init(&b, netdev_get_mtu(cli->netdev) + VLAN_ETH_HEADER_LEN);
+    for (; cli->received < 50; cli->received++) {
+        const struct ip_header *ip;
+        const struct dhcp_header *dhcp;
+        struct flow flow;
+        int error;
+
+        buffer_clear(&b);
+        error = netdev_recv(cli->netdev, &b);
+        if (error) {
+            break;
+        }
+
+        flow_extract(&b, 0, &flow);
+        if (flow.dl_type != htons(ETH_TYPE_IP)
+            || flow.nw_proto != IP_TYPE_UDP
+            || flow.tp_dst != htons(68)
+            || !(eth_addr_is_broadcast(flow.dl_dst)
+                 || eth_addr_equals(flow.dl_dst,
+                                    netdev_get_etheraddr(cli->netdev)))) {
+            continue;
+        }
+
+        ip = b.l3;
+        if (IP_IS_FRAGMENT(ip->ip_frag_off)) {
+            /* We don't do reassembly. */
+            VLOG_WARN("ignoring fragmented DHCP datagram");
+            continue;
+        }
+
+        dhcp = b.l7;
+        if (!dhcp) {
+            VLOG_WARN("ignoring DHCP datagram with missing payload");
+            continue;
+        }
+
+        buffer_pull(&b, b.l7 - b.data);
+        error = dhcp_parse(msg, &b);
+        if (!error) {
+            VLOG_DBG("received %s", dhcp_msg_to_string(msg, &cli->s));
+            buffer_uninit(&b);
+            return true;
+        }
+    }
+    netdev_drain(cli->netdev);
+    buffer_uninit(&b);
+    return false;
+}
+
+static void
+do_send_msg(struct dhclient *cli, const struct dhcp_msg *msg)
+{
+    struct buffer b;
+    struct eth_header eh;
+    struct ip_header nh;
+    struct udp_header th;
+    uint32_t udp_csum;
+    int error;
+
+    buffer_init(&b, ETH_TOTAL_MAX);
+    buffer_reserve(&b, ETH_HEADER_LEN + IP_HEADER_LEN + UDP_HEADER_LEN);
+
+    dhcp_assemble(msg, &b);
+
+    memcpy(eh.eth_src, netdev_get_etheraddr(cli->netdev), ETH_ADDR_LEN);
+    memcpy(eh.eth_dst, eth_addr_broadcast, ETH_ADDR_LEN);
+    eh.eth_type = htons(ETH_TYPE_IP);
+
+    nh.ip_ihl_ver = IP_IHL_VER(5, IP_VERSION);
+    nh.ip_tos = 0;
+    nh.ip_tot_len = htons(IP_HEADER_LEN + UDP_HEADER_LEN + b.size);
+    nh.ip_id = htons(cli->next_ip_id++);
+    /* Our choice of ip_id could collide with the host's, screwing up fragment
+     * reassembly, so prevent fragmentation.  */
+    nh.ip_frag_off = htons(IP_DONT_FRAGMENT);
+    nh.ip_ttl = 64;
+    nh.ip_proto = IP_TYPE_UDP;
+    nh.ip_csum = 0;
+    nh.ip_src = dhclient_get_ip(cli);
+    /* XXX need to use UDP socket for nonzero server IPs so that we can get
+     * routing table support.
+     *
+     * if (...have server IP and in appropriate state...) {
+     *    nh.ip_dst = cli->server_ip;
+     * } else {
+     *    nh.ip_dst = INADDR_BROADCAST;
+     * }
+     */
+    nh.ip_dst = INADDR_BROADCAST;
+    nh.ip_csum = csum(&nh, sizeof nh);
+
+    th.udp_src = htons(66);
+    th.udp_dst = htons(67);
+    th.udp_len = htons(UDP_HEADER_LEN + b.size);
+    th.udp_csum = 0;
+    udp_csum = csum_add32(0, nh.ip_src);
+    udp_csum = csum_add32(udp_csum, nh.ip_dst);
+    udp_csum = csum_add16(udp_csum, IP_TYPE_UDP << 8);
+    udp_csum = csum_add16(udp_csum, th.udp_len);
+    udp_csum = csum_continue(udp_csum, &th, sizeof th);
+    th.udp_csum = csum_finish(csum_continue(udp_csum, b.data, b.size));
+
+    buffer_push(&b, &th, sizeof th);
+    buffer_push(&b, &nh, sizeof nh);
+    buffer_push(&b, &eh, sizeof eh);
+
+    /* Don't try to send the frame if it's too long for an Ethernet frame.  We
+     * disregard the network device's actual MTU because we don't want the
+     * frame to have to be discarded or fragmented if it travels over a regular
+     * Ethernet at some point.  1500 bytes should be enough for anyone. */
+    if (b.size <= ETH_TOTAL_MAX) {
+        VLOG_DBG("sending %s", dhcp_msg_to_string(msg, &cli->s));
+        error = netdev_send(cli->netdev, &b);
+        if (error) {
+            VLOG_ERR("send failed on %s: %s",
+                     netdev_get_name(cli->netdev), strerror(error));
+        }
+    } else {
+        VLOG_ERR("cannot send %zu-byte Ethernet frame", b.size);
+    }
+
+    buffer_uninit(&b);
+}
+
+static unsigned int
+fuzz(unsigned int x, int max_fuzz)
+{
+    /* Generate number in range [-max_fuzz, +max_fuzz]. */
+    int fuzz = random_range(max_fuzz * 2 + 1) - max_fuzz;
+    unsigned int y = x + fuzz;
+    return fuzz >= 0 ? (y >= x ? y : UINT_MAX) : (y <= x ? y : 0);
+}
+
+static unsigned int
+sat_add(unsigned int x, unsigned int y)
+{
+    return x + y >= x ? x + y : UINT_MAX;
+}
+
+static unsigned int
+sat_sub(unsigned int x, unsigned int y)
+{
+    return x >= y ? x - y : 0;
+}
+
+static unsigned int
+sat_mul(unsigned int x, unsigned int y)
+{
+    assert(y);
+    return x <= UINT_MAX / y ? x * y : UINT_MAX;
+}
+
+static unsigned int
+clamp(unsigned int x, unsigned int min, unsigned int max)
+{
+    return x < min ? min : x > max ? max : x;
+}
+
+static unsigned int
+calc_t2(unsigned int lease)
+{
+    unsigned int base = lease * 0.875;
+    return lease >= 60 ? clamp(fuzz(base, 10), 0, lease - 1) : base;
+}
+
+static unsigned int
+calc_t1(unsigned int lease, unsigned int t2)
+{
+    unsigned int base = lease / 2;
+    return lease >= 60 ? clamp(fuzz(base, 10), 0, t2 - 1) : base;
+}
diff --git a/lib/dhcp.c b/lib/dhcp.c
new file mode 100644 (file)
index 0000000..851c563
--- /dev/null
@@ -0,0 +1,791 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include "dhcp.h"
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <inttypes.h>
+#include <stdlib.h>
+#include "buffer.h"
+#include "dynamic-string.h"
+
+#define THIS_MODULE VLM_dhcp
+#include "vlog.h"
+
+/* Information about a DHCP argument type. */
+struct arg_type {
+    const char *name;           /* Name. */
+    size_t size;                /* Number of bytes per argument. */
+};
+
+static struct arg_type types[] = {
+#define DHCP_ARG(NAME, SIZE) [DHCP_ARG_##NAME] = {#NAME, SIZE},
+    DHCP_ARGS
+#undef DHCP_ARG
+};
+
+/* Information about a DHCP option. */
+struct option_class {
+    const char *name;           /* Name. */
+    enum dhcp_arg_type type;    /* Argument type. */
+    size_t min_args;            /* Minimum number of arguments. */
+    size_t max_args;            /* Maximum number of arguments. */
+};
+
+static struct option_class classes[DHCP_N_OPTIONS] = {
+    [0 ... 255] = {NULL, DHCP_ARG_UINT8, 0, SIZE_MAX},
+#define DHCP_OPT(NAME, CODE, TYPE, MIN, MAX) \
+    [CODE] = {#NAME, DHCP_ARG_##TYPE, MIN, MAX},
+    DHCP_OPTS
+#undef DHCP_OPT
+};
+
+static void copy_data(struct dhcp_msg *);
+
+const char *
+dhcp_type_name(enum dhcp_msg_type type)
+{
+    switch (type) {
+#define DHCP_MSG(NAME, VALUE) case NAME: return #NAME;
+        DHCP_MSGS
+#undef DHCP_MSG
+    }
+    return "<<unknown DHCP message type>>";
+}
+
+/* Initializes 'msg' as a DHCP message.  The message should be freed with
+ * dhcp_msg_uninit() when it is no longer needed. */
+void
+dhcp_msg_init(struct dhcp_msg *msg)
+{
+    memset(msg, 0, sizeof *msg);
+}
+
+/* Frees the contents of 'msg'.  The caller is responsible for freeing 'msg',
+ * if necessary. */
+void
+dhcp_msg_uninit(struct dhcp_msg *msg)
+{
+    if (msg) {
+        free(msg->data);
+    }
+}
+
+/* Initializes 'dst' as a copy of 'src'.  'dst' (and 'src') should be freed
+ * with dhcp_msg_uninit() when it is no longer needed. */
+void
+dhcp_msg_copy(struct dhcp_msg *dst, const struct dhcp_msg *src)
+{
+    *dst = *src;
+    dst->data_allocated = src->data_used;
+    dst->data_used = 0;
+    dst->data = xmalloc(dst->data_allocated);
+    copy_data(dst);
+}
+
+static void
+prealloc_data(struct dhcp_msg *msg, size_t n)
+{
+    size_t needed = msg->data_used + n;
+    if (needed > msg->data_allocated) {
+        uint8_t *old_data = msg->data;
+        msg->data_allocated = MAX(needed * 2, 64);
+        msg->data = xmalloc(msg->data_allocated);
+        if (old_data) {
+            copy_data(msg);
+            free(old_data);
+        }
+    }
+}
+
+static void *
+append_data(struct dhcp_msg *msg, const void *data, size_t n)
+{
+    uint8_t *p = &msg->data[msg->data_used];
+    memcpy(p, data, n);
+    msg->data_used += n;
+    return p;
+}
+
+static void
+copy_data(struct dhcp_msg *msg)
+{
+    int code;
+
+    msg->data_used = 0;
+    for (code = 0; code < DHCP_N_OPTIONS; code++) {
+        struct dhcp_option *opt = &msg->options[code];
+        if (opt->data) {
+            assert(msg->data_used + opt->n <= msg->data_allocated);
+            opt->data = append_data(msg, opt->data, opt->n);
+        }
+    }
+}
+
+/* Appends the 'n' bytes in 'data' to the DHCP option in 'msg' represented by
+ * 'code' (which must be in the range 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put(struct dhcp_msg *msg, int code,
+             const void *data, size_t n)
+{
+    struct dhcp_option *opt;
+    if (code == DHCP_CODE_PAD || code == DHCP_CODE_END) {
+        return;
+    }
+
+    opt = &msg->options[code];
+    prealloc_data(msg, n + opt->n);
+    if (opt->n) {
+        if (&msg->data[msg->data_used - opt->n] != opt->data) {
+            opt->data = append_data(msg, opt->data, opt->n);
+        }
+        append_data(msg, data, n);
+    } else {
+        opt->data = append_data(msg, data, n);
+    }
+    opt->n += n;
+}
+
+/* Appends the boolean value 'b', as a octet with value 0 (false) or 1 (true),
+ * to the DHCP option in 'msg' represented by 'code' (which must be in the
+ * range 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_bool(struct dhcp_msg *msg, int code, bool b_)
+{
+    char b = !!b_;
+    dhcp_msg_put(msg, code, &b, 1);
+}
+
+/* Appends the number of seconds 'secs', as a 32-bit number in network byte
+ * order, to the DHCP option in 'msg' represented by 'code' (which must be in
+ * the range 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_secs(struct dhcp_msg *msg, int code, uint32_t secs_)
+{
+    uint32_t secs = htonl(secs_);
+    dhcp_msg_put(msg, code, &secs, sizeof secs);
+}
+
+/* Appends the IP address 'ip', as a 32-bit number in network byte order, to
+ * the DHCP option in 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_ip(struct dhcp_msg *msg, int code, uint32_t ip)
+{
+    dhcp_msg_put(msg, code, &ip, sizeof ip);
+}
+
+/* Appends the ASCII string 'string', to the DHCP option in 'msg' represented
+ * by 'code' (which must be in the range 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_string(struct dhcp_msg *msg, int code, const char *string)
+{
+    dhcp_msg_put(msg, code, string, strlen(string));
+}
+
+/* Appends octet 'x' to DHCP option in 'msg' represented by 'code' (which must
+ * be in the range 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_uint8(struct dhcp_msg *msg, int code, uint8_t x)
+{
+    dhcp_msg_put(msg, code, &x, sizeof x);
+}
+
+/* Appends the 'n' octets in 'data' to DHCP option in 'msg' represented by
+ * 'code' (which must be in the range 0...DHCP_N_OPTIONS). */
+void dhcp_msg_put_uint8_array(struct dhcp_msg *msg, int code,
+                              const uint8_t data[], size_t n)
+{
+    dhcp_msg_put(msg, code, data, n);
+}
+
+/* Appends the 16-bit value in 'x', in network byte order, to DHCP option in
+ * 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_uint16(struct dhcp_msg *msg, int code, uint16_t x_)
+{
+    uint16_t x = htons(x_);
+    dhcp_msg_put(msg, code, &x, sizeof x);
+}
+
+
+/* Appends the 'n' 16-bit values in 'data', in network byte order, to DHCP
+ * option in 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS). */
+void
+dhcp_msg_put_uint16_array(struct dhcp_msg *msg, int code,
+                          const uint16_t data[], size_t n)
+{
+    size_t i;
+
+    for (i = 0; i < n; i++) {
+        dhcp_msg_put_uint16(msg, code, data[i]);
+    }
+}
+
+/* Returns a pointer to the 'size' bytes starting at byte offset 'offset' in
+ * the DHCP option in 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS).  If the option has fewer than 'offset + size' bytes,
+ * returns a null pointer. */
+const void *
+dhcp_msg_get(const struct dhcp_msg *msg, int code,
+             size_t offset, size_t size)
+{
+    const struct dhcp_option *opt = &msg->options[code];
+    return offset + size <= opt->n ? (const char *) opt->data + offset : NULL;
+}
+
+/* Stores in '*out' the boolean value at byte offset 'offset' in the DHCP
+ * option in 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS).  Returns true if successful, false if the option has
+ * fewer than 'offset + 1' bytes. */
+bool
+dhcp_msg_get_bool(const struct dhcp_msg *msg, int code, size_t offset,
+                  bool *out)
+{
+    const uint8_t *uint8 = dhcp_msg_get(msg, code, offset, sizeof *uint8);
+    if (uint8) {
+        *out = *uint8 != 0;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Stores in '*out' the 32-bit count of seconds at offset 'offset' (in
+ * 4-byte increments) in the DHCP option in 'msg' represented by 'code'
+ * (which must be in the range 0...DHCP_N_OPTIONS).  The value is converted to
+ * native byte order.  Returns true if successful, false if the option has
+ * fewer than '4 * (offset + 1)' bytes. */
+bool
+dhcp_msg_get_secs(const struct dhcp_msg *msg, int code, size_t offset,
+                  uint32_t *out)
+{
+    const uint32_t *uint32 = dhcp_msg_get(msg, code, offset * sizeof *uint32,
+                                          sizeof *uint32);
+    if (uint32) {
+        *out = ntohl(*uint32);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Stores in '*out' the IP address at offset 'offset' (in 4-byte increments) in
+ * the DHCP option in 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS).  The IP address is stored in network byte order.
+ * Returns true if successful, false if the option has fewer than '4 * (offset
+ * + 1)' bytes. */
+bool
+dhcp_msg_get_ip(const struct dhcp_msg *msg, int code,
+                size_t offset, uint32_t *out)
+{
+    const uint32_t *uint32 = dhcp_msg_get(msg, code, offset * sizeof *uint32,
+                                          sizeof *uint32);
+    if (uint32) {
+        *out = *uint32;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Returns the string in the DHCP option in 'msg' represented by 'code' (which
+ * must be in the range 0...DHCP_N_OPTIONS).  The caller is responsible for
+ * freeing the string with free().
+ *
+ * If 'msg' has no option represented by 'code', returns a null pointer.  (If
+ * the option was specified but had no content, then an empty string is
+ * returned, not a null pointer.) */
+char *
+dhcp_msg_get_string(const struct dhcp_msg *msg, int code)
+{
+    const struct dhcp_option *opt = &msg->options[code];
+    return opt->data ? xmemdup0(opt->data, opt->n) : NULL;
+}
+
+/* Stores in '*out' the octet at byte offset 'offset' in the DHCP option in
+ * 'msg' represented by 'code' (which must be in the range 0...DHCP_N_OPTIONS).
+ * Returns true if successful, false if the option has fewer than 'offset + 1'
+ * bytes. */
+bool
+dhcp_msg_get_uint8(const struct dhcp_msg *msg, int code,
+                   size_t offset, uint8_t *out)
+{
+    const uint8_t *uint8 = dhcp_msg_get(msg, code, offset, sizeof *uint8);
+    if (uint8) {
+        *out = *uint8;
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Stores in '*out' the 16-bit value at offset 'offset' (in 2-byte units) in
+ * the DHCP option in 'msg' represented by 'code' (which must be in the range
+ * 0...DHCP_N_OPTIONS).  The value is converted to native byte order.  Returns
+ * true if successful, false if the option has fewer than '2 * (offset + 1)'
+ * bytes. */
+bool
+dhcp_msg_get_uint16(const struct dhcp_msg *msg, int code,
+                    size_t offset, uint16_t *out)
+{
+    const uint16_t *uint16 = dhcp_msg_get(msg, code, offset * sizeof *uint16,
+                                          sizeof *uint16);
+    if (uint16) {
+        *out = ntohs(*uint16);
+        return true;
+    } else {
+        return false;
+    }
+}
+
+/* Appends a string representation of 'opt', which has the given 'code', to
+ * 'ds'. */
+const char *
+dhcp_option_to_string(const struct dhcp_option *opt, int code, struct ds *ds)
+{
+    struct option_class *class = &classes[code];
+    const struct arg_type *type = &types[class->type];
+    size_t offset;
+
+    ds_put_char(ds, ' ');
+    if (class->name) {
+        const char *cp;
+        for (cp = class->name; *cp; cp++) {
+            unsigned char c = *cp;
+            ds_put_char(ds, c == '_' ? '-' : tolower(c));
+        }
+    } else {
+        ds_put_format(ds, "option-%d", code);
+    }
+    ds_put_char(ds, '=');
+
+    if (class->type == DHCP_ARG_STRING) {
+        ds_put_char(ds, '"');
+    }
+    for (offset = 0; offset + type->size <= opt->n; offset += type->size) {
+        const void *p = (const char *) opt->data + offset;
+        const uint8_t *uint8 = p;
+        const uint32_t *uint32 = p;
+        const uint16_t *uint16 = p;
+        const char *cp = p;
+        unsigned char c;
+        unsigned int secs;
+
+        if (offset && class->type != DHCP_ARG_STRING) {
+            ds_put_cstr(ds, class->type == DHCP_ARG_UINT8 ? ":" : ", ");
+        }
+        switch (class->type) {
+        case DHCP_ARG_FIXED:
+            NOT_REACHED();
+        case DHCP_ARG_IP:
+            ds_put_format(ds, IP_FMT, IP_ARGS(uint32));
+            break;
+        case DHCP_ARG_UINT8:
+            ds_put_format(ds, "%02"PRIx8, *uint8);
+            break;
+        case DHCP_ARG_UINT16:
+            ds_put_format(ds, "%"PRIu16, ntohs(*uint16));
+            break;
+        case DHCP_ARG_UINT32:
+            ds_put_format(ds, "%"PRIu32, ntohl(*uint32));
+            break;
+        case DHCP_ARG_SECS:
+            secs = ntohl(*uint32);
+            if (secs >= 86400) {
+                ds_put_format(ds, "%ud", secs / 86400);
+                secs %= 86400;
+            }
+            if (secs >= 3600) {
+                ds_put_format(ds, "%uh", secs / 3600);
+                secs %= 3600;
+            }
+            if (secs >= 60) {
+                ds_put_format(ds, "%umin", secs / 60);
+                secs %= 60;
+            }
+            if (secs > 0 || *uint32 == 0) {
+                ds_put_format(ds, "%us", secs);
+            }
+            break;
+        case DHCP_ARG_STRING:
+            c = *cp;
+            if (isprint(c) && (!isspace(c) || c == ' ') && c != '\\') {
+                ds_put_char(ds, *cp);
+            } else {
+                ds_put_format(ds, "\\%03o", (int) c);
+            }
+            break;
+        case DHCP_ARG_BOOLEAN:
+            if (*uint8 == 0) {
+                ds_put_cstr(ds, "false");
+            } else if (*uint8 == 1) {
+                ds_put_cstr(ds, "true");
+            } else {
+                ds_put_format(ds, "**%"PRIu8"**", *uint8);
+            }
+            break;
+        }
+    }
+    if (class->type == DHCP_ARG_STRING) {
+        ds_put_char(ds, '"');
+    }
+    if (offset != opt->n) {
+        if (offset) {
+            ds_put_cstr(ds, ", ");
+        }
+        ds_put_cstr(ds, "**leftovers:");
+        for (; offset < opt->n; offset++) {
+            const void *p = (const char *) opt->data + offset;
+            const uint8_t *uint8 = p;
+            ds_put_format(ds, " %"PRIu8, *uint8);
+        }
+        ds_put_cstr(ds, "**");
+    }
+    return ds_cstr(ds);
+}
+
+/* Replaces 'ds' by a string representation of 'msg'. */
+const char *
+dhcp_msg_to_string(const struct dhcp_msg *msg, struct ds *ds)
+{
+    int code;
+
+    ds_clear(ds);
+    ds_put_format(ds, "%s %s xid=%08"PRIx32" secs=%"PRIu16,
+                  (msg->op == DHCP_BOOTREQUEST ? "BOOTREQUEST"
+                   : msg->op == DHCP_BOOTREPLY ? "BOOTREPLY"
+                   : "<<bad DHCP op>>"),
+                  dhcp_type_name(msg->type), msg->xid, msg->secs);
+    if (msg->flags) {
+        ds_put_cstr(ds, " flags=");
+        if (msg->flags & DHCP_FLAGS_BROADCAST) {
+            ds_put_cstr(ds, "[BROADCAST]");
+        }
+        if (msg->flags & DHCP_FLAGS_MBZ) {
+            ds_put_format(ds, "[0x%04"PRIx16"]", msg->flags & DHCP_FLAGS_MBZ);
+        }
+    }
+    if (msg->ciaddr) {
+        ds_put_format(ds, " ciaddr="IP_FMT, IP_ARGS(&msg->ciaddr));
+    }
+    if (msg->yiaddr) {
+        ds_put_format(ds, " yiaddr="IP_FMT, IP_ARGS(&msg->yiaddr));
+    }
+    if (msg->siaddr) {
+        ds_put_format(ds, " siaddr="IP_FMT, IP_ARGS(&msg->siaddr));
+    }
+    if (msg->giaddr) {
+        ds_put_format(ds, " giaddr="IP_FMT, IP_ARGS(&msg->giaddr));
+    }
+    ds_put_format(ds, " chaddr="ETH_ADDR_FMT, ETH_ADDR_ARGS(msg->chaddr));
+
+    for (code = 0; code < DHCP_N_OPTIONS; code++) {
+        const struct dhcp_option *opt = &msg->options[code];
+        if (opt->data) {
+            dhcp_option_to_string(opt, code, ds);
+        }
+    }
+    return ds_cstr(ds);
+}
+
+static void
+parse_options(struct dhcp_msg *msg, const char *name, void *data, size_t size,
+              int option_offset)
+{
+    struct buffer b;
+
+    b.data = data;
+    b.size = size;
+    for (;;) {
+        uint8_t *code, *len;
+        void *payload;
+
+        code = buffer_try_pull(&b, 1);
+        if (!code || *code == DHCP_CODE_END) {
+            break;
+        } else if (*code == DHCP_CODE_PAD) {
+            continue;
+        }
+
+        len = buffer_try_pull(&b, 1);
+        if (!len) {
+            VLOG_DBG("reached end of %s expecting length byte", name);
+            break;
+        }
+
+        payload = buffer_try_pull(&b, *len);
+        if (!payload) {
+            VLOG_DBG("expected %"PRIu8" bytes of option-%"PRIu8" payload "
+                     "with only %zu bytes of %s left",
+                     *len, *code, b.size, name);
+            break;
+        }
+        dhcp_msg_put(msg, *code + option_offset, payload, *len);
+    }
+}
+
+static void
+validate_options(struct dhcp_msg *msg)
+{
+    int code;
+
+    for (code = 0; code < DHCP_N_OPTIONS; code++) {
+        struct dhcp_option *opt = &msg->options[code];
+        struct option_class *class = &classes[code];
+        struct arg_type *type = &types[class->type];
+        if (opt->data) {
+            size_t n_elems = opt->n / type->size;
+            size_t remainder = opt->n % type->size;
+            bool ok = true;
+            if (remainder) {
+                VLOG_DBG("%s option has %zu %zu-byte %s arguments with "
+                         "%zu bytes left over",
+                         class->name, n_elems, type->size,
+                         type->name, remainder);
+                ok = false;
+            }
+            if (n_elems < class->min_args || n_elems > class->max_args) {
+                VLOG_DBG("%s option has %zu %zu-byte %s arguments but "
+                         "between %zu and %zu are required",
+                         class->name, n_elems, type->size, type->name,
+                         class->min_args, class->max_args);
+                ok = false;
+            }
+            if (!ok) {
+                struct ds ds = DS_EMPTY_INITIALIZER;
+                VLOG_DBG("%s option contains: %s",
+                         class->name, dhcp_option_to_string(opt, code, &ds));
+                ds_destroy(&ds);
+
+                opt->n = 0;
+                opt->data = NULL;
+            }
+        }
+    }
+}
+
+/* Attempts to parse 'b' as a DHCP message.  If successful, initializes '*msg'
+ * to the parsed message and returns 0.  Otherwise, returns a positive errno
+ * value and '*msg' is indeterminate. */
+int
+dhcp_parse(struct dhcp_msg *msg, const struct buffer *b_)
+{
+    struct buffer b = *b_;
+    struct dhcp_header *dhcp;
+    uint32_t *cookie;
+    uint8_t type;
+    char *vendor_class;
+
+    dhcp = buffer_try_pull(&b, sizeof *dhcp);
+    if (!dhcp) {
+        VLOG_DBG("buffer too small for DHCP header (%zu bytes)", b.size);
+        goto error;
+    }
+
+    if (dhcp->op != DHCP_BOOTREPLY && dhcp->op != DHCP_BOOTREQUEST) {
+        VLOG_DBG("invalid DHCP op (%"PRIu8")", dhcp->op);
+        goto error;
+    }
+    if (dhcp->htype != ARP_HRD_ETHERNET) {
+        VLOG_DBG("invalid DHCP htype (%"PRIu8")", dhcp->htype);
+        goto error;
+    }
+    if (dhcp->hlen != ETH_ADDR_LEN) {
+        VLOG_DBG("invalid DHCP hlen (%"PRIu8")", dhcp->hlen);
+        goto error;
+    }
+
+    dhcp_msg_init(msg);
+    msg->op = dhcp->op;
+    msg->xid = ntohl(dhcp->xid);
+    msg->secs = ntohs(dhcp->secs);
+    msg->flags = ntohs(dhcp->flags);
+    msg->ciaddr = dhcp->ciaddr;
+    msg->yiaddr = dhcp->yiaddr;
+    msg->siaddr = dhcp->siaddr;
+    msg->giaddr = dhcp->giaddr;
+    memcpy(msg->chaddr, dhcp->chaddr, ETH_ADDR_LEN);
+
+    cookie = buffer_try_pull(&b, sizeof cookie);
+    if (cookie) {
+        if (ntohl(*cookie) == DHCP_OPTS_COOKIE) {
+            uint8_t overload;
+
+            parse_options(msg, "options", b.data, b.size, 0);
+            if (dhcp_msg_get_uint8(msg, DHCP_CODE_OPTION_OVERLOAD,
+                                   0, &overload)) {
+                if (overload & 1) {
+                    parse_options(msg, "file", dhcp->file, sizeof dhcp->file,
+                                  0);
+                }
+                if (overload & 2) {
+                    parse_options(msg, "sname",
+                                  dhcp->sname, sizeof dhcp->sname, 0);
+                }
+            }
+        } else {
+            VLOG_DBG("bad DHCP options cookie: %08"PRIx32, ntohl(*cookie));
+        }
+    } else {
+        VLOG_DBG("DHCP packet has no options");
+    }
+
+    vendor_class = dhcp_msg_get_string(msg, DHCP_CODE_VENDOR_CLASS);
+    if (vendor_class && !strcmp(vendor_class, "OpenFlow")) {
+        parse_options(msg, "vendor-specific",
+                      msg->options[DHCP_CODE_VENDOR_SPECIFIC].data,
+                      msg->options[DHCP_CODE_VENDOR_SPECIFIC].n,
+                      DHCP_VENDOR_OFS);
+    }
+    free(vendor_class);
+
+    validate_options(msg);
+    if (!dhcp_msg_get_uint8(msg, DHCP_CODE_DHCP_MSG_TYPE, 0, &type)) {
+        VLOG_DBG("missing DHCP message type");
+        dhcp_msg_uninit(msg);
+        goto error;
+    }
+    msg->type = type;
+    return 0;
+
+error:
+    if (VLOG_IS_DBG_ENABLED()) {
+        struct ds ds;
+
+        ds_init(&ds);
+        ds_put_hex_dump(&ds, b_->data, b_->size, 0, true);
+        VLOG_DBG("invalid DHCP message dump:\n%s", ds_cstr(&ds));
+
+        ds_clear(&ds);
+        dhcp_msg_to_string(msg, &ds);
+        VLOG_DBG("partially dissected DHCP message: %s", ds_cstr(&ds));
+
+        ds_destroy(&ds);
+    }
+    return EPROTO;
+}
+
+static void
+put_option_chunk(struct buffer *b, uint8_t code, void *data, size_t n)
+{
+    uint8_t header[2];
+
+    assert(n < 256);
+    header[0] = code;
+    header[1] = n;
+    buffer_put(b, header, sizeof header);
+    buffer_put(b, data, n);
+}
+
+static void
+put_option(struct buffer *b, uint8_t code, void *data, size_t n)
+{
+    if (data) {
+        if (n) {
+            /* Divide the data into chunks of 255 bytes or less.  Make
+             * intermediate chunks multiples of 8 bytes in case the
+             * recipient validates a chunk at a time instead of the
+             * concatenated value. */
+            uint8_t *p = data;
+            while (n) {
+                size_t chunk = n > 255 ? 248 : n;
+                put_option_chunk(b, code, p, chunk);
+                p += chunk;
+                n -= chunk;
+            }
+        } else {
+            /* Option should be present but carry no data. */
+            put_option_chunk(b, code, NULL, 0);
+        }
+    }
+}
+
+/* Appends to 'b' the DHCP message represented by 'msg'. */
+void
+dhcp_assemble(const struct dhcp_msg *msg, struct buffer *b)
+{
+    const uint8_t end = DHCP_CODE_END;
+    uint32_t cookie = htonl(DHCP_OPTS_COOKIE);
+    struct buffer vnd_data;
+    struct dhcp_header dhcp;
+    int i;
+
+    memset(&dhcp, 0, sizeof dhcp);
+    dhcp.op = msg->op;
+    dhcp.htype = ARP_HRD_ETHERNET;
+    dhcp.hlen = ETH_ADDR_LEN;
+    dhcp.hops = 0;
+    dhcp.xid = htonl(msg->xid);
+    dhcp.secs = htons(msg->secs);
+    dhcp.flags = htons(msg->flags);
+    dhcp.ciaddr = msg->ciaddr;
+    dhcp.yiaddr = msg->yiaddr;
+    dhcp.siaddr = msg->siaddr;
+    dhcp.giaddr = msg->giaddr;
+    memcpy(dhcp.chaddr, msg->chaddr, ETH_ADDR_LEN);
+    buffer_put(b, &dhcp, sizeof dhcp);
+    buffer_put(b, &cookie, sizeof cookie);
+
+    /* Put DHCP message type first.  (The ordering is not required but it
+     * seems polite.) */
+    if (msg->type) {
+        uint8_t type = msg->type;
+        put_option(b, DHCP_CODE_DHCP_MSG_TYPE, &type, 1);
+    }
+
+    /* Put the standard options. */
+    for (i = 0; i < DHCP_VENDOR_OFS; i++) {
+        const struct dhcp_option *option = &msg->options[i];
+        put_option(b, i, option->data, option->n);
+    }
+
+    /* Assemble vendor specific option and put it. */
+    buffer_init(&vnd_data, 0);
+    for (i = DHCP_VENDOR_OFS; i < DHCP_N_OPTIONS; i++) {
+        const struct dhcp_option *option = &msg->options[i];
+        put_option(&vnd_data, i - DHCP_VENDOR_OFS, option->data, option->n);
+    }
+    if (vnd_data.size) {
+        put_option(b, DHCP_CODE_VENDOR_SPECIFIC, vnd_data.data, vnd_data.size);
+    }
+    buffer_uninit(&vnd_data);
+
+    /* Put end-of-options option. */
+    buffer_put(b, &end, sizeof end);
+}
+
index 49b79675f8b0cadcd5711f0ff62a7335730dbf77..8c548c048c85e14346193db901f801ab2534a38c 100644 (file)
@@ -10,3 +10,8 @@ test_list_LDADD = ../lib/libopenflow.la
 TESTS += test-type-props
 check_PROGRAMS += test-type-props
 test_type_props_SOURCES = test-type-props.c
+
+check_PROGRAMS += test-dhcp-client
+test_dhcp_client_SOURCES = test-dhcp-client.c
+test_dhcp_client_LDADD = ../lib/libopenflow.la -ldl
+
diff --git a/tests/test-dhcp-client.c b/tests/test-dhcp-client.c
new file mode 100644 (file)
index 0000000..4ed6fc0
--- /dev/null
@@ -0,0 +1,185 @@
+/* Copyright (c) 2008 The Board of Trustees of The Leland Stanford
+ * Junior University
+ *
+ * We are making the OpenFlow specification and associated documentation
+ * (Software) available for public use and benefit with the expectation
+ * that others will use, modify and enhance the Software and contribute
+ * those enhancements back to the community. However, since we would
+ * like to make the Software available for broadest use, with as few
+ * restrictions as possible permission is hereby granted, free of
+ * charge, to any person obtaining a copy of this Software to deal in
+ * the Software under the copyrights without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
+ * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
+ * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ *
+ * The name and trademarks of copyright holder(s) may NOT be used in
+ * advertising or publicity pertaining to the Software or any
+ * derivatives without specific, written prior permission.
+ */
+
+#include "dhcp-client.h"
+#include <arpa/inet.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <limits.h>
+#include "command-line.h"
+#include "dhcp.h"
+#include "fatal-signal.h"
+#include "fault.h"
+#include "poll-loop.h"
+#include "util.h"
+#include "vlog.h"
+
+/* --request-ip: IP address to request from server.  If zero, then do not
+ * request a specific IP address. */
+static struct in_addr request_ip;
+
+/* --vendor-class: Vendor class string to include in request.  If null, no
+ * vendor class string is included. */
+static const char *vendor_class;
+
+static void parse_options(int argc, char *argv[]);
+static void usage(void);
+static void release(void *cli_);
+static void modify_dhcp_request(struct dhcp_msg *, void *aux);
+
+int
+main(int argc, char *argv[])
+{
+    struct dhclient *cli;
+    int error;
+
+    set_program_name(argv[0]);
+    register_fault_handlers();
+    vlog_init();
+    parse_options(argc, argv);
+
+    argc -= optind;
+    argv += optind;
+    if (argc != 1) {
+        fatal(0, "exactly one non-option argument required; "
+              "use --help for help");
+    }
+
+    error = dhclient_create(argv[0], modify_dhcp_request, NULL, NULL, &cli);
+    if (error) {
+        fatal(error, "dhclient_create failed");
+    }
+    dhclient_init(cli, request_ip.s_addr);
+    fatal_signal_add_hook(release, cli);
+
+    for (;;) {
+        fatal_signal_block();
+        dhclient_run(cli);
+        fatal_signal_unblock();
+        dhclient_wait(cli);
+        poll_block();
+    }
+}
+
+static void
+release(void *cli_)
+{
+    struct dhclient *cli = cli_;
+    dhclient_release(cli);
+}
+
+static void
+modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED)
+{
+    if (vendor_class) {
+        dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, vendor_class);
+    }
+}
+
+static void
+parse_options(int argc, char *argv[])
+{
+    enum {
+        OPT_REQUEST_IP = UCHAR_MAX + 1,
+        OPT_VENDOR_CLASS
+    };
+    static struct option long_options[] = {
+        {"request-ip",  required_argument, 0, OPT_REQUEST_IP },
+        {"vendor-class", required_argument, 0, OPT_VENDOR_CLASS },
+        {"verbose",     optional_argument, 0, 'v'},
+        {"help",        no_argument, 0, 'h'},
+        {"version",     no_argument, 0, 'V'},
+        {0, 0, 0, 0},
+    };
+    char *short_options = long_options_to_short_options(long_options);
+
+    for (;;) {
+        int c;
+
+        c = getopt_long(argc, argv, short_options, long_options, NULL);
+        if (c == -1) {
+            break;
+        }
+
+        switch (c) {
+        case OPT_REQUEST_IP:
+            if (!inet_aton(optarg, &request_ip)) {
+                fatal(0, "--request-ip argument is not a valid IP address");
+            }
+            break;
+
+        case OPT_VENDOR_CLASS:
+            vendor_class = optarg;
+            break;
+
+        case 'h':
+            usage();
+
+        case 'V':
+            printf("%s "VERSION" compiled "__DATE__" "__TIME__"\n", argv[0]);
+            exit(EXIT_SUCCESS);
+
+        case 'v':
+            vlog_set_verbosity(optarg);
+            break;
+
+        case '?':
+            exit(EXIT_FAILURE);
+
+        default:
+            abort();
+        }
+    }
+    free(short_options);
+}
+
+static void
+usage(void)
+{
+    printf("%s: standalone program for testing OpenFlow DHCP client.\n"
+           "usage: %s [OPTIONS] NETDEV\n"
+           "where NETDEV is a network device (e.g. eth0).\n"
+           "\nDHCP options:\n"
+           "  --request-ip=IP         request specified IP address (default:\n"
+           "                          do not request a specific IP)\n"
+           "  --vendor-class=STRING   use STRING as vendor class (default:\n"
+           "                          none); use OpenFlow to imitate secchan\n"
+           "\nOther options:\n"
+           "  -v, --verbose=MODULE:FACILITY:LEVEL  configure logging levels\n"
+           "  -v, --verbose           set maximum verbosity level\n"
+           "  -h, --help              display this help message\n"
+           "  -V, --version           display version information\n",
+           program_name, program_name);
+    exit(EXIT_SUCCESS);
+}
+