compiler.h \
csum.h \
daemon.h \
+ dhcp-client.h \
+ dhcp.h \
dynamic-string.h \
dpif.h \
fatal-signal.h \
--- /dev/null
+/* 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 */
--- /dev/null
+/* 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 */
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) \
command-line.c \
csum.c \
daemon.c \
+ dhcp-client.c \
+ dhcp.c \
dynamic-string.c \
fatal-signal.c \
fault.c \
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
+
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
+
--- /dev/null
+/* 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);
+}
+