From 6d841e9f81cad60d9e467bf1f0b0ae598bfb32c5 Mon Sep 17 00:00:00 2001 From: Ben Pfaff Date: Mon, 7 Jul 2008 17:18:34 -0700 Subject: [PATCH] Implement DHCP client. --- include/Makefile.am | 2 + include/dhcp-client.h | 61 +++ include/dhcp.h | 274 ++++++++++++ include/vlog.h | 2 + lib/Makefile.am | 2 + lib/dhcp-client.c | 868 +++++++++++++++++++++++++++++++++++++++ lib/dhcp.c | 791 +++++++++++++++++++++++++++++++++++ tests/Makefile.am | 5 + tests/test-dhcp-client.c | 185 +++++++++ 9 files changed, 2190 insertions(+) create mode 100644 include/dhcp-client.h create mode 100644 include/dhcp.h create mode 100644 lib/dhcp-client.c create mode 100644 lib/dhcp.c create mode 100644 tests/test-dhcp-client.c diff --git a/include/Makefile.am b/include/Makefile.am index e773ba0a..c37f1426 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -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 index 00000000..960e81ec --- /dev/null +++ b/include/dhcp-client.h @@ -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 +#include + +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 index 00000000..82f3d3e0 --- /dev/null +++ b/include/dhcp.h @@ -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 +#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 */ diff --git a/include/vlog.h b/include/vlog.h index 068a6277..fe5b4322 100644 --- a/include/vlog.h +++ b/include/vlog.h @@ -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) \ diff --git a/lib/Makefile.am b/lib/Makefile.am index cdc87ca4..404d925e 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -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 index 00000000..7fc621e1 --- /dev/null +++ b/lib/dhcp-client.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#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; +} + +/* 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 index 00000000..851c5638 --- /dev/null +++ b/lib/dhcp.c @@ -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 +#include +#include +#include +#include +#include +#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 "<>"; +} + +/* 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" + : "<>"), + 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); +} + diff --git a/tests/Makefile.am b/tests/Makefile.am index 49b79675..8c548c04 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 00000000..4ed6fc0a --- /dev/null +++ b/tests/test-dhcp-client.c @@ -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 +#include +#include +#include +#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); +} + -- 2.30.2