diff --git a/.gitignore b/.gitignore index 0332974..ff011f2 100644 --- a/.gitignore +++ b/.gitignore @@ -32,8 +32,6 @@ /configure-stamp /depcomp /install-sh -/libtool -/ltmain.sh /missing /stamp-h1 Module.symvers diff --git a/INSTALL b/INSTALL index fbacb67..742a18d 100644 --- a/INSTALL +++ b/INSTALL @@ -402,48 +402,72 @@ following instructions to set up remote switches: network. There are two ways to do this: - Use a "control network" that is completely separate from the - "data network" to be controlled. To do so, configure a - network device (one that has not been added to the datapath - with "dpctl addif") to access the control network in the usual - way. + "data network" to be controlled ("out-of-band control"). To + do so, configure a network device (one that has not been added + to the datapath with "dpctl addif") to access the control + network in the usual way. - - Use the same network for control and for data. For this - purpose, each datapath nl:K has a corresponding virtual - network device named ofK. Start by bringing up of0 before you - start the secure channel: + - Use the same network for control and for data ("in-band + control"). For this purpose, each datapath nl:K has a + corresponding virtual network device named ofK. - # ifconfig of0 up + When in-band control is used, the location of the controller + may be configured manually or discovered automatically: - Before the secure channel starts up, the of0 device cannot - send or receive any packets, so the next step depends on - whether connectivity is required to configure the device's IP - address: + * Manual configuration: Start by bringing up of0 before + you start the secure channel: - . If the switch has a static IP address, you may configure - its IP address now, e.g.: + # ifconfig of0 up - # ifconfig of0 192.168.1.1 + Before the secure channel starts up, the of0 device + cannot send or receive any packets, so the next step + depends on whether connectivity is required to configure + the device's IP address: - . If the switch does not have a static IP address, e.g. its - IP address is obtained dynamically via DHCP, then proceed - to step 4. The DHCP client will not be able to contact - the DHCP server until the secure channel has started up. + . If the switch has a static IP address, you may + configure its IP address now, e.g.: + + # ifconfig of0 192.168.1.1 + + . If the switch does not have a static IP address, + e.g. its IP address is obtained dynamically via + DHCP, then proceed to step 4. The DHCP client will + not be able to contact the DHCP server until the + secure channel has started up. + + * Controller discovery: No special setup is required at + the switch, but you must specially configure a DHCP + server to give out the switch's IP address and to tell + it the location of the controller. See secchan(8) for + details. 4. Run secchan on the datapath host to start the secure channel connecting the datapath to a remote controller. (See secchan(8) - for usage details). The channel should be configured to connect to - the controller's IP address on the port configured in step 2. + for usage details). The details depend on how you configured the + network in step 3: - If the controller is running on host 192.168.1.2 port 975 (the - default port) and the datapath ID is 0, the secchan invocation - would look like: + - If you are using in-band control and controller discovery, + invoke secchan something like this: - # secchan -v nl:0 tcp:192.168.1.2 + # secchan -v nl:0 - If you are using separate control and data networks, or if the - networks are combined and the switch has a static IP address, the - secure channel should quickly connect to the controller. Setup is - now complete. Otherwise, proceed to step 5. + The secure channel should connect to the controller after it + obtains its own IP address and the controller's location via + DHCP. This can take a few seconds. Switch setup is now + complete. + + - Otherwise, the secure channel should be configured to connect + to the controller's IP address on the port configured in step + 2. If the controller is running on host 192.168.1.2 port 975 + (the default port) and the datapath ID is 0, the secchan + invocation would look like: + + # secchan -v nl:0 tcp:192.168.1.2 + + If you are using out-of-band control, or if you are using + in-band control and the switch has a static IP address, the + secure channel should quickly connect to the controller. + Setup is now complete. Otherwise, proceed to step 5. 5. If you are using the same network for control and data, and the switch obtains its IP address dynamically, then you may now obtain diff --git a/Make.vars b/Make.vars index 98a9e44..583d3eb 100644 --- a/Make.vars +++ b/Make.vars @@ -1,7 +1,6 @@ # -*- makefile -*- AM_CPPFLAGS = $(SSL_CFLAGS) -LIBS = $(SSL_LIBS) COMMON_FLAGS = -DVERSION=\"$(VERSION)\" if NDEBUG diff --git a/configure.ac b/configure.ac index 00cf0f3..afa5e3b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,10 +1,12 @@ AC_PREREQ(2.59) AC_INIT(openflow, v0.8.1, info@openflowswitch.org) +AC_CONFIG_HEADERS([config.h]) AM_INIT_AUTOMAKE AC_PROG_CC AC_PROG_CPP AC_PROG_LD +AC_PROG_RANLIB AC_ARG_VAR([PERL], [path to Perl interpreter]) AC_PATH_PROG([PERL], perl, no) @@ -14,8 +16,6 @@ fi AC_USE_SYSTEM_EXTENSIONS -AC_PROG_LIBTOOL - AC_ARG_ENABLE( [ndebug], [AC_HELP_STRING([--enable-ndebug], @@ -132,8 +132,9 @@ if test "$HAVE_OPENSSL" = yes; then fi AC_CHECK_LIB([socket], [connect]) -AC_CHECK_LIB([resolv], [gethostbyname]) -AC_CHECK_LIB([dl], [dladdr]) +AC_SEARCH_LIBS([gethostbyname], [resolv], [RESOLVER_LIBS=-lresolv]) +AC_CHECK_LIB([dl], [dladdr], [FAULT_LIBS=-ldl]) +AC_SUBST([FAULT_LIBS]) CFLAGS="$CFLAGS -Wall -Wno-sign-compare" diff --git a/controller/Makefile.am b/controller/Makefile.am index 1a5e01a..81863ff 100644 --- a/controller/Makefile.am +++ b/controller/Makefile.am @@ -5,7 +5,7 @@ man_MANS = controller.8 DISTCLEANFILES = controller.8 controller_SOURCES = controller.c -controller_LDADD = ../lib/libopenflow.la -ldl +controller_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS) EXTRA_DIST = controller.8.in controller.8: controller.8.in Makefile diff --git a/controller/controller.8.in b/controller/controller.8.in index b3e6189..a829951 100644 --- a/controller/controller.8.in +++ b/controller/controller.8.in @@ -28,6 +28,11 @@ this form is used. Listens for TCP connections from remote OpenFlow switches on \fIport\fR (default: 975). +.TP +\fBpunix:\fIfile\fR +Listens for connections from OpenFlow switches on the Unix domain +server socket named \fIfile\fR. + .TP \fBnl:\fIdp_idx\fR The local Netlink datapath numbered \fIdp_idx\fR, as configured with @@ -46,6 +51,10 @@ The specified SSL \fIport\fR (default: 976) on the given remote The specified TCP \fIport\fR (default: 975) on the given remote \fIhost\fR. +.TP +\fBunix:\fIfile\fR +The Unix domain server socket named \fIfile\fR. + .SH OPTIONS .TP \fB-p\fR, \fB--private-key=\fIprivkey.pem\fR @@ -73,6 +82,21 @@ in the network passes through the controller. This option is most useful for debugging. It reduces switching performance, so it should not be used in production. +.TP +\fB--max-idle=\fIsecs\fR|\fBpermanent\fR +Sets \fIsecs\fR as the number of seconds that a flow set up by the +controller will remain in the switch's flow table without any matching +packets being seen. If \fBpermanent\fR is specified, which is not +recommended, flows will never expire. The default is 60 seconds. + +This option affects only flows set up by the OpenFlow controller. In +some configurations, the OpenFlow secure channel can set up some flows +on its own. To set the idle time for those flows, pass +\fB--max-idle\fR to \fBsecchan\fR(8). + +This option has no effect when \fB-n\fR (or \fB--noflow\fR) is in use +(because the controller does not set up flows in that case). + .TP .BR \-H ", " \-\^\-hub By default, the controller acts as an L2 MAC-learning switch. This @@ -103,17 +127,18 @@ run as a background process. Prints a brief help message to the console. .TP -\fB-v\fR \fImodule\fB:\fIfacility\fB:\fIlevel\fR, \fB--verbose=\fImodule\fB:\fIfacility\fB:\fIlevel\fR +\fB-v\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] Sets the logging level for \fImodule\fR in \fIfacility\fR to \fIlevel\fR. The \fImodule\fR may be any valid module name (as displayed by the \fB--list\fR action on \fBvlogconf\fR(8)), or the special name \fBANY\fR to set the logging levels for all modules. The \fIfacility\fR may be \fBsyslog\fR or \fBconsole\fR to set the levels for logging to the system log or to the console, respectively, or -\fBANY\fR to set the logging levels for both facilities. The -\fIlevel\fR must be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or -\fBdbg\fR, designating the minimum severity of a message for it to be -logged. +\fBANY\fR to set the logging levels for both facilities. If it is +omitted, \fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must +be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating +the minimum severity of a message for it to be logged. If it is +omitted, \fIlevel\fR defaults to \fBdbg\fR. .TP \fB-v\fR, \fB--verbose\fR diff --git a/controller/controller.c b/controller/controller.c index 284e2e9..61a550a 100644 --- a/controller/controller.c +++ b/controller/controller.c @@ -31,8 +31,11 @@ * derivatives without specific, written prior permission. */ +#include + #include #include +#include #include #include @@ -42,6 +45,7 @@ #include "daemon.h" #include "fault.h" #include "learning-switch.h" +#include "openflow.h" #include "poll-loop.h" #include "rconn.h" #include "util.h" @@ -66,6 +70,9 @@ static bool learn_macs = true; /* Set up flows? (If not, every packet is processed at the controller.) */ static bool setup_flows = true; +/* --max-idle: Maximum idle time, in seconds, before flows expire. */ +static int max_idle = 60; + static int do_switching(struct switch_ *); static void new_switch(struct switch_ *, struct vconn *, const char *name); static void parse_options(int argc, char *argv[]); @@ -189,7 +196,8 @@ static void new_switch(struct switch_ *sw, struct vconn *vconn, const char *name) { sw->rconn = rconn_new_from_vconn(name, 128, vconn); - sw->lswitch = lswitch_create(sw->rconn, learn_macs, setup_flows); + sw->lswitch = lswitch_create(sw->rconn, learn_macs, + setup_flows ? max_idle : -1); } static int @@ -215,11 +223,13 @@ do_switching(struct switch_ *sw) static void parse_options(int argc, char *argv[]) { + enum { OPT_MAX_IDLE = UCHAR_MAX + 1 }; static struct option long_options[] = { {"detach", no_argument, 0, 'D'}, {"pidfile", optional_argument, 0, 'P'}, {"hub", no_argument, 0, 'H'}, {"noflow", no_argument, 0, 'n'}, + {"max-idle", required_argument, 0, OPT_MAX_IDLE}, {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, @@ -243,7 +253,7 @@ parse_options(int argc, char *argv[]) break; case 'P': - set_pidfile(optarg ? optarg : "controller.pid"); + set_pidfile(optarg); break; case 'H': @@ -254,6 +264,18 @@ parse_options(int argc, char *argv[]) setup_flows = false; break; + case OPT_MAX_IDLE: + if (!strcmp(optarg, "permanent")) { + max_idle = OFP_FLOW_PERMANENT; + } else { + max_idle = atoi(optarg); + if (max_idle < 1 || max_idle > 65535) { + fatal(0, "--max-idle argument must be between 1 and " + "65535 or the word 'permanent'"); + } + } + break; + case 'h': usage(); @@ -290,7 +312,8 @@ usage(void) " -P, --pidfile[=FILE] create pidfile (default: %s/controller.pid)\n" " -H, --hub act as hub instead of learning switch\n" " -n, --noflow pass traffic, but don't add flows\n" - " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n" + " --max-idle=SECS max idle time for new flows\n" + " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n" " -v, --verbose set maximum verbosity level\n" " -h, --help display this help message\n" " -V, --version display version information\n", diff --git a/datapath/Makefile.am b/datapath/Makefile.am index 2e784f0..9785537 100644 --- a/datapath/Makefile.am +++ b/datapath/Makefile.am @@ -7,7 +7,7 @@ SUBDIRS += linux-2.4 endif EXTRA_DIST = $(dist_headers) $(dist_sources) -EXTRA_DIST += hwtable-dummy/Modules.mk hwtable-dummy/hwtable-dummy.c +EXTRA_DIST += hwtable_dummy/Modules.mk hwtable_dummy/hwtable_dummy.c # Suppress warnings about GNU extensions in Modules.mk files. AUTOMAKE_OPTIONS = -Wno-portability diff --git a/datapath/Modules.mk b/datapath/Modules.mk index 099be0e..23e54da 100644 --- a/datapath/Modules.mk +++ b/datapath/Modules.mk @@ -5,7 +5,6 @@ openflow_sources = \ chain.c \ crc32.c \ datapath.c \ - datapath_t.c \ dp_dev.c \ flow.c \ forward.c \ @@ -18,7 +17,6 @@ openflow_headers = \ compat.h \ crc32.h \ datapath.h \ - datapath_t.h \ dp_dev.h \ flow.h \ forward.h \ diff --git a/datapath/chain.c b/datapath/chain.c index c857e9a..71e00ee 100644 --- a/datapath/chain.c +++ b/datapath/chain.c @@ -62,8 +62,7 @@ error: /* Searches 'chain' for a flow matching 'key', which must not have any wildcard * fields. Returns the flow if successful, otherwise a null pointer. * - * Caller must hold rcu_read_lock, and not release it until it is done with the - * returned flow. */ + * Caller must hold rcu_read_lock or dp_mutex. */ struct sw_flow *chain_lookup(struct sw_chain *chain, const struct sw_flow_key *key) { @@ -85,12 +84,12 @@ struct sw_flow *chain_lookup(struct sw_chain *chain, * If successful, 'flow' becomes owned by the chain, otherwise it is retained * by the caller. * - * Caller must hold rcu_read_lock. If insertion is successful, it must not - * release rcu_read_lock until it is done with the inserted flow. */ + * Caller must hold dp_mutex. */ int chain_insert(struct sw_chain *chain, struct sw_flow *flow) { int i; + might_sleep(); for (i = 0; i < chain->n_tables; i++) { struct sw_table *t = chain->tables[i]; if (t->insert(t, flow)) @@ -107,18 +106,17 @@ int chain_insert(struct sw_chain *chain, struct sw_flow *flow) * iterating through the entire contents of each table for keys that contain * wildcards. Relatively cheap for fully specified keys. * - * The caller need not hold any locks. */ + * Caller must hold dp_mutex. */ int chain_delete(struct sw_chain *chain, const struct sw_flow_key *key, uint16_t priority, int strict) { int count = 0; int i; + might_sleep(); for (i = 0; i < chain->n_tables; i++) { struct sw_table *t = chain->tables[i]; - rcu_read_lock(); count += t->delete(t, key, priority, strict); - rcu_read_unlock(); } return count; @@ -130,17 +128,17 @@ int chain_delete(struct sw_chain *chain, const struct sw_flow_key *key, * Expensive as currently implemented, since it iterates through the entire * contents of each table. * - * The caller need not hold any locks. */ + * Caller must not hold dp_mutex, because individual tables take and release it + * as necessary. */ int chain_timeout(struct sw_chain *chain) { int count = 0; int i; + might_sleep(); for (i = 0; i < chain->n_tables; i++) { struct sw_table *t = chain->tables[i]; - rcu_read_lock(); count += t->timeout(chain->dp, t); - rcu_read_unlock(); } return count; } diff --git a/datapath/datapath.c b/datapath/datapath.c index 20d41cf..980b91e 100644 --- a/datapath/datapath.c +++ b/datapath/datapath.c @@ -28,6 +28,7 @@ #include #include #include +#include #include "openflow-netlink.h" #include "datapath.h" @@ -36,7 +37,6 @@ #include "dp_dev.h" #include "forward.h" #include "flow.h" -#include "datapath_t.h" #include "compat.h" @@ -64,15 +64,17 @@ static struct genl_multicast_group mc_group; /* It's hard to imagine wanting more than one datapath, but... */ #define DP_MAX 32 -/* datapaths. Protected on the read side by rcu_read_lock, on the write side - * by dp_mutex. +/* Datapaths. Protected on the read side by rcu_read_lock, on the write side + * by dp_mutex. dp_mutex is almost completely redundant with genl_mutex + * maintained by the Generic Netlink code, but the timeout path needs mutual + * exclusion too. * * It is safe to access the datapath and net_bridge_port structures with just - * the dp_mutex, but to access the chain you need to take the rcu_read_lock - * also (because dp_mutex doesn't prevent flows from being destroyed). + * dp_mutex. */ static struct datapath *dps[DP_MAX]; -static DEFINE_MUTEX(dp_mutex); +DEFINE_MUTEX(dp_mutex); +EXPORT_SYMBOL(dp_mutex); static int dp_maint_func(void *data); static int send_port_status(struct net_bridge_port *p, uint8_t status); @@ -201,13 +203,9 @@ alloc_openflow_skb(struct datapath *dp, size_t openflow_len, uint8_t type, static int send_openflow_skb(struct sk_buff *skb, const struct sender *sender) { - int err = (sender - ? genlmsg_unicast(skb, sender->pid) - : genlmsg_multicast(skb, 0, mc_group.id, GFP_ATOMIC)); - if (err && net_ratelimit()) - printk(KERN_WARNING "send_openflow_skb: send failed: %d\n", - err); - return err; + return (sender + ? genlmsg_unicast(skb, sender->pid) + : genlmsg_multicast(skb, 0, mc_group.id, GFP_ATOMIC)); } /* Generates a unique datapath id. It incorporates the datapath index @@ -246,9 +244,7 @@ uint64_t gen_datapath_id(uint16_t dp_idx) } /* Creates a new datapath numbered 'dp_idx'. Returns 0 for success or a - * negative error code. - * - * Not called with any locks. */ + * negative error code. */ static int new_dp(int dp_idx) { struct datapath *dp; @@ -260,9 +256,8 @@ static int new_dp(int dp_idx) if (!try_module_get(THIS_MODULE)) return -ENODEV; - mutex_lock(&dp_mutex); - dp = rcu_dereference(dps[dp_idx]); - if (dp != NULL) { + /* Exit early if a datapath with that number already exists. */ + if (dps[dp_idx]) { err = -EEXIST; goto err_unlock; } @@ -297,8 +292,7 @@ static int new_dp(int dp_idx) if (IS_ERR(dp->dp_task)) goto err_destroy_chain; - rcu_assign_pointer(dps[dp_idx], dp); - mutex_unlock(&dp_mutex); + dps[dp_idx] = dp; return 0; @@ -311,12 +305,11 @@ err_destroy_dp_dev: err_free_dp: kfree(dp); err_unlock: - mutex_unlock(&dp_mutex); module_put(THIS_MODULE); return err; } -/* Find and return a free port number under 'dp'. Called under dp_mutex. */ +/* Find and return a free port number under 'dp'. */ static int find_portno(struct datapath *dp) { int i; @@ -354,7 +347,6 @@ static struct net_bridge_port *new_nbp(struct datapath *dp, return p; } -/* Called with dp_mutex. */ int add_switch_port(struct datapath *dp, struct net_device *dev) { struct net_bridge_port *p; @@ -378,8 +370,7 @@ int add_switch_port(struct datapath *dp, struct net_device *dev) return 0; } -/* Delete 'p' from switch. - * Called with dp_mutex. */ +/* Delete 'p' from switch. */ static int del_switch_port(struct net_bridge_port *p) { /* First drop references to device. */ @@ -403,7 +394,6 @@ static int del_switch_port(struct net_bridge_port *p) return 0; } -/* Called with dp_mutex. */ static void del_dp(struct datapath *dp) { struct net_bridge_port *p, *n; @@ -447,10 +437,7 @@ static void do_port_input(struct net_bridge_port *p, struct sk_buff *skb) { /* Push the Ethernet header back on. */ - if (skb->protocol == htons(ETH_P_8021Q)) - skb_push(skb, VLAN_ETH_HLEN); - else - skb_push(skb, ETH_HLEN); + skb_push(skb, ETH_HLEN); fwd_port_input(p->dp->chain, skb, p->port_no); } @@ -474,8 +461,6 @@ static int dp_frame_hook(struct net_bridge_port *p, struct sk_buff **pskb) } #else /* NB: This has only been tested on 2.4.35 */ - -/* Called without any locks (?) */ static void dp_frame_hook(struct sk_buff *skb) { struct net_bridge_port *p = skb->dev->br_port; @@ -820,6 +805,22 @@ dp_send_error_msg(struct datapath *dp, const struct sender *sender, return send_openflow_skb(skb, sender); } +int +dp_send_echo_reply(struct datapath *dp, const struct sender *sender, + const struct ofp_header *rq) +{ + struct sk_buff *skb; + struct ofp_header *reply; + + reply = alloc_openflow_skb(dp, ntohs(rq->length), OFPT_ECHO_REPLY, + sender, &skb); + if (!reply) + return -ENOMEM; + + memcpy(reply + 1, rq + 1, ntohs(rq->length) - sizeof *rq); + return send_openflow_skb(skb, sender); +} + /* Generic Netlink interface. * * See netlink(7) for an introduction to netlink. See @@ -873,7 +874,6 @@ static int dp_genl_del(struct sk_buff *skb, struct genl_info *info) if (!info->attrs[DP_GENL_A_DP_IDX]) return -EINVAL; - mutex_lock(&dp_mutex); dp = dp_get(nla_get_u32((info->attrs[DP_GENL_A_DP_IDX]))); if (!dp) err = -ENOENT; @@ -881,7 +881,6 @@ static int dp_genl_del(struct sk_buff *skb, struct genl_info *info) del_dp(dp); err = 0; } - mutex_unlock(&dp_mutex); return err; } @@ -965,7 +964,6 @@ static int dp_genl_add_del_port(struct sk_buff *skb, struct genl_info *info) return -EINVAL; /* Get datapath. */ - mutex_lock(&dp_mutex); dp = dp_get(nla_get_u32(info->attrs[DP_GENL_A_DP_IDX])); if (!dp) { err = -ENOENT; @@ -994,7 +992,6 @@ static int dp_genl_add_del_port(struct sk_buff *skb, struct genl_info *info) out_put: dev_put(port); out: - mutex_unlock(&dp_mutex); return err; } @@ -1025,26 +1022,22 @@ static int dp_genl_openflow(struct sk_buff *skb, struct genl_info *info) if (!info->attrs[DP_GENL_A_DP_IDX] || !va) return -EINVAL; - rcu_read_lock(); dp = dp_get(nla_get_u32(info->attrs[DP_GENL_A_DP_IDX])); - if (!dp) { - err = -ENOENT; - goto out; - } + if (!dp) + return -ENOENT; - if (nla_len(va) < sizeof(struct ofp_header)) { - err = -EINVAL; - goto out; - } + if (nla_len(va) < sizeof(struct ofp_header)) + return -EINVAL; oh = nla_data(va); sender.xid = oh->xid; sender.pid = info->snd_pid; sender.seq = info->snd_seq; - err = fwd_control_input(dp->chain, &sender, nla_data(va), nla_len(va)); -out: - rcu_read_unlock(); + mutex_lock(&dp_mutex); + err = fwd_control_input(dp->chain, &sender, + nla_data(va), nla_len(va)); + mutex_unlock(&dp_mutex); return err; } @@ -1360,7 +1353,6 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) * struct genl_ops. This kluge supports earlier versions also. */ cb->done = dp_genl_openflow_done; - rcu_read_lock(); if (!cb->args[0]) { struct nlattr *attrs[DP_GENL_A_MAX + 1]; struct ofp_stats_request *rq; @@ -1373,21 +1365,17 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) if (err < 0) return err; - err = -EINVAL; - if (!attrs[DP_GENL_A_DP_IDX]) - goto out; + return -EINVAL; dp_idx = nla_get_u16(attrs[DP_GENL_A_DP_IDX]); dp = dp_get(dp_idx); - if (!dp) { - err = -ENOENT; - goto out; - } + if (!dp) + return -ENOENT; va = attrs[DP_GENL_A_OPENFLOW]; len = nla_len(va); if (!va || len < sizeof *rq) - goto out; + return -EINVAL; rq = nla_data(va); type = ntohs(rq->type); @@ -1396,12 +1384,12 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) || ntohs(rq->header.length) != len || type >= ARRAY_SIZE(stats) || !stats[type].dump) - goto out; + return -EINVAL; s = &stats[type]; body_len = len - offsetof(struct ofp_stats_request, body); if (body_len < s->min_body || body_len > s->max_body) - goto out; + return -EINVAL; cb->args[0] = 1; cb->args[1] = dp_idx; @@ -1411,7 +1399,7 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) void *state; err = s->init(dp, rq->body, body_len, &state); if (err) - goto out; + return err; cb->args[4] = (long) state; } } else if (cb->args[0] == 1) { @@ -1419,13 +1407,10 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) s = &stats[cb->args[2]]; dp = dp_get(dp_idx); - if (!dp) { - err = -ENOENT; - goto out; - } + if (!dp) + return -ENOENT; } else { - err = 0; - goto out; + return 0; } sender.xid = cb->args[3]; @@ -1434,10 +1419,8 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) osr = put_openflow_headers(dp, skb, OFPT_STATS_REPLY, &sender, &max_openflow_len); - if (IS_ERR(osr)) { - err = PTR_ERR(osr); - goto out; - } + if (IS_ERR(osr)) + return PTR_ERR(osr); osr->type = htons(s - stats); osr->flags = 0; resize_openflow_skb(skb, &osr->header, max_openflow_len); @@ -1456,8 +1439,6 @@ dp_genl_openflow_dumpit(struct sk_buff *skb, struct netlink_callback *cb) err = skb->len; } -out: - rcu_read_unlock(); return err; } @@ -1480,20 +1461,6 @@ static struct genl_ops dp_genl_ops_openflow = { .dumpit = dp_genl_openflow_dumpit, }; -static struct nla_policy dp_genl_benchmark_policy[DP_GENL_A_MAX + 1] = { - [DP_GENL_A_DP_IDX] = { .type = NLA_U32 }, - [DP_GENL_A_NPACKETS] = { .type = NLA_U32 }, - [DP_GENL_A_PSIZE] = { .type = NLA_U32 }, -}; - -static struct genl_ops dp_genl_ops_benchmark_nl = { - .cmd = DP_GENL_C_BENCHMARK_NL, - .flags = GENL_ADMIN_PERM, /* Requires CAP_NET_ADMIN privilege. */ - .policy = dp_genl_benchmark_policy, - .doit = dp_genl_benchmark_nl, - .dumpit = NULL, -}; - static struct genl_ops *dp_genl_all_ops[] = { /* Keep this operation first. Generic Netlink dispatching * looks up operations with linear search, so we want it at the @@ -1505,7 +1472,6 @@ static struct genl_ops *dp_genl_all_ops[] = { &dp_genl_ops_query_dp, &dp_genl_ops_add_port, &dp_genl_ops_del_port, - &dp_genl_ops_benchmark_nl, }; static int dp_init_netlink(void) diff --git a/datapath/datapath.h b/datapath/datapath.h index b822ae4..aa313e8 100644 --- a/datapath/datapath.h +++ b/datapath/datapath.h @@ -3,6 +3,7 @@ #ifndef DATAPATH_H #define DATAPATH_H 1 +#include #include #include #include @@ -67,6 +68,8 @@ struct sender { uint32_t seq; /* Netlink sequence ID of request. */ }; +extern struct mutex dp_mutex; + int dp_output_port(struct datapath *, struct sk_buff *, int out_port); int dp_output_control(struct datapath *, struct sk_buff *, uint32_t, size_t, int); @@ -77,6 +80,8 @@ int dp_send_flow_expired(struct datapath *, struct sw_flow *); int dp_send_error_msg(struct datapath *, const struct sender *, uint16_t, uint16_t, const uint8_t *, size_t); int dp_update_port_flags(struct datapath *dp, const struct ofp_phy_port *opp); +int dp_send_echo_reply(struct datapath *, const struct sender *, + const struct ofp_header *); /* Should hold at least RCU read lock when calling */ struct datapath *dp_get(int dp_idx); diff --git a/datapath/datapath_t.c b/datapath/datapath_t.c deleted file mode 100644 index 33a64a6..0000000 --- a/datapath/datapath_t.c +++ /dev/null @@ -1,118 +0,0 @@ -#include "datapath_t.h" -#include -#include -#include -#include -#include -#include - -#include "datapath.h" - -static struct sk_buff * -gen_sk_buff(struct datapath *dp, uint32_t packet_size) -{ - int in_port; - struct sk_buff *skb; - struct ethhdr *eh; - struct iphdr *ih; - struct udphdr *uh; - - for (in_port = 0; in_port < OFPP_MAX; in_port++) { - if (dp->ports[in_port] != NULL) - break; - } - - if (in_port == OFPP_MAX) { - printk("benchmark: no in_port to send packets as\n"); - return NULL; - } - - skb = alloc_skb(packet_size, GFP_ATOMIC); - if (!skb) { - printk("benchmark: cannot allocate skb for benchmark\n"); - return NULL; - } - - skb_put(skb, packet_size); - skb_set_mac_header(skb, 0); - eh = eth_hdr(skb); - memcpy(eh->h_dest, "\x12\x34\x56\x78\x9a\xbc", ETH_ALEN); - memcpy(eh->h_source, "\xab\xcd\xef\x12\x34\x56", ETH_ALEN); - eh->h_proto = htons(ETH_P_IP); - skb_set_network_header(skb, sizeof(*eh)); - ih = ip_hdr(skb); - ih->ihl = 5; - ih->version = IPVERSION; - ih->tos = 0; - ih->tot_len = htons(packet_size - sizeof(*eh)); - ih->id = htons(12345); - ih->frag_off = 0; - ih->ttl = IPDEFTTL; - ih->protocol = IPPROTO_UDP; - ih->check = 0; /* want this to be right?! */ - ih->saddr = 0x12345678; - ih->daddr = 0x1234abcd; - skb_set_transport_header(skb, sizeof(*eh) + sizeof(*ih)); - uh = udp_hdr(skb); - uh->source = htons(1234); - uh->dest = htons(5678); - uh->len = htons(packet_size - sizeof(*eh) - sizeof(*ih)); - uh->check = 0; - if (dp_set_origin(dp, in_port, skb)) { - printk("benchmark: could not set origin\n"); - kfree_skb(skb); - return NULL; - } - - return skb; -} - -int -dp_genl_benchmark_nl(struct sk_buff *skb, struct genl_info *info) -{ - struct datapath *dp; - uint32_t num_packets = 0; - int i, err = 0; - struct sk_buff *skb2; - - if (!info->attrs[DP_GENL_A_DP_IDX] || !info->attrs[DP_GENL_A_NPACKETS] - || !info->attrs[DP_GENL_A_PSIZE]) - return -EINVAL; - - num_packets = nla_get_u32((info->attrs[DP_GENL_A_NPACKETS])); - - rcu_read_lock(); - dp = dp_get(nla_get_u32((info->attrs[DP_GENL_A_DP_IDX]))); - if (!dp) - err = -ENOENT; - else { - if (num_packets == 0) - goto benchmark_unlock; - - skb2 = gen_sk_buff(dp, nla_get_u32((info->attrs[DP_GENL_A_PSIZE]))); - if (skb2 == NULL) { - err = -ENOMEM; - goto benchmark_unlock; - } - - for (i = 0; i < num_packets; i++) { - struct sk_buff *copy = skb_get(skb2); - if (copy == NULL) { - printk("benchmark: skb_get failed\n"); - err = -ENOMEM; - break; - } - if ((err = dp_output_control(dp, copy, -1, - 0, OFPR_ACTION))) - { - printk("benchmark: output control ret %d on iter %d\n", err, i); - break; - } - } - kfree_skb(skb2); - } - -benchmark_unlock: - rcu_read_unlock(); - return err; -} diff --git a/datapath/datapath_t.h b/datapath/datapath_t.h deleted file mode 100644 index 868e734..0000000 --- a/datapath/datapath_t.h +++ /dev/null @@ -1,12 +0,0 @@ -#ifndef DATAPATH_T_H -#define DATAPATH_T_H 1 - -#include -#include -#include -#include -#include "openflow-netlink.h" - -int dp_genl_benchmark_nl(struct sk_buff *, struct genl_info *); - -#endif diff --git a/datapath/dp_dev.c b/datapath/dp_dev.c index 4898137..02468eb 100644 --- a/datapath/dp_dev.c +++ b/datapath/dp_dev.c @@ -27,6 +27,7 @@ int dp_dev_recv(struct net_device *netdev, struct sk_buff *skb) { int len = skb->len; struct dp_dev *dp_dev = dp_dev_priv(netdev); + skb->dev = netdev; skb->pkt_type = PACKET_HOST; skb->protocol = eth_type_trans(skb, netdev); if (in_interrupt()) @@ -54,19 +55,16 @@ static int dp_dev_mac_addr(struct net_device *dev, void *p) static int dp_dev_xmit(struct sk_buff *skb, struct net_device *netdev) { struct dp_dev *dp_dev = dp_dev_priv(netdev); - struct datapath *dp; + struct datapath *dp = dp_dev->dp; + + dp_dev->stats.tx_packets++; + dp_dev->stats.tx_bytes += skb->len; + + skb_reset_mac_header(skb); rcu_read_lock(); - dp = dp_dev->dp; - if (likely(dp != NULL)) { - dp_dev->stats.tx_packets++; - dp_dev->stats.tx_bytes += skb->len; - skb_reset_mac_header(skb); - fwd_port_input(dp->chain, skb, OFPP_LOCAL); - } else { - dp_dev->stats.tx_dropped++; - kfree_skb(skb); - } + fwd_port_input(dp->chain, skb, OFPP_LOCAL); rcu_read_unlock(); + return 0; } @@ -126,8 +124,7 @@ int dp_dev_setup(struct datapath *dp) void dp_dev_destroy(struct datapath *dp) { - struct dp_dev *dp_dev = dp_dev_priv(dp->netdev); - dp_dev->dp = NULL; + netif_tx_disable(dp->netdev); synchronize_net(); unregister_netdev(dp->netdev); free_netdev(dp->netdev); diff --git a/datapath/flow.c b/datapath/flow.c index 8cf09a1..e3e1eb9 100644 --- a/datapath/flow.c +++ b/datapath/flow.c @@ -125,16 +125,6 @@ void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from) memset(to->pad, '\0', sizeof(to->pad)); } -/* Returns true if 'flow' can be deleted and set up for a deferred free, false - * if deletion has already been scheduled (by another thread). - * - * Caller must hold rcu_read_lock. */ -int flow_del(struct sw_flow *flow) -{ - return !atomic_cmpxchg(&flow->deleted, 0, 1); -} -EXPORT_SYMBOL(flow_del); - /* Allocates and returns a new flow with 'n_actions' action, using allocation * flags 'flags'. Returns the new flow or a null pointer on failure. */ struct sw_flow *flow_alloc(int n_actions, gfp_t flags) diff --git a/datapath/flow.h b/datapath/flow.h index 6ecf778..e964c71 100644 --- a/datapath/flow.h +++ b/datapath/flow.h @@ -2,7 +2,6 @@ #define FLOW_H 1 #include -#include #include #include #include @@ -49,15 +48,9 @@ static inline void check_key_align(void) /* Locking: * * - Readers must take rcu_read_lock and hold it the entire time that the flow - * must continue to exist. Readers need not take delete_lock. They *may* - * examine 'deleted' *if* it is important not to read stale data. + * must continue to exist. * - * - Deleters must take rcu_read_lock and call flow_del to verify that another - * thread has not already deleted the flow. If not, do a deferred free of - * the flow with call_rcu, then rcu_assign_pointer or [h]list_del_rcu the - * flow. - * - * - In-place update not yet contemplated. + * - Writers must hold dp_mutex. */ struct sw_flow { struct sw_flow_key key; @@ -81,7 +74,6 @@ struct sw_flow { uint64_t packet_count; /* Number of packets associated with this entry */ uint64_t byte_count; /* Number of bytes associated with this entry */ - atomic_t deleted; /* 0 if not deleted, 1 if deleted. */ struct rcu_head rcu; }; @@ -92,7 +84,6 @@ struct sw_flow *flow_alloc(int n_actions, gfp_t flags); void flow_free(struct sw_flow *); void flow_deferred_free(struct sw_flow *); void flow_extract(struct sk_buff *, uint16_t in_port, struct sw_flow_key *); -int flow_del(struct sw_flow *); void flow_extract_match(struct sw_flow_key* to, const struct ofp_match* from); void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from); diff --git a/datapath/forward.c b/datapath/forward.c index 4496720..c8df1a0 100644 --- a/datapath/forward.c +++ b/datapath/forward.c @@ -87,10 +87,16 @@ void execute_actions(struct datapath *dp, struct sk_buff *skb, max_len = ntohs(a->arg.output.max_len); } else { if (!make_writable(&skb)) { - printk("make_writable failed\n"); + if (net_ratelimit()) + printk("make_writable failed\n"); break; } skb = execute_setter(skb, eth_proto, key, a); + if (!skb) { + if (net_ratelimit()) + printk("execute_setter lost skb\n"); + return; + } } } if (prev_port != -1) @@ -199,17 +205,23 @@ static struct sk_buff *vlan_pull_tag(struct sk_buff *skb) static struct sk_buff *modify_vlan(struct sk_buff *skb, const struct sw_flow_key *key, const struct ofp_action *a) { - uint16_t new_id = a->arg.vlan_id; + uint16_t new_id = ntohs(a->arg.vlan_id); if (new_id != OFP_VLAN_NONE) { if (key->dl_vlan != htons(OFP_VLAN_NONE)) { /* Modify vlan id, but maintain other TCI values */ struct vlan_ethhdr *vh = vlan_eth_hdr(skb); vh->h_vlan_TCI = (vh->h_vlan_TCI - & ~(htons(VLAN_VID_MASK))) | htons(new_id); + & ~(htons(VLAN_VID_MASK))) | a->arg.vlan_id; } else { /* Add vlan header */ - skb = vlan_put_tag(skb, new_id); + + /* xxx The vlan_put_tag function, doesn't seem to work + * xxx reliably when it attempts to use the hardware-accelerated + * xxx version. We'll directly use the software version + * xxx until the problem can be diagnosed. + */ + skb = __vlan_put_tag(skb, new_id); } } else { /* Remove an existing vlan header if it exists */ @@ -342,6 +354,20 @@ recv_port_mod(struct sw_chain *chain, const struct sender *sender, return 0; } +static int +recv_echo_request(struct sw_chain *chain, const struct sender *sender, + const void *msg) +{ + return dp_send_echo_reply(chain->dp, sender, msg); +} + +static int +recv_echo_reply(struct sw_chain *chain, const struct sender *sender, + const void *msg) +{ + return 0; +} + static int add_flow(struct sw_chain *chain, const struct ofp_flow_mod *ofm) { @@ -359,7 +385,8 @@ add_flow(struct sw_chain *chain, const struct ofp_flow_mod *ofm) const struct ofp_action *a = &ofm->actions[i]; if (a->type == htons(OFPAT_OUTPUT) - && a->arg.output.port == htons(OFPP_TABLE)) { + && (a->arg.output.port == htons(OFPP_TABLE) + || a->arg.output.port == htons(OFPP_NONE))) { /* xxx Send fancy new error message? */ goto error; } @@ -379,7 +406,6 @@ add_flow(struct sw_chain *chain, const struct ofp_flow_mod *ofm) flow->init_time = jiffies; flow->byte_count = 0; flow->packet_count = 0; - atomic_set(&flow->deleted, 0); spin_lock_init(&flow->lock); memcpy(flow->actions, ofm->actions, n_acts * sizeof *flow->actions); @@ -471,6 +497,14 @@ fwd_control_input(struct sw_chain *chain, const struct sender *sender, sizeof (struct ofp_port_mod), recv_port_mod, }, + [OFPT_ECHO_REQUEST] = { + sizeof (struct ofp_header), + recv_echo_request, + }, + [OFPT_ECHO_REPLY] = { + sizeof (struct ofp_header), + recv_echo_reply, + }, }; const struct openflow_packet *pkt; @@ -603,7 +637,7 @@ make_writable(struct sk_buff **pskb) if (skb_shared(*pskb) || skb_cloned(*pskb)) goto copy_skb; - return pskb_may_pull(*pskb, 64); /* FIXME? */ + return pskb_may_pull(*pskb, 40); /* FIXME? */ copy_skb: nskb = skb_copy(*pskb, GFP_ATOMIC); diff --git a/datapath/hwtable_dummy/Modules.mk b/datapath/hwtable_dummy/Modules.mk index 83cdd92..2a3014d 100644 --- a/datapath/hwtable_dummy/Modules.mk +++ b/datapath/hwtable_dummy/Modules.mk @@ -3,5 +3,5 @@ all_modules += hwtable_dummy # Specify the source files that comprise the module. hwtable_dummy_sources = \ - hwtable_dummy/hwtable-dummy.c + hwtable_dummy/hwtable_dummy.c diff --git a/datapath/linux-2.4/.gitignore b/datapath/linux-2.4/.gitignore index af45ff2..a503159 100644 --- a/datapath/linux-2.4/.gitignore +++ b/datapath/linux-2.4/.gitignore @@ -7,7 +7,6 @@ /flow.c /forward.c /forward_t.c -/datapath_t.c /table-hash.c /table-linear.c /table-mac.c diff --git a/datapath/linux-2.4/Modules.mk b/datapath/linux-2.4/Modules.mk index 7681d76..278f16a 100644 --- a/datapath/linux-2.4/Modules.mk +++ b/datapath/linux-2.4/Modules.mk @@ -41,6 +41,7 @@ compat24_headers = \ linux-2.4/compat-2.4/include/linux/netdevice.h \ linux-2.4/compat-2.4/include/linux/netlink.h \ linux-2.4/compat-2.4/include/linux/random.h \ + linux-2.4/compat-2.4/include/linux/rculist.h \ linux-2.4/compat-2.4/include/linux/rcupdate.h \ linux-2.4/compat-2.4/include/linux/sched.h \ linux-2.4/compat-2.4/include/linux/skbuff.h \ diff --git a/datapath/linux-2.4/compat-2.4/include/linux/kernel.h b/datapath/linux-2.4/compat-2.4/include/linux/kernel.h index 329dab5..543ba9b 100644 --- a/datapath/linux-2.4/compat-2.4/include/linux/kernel.h +++ b/datapath/linux-2.4/compat-2.4/include/linux/kernel.h @@ -2,6 +2,7 @@ #define __LINUX_KERNEL_WRAPPER_H 1 #include_next +#include /** * container_of - cast a member of a structure out to the containing structure @@ -40,4 +41,26 @@ int vprintk(const char *msg, ...) /* Force a compilation error if condition is true */ #define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)])) +/** + * might_sleep - annotation for functions that can sleep + * + * this macro will print a stack trace if it is executed in an atomic + * context (spinlock, irq-handler, ...). + * + * This is a useful debugging help to be able to catch problems early and not + * be bitten later when the calling function happens to sleep when it is not + * supposed to. + */ +#define might_resched() do { } while (0) + +#ifdef CONFIG_DEBUG_SPINLOCK + void __might_sleep(char *file, int line); +# define might_sleep() \ + do { __might_sleep(__FILE__, __LINE__); might_resched(); } while (0) +#else +# define might_sleep() do { might_resched(); } while (0) +#endif + +#define might_sleep_if(cond) do { if (cond) might_sleep(); } while (0) + #endif diff --git a/datapath/linux-2.4/compat-2.4/include/linux/rculist.h b/datapath/linux-2.4/compat-2.4/include/linux/rculist.h new file mode 100644 index 0000000..c7a69f9 --- /dev/null +++ b/datapath/linux-2.4/compat-2.4/include/linux/rculist.h @@ -0,0 +1,2 @@ +/* In Linux 2.6.26, part of list.h was broken out into rculist.h. */ +#include diff --git a/datapath/linux-2.4/compat-2.4/kernel.c b/datapath/linux-2.4/compat-2.4/kernel.c index a08bb2d..36d2d8a 100644 --- a/datapath/linux-2.4/compat-2.4/kernel.c +++ b/datapath/linux-2.4/compat-2.4/kernel.c @@ -2,9 +2,11 @@ * Distributed under the terms of the GNU GPL version 2. */ +#include #include #include #include +#include int vprintk(const char *msg, ...) { @@ -25,3 +27,20 @@ int vprintk(const char *msg, ...) } EXPORT_SYMBOL(vprintk); + +#ifdef CONFIG_DEBUG_SPINLOCK +void __might_sleep(char *file, int line) +{ + static unsigned long prev_jiffy; /* ratelimiting */ + + if ((in_interrupt()) && !oops_in_progress) { + if (time_before(jiffies, prev_jiffy + HZ) && prev_jiffy) + return; + prev_jiffy = jiffies; + printk(KERN_ERR "BUG: sleeping function called from invalid" + " context at %s:%d\n", file, line); + dump_stack(); + } +} +EXPORT_SYMBOL(__might_sleep); +#endif diff --git a/datapath/linux-2.6/.gitignore b/datapath/linux-2.6/.gitignore index eee68d0..20b4829 100644 --- a/datapath/linux-2.6/.gitignore +++ b/datapath/linux-2.6/.gitignore @@ -5,7 +5,6 @@ /crc32.c /crc_t.c /datapath.c -/datapath_t.c /dp_dev.c /flow.c /forward.c diff --git a/datapath/linux-2.6/Modules.mk b/datapath/linux-2.6/Modules.mk index 3a1d653..b950355 100644 --- a/datapath/linux-2.6/Modules.mk +++ b/datapath/linux-2.6/Modules.mk @@ -10,6 +10,7 @@ openflow_headers += \ linux-2.6/compat-2.6/include/linux/mutex.h \ linux-2.6/compat-2.6/include/linux/netlink.h \ linux-2.6/compat-2.6/include/linux/random.h \ + linux-2.6/compat-2.6/include/linux/rculist.h \ linux-2.6/compat-2.6/include/linux/skbuff.h \ linux-2.6/compat-2.6/include/linux/tcp.h \ linux-2.6/compat-2.6/include/linux/timer.h \ diff --git a/datapath/linux-2.6/compat-2.6/include/linux/rculist.h b/datapath/linux-2.6/compat-2.6/include/linux/rculist.h new file mode 100644 index 0000000..4164c0e --- /dev/null +++ b/datapath/linux-2.6/compat-2.6/include/linux/rculist.h @@ -0,0 +1,12 @@ +#ifndef __LINUX_RCULIST_WRAPPER_H +#define __LINUX_RCULIST_WRAPPER_H + +#include +#if LINUX_VERSION_CODE >= KERNEL_VERSION(2,6,26) +#include_next +#else +/* Prior to 2.6.26, the contents of rculist.h were part of list.h. */ +#include +#endif + +#endif diff --git a/datapath/table-hash.c b/datapath/table-hash.c index 9c8b532..96525ec 100644 --- a/datapath/table-hash.c +++ b/datapath/table-hash.c @@ -21,9 +21,8 @@ static void kmem_free(void *, size_t); struct sw_table_hash { struct sw_table swt; - spinlock_t lock; struct crc32 crc32; - atomic_t n_flows; + unsigned int n_flows; unsigned int bucket_mask; /* Number of buckets minus 1. */ struct sw_flow **buckets; }; @@ -47,22 +46,19 @@ static int table_hash_insert(struct sw_table *swt, struct sw_flow *flow) { struct sw_table_hash *th = (struct sw_table_hash *) swt; struct sw_flow **bucket; - unsigned long int flags; int retval; if (flow->key.wildcards != 0) return 0; - spin_lock_irqsave(&th->lock, flags); bucket = find_bucket(swt, &flow->key); if (*bucket == NULL) { - atomic_inc(&th->n_flows); + th->n_flows++; rcu_assign_pointer(*bucket, flow); retval = 1; } else { struct sw_flow *old_flow = *bucket; - if (!memcmp(&old_flow->key, &flow->key, sizeof flow->key) - && flow_del(old_flow)) { + if (!memcmp(&old_flow->key, &flow->key, sizeof flow->key)) { rcu_assign_pointer(*bucket, flow); flow_deferred_free(old_flow); retval = 1; @@ -70,19 +66,15 @@ static int table_hash_insert(struct sw_table *swt, struct sw_flow *flow) retval = 0; } } - spin_unlock_irqrestore(&th->lock, flags); return retval; } /* Caller must update n_flows. */ static int do_delete(struct sw_flow **bucket, struct sw_flow *flow) { - if (flow_del(flow)) { - rcu_assign_pointer(*bucket, NULL); - flow_deferred_free(flow); - return 1; - } - return 0; + rcu_assign_pointer(*bucket, NULL); + flow_deferred_free(flow); + return 1; } /* Returns number of deleted flows. We can ignore the priority @@ -110,8 +102,7 @@ static int table_hash_delete(struct sw_table *swt, count += do_delete(bucket, flow); } } - if (count) - atomic_sub(count, &th->n_flows); + th->n_flows -= count; return count; } @@ -121,6 +112,7 @@ static int table_hash_timeout(struct datapath *dp, struct sw_table *swt) unsigned int i; int count = 0; + mutex_lock(&dp_mutex); for (i = 0; i <= th->bucket_mask; i++) { struct sw_flow **bucket = &th->buckets[i]; struct sw_flow *flow = *bucket; @@ -130,9 +122,9 @@ static int table_hash_timeout(struct datapath *dp, struct sw_table *swt) dp_send_flow_expired(dp, flow); } } + th->n_flows -= count; + mutex_unlock(&dp_mutex); - if (count) - atomic_sub(count, &th->n_flows); return count; } @@ -191,7 +183,7 @@ static void table_hash_stats(struct sw_table *swt, { struct sw_table_hash *th = (struct sw_table_hash *) swt; stats->name = "hash"; - stats->n_flows = atomic_read(&th->n_flows); + stats->n_flows = th->n_flows; stats->max_flows = th->bucket_mask + 1; } @@ -223,9 +215,8 @@ struct sw_table *table_hash_create(unsigned int polynomial, swt->iterate = table_hash_iterate; swt->stats = table_hash_stats; - spin_lock_init(&th->lock); crc32_init(&th->crc32, polynomial); - atomic_set(&th->n_flows, 0); + th->n_flows = 0; return swt; } diff --git a/datapath/table-linear.c b/datapath/table-linear.c index 68c3aed..b4d4a77 100644 --- a/datapath/table-linear.c +++ b/datapath/table-linear.c @@ -10,14 +10,13 @@ #include #include -#include +#include struct sw_table_linear { struct sw_table swt; - spinlock_t lock; unsigned int max_flows; - atomic_t n_flows; + unsigned int n_flows; struct list_head flows; struct list_head iter_flows; unsigned long int next_serial; @@ -38,7 +37,6 @@ static struct sw_flow *table_linear_lookup(struct sw_table *swt, static int table_linear_insert(struct sw_table *swt, struct sw_flow *flow) { struct sw_table_linear *tl = (struct sw_table_linear *) swt; - unsigned long int flags; struct sw_flow *f; @@ -46,16 +44,13 @@ static int table_linear_insert(struct sw_table *swt, struct sw_flow *flow) * always be placed behind those with equal priority. Just replace * any flows that match exactly. */ - spin_lock_irqsave(&tl->lock, flags); - list_for_each_entry_rcu (f, &tl->flows, node) { + list_for_each_entry (f, &tl->flows, node) { if (f->priority == flow->priority && f->key.wildcards == flow->key.wildcards - && flow_matches(&f->key, &flow->key) - && flow_del(f)) { + && flow_matches(&f->key, &flow->key)) { flow->serial = f->serial; list_replace_rcu(&f->node, &flow->node); list_replace_rcu(&f->iter_node, &flow->iter_node); - spin_unlock_irqrestore(&tl->lock, flags); flow_deferred_free(f); return 1; } @@ -65,29 +60,24 @@ static int table_linear_insert(struct sw_table *swt, struct sw_flow *flow) } /* Make sure there's room in the table. */ - if (atomic_read(&tl->n_flows) >= tl->max_flows) { - spin_unlock_irqrestore(&tl->lock, flags); + if (tl->n_flows >= tl->max_flows) { return 0; } - atomic_inc(&tl->n_flows); + tl->n_flows++; /* Insert the entry immediately in front of where we're pointing. */ flow->serial = tl->next_serial++; list_add_tail_rcu(&flow->node, &f->node); list_add_rcu(&flow->iter_node, &tl->iter_flows); - spin_unlock_irqrestore(&tl->lock, flags); return 1; } static int do_delete(struct sw_table *swt, struct sw_flow *flow) { - if (flow_del(flow)) { - list_del_rcu(&flow->node); - list_del_rcu(&flow->iter_node); - flow_deferred_free(flow); - return 1; - } - return 0; + list_del_rcu(&flow->node); + list_del_rcu(&flow->iter_node); + flow_deferred_free(flow); + return 1; } static int table_linear_delete(struct sw_table *swt, @@ -97,13 +87,12 @@ static int table_linear_delete(struct sw_table *swt, struct sw_flow *flow; unsigned int count = 0; - list_for_each_entry_rcu (flow, &tl->flows, node) { + list_for_each_entry (flow, &tl->flows, node) { if (flow_del_matches(&flow->key, key, strict) && (!strict || (flow->priority == priority))) count += do_delete(swt, flow); } - if (count) - atomic_sub(count, &tl->n_flows); + tl->n_flows -= count; return count; } @@ -113,15 +102,16 @@ static int table_linear_timeout(struct datapath *dp, struct sw_table *swt) struct sw_flow *flow; int count = 0; - list_for_each_entry_rcu (flow, &tl->flows, node) { + mutex_lock(&dp_mutex); + list_for_each_entry (flow, &tl->flows, node) { if (flow_timeout(flow)) { count += do_delete(swt, flow); if (dp->flags & OFPC_SEND_FLOW_EXP) dp_send_flow_expired(dp, flow); } } - if (count) - atomic_sub(count, &tl->n_flows); + tl->n_flows -= count; + mutex_unlock(&dp_mutex); return count; } @@ -149,7 +139,7 @@ static int table_linear_iterate(struct sw_table *swt, unsigned long start; start = position->private[0]; - list_for_each_entry_rcu (flow, &tl->iter_flows, iter_node) { + list_for_each_entry (flow, &tl->iter_flows, iter_node) { if (flow->serial >= start && flow_matches(key, &flow->key)) { int error = callback(flow, private); if (error) { @@ -166,7 +156,7 @@ static void table_linear_stats(struct sw_table *swt, { struct sw_table_linear *tl = (struct sw_table_linear *) swt; stats->name = "linear"; - stats->n_flows = atomic_read(&tl->n_flows); + stats->n_flows = tl->n_flows; stats->max_flows = tl->max_flows; } @@ -190,10 +180,9 @@ struct sw_table *table_linear_create(unsigned int max_flows) swt->stats = table_linear_stats; tl->max_flows = max_flows; - atomic_set(&tl->n_flows, 0); + tl->n_flows = 0; INIT_LIST_HEAD(&tl->flows); INIT_LIST_HEAD(&tl->iter_flows); - spin_lock_init(&tl->lock); tl->next_serial = 0; return swt; diff --git a/debian/ofp-switch-setup b/debian/ofp-switch-setup index 0c03c33..aa318d7 100755 --- a/debian/ofp-switch-setup +++ b/debian/ofp-switch-setup @@ -12,15 +12,20 @@ my $debconf_owner = 'openflow-switch'; my $default = '/etc/default/openflow-switch'; my $etc = '/etc/openflow-switch'; +my $rundir = '/var/run'; my $privkey_file = "$etc/of0-privkey.pem"; my $req_file = "$etc/of0-req.pem"; my $cert_file = "$etc/of0-cert.pem"; my $cacert_file = "$etc/cacert.pem"; +my $ofp_discover_pidfile = "$rundir/ofp-discover.pid"; my $ua = LWP::UserAgent->new; $ua->timeout(10); $ua->env_proxy; +system("/etc/init.d/openflow-switch stop 1>&2"); +kill_ofp_discover(); + version('2.0'); capb('backup'); title('OpenFlow Switch Setup'); @@ -38,8 +43,9 @@ if (-e $default) { db_set('netdevs', join(', ', map($netdevs{$_}, grep(exists $netdevs{$_}, split)))) }, - IN_BAND => sub { - db_set('band', $_ eq 'no' ? 'in-band' : 'out-of-band') + MODE => sub { + db_set('mode', + $_ eq 'in-band' || $_ eq 'out-of-band' ? $_ : 'discovery') }, SWITCH_IP => sub { db_set('switch-ip', $_) }, CONTROLLER => sub { db_set('controller-vconn', $_) }, @@ -55,21 +61,9 @@ if (-e $default) { } my $cacert_preverified = -e $cacert_file; - -if (! -e $privkey_file) { - my $old_umask = umask(077); - run_cmd("ofp-pki req $etc/of0 >&2 2>/dev/null"); - chmod(0644, $req_file) or die "$req_file: chmod: $!\n"; - umask($old_umask); -} - my ($req, $req_fingerprint); -if (! -e $cert_file) { - open(REQ, '<', $req_file) or die "$req_file: open: $!\n"; - $req = join('', ); - close(REQ); - $req_fingerprint = sha1_hex($req); -} + +my %options; my (@states) = (sub { @@ -101,12 +95,71 @@ my (@states) = } }, sub { - # In-band or out-of-band controller? - db_input('band'); + # Discovery or in-band or out-of-band controller? + db_input('mode'); return; }, sub { - return 'skip' if db_get('band') eq 'out-of-band'; + return 'skip' if db_get('mode') ne 'discovery'; + for (;;) { + # Notify user that we are going to do discovery. + db_input('discover'); + return 'prev' if db_go(); + print STDERR "Please wait up to 30 seconds for discovery...\n"; + + # Make sure that there's no running discovery process. + kill_ofp_discover(); + + # Do discovery. + %options = (); + open(DISCOVER, '-|', 'ofp-discover --timeout=30 --pidfile ' + . join(' ', netdev_names())); + while () { + chomp; + if (my ($name, $value) = /^([^=]+)=(.*)$/) { + if ($value =~ /^"(.*)"$/) { + $value = $1; + $value =~ s/\\([0-7][0-7][0-7])/chr($1)/ge; + } else { + $value =~ s/^(0x[[:xdigit:]]+)$/hex($1)/e; + $value = '' if $value eq 'empty'; + next if $value eq 'null'; # Shouldn't happen. + } + $options{$name} = $value; + } + last if /^$/; + } + + # Check results. + my $vconn = $options{'ofp-controller-vconn'}; + my $pki_uri = $options{'ofp-pki-uri'}; + return 'next' + if (defined($vconn) + && is_valid_vconn($vconn) + && (!is_ssl_vconn($vconn) || defined($pki_uri))); + + # Try again? + kill_ofp_discover(); + db_input('discovery-failure'); + db_go(); + } + }, + sub { + return 'skip' if db_get('mode') ne 'discovery'; + + my $vconn = $options{'ofp-controller-vconn'}; + my $pki_uri = $options{'ofp-pki-uri'}; + db_subst('discovery-success', 'controller-vconn', $vconn); + db_subst('discovery-success', + 'pki-uri', is_ssl_vconn($vconn) ? $pki_uri : "no PKI in use"); + db_input('discovery-success'); + return 'prev' if db_go(); + db_set('controller-vconn', $vconn); + db_set('pki-uri', $pki_uri); + return 'next'; + }, + sub { + return 'skip' if db_get('mode') ne 'in-band'; for (;;) { db_input('switch-ip'); return 'prev' if db_go(); @@ -119,16 +172,16 @@ my (@states) = } }, sub { + return 'skip' if db_get('mode') eq 'discovery'; for (;;) { my $old_vconn = db_get('controller-vconn'); db_input('controller-vconn'); return 'prev' if db_go(); my $vconn = db_get('controller-vconn'); - if ($vconn =~ /^(tcp|ssl):([^:]+)(:.*)?/) { - if ($old_vconn ne $vconn - || db_get('pki-host') eq '') { - db_set('pki-host', $2); + if (is_valid_vconn($vconn)) { + if ($old_vconn ne $vconn || db_get('pki-uri') eq '') { + db_set('pki-uri', pki_host_to_uri($2)); } return 'next'; } @@ -137,11 +190,29 @@ my (@states) = db_go(); } }, + sub { + return 'skip' if !ssl_enabled(); + + if (! -e $privkey_file) { + my $old_umask = umask(077); + run_cmd("ofp-pki req $etc/of0 >&2 2>/dev/null"); + chmod(0644, $req_file) or die "$req_file: chmod: $!\n"; + umask($old_umask); + } + + if (! -e $cert_file) { + open(REQ, '<', $req_file) or die "$req_file: open: $!\n"; + $req = join('', ); + close(REQ); + $req_fingerprint = sha1_hex($req); + } + return 'skip'; + }, sub { return 'skip' if !ssl_enabled(); return 'skip' if -e $cacert_file && -e $cert_file; - db_input('pki-host'); + db_input('pki-uri'); return 'prev' if db_go(); return; }, @@ -149,8 +220,16 @@ my (@states) = return 'skip' if !ssl_enabled(); return 'skip' if -e $cacert_file; - my $pki_host = db_get('pki-host'); - my $url = "http://$pki_host/openflow/pki/controllerca/cacert.pem"; + my $pki_uri = db_get('pki-uri'); + if ($pki_uri !~ /:/) { + $pki_uri = pki_host_to_uri($pki_uri); + } else { + # Trim trailing slashes. + $pki_uri =~ s%/+$%%; + } + db_set('pki-uri', $pki_uri); + + my $url = "$pki_uri/controllerca/cacert.pem"; my $response = $ua->get($url, ':content_file' => $cacert_file); if ($response->is_success) { return 'next'; @@ -158,7 +237,7 @@ my (@states) = db_subst('fetch-cacert-failed', 'url', $url); db_subst('fetch-cacert-failed', 'error', $response->status_line); - db_subst('fetch-cacert-failed', 'pki-host', $pki_host); + db_subst('fetch-cacert-failed', 'pki-uri', $pki_uri); db_input('fetch-cacert-failed'); db_go(); return 'prev'; @@ -173,8 +252,9 @@ my (@states) = return 'prev' if db_go(); return 'next' if db_get('send-cert-req') eq 'no'; - my $pki_host = db_get('pki-host'); - my $url = "http://$pki_host/cgi-bin/ofp-pki-cgi"; + my $pki_uri = db_get('pki-uri'); + my ($pki_base_uri) = $pki_uri =~ m%^([^/]+://[^/]+)/%; + my $url = "$pki_base_uri/cgi-bin/ofp-pki-cgi"; my $response = $ua->post($url, {'type' => 'switch', 'req' => $req}); return 'next' if $response->is_success; @@ -182,8 +262,7 @@ my (@states) = db_subst('send-cert-req-failed', 'url', $url); db_subst('send-cert-req-failed', 'error', $response->status_line); - db_subst('send-cert-req-failed', 'pki-host', - $pki_host); + db_subst('send-cert-req-failed', 'pki-uri', $pki_uri); db_input('send-cert-req-failed'); db_go(); } @@ -210,8 +289,8 @@ my (@states) = return 'prev' if db_go(); exit(1) if db_get('fetch-switch-cert') eq 'no'; - my $pki_host = db_get('pki-host'); - my $url = "http://$pki_host/openflow/pki/switchca/certs/$req_fingerprint-cert.pem"; + my $pki_uri = db_get('pki-uri'); + my $url = "$pki_uri/switchca/certs/$req_fingerprint-cert.pem"; my $response = $ua->get($url, ':content_file' => $cert_file); if ($response->is_success) { return 'next'; @@ -220,8 +299,7 @@ my (@states) = db_subst('fetch-switch-cert-failed', 'url', $url); db_subst('fetch-switch-cert-failed', 'error', $response->status_line); - db_subst('fetch-switch-cert-failed', 'pki-host', - $pki_host); + db_subst('fetch-switch-cert-failed', 'pki-uri', $pki_uri); db_input('fetch-switch-cert-failed'); db_go(); } @@ -256,24 +334,25 @@ for (;;) { } my %config; -$config{NETDEVS} = join(' ', map(/^(\S+)/, split(', ', db_get('netdevs')))); -if (db_get('band') eq 'in-band') { - $config{IN_BAND} = 'yes'; +$config{NETDEVS} = join(' ', netdev_names()); +$config{MODE} = db_get('mode'); +if (db_get('mode') eq 'in-band') { $config{SWITCH_IP} = db_get('switch-ip'); -} else { - $config{IN_BAND} = 'no'; } -$config{CONTROLLER} = db_get('controller-vconn'); +if (db_get('mode') ne 'discovery') { + $config{CONTROLLER} = db_get('controller-vconn'); +} $config{PRIVKEY} = $privkey_file; $config{CERT} = $cert_file; $config{CACERT} = $cacert_file; save_config($default, %config); dup2(2, 1); # Get stdout back. -system("/etc/init.d/openflow-switch restart"); +kill_ofp_discover(); +system("/etc/init.d/openflow-switch start"); sub ssl_enabled { - return db_get('controller-vconn') =~ /^ssl:/; + return is_ssl_vconn(db_get('controller-vconn')); } sub db_subst { @@ -288,7 +367,7 @@ sub db_subst { sub db_set { my ($question, $value) = @_; - $question = "$debconf_owner/$question"; + $question = "$debconf_owner/$question"; my ($ret, $seen) = set($question, $value); if ($ret && $ret != 30) { die "Error setting debconf question $question to $value: $seen"; @@ -415,7 +494,9 @@ sub load_config { sub shell_escape { local $_ = $_[0]; - if (m&^[-a-zA-Z0-9:./%^_+,]*$&) { + if ($_ eq '') { + return '""'; + } elsif (m&^[-a-zA-Z0-9:./%^_+,]*$&) { return $_; } else { s/'/'\\''/; @@ -485,3 +566,28 @@ sub save_config { close(NEWFILE); rename("$file.tmp", $file) or die "$file.tmp: rename to $file: $!\n"; } + +sub pki_host_to_uri { + my ($pki_host) = @_; + return "http://$pki_host/openflow/pki"; +} + +sub kill_ofp_discover { + # Delegate this to a subprocess because there is no portable way + # to invoke fcntl(F_GETLK) from Perl. + system("ofp-kill --force $ofp_discover_pidfile"); +} + +sub netdev_names { + return map(/^(\S+)/, split(', ', db_get('netdevs'))); +} + +sub is_valid_vconn { + my ($vconn) = @_; + return scalar($vconn =~ /^(tcp|ssl):([^:]+)(:.*)?/); +} + +sub is_ssl_vconn { + my ($vconn) = @_; + return scalar($vconn =~ /^ssl:/); +} diff --git a/debian/openflow-common.install b/debian/openflow-common.install index 4175c30..624bc34 100644 --- a/debian/openflow-common.install +++ b/debian/openflow-common.install @@ -1,2 +1,2 @@ -utilities/ofp-pki usr/sbin -utilities/vlogconf usr/sbin +_debian/utilities/ofp-pki usr/sbin +_debian/utilities/vlogconf usr/sbin diff --git a/debian/openflow-common.manpages b/debian/openflow-common.manpages index 282ec32..ee20d1e 100644 --- a/debian/openflow-common.manpages +++ b/debian/openflow-common.manpages @@ -1,2 +1,2 @@ utilities/vlogconf.8 -utilities/ofp-pki.8 +_debian/utilities/ofp-pki.8 diff --git a/debian/openflow-controller.README.Debian b/debian/openflow-controller.README.Debian index c54e569..19d5cb9 100644 --- a/debian/openflow-controller.README.Debian +++ b/debian/openflow-controller.README.Debian @@ -1,5 +1,10 @@ README.Debian for openflow-controller ------------------------------------- -* To reconfigure the controller, edit /etc/default/openflow-controller +* To (re)configure the controller, edit /etc/default/openflow-controller and run "/etc/init.d/openflow-controller restart". + +* To enable OpenFlow switches to automatically discover the location + of the controller, you must install and configure a DHCP server. + The secchan(8) manpage (found in the openflow-switch package) gives + a working example configuration file for the ISC DHCP server. diff --git a/debian/openflow-controller.install b/debian/openflow-controller.install index 75ab435..3932ab6 100644 --- a/debian/openflow-controller.install +++ b/debian/openflow-controller.install @@ -1 +1 @@ -controller/controller usr/sbin +_debian/controller/controller usr/sbin diff --git a/debian/openflow-controller.manpages b/debian/openflow-controller.manpages index c30c912..3fbaaea 100644 --- a/debian/openflow-controller.manpages +++ b/debian/openflow-controller.manpages @@ -1 +1 @@ -controller/controller.8 +_debian/controller/controller.8 diff --git a/debian/openflow-pki.install b/debian/openflow-pki.install index 58cfb4e..cd530ca 100644 --- a/debian/openflow-pki.install +++ b/debian/openflow-pki.install @@ -1 +1 @@ -utilities/ofp-pki-cgi usr/lib/cgi-bin +_debian/utilities/ofp-pki-cgi usr/lib/cgi-bin diff --git a/debian/openflow-switch.config b/debian/openflow-switch.config deleted file mode 100755 index 7546e02..0000000 --- a/debian/openflow-switch.config +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh -set -e -. /usr/share/debconf/confmodule -# Nothing more to do -- the user is responsible for running ofp-switch-setup. diff --git a/debian/openflow-switch.default b/debian/openflow-switch.default index f2ce5a6..e4fb07b 100644 --- a/debian/openflow-switch.default +++ b/debian/openflow-switch.default @@ -21,13 +21,18 @@ # become part of the switch and cannot be used for other purposes. #NETDEVS="" -# IN_BAND: The OpenFlow switch must be able to contact the OpenFlow -# controller over the network. It can do so in one of two ways: +# MODE: The OpenFlow switch has three modes that determine how it +# reaches the controller: +# +# * in-band with discovery: A single network is used for OpenFlow +# traffic and other data traffic; that is, the switch contacts the +# controller over one of the network devices selected as OpenFlow +# switch ports. The switch automatically determines the location of +# the controller using a DHCP request with an OpenFlow-specific +# vendor option. This is the most common case. # -# * in-band: A single network is used for OpenFlow traffic and other -# data traffic; that is, the switch contacts the controller over one -# of the network devices selected as OpenFlow switch ports in the -# previous question. This is the most common case. +# * in-band: As above, but the location of the controller is manually +# configured. # # * out-of-band: OpenFlow traffic uses a network separate from the # data traffic that it controls. If this is the case, the control @@ -35,13 +40,12 @@ # one of those selected as an OpenFlow switch port in the previous # question. # -# Set IN_BAND to yes for in-band control, or to no for out-of-band -# control. -IN_BAND=yes +# Set MODE to 'discovery', 'in-band', or 'out-of-band' for these +# respective cases. +MODE=discovery -# SWITCH_IP: For in-band communication with the controller, the -# OpenFlow switch must be able to determine its own IP address. Its -# IP address may be configured statically or dynamically: +# SWITCH_IP: In 'in-band' mode, the switch's IP address may be +# configured statically or dynamically: # # * For static configuration, specify the switch's IP address as a # string. @@ -51,7 +55,7 @@ IN_BAND=yes # if the network topology allows the switch to contact the DHCP # server before it connects to the OpenFlow controller. # -# If IN_BAND is set to "no" above, this setting has no effect. +# This setting has no effect unless MODE is set to 'in-band'. SWITCH_IP=dhcp # CONTROLLER: Location of controller. @@ -59,6 +63,7 @@ SWITCH_IP=dhcp # tcp:HOST[:PORT] via TCP to PORT (default: 975) on HOST # ssl:HOST[:PORT] via SSL to PORT (default: 976) on HOST # The default below assumes that the controller is running locally. +# This setting has no effect when MODE is set to 'discovery'. #CONTROLLER="tcp:127.0.0.1" # PRIVKEY: Name of file containing switch's private key. diff --git a/debian/openflow-switch.init b/debian/openflow-switch.init index 1f03df1..5d658ed 100755 --- a/debian/openflow-switch.init +++ b/debian/openflow-switch.init @@ -27,6 +27,7 @@ test -x $DAEMON || exit 0 LOGDIR=/var/log/openflow PIDFILE=/var/run/$NAME.pid +MGMTSOCK=/var/run/$NAME.socket DHCLIENT_PIDFILE=/var/run/dhclient.of0.pid DODTIME=1 # Time to wait for the server to die, in seconds # If this value is set too low you might not @@ -50,8 +51,14 @@ running_pid() [ ! -d /proc/$pid ] && return 1 cmd=`cat /proc/$pid/cmdline | tr "\000" "\n"|head -n 1 |cut -d : -f 1` # Is this the expected child? - [ "$cmd" != "$name" ] && return 1 - return 0 + case $cmd in + $name|*/$name) + return 0 + ;; + *) + return 1 + ;; + esac } running() @@ -108,6 +115,26 @@ check_op() { fi } +configure_ssl() { + if test ! -e "$PRIVKEY" || test ! -e "$CERT" || test ! -e "$CACERT"; then + if test ! -e "$PRIVKEY"; then + echo "$PRIVKEY: private key missing" >&2 + fi + if test ! -e "$CERT"; then + echo "$CERT: certificate for private key missing" >&2 + fi + if test ! -e "$CACERT"; then + echo "$CACERT: CA certificate missing" >&2 + fi + echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 + if test "$MODE" = discovery; then + echo "You may also delete or rename $PRIVKEY to disable SSL requirement" >&2 + fi + exit 1 + fi + SSL_OPTS="--private-key=$PRIVKEY --certificate=$CERT --ca-cert=$CACERT" +} + case "$1" in start) if test -z "$NETDEVS"; then @@ -115,40 +142,36 @@ case "$1" in echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 exit 0 fi - if test -z "$CONTROLLER"; then - echo "$default: No controller configured, switch disabled" >&2 - echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 - exit 0 - fi - if test "$IN_BAND" != yes && test "$IN_BAND" != no; then - echo "$default: IN_BAND must set to 'yes' or 'no'" >&2 + if test "$MODE" = discovery; then + unset CONTROLLER + elif test "$MODE" = in-band || test "$MODE" = out-of-band; then + if test -z "$CONTROLLER"; then + echo "$default: No controller configured and not configured for discovery, switch disabled" >&2 + echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 + exit 0 + fi + else + echo "$default: MODE must set to 'discovery', 'in-band', or 'out-of-band'" >&2 echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 exit 1 fi + : ${PRIVKEY:=/etc/openflow-switch/of0-privkey.pem} + : ${CERT:=/etc/openflow-switch/of0-cert.pem} + : ${CACERT:=/etc/openflow-switch/cacert.pem} case $CONTROLLER in + '') + # Discovery mode. + if test -e "$PRIVKEY"; then + configure_ssl + fi + ;; tcp:*) ;; ssl:*) - : ${PRIVKEY:=/etc/openflow-switch/of0-privkey.pem} - : ${CERT:=/etc/openflow-switch/of0-cert.pem} - : ${CACERT:=/etc/openflow-switch/cacert.pem} - if test ! -e "$PRIVKEY" || test ! -e "$CERT" || - test ! -e "$CACERT"; then - if test ! -e "$PRIVKEY"; then - echo "$PRIVKEY: private key missing" >&2 - fi - if test ! -e "$CERT"; then - echo "$CERT: certificate for private key missing" >&2 - fi - if test ! -e "$CACERT"; then - echo "$CACERT: CA certificate missing" >&2 - fi - echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 - exit 1 - fi + configure_ssl ;; *) - echo "$default: CONTROLLER must be in the form 'ssl:HOST[:PORT]' or 'tcp:HOST[:PORT]'" >&2 + echo "$default: CONTROLLER must be in the form 'ssl:HOST[:PORT]' or 'tcp:HOST[:PORT]' when not in discovery mode" >&2 echo "Run ofp-switch-setup or edit /etc/default/openflow-switch to configure" >&2 exit 1 esac @@ -174,7 +197,7 @@ case "$1" in must_succeed "Adding $netdev to datapath" dpctl addif nl:0 $netdev done - if test "$IN_BAND" = yes; then + if test "$MODE" = in-band; then if test "$SWITCH_IP" = dhcp; then must_succeed "Temporarily disabling of0" ifconfig of0 down else @@ -187,6 +210,7 @@ case "$1" in echo -n "Starting $DESC: " start-stop-daemon --start --quiet --pidfile $PIDFILE \ --exec $DAEMON -- nl:0 $CONTROLLER --detach --pidfile=$PIDFILE \ + --verbose=ANY:console:emer --listen=punix:$MGMTSOCK \ $DAEMON_OPTS $SSL_OPTS if running; then echo "$NAME." @@ -194,7 +218,7 @@ case "$1" in echo " ERROR." fi - if test "$IN_BAND" = yes && test "$SWITCH_IP" = dhcp; then + if test "$MODE" = in-band && test "$SWITCH_IP" = dhcp; then echo -n "Starting dhclient on of0: " start-stop-daemon --start --quiet --pidfile $DHCLIENT_PIDFILE \ --exec /sbin/dhclient -- -q -pf $DHCLIENT_PIDFILE of0 diff --git a/debian/openflow-switch.install b/debian/openflow-switch.install index 96db727..2c85136 100644 --- a/debian/openflow-switch.install +++ b/debian/openflow-switch.install @@ -1,4 +1,6 @@ -switch/switch usr/sbin -secchan/secchan usr/sbin -utilities/dpctl usr/sbin +_debian/switch/switch usr/sbin +_debian/secchan/secchan usr/sbin +_debian/utilities/dpctl usr/sbin +_debian/utilities/ofp-discover usr/sbin +_debian/utilities/ofp-kill usr/sbin debian/ofp-switch-setup usr/sbin diff --git a/debian/openflow-switch.manpages b/debian/openflow-switch.manpages index 905d060..b012d8d 100644 --- a/debian/openflow-switch.manpages +++ b/debian/openflow-switch.manpages @@ -1,4 +1,6 @@ debian/ofp-switch-setup.8 -secchan/secchan.8 -switch/switch.8 +_debian/secchan/secchan.8 +_debian/switch/switch.8 +_debian/utilities/ofp-discover.8 +_debian/utilities/ofp-kill.8 utilities/dpctl.8 diff --git a/debian/openflow-switch.postinst b/debian/openflow-switch.postinst deleted file mode 100755 index c8dc9a4..0000000 --- a/debian/openflow-switch.postinst +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh -# postinst script for openflow-switch -# -# see: dh_installdeb(1) - -set -e - -. /usr/share/debconf/confmodule - -#DEBHELPER# - -exit 0 - - diff --git a/debian/openflow-switch.templates b/debian/openflow-switch.templates index c6d4b5e..7876109 100644 --- a/debian/openflow-switch.templates +++ b/debian/openflow-switch.templates @@ -35,24 +35,59 @@ _Description: Some Network Devices Have IP or IPv6 Addresses If this is an unintentional mistake, move back and fix the selection, or de-configure the IP or IPv6 from these network devices. -Template: openflow-switch/band +Template: openflow-switch/mode Type: select -_Choices: in-band, out-of-band -Default: in-band +_Choices: discovery, in-band, out-of-band +Default: discovery _Description: Switch-to-controller access method: The OpenFlow switch must be able to contact the OpenFlow controller over - the network. It can do so in one of two ways: + the network. It can do so in one of three ways: . - in-band: A single network is used for OpenFlow traffic and other data - traffic; that is, the switch contacts the controller over one of the - network devices selected as OpenFlow switch netdevs in the previous - question. This is the most common case. + discovery: A single network is used for OpenFlow traffic and other + data traffic; that is, the switch contacts the controller over one of + the network devices selected as OpenFlow switch network devices in + the previous question. The switch automatically determines the + location of the controller using a DHCP request with an + OpenFlow-specific vendor option. This is the most common case. + . + in-band: As above, but the location of the controller is manually + configured. . out-of-band: OpenFlow traffic uses a network separate from the data traffic that it controls. If this is the case, the control network must already be configured on a network device other than one of those selected as an OpenFlow switch netdev in the previous question. +Template: openflow-switch/discover +Type: note +_Description: Preparing to discover controller. + The setup program will now attempt to discover the OpenFlow controller. + Controller discovery may take up to 30 seconds. Please be patient. + . + See secchan(8) for instructions on how to configure a DHCP server for + controller discovery. + +Template: openflow-switch/discovery-failure +Type: error +_Description: Controller discovery failed. + The controller's location could not be determined automatically. + . + Ensure that the OpenFlow DHCP server is properly configured. See + secchan(8) for instructions on how to configure a DHCP server for + controller discovery. + +Template: openflow-switch/discovery-success +Type: boolean +Default: true +_Description: Use discovered settings? + Controller discovery obtained the following settings: + . + Controller location: ${controller-vconn} + . + PKI URL: ${pki-uri} + . + Please verify that these settings are correct. + Template: openflow-switch/switch-ip Type: string Default: dhcp @@ -89,12 +124,16 @@ _Description: The controller location is invalid. connect to the controller over SSL (recommended for security) or "tcp:HOST[:PORT]" to connect over cleartext TCP. -Template: openflow-switch/pki-host +Template: openflow-switch/pki-uri Type: string -_Description: OpenFlow PKI server host name: - Specify the host name or IP address of the server that hosts the OpenFlow - public key infrastructure (PKI). This is usually the same host as the - OpenFlow controller. +_Description: OpenFlow PKI server host name or URL: + Specify a URL to the OpenFlow public key infrastructure (PKI). If a + host name or IP address is specified in place of a URL, then + http:///openflow/pki/ will be used, + where is the specified host name or IP address. + . + The OpenFlow PKI is usually on the same machine as the OpenFlow + controller. . The setup process will connect to the OpenFlow PKI server over HTTP, using the system's configured default HTTP proxy (if any). @@ -105,7 +144,7 @@ _Description: The switch CA certificate could not be retrieved. Retrieval of ${url} failed, with the following status: "${error}". . Ensure that the OpenFlow PKI server is correctly configured and - available on ${pki-host}. If the system is configured to use an HTTP + available at ${pki-uri}. If the system is configured to use an HTTP proxy, also make sure that the HTTP proxy is available and that the PKI server can be reached through it. @@ -147,7 +186,7 @@ _Description: The certificate request could not be sent. Posting to ${url} failed, with the following status: "${error}". . Ensure that the OpenFlow PKI server is correctly configured and - available on ${pki-host}. + available at ${pki-uri}. Template: openflow-switch/fetch-switch-cert Type: select @@ -179,7 +218,7 @@ _Description: Signed switch certificate could not be retrieved. . This probably indicates that the switch's certificate request has not yet been signed. If this is the problem, it may be fixed by signing - the certificate request at ${pki-host}, then trying to fetch the + the certificate request at ${pki-uri}, then trying to fetch the signed switch certificate again. Template: openflow-switch/complete diff --git a/debian/po/templates.pot b/debian/po/templates.pot index 927eb09..96f7f02 100644 --- a/debian/po/templates.pot +++ b/debian/po/templates.pot @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: openflow-dev@lists.stanford.edu\n" -"POT-Creation-Date: 2008-06-12 14:45-0700\n" +"POT-Creation-Date: 2008-07-23 14:55-0700\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -104,7 +104,7 @@ msgstr "" #. Type: select #. Choices #: ../openflow-switch.templates:4001 -msgid "in-band, out-of-band" +msgid "discovery, in-band, out-of-band" msgstr "" #. Type: select @@ -118,17 +118,26 @@ msgstr "" #: ../openflow-switch.templates:4002 msgid "" "The OpenFlow switch must be able to contact the OpenFlow controller over the " -"network. It can do so in one of two ways:" +"network. It can do so in one of three ways:" msgstr "" #. Type: select #. Description #: ../openflow-switch.templates:4002 msgid "" -"in-band: A single network is used for OpenFlow traffic and other data " +"discovery: A single network is used for OpenFlow traffic and other data " "traffic; that is, the switch contacts the controller over one of the network " -"devices selected as OpenFlow switch netdevs in the previous question. This " -"is the most common case." +"devices selected as OpenFlow switch network devices in the previous " +"question. The switch automatically determines the location of the " +"controller using a DHCP request with an OpenFlow-specific vendor option. " +"This is the most common case." +msgstr "" + +#. Type: select +#. Description +#: ../openflow-switch.templates:4002 +msgid "" +"in-band: As above, but the location of the controller is manually configured." msgstr "" #. Type: select @@ -141,15 +150,87 @@ msgid "" "OpenFlow switch netdev in the previous question." msgstr "" -#. Type: string +#. Type: note #. Description #: ../openflow-switch.templates:5001 +msgid "Preparing to discover controller." +msgstr "" + +#. Type: note +#. Description +#: ../openflow-switch.templates:5001 +msgid "" +"The setup program will now attempt to discover the OpenFlow controller. " +"Controller discovery may take up to 30 seconds. Please be patient." +msgstr "" + +#. Type: note +#. Description +#: ../openflow-switch.templates:5001 +msgid "" +"See secchan(8) for instructions on how to configure a DHCP server for " +"controller discovery." +msgstr "" + +#. Type: error +#. Description +#: ../openflow-switch.templates:6001 +msgid "Controller discovery failed." +msgstr "" + +#. Type: error +#. Description +#: ../openflow-switch.templates:6001 +msgid "The controller's location could not be determined automatically." +msgstr "" + +#. Type: error +#. Description +#: ../openflow-switch.templates:6001 +msgid "" +"Ensure that the OpenFlow DHCP server is properly configured. See secchan(8) " +"for instructions on how to configure a DHCP server for controller discovery." +msgstr "" + +#. Type: boolean +#. Description +#: ../openflow-switch.templates:7001 +msgid "Use discovered settings?" +msgstr "" + +#. Type: boolean +#. Description +#: ../openflow-switch.templates:7001 +msgid "Controller discovery obtained the following settings:" +msgstr "" + +#. Type: boolean +#. Description +#: ../openflow-switch.templates:7001 +msgid "Controller location: ${controller-vconn}" +msgstr "" + +#. Type: boolean +#. Description +#: ../openflow-switch.templates:7001 +msgid "PKI URL: ${pki-uri}" +msgstr "" + +#. Type: boolean +#. Description +#: ../openflow-switch.templates:7001 +msgid "Please verify that these settings are correct." +msgstr "" + +#. Type: string +#. Description +#: ../openflow-switch.templates:8001 msgid "Switch IP address:" msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:5001 +#: ../openflow-switch.templates:8001 msgid "" "For in-band communication with the controller, the OpenFlow switch must be " "able to determine its own IP address. Its IP address may be configured " @@ -158,13 +239,13 @@ msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:5001 +#: ../openflow-switch.templates:8001 msgid "For static configuration, specify the switch's IP address as a string." msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:5001 +#: ../openflow-switch.templates:8001 msgid "" "For dynamic configuration with DHCP (the most common case), specify \"dhcp" "\". Configuration with DHCP will only work reliably if the network topology " @@ -174,13 +255,13 @@ msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:6001 +#: ../openflow-switch.templates:9001 msgid "The switch IP address is invalid." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:6001 +#: ../openflow-switch.templates:9001 msgid "" "The switch IP address must specified as \"dhcp\" or a valid IP address in " "dotted-octet form (e.g. \"1.2.3.4\")." @@ -188,13 +269,13 @@ msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:7001 +#: ../openflow-switch.templates:10001 msgid "Controller location:" msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:7001 +#: ../openflow-switch.templates:10001 msgid "" "Specify how the OpenFlow switch should connect to the OpenFlow controller. " "The value should be in form \"ssl:HOST[:PORT]\" to connect to the controller " @@ -204,13 +285,13 @@ msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:8001 +#: ../openflow-switch.templates:11001 msgid "The controller location is invalid." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:8001 +#: ../openflow-switch.templates:11001 msgid "" "The controller location must be specifed as \"ssl:HOST[:PORT]\" to connect " "to the controller over SSL (recommended for security) or \"tcp:HOST[:PORT]\" " @@ -219,22 +300,30 @@ msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:9001 -msgid "OpenFlow PKI server host name:" +#: ../openflow-switch.templates:12001 +msgid "OpenFlow PKI server host name or URL:" msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:9001 +#: ../openflow-switch.templates:12001 msgid "" -"Specify the host name or IP address of the server that hosts the OpenFlow " -"public key infrastructure (PKI). This is usually the same host as the " -"OpenFlow controller." +"Specify a URL to the OpenFlow public key infrastructure (PKI). If a host " +"name or IP address is specified in place of a URL, then http:///" +"openflow/pki/ will be used, where is the specified host name or IP " +"address." msgstr "" #. Type: string #. Description -#: ../openflow-switch.templates:9001 +#: ../openflow-switch.templates:12001 +msgid "" +"The OpenFlow PKI is usually on the same machine as the OpenFlow controller." +msgstr "" + +#. Type: string +#. Description +#: ../openflow-switch.templates:12001 msgid "" "The setup process will connect to the OpenFlow PKI server over HTTP, using " "the system's configured default HTTP proxy (if any)." @@ -242,22 +331,22 @@ msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:10001 +#: ../openflow-switch.templates:13001 msgid "The switch CA certificate could not be retrieved." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:10001 +#: ../openflow-switch.templates:13001 msgid "Retrieval of ${url} failed, with the following status: \"${error}\"." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:10001 +#: ../openflow-switch.templates:13001 msgid "" -"Ensure that the OpenFlow PKI server is correctly configured and available on " -"${pki-host}. If the system is configured to use an HTTP proxy, also make " +"Ensure that the OpenFlow PKI server is correctly configured and available at " +"${pki-uri}. If the system is configured to use an HTTP proxy, also make " "sure that the HTTP proxy is available and that the PKI server can be reached " "through it." msgstr "" @@ -268,20 +357,20 @@ msgstr "" #. Choices #. Type: select #. Choices -#: ../openflow-switch.templates:11001 ../openflow-switch.templates:12001 -#: ../openflow-switch.templates:14001 +#: ../openflow-switch.templates:14001 ../openflow-switch.templates:15001 +#: ../openflow-switch.templates:17001 msgid "yes, no" msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:11002 +#: ../openflow-switch.templates:14002 msgid "Is ${fingerprint} the controller CA's fingerprint?" msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:11002 +#: ../openflow-switch.templates:14002 msgid "" "If a man-in-the-middle attack is possible in your network environment, check " "that the controller CA's fingerprint is really ${fingerprint}. Answer \"yes" @@ -290,7 +379,7 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:11002 +#: ../openflow-switch.templates:14002 msgid "" "If a man-in-the-middle attack is not a concern, there is no need to verify " "the fingerprint. Simply answer \"yes\"." @@ -298,13 +387,13 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:12002 +#: ../openflow-switch.templates:15002 msgid "Send certificate request to switch CA?" msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:12002 +#: ../openflow-switch.templates:15002 msgid "" "Before it can connect to the controller over SSL, the OpenFlow switch's key " "must be signed by the switch certificate authority (CA) located on the " @@ -314,7 +403,7 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:12002 +#: ../openflow-switch.templates:15002 msgid "" "Answer \"yes\" to send a signing request to the switch CA now. This is " "ordinarily the correct choice. There is no harm in sending a given signing " @@ -323,7 +412,7 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:12002 +#: ../openflow-switch.templates:15002 msgid "" "Answer \"no\" to skip sending a signing request to the switch CA. Unless the " "request has already been sent to the switch CA, manual sending of the " @@ -332,33 +421,33 @@ msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:13001 +#: ../openflow-switch.templates:16001 msgid "The certificate request could not be sent." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:13001 +#: ../openflow-switch.templates:16001 msgid "Posting to ${url} failed, with the following status: \"${error}\"." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:13001 +#: ../openflow-switch.templates:16001 msgid "" -"Ensure that the OpenFlow PKI server is correctly configured and available on " -"${pki-host}." +"Ensure that the OpenFlow PKI server is correctly configured and available at " +"${pki-uri}." msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:14002 +#: ../openflow-switch.templates:17002 msgid "Fetch signed switch certificate from PKI server?" msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:14002 +#: ../openflow-switch.templates:17002 msgid "" "Before it can connect to the controller over SSL, the OpenFlow switch's key " "must be signed by the switch certificate authority (CA) located on the " @@ -368,7 +457,7 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:14002 +#: ../openflow-switch.templates:17002 msgid "" "At this point, a signing request has been sent to the switch CA (or sending " "a request has been manually skipped), but the signed certificate has not yet " @@ -378,7 +467,7 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:14002 +#: ../openflow-switch.templates:17002 msgid "" "Answer \"yes\" to attempt to retrieve the signed switch certificate from the " "switch CA. If the switch certificate request has been signed at the PKI " @@ -387,7 +476,7 @@ msgstr "" #. Type: select #. Description -#: ../openflow-switch.templates:14002 +#: ../openflow-switch.templates:17002 msgid "" "Answer \"no\" to postpone switch configuration. The configuration process " "must be restarted later, when the switch certificate request has been signed." @@ -395,13 +484,13 @@ msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:15001 +#: ../openflow-switch.templates:18001 msgid "Signed switch certificate could not be retrieved." msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:15001 +#: ../openflow-switch.templates:18001 msgid "" "The signed switch certificate could not be retrieved from the switch CA: " "retrieval of ${url} failed, with the following status: \"${error}\"." @@ -409,23 +498,23 @@ msgstr "" #. Type: error #. Description -#: ../openflow-switch.templates:15001 +#: ../openflow-switch.templates:18001 msgid "" "This probably indicates that the switch's certificate request has not yet " "been signed. If this is the problem, it may be fixed by signing the " -"certificate request at ${pki-host}, then trying to fetch the signed switch " +"certificate request at ${pki-uri}, then trying to fetch the signed switch " "certificate again." msgstr "" #. Type: note #. Description -#: ../openflow-switch.templates:16001 +#: ../openflow-switch.templates:19001 msgid "OpenFlow Switch Setup Finished" msgstr "" #. Type: note #. Description -#: ../openflow-switch.templates:16001 +#: ../openflow-switch.templates:19001 msgid "" "Setup of this OpenFlow switch is finished. Complete the setup procedure to " "enable the switch." diff --git a/debian/rules b/debian/rules index ab44b77..ecd706f 100755 --- a/debian/rules +++ b/debian/rules @@ -29,8 +29,10 @@ configure: configure-stamp configure-stamp: dh_testdir test -e configure || ./boot.sh - test -e Makefile || \ - ./configure --prefix=/usr --localstatedir=/var --enable-ssl + test -d _debian || mkdir _debian + cd _debian && ( \ + test -e Makefile || \ + ../configure --prefix=/usr --localstatedir=/var --enable-ssl) touch configure-stamp #Architecture @@ -38,18 +40,19 @@ build: build-arch build-indep build-arch: build-arch-stamp build-arch-stamp: configure-stamp - $(MAKE) + $(MAKE) -C _debian touch $@ build-indep: build-indep-stamp build-indep-stamp: configure-stamp - $(MAKE) dist distdir=openflow + $(MAKE) -C _debian dist distdir=openflow touch $@ clean: dh_testdir dh_testroot rm -f build-arch-stamp build-indep-stamp configure-stamp + rm -rf _debian [ ! -f Makefile ] || $(MAKE) distclean dh_clean debconf-updatepo @@ -67,7 +70,7 @@ endif kdist_clean: dh_clean - make KERNELDIR=$(KSRC) KVERREL=$(KVERS) clean + rm -rf openflow kdist_config: prep-deb-files @@ -97,7 +100,7 @@ install-indep: build-indep dh_testroot dh_clean -k -i dh_installdirs -i - cp openflow.tar.gz $(MODDIR) + cp _debian/openflow.tar.gz $(MODDIR) cd debian; cp changelog control compat *.modules.in rules $(MODDIR)/debian cd debian/openflow-datapath-source/usr/src && tar -c modules | bzip2 -9 > openflow-datapath.tar.bz2 && rm -rf modules install -m644 debian/openflow-pki.apache2 debian/openflow-pki/etc/apache2/sites-available/openflow-pki @@ -108,7 +111,7 @@ install-arch: build-arch dh_testroot dh_clean -k -s dh_installdirs -s - $(MAKE) DESTDIR=$(CURDIR)/debian/openflow install + $(MAKE) -C _debian DESTDIR=$(CURDIR)/debian/openflow install cp debian/openflow-switch.overrides debian/openflow-switch/usr/share/lintian/overrides/openflow-switch dh_install -s diff --git a/include/Makefile.am b/include/Makefile.am index 1fedb24..062f7a6 100644 --- a/include/Makefile.am +++ b/include/Makefile.am @@ -2,7 +2,10 @@ noinst_HEADERS = \ buffer.h \ command-line.h \ compiler.h \ + csum.h \ daemon.h \ + dhcp-client.h \ + dhcp.h \ dynamic-string.h \ dpif.h \ fatal-signal.h \ @@ -21,11 +24,15 @@ noinst_HEADERS = \ packets.h \ poll-loop.h \ queue.h \ + random.h \ rconn.h \ socket-util.h \ + type-props.h \ + timeval.h \ util.h \ vconn.h \ vconn-ssl.h \ + vconn-stream.h \ vlog-socket.h \ vlog.h \ xtoxll.h \ diff --git a/include/buffer.h b/include/buffer.h index 0d024ec..e171800 100644 --- a/include/buffer.h +++ b/include/buffer.h @@ -48,6 +48,7 @@ struct buffer { void *l2; /* Link-level header. */ void *l3; /* Network-level header. */ void *l4; /* Transport-level header. */ + void *l7; /* Application data. */ struct buffer *next; /* Next in a list of buffers. */ }; @@ -69,13 +70,17 @@ void *buffer_end(const struct buffer *); void *buffer_put_uninit(struct buffer *, size_t); void *buffer_put(struct buffer *, const void *, size_t); +void buffer_reserve(struct buffer *, size_t); void *buffer_push_uninit(struct buffer *b, size_t); +void *buffer_push(struct buffer *b, const void *, size_t); size_t buffer_headroom(struct buffer *); size_t buffer_tailroom(struct buffer *); -void buffer_reserve_tailroom(struct buffer *, size_t); +void buffer_prealloc_headroom(struct buffer *, size_t); +void buffer_prealloc_tailroom(struct buffer *, size_t); void buffer_clear(struct buffer *); -void buffer_pull(struct buffer *, size_t); +void *buffer_pull(struct buffer *, size_t); +void *buffer_try_pull(struct buffer *, size_t); #endif /* buffer.h */ diff --git a/include/csum.h b/include/csum.h new file mode 100644 index 0000000..d9253e0 --- /dev/null +++ b/include/csum.h @@ -0,0 +1,48 @@ +/* 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 CSUM_H +#define CSUM_H 1 + +#include +#include + +uint16_t csum(const void *, size_t); +uint32_t csum_add16(uint32_t partial, uint16_t); +uint32_t csum_add32(uint32_t partial, uint32_t); +uint32_t csum_continue(uint32_t partial, const void *, size_t); +uint16_t csum_finish(uint32_t partial); +uint16_t recalc_csum16(uint16_t old_csum, uint16_t old_u16, uint16_t new_u16); +uint16_t recalc_csum32(uint16_t old_csum, uint32_t old_u32, uint32_t new_u32); + +#endif /* csum.h */ diff --git a/include/daemon.h b/include/daemon.h index 208cee9..d7273e0 100644 --- a/include/daemon.h +++ b/include/daemon.h @@ -34,6 +34,7 @@ #ifndef DAEMON_H #define DAEMON_H 1 +char *make_pidfile_name(const char *name); void set_pidfile(const char *name); void set_detach(void); void daemonize(void); diff --git a/include/dhcp-client.h b/include/dhcp-client.h new file mode 100644 index 0000000..9019ff1 --- /dev/null +++ b/include/dhcp-client.h @@ -0,0 +1,66 @@ +/* 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; +struct netdev; +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 *); +uint32_t dhclient_get_router(const struct dhclient *); +const struct dhcp_msg *dhclient_get_config(const struct dhclient *); + +int dhclient_configure_netdev(struct dhclient *); +int dhclient_update_resolv_conf(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 0000000..f44c4c6 --- /dev/null +++ b/include/dhcp.h @@ -0,0 +1,279 @@ +/* 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) \ + DHCP_VNDOPT(OFP_PKI_URI, 2, 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. */ +}; + +const char *dhcp_option_to_string(const struct dhcp_option *, int code, + struct ds *); +bool dhcp_option_equals(const struct dhcp_option *, + const struct dhcp_option *); + +/* 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_msg_to_string(const struct dhcp_msg *, bool multiline, + 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/dpif.h b/include/dpif.h index 4f806d7..122c64d 100644 --- a/include/dpif.h +++ b/include/dpif.h @@ -60,6 +60,5 @@ int dpif_add_dp(struct dpif *); int dpif_del_dp(struct dpif *); int dpif_add_port(struct dpif *, const char *netdev); int dpif_del_port(struct dpif *, const char *netdev); -int dpif_benchmark_nl(struct dpif *, uint32_t, uint32_t); #endif /* dpif.h */ diff --git a/include/dynamic-string.h b/include/dynamic-string.h index e43d006..ea69c6a 100644 --- a/include/dynamic-string.h +++ b/include/dynamic-string.h @@ -49,6 +49,7 @@ struct ds { #define DS_EMPTY_INITIALIZER { NULL, 0, 0 } void ds_init(struct ds *); +void ds_clear(struct ds *); void ds_reserve(struct ds *, size_t min_length); void ds_put_char(struct ds *, char); void ds_put_cstr(struct ds *, const char *); diff --git a/include/learning-switch.h b/include/learning-switch.h index 8e30446..07bb15d 100644 --- a/include/learning-switch.h +++ b/include/learning-switch.h @@ -39,8 +39,7 @@ struct buffer; struct rconn; -struct lswitch *lswitch_create(struct rconn *, - bool learn_macs, bool setup_flows); +struct lswitch *lswitch_create(struct rconn *, bool learn_macs, int max_idle); void lswitch_destroy(struct lswitch *); void lswitch_process_packet(struct lswitch *, struct rconn *, const struct buffer *); diff --git a/include/netdev.h b/include/netdev.h index 3dfd5db..e58a90d 100644 --- a/include/netdev.h +++ b/include/netdev.h @@ -52,20 +52,32 @@ enum netdev_flags { NETDEV_PROMISC = 0x0002 /* Promiscuous mode? */ }; +enum netdev_pseudo_ethertype { + NETDEV_ETH_TYPE_NONE = -128, /* Receive no frames. */ + NETDEV_ETH_TYPE_ANY, /* Receive all frames. */ + NETDEV_ETH_TYPE_802_2 /* Receive all IEEE 802.2 frames. */ +}; + struct netdev; -int netdev_open(const char *name, struct netdev **); +int netdev_open(const char *name, int ethertype, struct netdev **); void netdev_close(struct netdev *); int netdev_recv(struct netdev *, struct buffer *); void netdev_recv_wait(struct netdev *); -int netdev_send(struct netdev *, struct buffer *); +void netdev_drain(struct netdev *); +int netdev_send(struct netdev *, const struct buffer *); const uint8_t *netdev_get_etheraddr(const struct netdev *); const char *netdev_get_name(const struct netdev *); int netdev_get_mtu(const struct netdev *); int netdev_get_speed(const struct netdev *); uint32_t netdev_get_features(const struct netdev *); bool netdev_get_in4(const struct netdev *, struct in_addr *); +int netdev_set_in4(struct netdev *, struct in_addr addr, struct in_addr mask); +int netdev_add_router(struct netdev *, struct in_addr router); bool netdev_get_in6(const struct netdev *, struct in6_addr *); int netdev_get_flags(const struct netdev *, enum netdev_flags *); -int netdev_set_flags(struct netdev *, enum netdev_flags); +int netdev_set_flags(struct netdev *, enum netdev_flags, bool permanent); +int netdev_turn_flags_on(struct netdev *, enum netdev_flags, bool permanent); +int netdev_turn_flags_off(struct netdev *, enum netdev_flags, bool permanent); +int netdev_arp_lookup(const struct netdev *, uint32_t ip, uint8_t mac[6]); #endif /* netdev.h */ diff --git a/include/openflow-netlink.h b/include/openflow-netlink.h index 7774cd5..0b67c99 100644 --- a/include/openflow-netlink.h +++ b/include/openflow-netlink.h @@ -61,8 +61,6 @@ enum dp_genl_command { DP_GENL_C_DEL_PORT, /* Remove port from datapath. */ DP_GENL_C_OPENFLOW, /* Encapsulated OpenFlow protocol. */ - DP_GENL_C_BENCHMARK_NL, /* Benchmark netlink connection */ - __DP_GENL_C_MAX, DP_GENL_C_MAX = __DP_GENL_C_MAX - 1 }; diff --git a/include/openflow.h b/include/openflow.h index 6303b49..2c4746c 100644 --- a/include/openflow.h +++ b/include/openflow.h @@ -42,6 +42,24 @@ #include #endif +#ifdef SWIG +#define OFP_ASSERT(EXPR) /* SWIG can't handle OFP_ASSERT. */ +#elif !defined(__cplusplus) +/* Build-time assertion for use in a declaration context. */ +#define OFP_ASSERT(EXPR) \ + extern int (*build_assert(void))[ sizeof(struct { \ + unsigned int build_assert_failed : (EXPR) ? 1 : -1; })] +#else /* __cplusplus */ +#include +#define OFP_ASSERT BOOST_STATIC_ASSERT +#endif /* __cplusplus */ + +#ifndef SWIG +#define OFP_PACKED __attribute__((packed)) +#else +#define OFP_PACKED /* SWIG doesn't understand __attribute. */ +#endif + /* Maximum length of a OpenFlow packet. */ #define OFP_MAXLEN (sizeof(struct ofp_switch_features) \ + (sizeof(struct ofp_phy_port) * OFPP_MAX) + 200) @@ -50,7 +68,7 @@ /* The most significant bit being set in the version field indicates an * experimental OpenFlow version. */ -#define OFP_VERSION 0x83 +#define OFP_VERSION 0x85 #define OFP_MAX_TABLE_NAME_LEN 32 #define OFP_MAX_PORT_NAME_LEN 16 @@ -94,7 +112,9 @@ enum ofp_type { OFPT_PORT_STATUS, /* 11 Async message */ OFPT_ERROR_MSG, /* 12 Async message */ OFPT_STATS_REQUEST, /* 13 Controller/switch message */ - OFPT_STATS_REPLY /* 14 Controller/switch message */ + OFPT_STATS_REPLY, /* 14 Controller/switch message */ + OFPT_ECHO_REQUEST, /* 15 Symmetric message */ + OFPT_ECHO_REPLY /* 16 Symmetric message */ }; /* Header on all OpenFlow packets. */ @@ -106,6 +126,7 @@ struct ofp_header { Replies use the same id as was in the request to facilitate pairing. */ }; +OFP_ASSERT(sizeof(struct ofp_header) == 8); #define OFP_DEFAULT_MISS_SEND_LEN 128 @@ -121,6 +142,7 @@ struct ofp_switch_config { uint16_t miss_send_len; /* Max bytes of new flow that datapath should send to the controller. */ }; +OFP_ASSERT(sizeof(struct ofp_switch_config) == 12); /* Capabilities supported by the datapath. */ enum ofp_capabilities { @@ -158,6 +180,7 @@ struct ofp_phy_port { uint32_t speed; /* Current speed in Mbps */ uint32_t features; /* Bitmap of supported "ofp_port_features"s. */ }; +OFP_ASSERT(sizeof(struct ofp_phy_port) == 36); /* Switch features. */ struct ofp_switch_features { @@ -177,12 +200,14 @@ struct ofp_switch_features { /* Features. */ uint32_t capabilities; /* Bitmap of support "ofp_capabilities". */ uint32_t actions; /* Bitmap of supported "ofp_action_type"s. */ + uint8_t pad[4]; /* Align to 64-bits. */ /* Port info.*/ struct ofp_phy_port ports[0]; /* Port definitions. The number of ports is inferred from the length field in the header. */ }; +OFP_ASSERT(sizeof(struct ofp_switch_features) == 48); /* What changed about the phsyical port */ enum ofp_port_reason { @@ -198,12 +223,14 @@ struct ofp_port_status { uint8_t pad[3]; /* Align to 32-bits */ struct ofp_phy_port desc; }; +OFP_ASSERT(sizeof(struct ofp_port_status) == 48); /* Modify behavior of the physical port */ struct ofp_port_mod { struct ofp_header header; struct ofp_phy_port desc; }; +OFP_ASSERT(sizeof(struct ofp_port_mod) == 44); /* Why is this packet being sent to the controller? */ enum ofp_reason { @@ -226,6 +253,7 @@ struct ofp_packet_in { offsetof(struct ofp_packet_in, data) == sizeof(struct ofp_packet_in) - 2. */ }; +OFP_ASSERT(sizeof(struct ofp_packet_in) == 20); enum ofp_action_type { OFPAT_OUTPUT, /* Output to switch port. */ @@ -245,6 +273,7 @@ struct ofp_action_output { uint16_t max_len; uint16_t port; }; +OFP_ASSERT(sizeof(struct ofp_action_output) == 4); /* The VLAN id is 12-bits, so we'll use the entire 16 bits to indicate * special conditions. All ones is used to indicate that no VLAN id was @@ -259,10 +288,11 @@ struct ofp_action { struct ofp_action_output output; /* OFPAT_OUTPUT: output struct. */ uint16_t vlan_id; /* OFPAT_SET_DL_VLAN: VLAN id. */ uint8_t dl_addr[OFP_ETH_ALEN]; /* OFPAT_SET_DL_SRC/DST */ - uint32_t nw_addr; /* OFPAT_SET_NW_SRC/DST */ + uint32_t nw_addr OFP_PACKED; /* OFPAT_SET_NW_SRC/DST */ uint16_t tp; /* OFPAT_SET_TP_SRC/DST */ } arg; }; +OFP_ASSERT(sizeof(struct ofp_action) == 8); /* Send packet (controller -> datapath). */ struct ofp_packet_out { @@ -275,6 +305,7 @@ struct ofp_packet_out { uint8_t data[0]; /* buffer_id == -1 */ } u; }; +OFP_ASSERT(sizeof(struct ofp_packet_out) == 16); enum ofp_flow_mod_command { OFPFC_ADD, /* New flow. */ @@ -323,6 +354,7 @@ struct ofp_match { uint16_t tp_src; /* TCP/UDP source port. */ uint16_t tp_dst; /* TCP/UDP destination port. */ }; +OFP_ASSERT(sizeof(struct ofp_match) == 36); /* Value used in "max_idle" to indicate that the entry is permanent */ #define OFP_FLOW_PERMANENT 0 @@ -345,6 +377,7 @@ struct ofp_flow_mod { struct ofp_action actions[0]; /* The number of actions is inferred from the length field in the header. */ }; +OFP_ASSERT(sizeof(struct ofp_flow_mod) == 60); /* Flow expiration (datapath -> controller). */ struct ofp_flow_expired { @@ -355,9 +388,11 @@ struct ofp_flow_expired { uint8_t pad[2]; /* Align to 32-bits. */ uint32_t duration; /* Time flow was alive in seconds. */ + uint8_t pad2[4]; /* Align to 64-bits. */ uint64_t packet_count; uint64_t byte_count; }; +OFP_ASSERT(sizeof(struct ofp_flow_expired) == 72); /* Error message (datapath -> controller). */ struct ofp_error_msg { @@ -368,6 +403,7 @@ struct ofp_error_msg { uint8_t data[0]; /* Variable-length data. Interpreted based on the type and code. */ }; +OFP_ASSERT(sizeof(struct ofp_error_msg) == 12); enum ofp_stats_types { /* Individual flow statistics. @@ -397,6 +433,7 @@ struct ofp_stats_request { uint16_t flags; /* OFPSF_REQ_* flags (none yet defined). */ uint8_t body[0]; /* Body of the request. */ }; +OFP_ASSERT(sizeof(struct ofp_stats_request) == 12); enum ofp_stats_reply_flags { OFPSF_REPLY_MORE = 1 << 0, /* More replies to follow */ @@ -408,6 +445,7 @@ struct ofp_stats_reply { uint16_t flags; /* OFPSF_REPLY_* flags. */ uint8_t body[0]; /* Body of the reply. */ }; +OFP_ASSERT(sizeof(struct ofp_stats_reply) == 12); /* Body for ofp_stats_request of type OFPST_FLOW. */ struct ofp_flow_stats_request { @@ -416,6 +454,7 @@ struct ofp_flow_stats_request { or 0xff for all tables. */ uint8_t pad[3]; /* Align to 32 bits. */ }; +OFP_ASSERT(sizeof(struct ofp_flow_stats_request) == 40); /* Body of reply to OFPST_FLOW request. */ struct ofp_flow_stats { @@ -424,13 +463,14 @@ struct ofp_flow_stats { uint8_t pad; struct ofp_match match; /* Description of fields. */ uint32_t duration; /* Time flow has been alive in seconds. */ - uint64_t packet_count; /* Number of packets in flow. */ - uint64_t byte_count; /* Number of bytes in flow. */ uint16_t priority; /* Priority of the entry. Only meaningful when this is not an exact-match entry. */ uint16_t max_idle; /* Number of seconds idle before expiration. */ + uint64_t packet_count; /* Number of packets in flow. */ + uint64_t byte_count; /* Number of bytes in flow. */ struct ofp_action actions[0]; /* Actions. */ }; +OFP_ASSERT(sizeof(struct ofp_flow_stats) == 64); /* Body for ofp_stats_request of type OFPST_AGGREGATE. */ struct ofp_aggregate_stats_request { @@ -439,13 +479,16 @@ struct ofp_aggregate_stats_request { or 0xff for all tables. */ uint8_t pad[3]; /* Align to 32 bits. */ }; +OFP_ASSERT(sizeof(struct ofp_aggregate_stats_request) == 40); /* Body of reply to OFPST_AGGREGATE request. */ struct ofp_aggregate_stats_reply { uint64_t packet_count; /* Number of packets in flows. */ uint64_t byte_count; /* Number of bytes in flows. */ uint32_t flow_count; /* Number of flows. */ + uint8_t pad[4]; /* Align to 64 bits. */ }; +OFP_ASSERT(sizeof(struct ofp_aggregate_stats_reply) == 24); /* Body of reply to OFPST_TABLE request. */ struct ofp_table_stats { @@ -454,16 +497,19 @@ struct ofp_table_stats { char name[OFP_MAX_TABLE_NAME_LEN]; uint32_t max_entries; /* Max number of entries supported */ uint32_t active_count; /* Number of active entries */ + uint8_t pad2[4]; /* Align to 64 bits. */ uint64_t matched_count; /* Number of packets that hit table */ }; +OFP_ASSERT(sizeof(struct ofp_table_stats) == 56); /* Statistics about a particular port */ struct ofp_port_stats { uint16_t port_no; - uint8_t pad[2]; /* Align to 32-bits */ + uint8_t pad[6]; /* Align to 64-bits */ uint64_t rx_count; /* Number of received packets */ uint64_t tx_count; /* Number of transmitted packets */ uint64_t drop_count; /* Number of packets dropped by interface */ }; +OFP_ASSERT(sizeof(struct ofp_port_stats) == 32); #endif /* openflow.h */ diff --git a/include/packets.h b/include/packets.h index b4a5f05..177bc30 100644 --- a/include/packets.h +++ b/include/packets.h @@ -35,10 +35,15 @@ #include #include +#include "compiler.h" +#include "random.h" #include "util.h" #define ETH_ADDR_LEN 6 +static const uint8_t eth_addr_broadcast[ETH_ADDR_LEN] UNUSED + = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + static inline bool eth_addr_is_broadcast(const uint8_t ea[6]) { return (ea[0] & ea[1] & ea[2] & ea[3] & ea[4] & ea[5]) == 0xff; @@ -51,11 +56,38 @@ static inline bool eth_addr_is_local(const uint8_t ea[6]) { return ea[0] & 2; } +static inline bool eth_addr_is_zero(const uint8_t ea[6]) +{ + return !(ea[0] | ea[1] | ea[2] | ea[3] | ea[4] | ea[5]); +} static inline bool eth_addr_equals(const uint8_t a[ETH_ADDR_LEN], const uint8_t b[ETH_ADDR_LEN]) { return !memcmp(a, b, ETH_ADDR_LEN); } +static inline uint64_t eth_addr_to_uint64(const uint8_t ea[ETH_ADDR_LEN]) +{ + return (((uint64_t) ea[0] << 40) + | ((uint64_t) ea[1] << 32) + | ((uint64_t) ea[2] << 24) + | ((uint64_t) ea[3] << 16) + | ((uint64_t) ea[4] << 8) + | ea[5]); +} +static inline void eth_addr_from_uint64(uint64_t x, uint8_t ea[ETH_ADDR_LEN]) +{ + ea[0] = x >> 40; + ea[1] = x >> 32; + ea[2] = x >> 24; + ea[3] = x >> 16; + ea[4] = x >> 8; + ea[5] = x; +} +static inline void eth_addr_random(uint8_t ea[ETH_ADDR_LEN]) +{ + random_bytes(ea, ETH_ADDR_LEN); +} + #define ETH_ADDR_FMT \ "%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8":%02"PRIx8 #define ETH_ADDR_ARGS(ea) \ @@ -67,8 +99,10 @@ static inline bool eth_addr_equals(const uint8_t a[ETH_ADDR_LEN], #define ETH_HEADER_LEN 14 #define ETH_PAYLOAD_MIN 46 +#define ETH_PAYLOAD_MAX 1500 #define ETH_TOTAL_MIN (ETH_HEADER_LEN + ETH_PAYLOAD_MIN) -#define ETH_TOTAL_MAX (ETH_HEADER_LEN + VLAN_HEADER_LEN + 1500) +#define ETH_TOTAL_MAX (ETH_HEADER_LEN + ETH_PAYLOAD_MAX) +#define ETH_VLAN_TOTAL_MAX (ETH_HEADER_LEN + VLAN_HEADER_LEN + ETH_PAYLOAD_MAX) struct eth_header { uint8_t eth_dst[ETH_ADDR_LEN]; uint8_t eth_src[ETH_ADDR_LEN]; @@ -132,10 +166,19 @@ BUILD_ASSERT_DECL(VLAN_ETH_HEADER_LEN == sizeof(struct vlan_eth_header)); #define IP_VER(ip_ihl_ver) ((ip_ihl_ver) >> 4) #define IP_IHL(ip_ihl_ver) ((ip_ihl_ver) & 15) +#define IP_IHL_VER(ihl, ver) (((ver) << 4) | (ihl)) #define IP_TYPE_TCP 6 #define IP_TYPE_UDP 17 +#define IP_VERSION 4 + +#define IP_DONT_FRAGMENT 0x4000 /* Don't fragment. */ +#define IP_MORE_FRAGMENTS 0x2000 /* More fragments. */ +#define IP_FRAG_OFF_MASK 0x1fff /* Fragment offset. */ +#define IP_IS_FRAGMENT(ip_frag_off) \ + (ntohs(ip_frag_off) & (IP_MORE_FRAGMENTS | IP_FRAG_OFF_MASK)) + #define IP_HEADER_LEN 20 struct ip_header { uint8_t ip_ihl_ver; diff --git a/include/random.h b/include/random.h new file mode 100644 index 0000000..d9f5f9d --- /dev/null +++ b/include/random.h @@ -0,0 +1,44 @@ +/* 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 RANDOM_H +#define RANDOM_H 1 + +#include +#include + +void random_bytes(void *, size_t); +uint32_t random_uint32(void); +int random_range(int max); + +#endif /* random.h */ diff --git a/include/rconn.h b/include/rconn.h index af476cb..228b8e8 100644 --- a/include/rconn.h +++ b/include/rconn.h @@ -36,6 +36,7 @@ #include "queue.h" #include +#include #include /* A wrapper around vconn that provides queuing and optionally reliability. @@ -52,9 +53,16 @@ struct vconn; -struct rconn *rconn_new(const char *name, int txq_limit); +struct rconn *rconn_new(const char *name, int txq_limit, + int inactivity_probe_interval, int max_backoff); struct rconn *rconn_new_from_vconn(const char *name, int txq_limit, struct vconn *); +struct rconn *rconn_create(int txq_limit, int inactivity_probe_interval, + int max_backoff); +int rconn_connect(struct rconn *, const char *name); +void rconn_connect_unreliably(struct rconn *, + const char *name, struct vconn *vconn); +void rconn_disconnect(struct rconn *); void rconn_destroy(struct rconn *); void rconn_run(struct rconn *); @@ -70,5 +78,8 @@ const char *rconn_get_name(const struct rconn *); bool rconn_is_alive(const struct rconn *); bool rconn_is_connected(const struct rconn *); int rconn_disconnected_duration(const struct rconn *); +bool rconn_is_connectivity_questionable(struct rconn *); + +uint32_t rconn_get_ip(const struct rconn *); #endif /* rconn.h */ diff --git a/include/socket-util.h b/include/socket-util.h index c1afaa2..3f6289a 100644 --- a/include/socket-util.h +++ b/include/socket-util.h @@ -35,11 +35,15 @@ #define SOCKET_UTIL_H 1 #include +#include #include int set_nonblocking(int fd); int lookup_ip(const char *host_name, struct in_addr *address); int get_socket_error(int sock); int check_connection_completion(int fd); +int drain_rcvbuf(int fd); +int make_unix_socket(int style, bool nonblock, bool passcred, + const char *bind_path, const char *connect_path); #endif /* socket-util.h */ diff --git a/include/timeval.h b/include/timeval.h new file mode 100644 index 0000000..6a30f97 --- /dev/null +++ b/include/timeval.h @@ -0,0 +1,50 @@ +/* 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 TIMEVAL_H +#define TIMEVAL_H 1 + +#include "type-props.h" +#include "util.h" + +/* POSIX allows floating-point time_t, but we don't support it. */ +BUILD_ASSERT_DECL(TYPE_IS_INTEGER(time_t)); + +/* We do try to cater to unsigned time_t, but I want to know about it if we + * ever encounter such a platform. */ +BUILD_ASSERT_DECL(TYPE_IS_SIGNED(time_t)); + +#define TIME_MAX TYPE_MAXIMUM(time_t) +#define TIME_MIN TYPE_MINIMUM(time_t) + +#endif /* timeval.h */ diff --git a/include/type-props.h b/include/type-props.h new file mode 100644 index 0000000..0940300 --- /dev/null +++ b/include/type-props.h @@ -0,0 +1,49 @@ +/* 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 TYPE_PROPS_H +#define TYPE_PROPS_H 1 + +#include + +#define TYPE_IS_INTEGER(TYPE) ((TYPE) 1.5 == (TYPE) 1) +#define TYPE_IS_SIGNED(TYPE) ((TYPE) 0 > (TYPE) -1) +#define TYPE_VALUE_BITS(TYPE) (sizeof(TYPE) * CHAR_BIT - TYPE_IS_SIGNED(TYPE)) +#define TYPE_MINIMUM(TYPE) (TYPE_IS_SIGNED(TYPE) \ + ? ~(TYPE)0 << TYPE_VALUE_BITS(TYPE) \ + : 0) +#define TYPE_MAXIMUM(TYPE) (TYPE_IS_SIGNED(TYPE) \ + ? ~(~(TYPE)0 << TYPE_VALUE_BITS(TYPE)) \ + : (TYPE)-1) + +#endif /* type-props.h */ diff --git a/include/util.h b/include/util.h index 8b0bb40..99319d4 100644 --- a/include/util.h +++ b/include/util.h @@ -81,6 +81,7 @@ void *xmalloc(size_t); void *xcalloc(size_t, size_t); void *xrealloc(void *, size_t); void *xmemdup(const void *, size_t); +char *xmemdup0(const char *, size_t); char *xstrdup(const char *); char *xasprintf(const char *format, ...) PRINTF_FORMAT(1, 2); diff --git a/include/vconn-ssl.h b/include/vconn-ssl.h index 1d4a923..fb80068 100644 --- a/include/vconn-ssl.h +++ b/include/vconn-ssl.h @@ -33,7 +33,10 @@ #ifndef VCONN_SSL_H #define VCONN_SSL_H 1 +#include + #ifdef HAVE_OPENSSL +bool vconn_ssl_is_configured(void); void vconn_ssl_set_private_key_file(const char *file_name); void vconn_ssl_set_certificate_file(const char *file_name); void vconn_ssl_set_ca_cert_file(const char *file_name); @@ -56,6 +59,10 @@ void vconn_ssl_set_ca_cert_file(const char *file_name); vconn_ssl_set_ca_cert_file(optarg); \ break; #else /* !HAVE_OPENSSL */ +static inline bool vconn_ssl_is_configured(void) +{ + return false; +} #define VCONN_SSL_LONG_OPTIONS #define VCONN_SSL_OPTION_HANDLERS #endif /* !HAVE_OPENSSL */ diff --git a/include/vconn-stream.h b/include/vconn-stream.h new file mode 100644 index 0000000..d7eb59f --- /dev/null +++ b/include/vconn-stream.h @@ -0,0 +1,50 @@ +/* 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 VCONN_STREAM_H +#define VCONN_STREAM_H 1 + +#include +#include + +struct vconn; +struct sockaddr; + +int new_stream_vconn(const char *name, int fd, int connect_status, + uint32_t ip, struct vconn **vconnp); +int new_pstream_vconn(const char *name, int fd, + int (*accept_cb)(int fd, const struct sockaddr *, + size_t sa_len, struct vconn **), + struct vconn **vconnp); + +#endif /* vconn-stream.h */ diff --git a/include/vconn.h b/include/vconn.h index ecaab0e..4b0eaa7 100644 --- a/include/vconn.h +++ b/include/vconn.h @@ -35,11 +35,13 @@ #define VCONN_H 1 #include +#include #include struct buffer; struct flow; struct pollfd; +struct ofp_header; /* Client interface. */ @@ -47,16 +49,19 @@ struct pollfd; struct vconn { struct vconn_class *class; int connect_status; + uint32_t ip; }; void vconn_usage(bool active, bool passive); int vconn_open(const char *name, struct vconn **); void vconn_close(struct vconn *); bool vconn_is_passive(const struct vconn *); +uint32_t vconn_get_ip(const struct vconn *); int vconn_connect(struct vconn *); int vconn_accept(struct vconn *, struct vconn **); int vconn_recv(struct vconn *, struct buffer **); int vconn_send(struct vconn *, struct buffer *); +int vconn_transact(struct vconn *, struct buffer *, struct buffer **); int vconn_open_block(const char *name, struct vconn **); int vconn_send_block(struct vconn *, struct buffer *); @@ -74,12 +79,19 @@ void vconn_accept_wait(struct vconn *); void vconn_recv_wait(struct vconn *); void vconn_send_wait(struct vconn *); +void *make_openflow(size_t openflow_len, uint8_t type, struct buffer **); +void *make_openflow_xid(size_t openflow_len, uint8_t type, + uint32_t xid, struct buffer **); +void update_openflow_length(struct buffer *); struct buffer *make_add_simple_flow(const struct flow *, - uint32_t buffer_id, uint16_t out_port); + uint32_t buffer_id, uint16_t out_port, + uint16_t max_idle); struct buffer *make_buffered_packet_out(uint32_t buffer_id, uint16_t in_port, uint16_t out_port); struct buffer *make_unbuffered_packet_out(const struct buffer *packet, uint16_t in_port, uint16_t out_port); +struct buffer *make_echo_request(void); +struct buffer *make_echo_reply(const struct ofp_header *rq); /* Provider interface. */ @@ -161,6 +173,8 @@ struct vconn_class { extern struct vconn_class tcp_vconn_class; extern struct vconn_class ptcp_vconn_class; +extern struct vconn_class unix_vconn_class; +extern struct vconn_class punix_vconn_class; #ifdef HAVE_OPENSSL extern struct vconn_class ssl_vconn_class; extern struct vconn_class pssl_vconn_class; diff --git a/include/vlog.h b/include/vlog.h index 1988e5e..f2641b0 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) \ @@ -74,6 +76,7 @@ enum vlog_facility vlog_get_facility_val(const char *name); VLOG_MODULE(mac_learning) \ VLOG_MODULE(netdev) \ VLOG_MODULE(netlink) \ + VLOG_MODULE(ofp_discover) \ VLOG_MODULE(poll_loop) \ VLOG_MODULE(secchan) \ VLOG_MODULE(rconn) \ @@ -82,7 +85,10 @@ enum vlog_facility vlog_get_facility_val(const char *name); VLOG_MODULE(vconn_netlink) \ VLOG_MODULE(vconn_tcp) \ VLOG_MODULE(vconn_ssl) \ + VLOG_MODULE(vconn_stream) \ + VLOG_MODULE(vconn_unix) \ VLOG_MODULE(vconn) \ + VLOG_MODULE(vlog) \ /* VLM_ constant for each vlog module. */ enum vlog_module { @@ -113,7 +119,7 @@ void vlog(enum vlog_module, enum vlog_level, const char *format, ...) /* Convenience macros. To use these, define THIS_MODULE as a macro that * expands to the module used by the current source file, e.g. * #include "vlog.h" - * #define THIS_MODULE VLM_NETLINK + * #define THIS_MODULE VLM_netlink * Guaranteed to preserve errno. */ #define VLOG_EMER(...) vlog(THIS_MODULE, VLL_EMER, __VA_ARGS__) diff --git a/lib/Makefile.am b/lib/Makefile.am index 77aadc8..b802ef3 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,11 +1,14 @@ include ../Make.vars -noinst_LTLIBRARIES = libopenflow.la +noinst_LIBRARIES = libopenflow.a -libopenflow_la_SOURCES = \ +libopenflow_a_SOURCES = \ buffer.c \ command-line.c \ + csum.c \ daemon.c \ + dhcp-client.c \ + dhcp.c \ dynamic-string.c \ fatal-signal.c \ fault.c \ @@ -18,16 +21,19 @@ libopenflow_la_SOURCES = \ ofp-print.c \ poll-loop.c \ queue.c \ + random.c \ rconn.c \ socket-util.c \ util.c \ vconn-tcp.c \ + vconn-unix.c \ + vconn-stream.c \ vconn.c \ vlog-socket.c \ vlog.c if HAVE_NETLINK -libopenflow_la_SOURCES += \ +libopenflow_a_SOURCES += \ dpif.c \ netlink.c \ vconn-netlink.c @@ -39,9 +45,9 @@ libopenflow_la_SOURCES += \ endif if HAVE_OPENSSL -libopenflow_la_SOURCES += \ +libopenflow_a_SOURCES += \ vconn-ssl.c -nodist_libopenflow_la_SOURCES = dhparams.c +nodist_libopenflow_a_SOURCES = dhparams.c dhparams.c: dh1024.pem dh2048.pem dh4096.pem (echo '#include "dhparams.h"' && \ openssl dhparam -C -in $(srcdir)/dh1024.pem -noout && \ diff --git a/lib/buffer.c b/lib/buffer.c index 2b6e3b7..2014248 100644 --- a/lib/buffer.c +++ b/lib/buffer.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "buffer.h" #include #include @@ -50,7 +51,7 @@ buffer_use(struct buffer *b, void *base, size_t allocated) b->base = b->data = base; b->allocated = allocated; b->size = 0; - b->l2 = b->l3 = b->l4 = NULL; + b->l2 = b->l3 = b->l4 = b->l7 = NULL; b->next = NULL; } @@ -128,7 +129,7 @@ buffer_tailroom(struct buffer *b) /* Ensures that 'b' has room for at least 'size' bytes at its tail end, * reallocating and copying its data if necessary. */ void -buffer_reserve_tailroom(struct buffer *b, size_t size) +buffer_prealloc_tailroom(struct buffer *b, size_t size) { if (size > buffer_tailroom(b)) { size_t new_allocated = b->allocated + MAX(size, 64); @@ -148,11 +149,14 @@ buffer_reserve_tailroom(struct buffer *b, size_t size) if (b->l4) { b->l4 += base_delta; } + if (b->l7) { + b->l7 += base_delta; + } } } void -buffer_reserve_headroom(struct buffer *b, size_t size) +buffer_prealloc_headroom(struct buffer *b, size_t size) { assert(size <= buffer_headroom(b)); } @@ -164,7 +168,7 @@ void * buffer_put_uninit(struct buffer *b, size_t size) { void *p; - buffer_reserve_tailroom(b, size); + buffer_prealloc_tailroom(b, size); p = buffer_tail(b); b->size += size; return p; @@ -181,15 +185,33 @@ buffer_put(struct buffer *b, const void *p, size_t size) return dst; } +/* Reserves 'size' bytes of headroom so that they can be later allocated with + * buffer_push_uninit() without reallocating the buffer. */ +void +buffer_reserve(struct buffer *b, size_t size) +{ + assert(!b->size); + buffer_prealloc_tailroom(b, size); + b->data += size; +} + void * buffer_push_uninit(struct buffer *b, size_t size) { - buffer_reserve_headroom(b, size); + buffer_prealloc_headroom(b, size); b->data -= size; b->size += size; return b->data; } +void * +buffer_push(struct buffer *b, const void *p, size_t size) +{ + void *dst = buffer_push_uninit(b, size); + memcpy(dst, p, size); + return dst; +} + /* If 'b' contains at least 'offset + size' bytes of data, returns a pointer to * byte 'offset'. Otherwise, returns a null pointers. */ void * @@ -231,12 +253,22 @@ buffer_clear(struct buffer *b) } /* Removes 'size' bytes from the head end of 'b', which must contain at least - * 'size' bytes of data. */ -void + * 'size' bytes of data. Returns the first byte of data removed. */ +void * buffer_pull(struct buffer *b, size_t size) { + void *data = b->data; assert(b->size >= size); b->data += size; b->size -= size; + return data; } +/* If 'b' has at least 'size' bytes of data, removes that many bytes from the + * head end of 'b' and returns the first byte removed. Otherwise, returns a + * null pointer without modifying 'b'. */ +void * +buffer_try_pull(struct buffer *b, size_t size) +{ + return b->size >= size ? buffer_pull(b, size) : NULL; +} diff --git a/lib/command-line.c b/lib/command-line.c index 5ad98d7..7ab08a4 100644 --- a/lib/command-line.c +++ b/lib/command-line.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "command-line.h" #include #include diff --git a/lib/csum.c b/lib/csum.c new file mode 100644 index 0000000..81b3dc1 --- /dev/null +++ b/lib/csum.c @@ -0,0 +1,115 @@ +/* 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 +#include "csum.h" + +/* Returns the IP checksum of the 'n' bytes in 'data'. */ +uint16_t +csum(const void *data, size_t n) +{ + return csum_finish(csum_continue(0, data, n)); +} + +/* Adds the 16 bits in 'new' to the partial IP checksum 'partial' and returns + * the updated checksum. (To start a new checksum, pass 0 for 'partial'. To + * obtain the finished checksum, pass the return value to csum_finish().) */ +uint32_t +csum_add16(uint32_t partial, uint16_t new) +{ + return partial + new; +} + +/* Adds the 32 bits in 'new' to the partial IP checksum 'partial' and returns + * the updated checksum. (To start a new checksum, pass 0 for 'partial'. To + * obtain the finished checksum, pass the return value to csum_finish().) */ +uint32_t +csum_add32(uint32_t partial, uint32_t new) +{ + return partial + (new >> 16) + (new & 0xffff); +} + + +/* Adds the 'n' bytes in 'data' to the partial IP checksum 'partial' and + * returns the updated checksum. (To start a new checksum, pass 0 for + * 'partial'. To obtain the finished checksum, pass the return value to + * csum_finish().) */ +uint32_t +csum_continue(uint32_t partial, const void *data_, size_t n) +{ + const uint16_t *data = data_; + + for (; n > 1; n -= 2) { + partial = csum_add16(partial, *data++); + } + if (n) { + partial += *(uint8_t *) data; + } + return partial; +} + +/* Returns the IP checksum corresponding to 'partial', which is a value updated + * by some combination of csum_add16(), csum_add32(), and csum_continue(). */ +uint16_t +csum_finish(uint32_t partial) +{ + return ~((partial & 0xffff) + (partial >> 16)); +} + +/* Returns the new checksum for a packet in which the checksum field previously + * contained 'old_csum' and in which a field that contained 'old_u16' was + * changed to contain 'new_u16'. */ +uint16_t +recalc_csum16(uint16_t old_csum, uint16_t old_u16, uint16_t new_u16) +{ + /* Ones-complement arithmetic is endian-independent, so this code does not + * use htons() or ntohs(). + * + * See RFC 1624 for formula and explanation. */ + uint16_t hc_complement = ~old_csum; + uint16_t m_complement = ~old_u16; + uint16_t m_prime = new_u16; + uint32_t sum = hc_complement + m_complement + m_prime; + uint16_t hc_prime_complement = sum + (sum >> 16); + return ~hc_prime_complement; +} + +/* Returns the new checksum for a packet in which the checksum field previously + * contained 'old_csum' and in which a field that contained 'old_u32' was + * changed to contain 'new_u32'. */ +uint16_t +recalc_csum32(uint16_t old_csum, uint32_t old_u32, uint32_t new_u32) +{ + return recalc_csum16(recalc_csum16(old_csum, old_u32, new_u32), + old_u32 >> 16, new_u32 >> 16); +} diff --git a/lib/daemon.c b/lib/daemon.c index e3c2b13..b597717 100644 --- a/lib/daemon.c +++ b/lib/daemon.c @@ -31,8 +31,10 @@ * derivatives without specific, written prior permission. */ +#include #include "daemon.h" #include +#include #include #include #include @@ -48,15 +50,27 @@ static bool detach; /* Name of pidfile (null if none). */ static char *pidfile; +/* Returns the file name that would be used for a pidfile if 'name' were + * provided to set_pidfile(). The caller must free the returned string. */ +char * +make_pidfile_name(const char *name) +{ + return (!name ? xasprintf("%s/%s.pid", RUNDIR, program_name) + : *name == '/' ? xstrdup(name) + : xasprintf("%s/%s", RUNDIR, name)); +} + /* Sets up a following call to daemonize() to create a pidfile named 'name'. * If 'name' begins with '/', then it is treated as an absolute path. * Otherwise, it is taken relative to RUNDIR, which is $(prefix)/var/run by - * default. */ + * default. + * + * If 'name' is null, then program_name followed by ".pid" is used. */ void set_pidfile(const char *name) { free(pidfile); - pidfile = *name == '/' ? xstrdup(name) : xasprintf("%s/%s", RUNDIR, name); + pidfile = make_pidfile_name(name); } /* Sets up a following call to daemonize() to detach from the foreground @@ -67,25 +81,56 @@ set_detach(void) detach = true; } -/* If a pidfile has been configured, creates it and stores 'pid' in it. It is - * the caller's responsibility to make sure that the pidfile will eventually - * be deleted. */ +/* If a pidfile has been configured, creates it and stores the running process' + * pid init. Ensures that the pidfile will be deleted when the process + * exits. */ static void -make_pidfile(pid_t pid) +make_pidfile(void) { if (pidfile) { - FILE *file; + /* Create pidfile via temporary file, so that observers never see an + * empty pidfile or an unlocked pidfile. */ + long int pid = getpid(); + char *tmpfile; + int fd; - file = fopen(pidfile, "w"); - if (file) { - fprintf(file, "%ld\n", (long int) pid); - fclose(file); + tmpfile = xasprintf("%s.tmp%ld", pidfile, pid); + fatal_signal_add_file_to_unlink(tmpfile); + fd = open(tmpfile, O_CREAT | O_WRONLY | O_TRUNC, 0666); + if (fd >= 0) { + struct flock lck; + lck.l_type = F_WRLCK; + lck.l_whence = SEEK_SET; + lck.l_start = 0; + lck.l_len = 0; + if (fcntl(fd, F_SETLK, &lck) >= 0) { + char *text = xasprintf("%ld\n", pid); + if (write(fd, text, strlen(text)) == strlen(text)) { + fatal_signal_add_file_to_unlink(pidfile); + if (rename(tmpfile, pidfile) < 0) { + VLOG_ERR("failed to rename \"%s\" to \"%s\": %s", + tmpfile, pidfile, strerror(errno)); + fatal_signal_remove_file_to_unlink(pidfile); + close(fd); + } else { + /* Keep 'fd' open to retain the lock. */ + } + } else { + VLOG_ERR("%s: write failed: %s", tmpfile, strerror(errno)); + close(fd); + } + } else { + VLOG_ERR("%s: fcntl failed: %s", tmpfile, strerror(errno)); + close(fd); + } } else { - VLOG_ERR("failed to create \"%s\": %s", pidfile, strerror(errno)); + VLOG_ERR("%s: create failed: %s", tmpfile, strerror(errno)); } - free(pidfile); - pidfile = NULL; + fatal_signal_remove_file_to_unlink(tmpfile); + free(tmpfile); } + free(pidfile); + pidfile = NULL; } /* If configured with set_pidfile() or set_detach(), creates the pid file and @@ -94,28 +139,37 @@ void daemonize(void) { if (detach) { - pid_t pid; + char c = 0; + int fds[2]; + if (pipe(fds) < 0) { + fatal(errno, "pipe failed"); + } - /* Fork and exit from the parent. */ - pid = fork(); - if (pid < 0) { - fatal(errno, "could not fork"); - } else if (pid) { + switch (fork()) { + default: + /* Parent process: wait for child to create pidfile, then exit. */ + close(fds[1]); fatal_signal_fork(); - make_pidfile(pid); + read(fds[0], &c, 1); exit(0); - } - if (pidfile) { - fatal_signal_add_file_to_unlink(pidfile); + case 0: + /* Child process. */ + close(fds[0]); + make_pidfile(); + write(fds[1], &c, 1); + close(fds[1]); + setsid(); + chdir("/"); + break; + + case -1: + /* Error. */ + fatal(errno, "could not fork"); + break; } - setsid(); - chdir("/"); } else { - if (pidfile) { - fatal_signal_add_file_to_unlink(pidfile); - } - make_pidfile(getpid()); + make_pidfile(); } } diff --git a/lib/dhcp-client.c b/lib/dhcp-client.c new file mode 100644 index 0000000..3bed591 --- /dev/null +++ b/lib/dhcp-client.c @@ -0,0 +1,1029 @@ +/* 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 +#include "dhcp-client.h" +#include +#include +#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(). */ + + 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 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_turn_flags_on(netdev, NETDEV_UP, false); + if (error) { + VLOG_ERR("could not bring %s device up: %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->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->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 and 'cli' has a default gateway, returns + * that default gateway; otherwise, returns 0. */ +uint32_t +dhclient_get_router(const struct dhclient *cli) +{ + return dhclient_is_bound(cli) ? cli->router : 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; +} + +/* Configures the network device backing 'cli' to the network address and other + * parameters obtained via DHCP. If no address is bound on 'cli', removes any + * configured address from 'cli'. + * + * To use a dhclient as a regular DHCP client that binds and unbinds from IP + * addresses in the usual fashion, call this function after dhclient_run() if + * anything has changed, like so: + * + * dhclient_run(cli); + * if (dhclient_changed(cli)) { + * dhclient_configure_netdev(cli); + * } + * + */ +int +dhclient_configure_netdev(struct dhclient *cli) +{ + struct in_addr addr = { dhclient_get_ip(cli) }; + struct in_addr mask = { dhclient_get_netmask(cli) }; + struct in_addr router = { dhclient_get_router(cli) }; + int error; + + 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 (!error && router.s_addr) { + error = netdev_add_router(cli->netdev, 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)); + } + } + + return error; +} + +/* If 'cli' is bound and the binding includes DNS domain parameters, updates + * /etc/resolv.conf will be updated to match the received parameters. Returns + * 0 if successful, otherwise a positive errno value. */ +int +dhclient_update_resolv_conf(struct dhclient *cli) +{ + uint32_t dns_server; + char *domain_name; + bool has_domain_name; + char new_name[128]; + FILE *old, *new; + int i; + + if (!dhclient_is_bound(cli)) { + return 0; + } + if (!dhcp_msg_get_ip(cli->binding, DHCP_CODE_DNS_SERVER, 0, &dns_server)) { + VLOG_DBG("binding does not include any DNS servers"); + return 0; + } + + sprintf(new_name, "/etc/resolv.conf.tmp%ld", (long int) getpid()); + new = fopen(new_name, "w"); + if (!new) { + VLOG_WARN("%s: create: %s", new_name, strerror(errno)); + return errno; + } + + domain_name = dhcp_msg_get_string(cli->binding, DHCP_CODE_DOMAIN_NAME); + has_domain_name = domain_name != NULL; + if (domain_name) { + if (strspn(domain_name, "-_.0123456789abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ") == strlen(domain_name)) { + fprintf(new, "domain %s\n", domain_name); + } else { + VLOG_WARN("ignoring invalid domain name %s", domain_name); + has_domain_name = false; + } + } else { + VLOG_DBG("binding does not include domain name"); + } + free(domain_name); + + for (i = 0; dhcp_msg_get_ip(cli->binding, DHCP_CODE_DNS_SERVER, + i, &dns_server); i++) { + fprintf(new, "nameserver "IP_FMT"\n", IP_ARGS(&dns_server)); + } + + old = fopen("/etc/resolv.conf", "r"); + if (old) { + char line[128]; + + while (fgets(line, sizeof line, old)) { + char *kw = xmemdup0(line, strcspn(line, " \t\r\n")); + if (strcmp(kw, "nameserver") + && (!has_domain_name + || (strcmp(kw, "domain") && strcmp(kw, "search")))) { + fputs(line, new); + } + free(kw); + } + fclose(old); + } else { + VLOG_DBG("/etc/resolv.conf: open: %s", strerror(errno)); + } + + if (fclose(new) < 0) { + VLOG_WARN("%s: close: %s", new_name, strerror(errno)); + return errno; + } + + if (rename(new_name, "/etc/resolv.conf") < 0) { + VLOG_WARN("failed to rename %s to /etc/resolv.conf: %s", + new_name, strerror(errno)); + return errno; + } + + return 0; +} + +/* 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, false, &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, false, &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, false, &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, false, &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, false, &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, false, &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, false, &cli->s)); + continue; + } + + VLOG_DBG("accepting DHCPOFFER: %s", + dhcp_msg_to_string(&msg, false, &cli->s)); + cli->ipaddr = msg.yiaddr; + state_transition(cli, S_REQUESTING); + break; + } +} + +static bool +same_binding(const struct dhcp_msg *old, const struct dhcp_msg *new) +{ + static const int codes[] = { + DHCP_CODE_SUBNET_MASK, + DHCP_CODE_ROUTER, + DHCP_CODE_DNS_SERVER, + DHCP_CODE_HOST_NAME, + DHCP_CODE_DOMAIN_NAME, + DHCP_CODE_IP_TTL, + DHCP_CODE_MTU, + DHCP_CODE_BROADCAST_ADDRESS, + DHCP_CODE_STATIC_ROUTE, + DHCP_CODE_ARP_CACHE_TIMEOUT, + DHCP_CODE_ETHERNET_ENCAPSULATION, + DHCP_CODE_TCP_TTL, + DHCP_CODE_SERVER_IDENTIFIER, + DHCP_CODE_OFP_CONTROLLER_VCONN, + DHCP_CODE_OFP_PKI_URI, + }; + int i; + bool same = true; + + if (old->yiaddr != new->yiaddr) { + VLOG_WARN("DHCP binding changed IP address from "IP_FMT" to "IP_FMT, + IP_ARGS(&old->yiaddr), IP_ARGS(&new->yiaddr)); + same = false; + } + for (i = 0; i < ARRAY_SIZE(codes); i++) { + int code = codes[i]; + const struct dhcp_option *old_opt = &old->options[code]; + const struct dhcp_option *new_opt = &new->options[code]; + if (!dhcp_option_equals(old_opt, new_opt)) { + struct ds old_string = DS_EMPTY_INITIALIZER; + struct ds new_string = DS_EMPTY_INITIALIZER; + VLOG_WARN("DHCP binding changed option from %s to %s", + dhcp_option_to_string(old_opt, code, &old_string), + dhcp_option_to_string(new_opt, code, &new_string)); + ds_destroy(&old_string); + ds_destroy(&new_string); + same = false; + } + } + return same; +} + +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) { + if (!same_binding(cli->binding, &msg)) { + cli->changed = true; + } + 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, false, &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; + if (cli->state != state) { + 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) { + cli->changed = true; + if (am_bound) { + assert(cli->binding != NULL); + VLOG_WARN("%s: obtained address "IP_FMT", netmask "IP_FMT, + netdev_get_name(cli->netdev), + IP_ARGS(&cli->ipaddr), IP_ARGS(&cli->netmask)); + if (cli->router) { + VLOG_WARN("%s: obtained default gateway "IP_FMT, + netdev_get_name(cli->netdev), IP_ARGS(&cli->router)); + } + } else { + dhcp_msg_uninit(cli->binding); + free(cli->binding); + cli->binding = NULL; + + VLOG_WARN("%s: network address unbound", + netdev_get_name(cli->netdev)); + } + } + 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) { + goto drained; + } + + 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, false, &cli->s)); + buffer_uninit(&b); + return true; + } + } + netdev_drain(cli->netdev); +drained: + 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); + /* We can't guarantee uniqueness of ip_id versus the host's, screwing up + * fragment reassembly, so prevent fragmentation and use an all-zeros + * ip_id. RFC 791 doesn't say we can do this, but Linux does the same + * thing for DF packets, so it must not screw anything up. */ + nh.ip_id = 0; + 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, false, &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 0000000..0d3cf5f --- /dev/null +++ b/lib/dhcp.c @@ -0,0 +1,825 @@ +/* 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 +#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 representing 'duration' seconds to 'ds'. */ +static void +put_duration(struct ds *ds, unsigned int duration) +{ + if (duration) { + if (duration >= 86400) { + ds_put_format(ds, "%ud", duration / 86400); + duration %= 86400; + } + if (duration >= 3600) { + ds_put_format(ds, "%uh", duration / 3600); + duration %= 3600; + } + if (duration >= 60) { + ds_put_format(ds, "%umin", duration / 60); + duration %= 60; + } + if (duration > 0) { + ds_put_format(ds, "%us", duration); + } + } else { + ds_put_cstr(ds, "0s"); + } +} + +/* 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; + + 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 (!opt->data || !opt->n) { + ds_put_cstr(ds, opt->data ? "empty" : "null"); + return ds_cstr(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; + + 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: + put_duration(ds, ntohl(*uint32)); + 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); +} + +/* Returns true if 'a' and 'b' have the same content, false otherwise. */ +bool +dhcp_option_equals(const struct dhcp_option *a, const struct dhcp_option *b) +{ + return ((a->data != NULL) == (b->data != NULL) + && a->n == b->n + && !memcmp(a->data, b->data, a->n)); +} + +/* Replaces 'ds' by a string representation of 'msg'. If 'multiline' is + * false, 'ds' receives a single-line representation of 'msg', otherwise a + * multiline representation. */ +const char * +dhcp_msg_to_string(const struct dhcp_msg *msg, bool multiline, struct ds *ds) +{ + char separator = multiline ? '\n' : ' '; + int code; + + ds_clear(ds); + ds_put_format(ds, "op=%s", + (msg->op == DHCP_BOOTREQUEST ? "request" + : msg->op == DHCP_BOOTREPLY ? "reply" + : "error")); + ds_put_format(ds, "%ctype=%s", separator, dhcp_type_name(msg->type)); + ds_put_format(ds, "%cxid=0x%08"PRIx32, separator, msg->xid); + ds_put_format(ds, "%csecs=", separator); + put_duration(ds, msg->secs); + if (msg->flags) { + ds_put_format(ds, "%cflags=", separator); + 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, "%cciaddr="IP_FMT, separator, IP_ARGS(&msg->ciaddr)); + } + if (msg->yiaddr) { + ds_put_format(ds, "%cyiaddr="IP_FMT, separator, IP_ARGS(&msg->yiaddr)); + } + if (msg->siaddr) { + ds_put_format(ds, "%csiaddr="IP_FMT, separator, IP_ARGS(&msg->siaddr)); + } + if (msg->giaddr) { + ds_put_format(ds, "%cgiaddr="IP_FMT, separator, IP_ARGS(&msg->giaddr)); + } + ds_put_format(ds, "%cchaddr="ETH_ADDR_FMT, + separator, ETH_ADDR_ARGS(msg->chaddr)); + + for (code = 0; code < DHCP_N_OPTIONS; code++) { + const struct dhcp_option *opt = &msg->options[code]; + if (opt->data) { + ds_put_char(ds, separator); + dhcp_option_to_string(opt, code, ds); + } + } + if (multiline) { + ds_put_char(ds, separator); + } + 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, false, &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/lib/dpif.c b/lib/dpif.c index 506efe3..202a182 100644 --- a/lib/dpif.c +++ b/lib/dpif.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "dpif.h" #include @@ -269,25 +270,6 @@ dpif_del_port(struct dpif *dp, const char *netdev) { return send_mgmt_command(dp, DP_GENL_C_DEL_PORT, netdev); } - -/* Tells dp to send num_packets up through netlink for benchmarking*/ -int -dpif_benchmark_nl(struct dpif *dp, uint32_t num_packets, uint32_t packet_size) -{ - struct buffer request; - int retval; - - buffer_init(&request, 0); - nl_msg_put_genlmsghdr(&request, dp->sock, 0, openflow_family, - NLM_F_REQUEST, DP_GENL_C_BENCHMARK_NL, 1); - nl_msg_put_u32(&request, DP_GENL_A_DP_IDX, dp->dp_idx); - nl_msg_put_u32(&request, DP_GENL_A_NPACKETS, num_packets); - nl_msg_put_u32(&request, DP_GENL_A_PSIZE, packet_size); - retval = nl_sock_send(dp->sock, &request, true); - buffer_uninit(&request); - - return retval; -} static const struct nl_policy openflow_multicast_policy[] = { [DP_GENL_A_DP_IDX] = { .type = NL_A_U32 }, diff --git a/lib/dynamic-string.c b/lib/dynamic-string.c index 68dd341..d12739b 100644 --- a/lib/dynamic-string.c +++ b/lib/dynamic-string.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "dynamic-string.h" #include #include @@ -45,6 +46,12 @@ ds_init(struct ds *ds) ds->allocated = 0; } +void +ds_clear(struct ds *ds) +{ + ds->length = 0; +} + void ds_reserve(struct ds *ds, size_t min_length) { diff --git a/lib/fatal-signal.c b/lib/fatal-signal.c index 43b27a3..fe1f52b 100644 --- a/lib/fatal-signal.c +++ b/lib/fatal-signal.c @@ -30,6 +30,7 @@ * advertising or publicity pertaining to the Software or any * derivatives without specific, written prior permission. */ +#include #include "fatal-signal.h" #include #include @@ -42,7 +43,7 @@ #include "util.h" /* Signals to catch. */ -static const int fatal_signals[] = { SIGTERM, SIGINT, SIGHUP }; +static const int fatal_signals[] = { SIGTERM, SIGINT, SIGHUP, SIGALRM }; /* Signals to catch as a sigset_t. */ static sigset_t fatal_signal_set; diff --git a/lib/fault.c b/lib/fault.c index 0967f7d..7623570 100644 --- a/lib/fault.c +++ b/lib/fault.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "fault.h" #include #include diff --git a/lib/flow.c b/lib/flow.c index 53da6e9..7340e7d 100644 --- a/lib/flow.c +++ b/lib/flow.c @@ -30,6 +30,7 @@ * advertising or publicity pertaining to the Software or any * derivatives without specific, written prior permission. */ +#include #include #include "flow.h" #include @@ -43,6 +44,44 @@ #include "vlog.h" #define THIS_MODULE VLM_flow +struct ip_header * +pull_ip(struct buffer *packet) +{ + if (packet->size >= IP_HEADER_LEN) { + struct ip_header *ip = packet->data; + int ip_len = IP_IHL(ip->ip_ihl_ver) * 4; + if (ip_len >= IP_HEADER_LEN && packet->size >= ip_len) { + return buffer_pull(packet, ip_len); + } + } + return NULL; +} + +struct tcp_header * +pull_tcp(struct buffer *packet) +{ + if (packet->size >= TCP_HEADER_LEN) { + struct tcp_header *tcp = packet->data; + int tcp_len = TCP_OFFSET(tcp->tcp_ctl) * 4; + if (tcp_len >= TCP_HEADER_LEN && packet->size >= tcp_len) { + return buffer_pull(packet, tcp_len); + } + } + return NULL; +} + +struct udp_header * +pull_udp(struct buffer *packet) +{ + return buffer_try_pull(packet, UDP_HEADER_LEN); +} + +struct eth_header * +pull_eth(struct buffer *packet) +{ + return buffer_try_pull(packet, ETH_HEADER_LEN); +} + void flow_extract(struct buffer *packet, uint16_t in_port, struct flow *flow) { @@ -64,10 +103,10 @@ flow_extract(struct buffer *packet, uint16_t in_port, struct flow *flow) packet->l2 = b.data; packet->l3 = NULL; packet->l4 = NULL; + packet->l7 = NULL; - eth = buffer_at(&b, 0, sizeof *eth); + eth = pull_eth(&b); if (eth) { - buffer_pull(&b, ETH_HEADER_LEN); if (ntohs(eth->eth_type) >= OFP_DL_TYPE_ETH2_CUTOFF) { /* This is an Ethernet II frame */ flow->dl_type = eth->eth_type; @@ -104,23 +143,29 @@ flow_extract(struct buffer *packet, uint16_t in_port, struct flow *flow) packet->l3 = b.data; if (flow->dl_type == htons(ETH_TYPE_IP)) { - const struct ip_header *nh = buffer_at(&b, 0, sizeof *nh); + const struct ip_header *nh = pull_ip(&b); if (nh) { - int ip_len = IP_IHL(nh->ip_ihl_ver) * 4; - if (ip_len < IP_HEADER_LEN) { - return; - } - flow->nw_src = nh->ip_src; flow->nw_dst = nh->ip_dst; flow->nw_proto = nh->ip_proto; - if (flow->nw_proto == IP_TYPE_TCP - || flow->nw_proto == IP_TYPE_UDP) { - const struct udp_header *th; - th = packet->l4 = buffer_at(&b, ip_len, sizeof *th); - if (th) { - flow->tp_src = th->udp_src; - flow->tp_dst = th->udp_dst; + packet->l4 = b.data; + if (flow->nw_proto == IP_TYPE_TCP) { + const struct tcp_header *tcp = pull_tcp(&b); + if (tcp) { + flow->tp_src = tcp->tcp_src; + flow->tp_dst = tcp->tcp_dst; + packet->l7 = b.data; + } else { + /* Avoid tricking other code into thinking that this + * packet has an L4 header. */ + flow->nw_proto = 0; + } + } else if (flow->nw_proto == IP_TYPE_UDP) { + const struct udp_header *udp = pull_udp(&b); + if (udp) { + flow->tp_src = udp->udp_src; + flow->tp_dst = udp->udp_dst; + packet->l7 = b.data; } else { /* Avoid tricking other code into thinking that this * packet has an L4 header. */ diff --git a/lib/hash.c b/lib/hash.c index b65a49d..8e87216 100644 --- a/lib/hash.c +++ b/lib/hash.c @@ -30,6 +30,7 @@ * advertising or publicity pertaining to the Software or any * derivatives without specific, written prior permission. */ +#include #include "hash.h" uint32_t diff --git a/lib/learning-switch.c b/lib/learning-switch.c index 5768fd4..e3b4df5 100644 --- a/lib/learning-switch.c +++ b/lib/learning-switch.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "learning-switch.h" #include @@ -53,7 +54,11 @@ #include "vlog.h" struct lswitch { - bool setup_flows; /* Set up flows? (or controller processes all packets) */ + /* If nonnegative, the switch sets up flows that expire after the given + * number of seconds (or never expire, if the value is OFP_FLOW_PERMANENT). + * Otherwise, the switch processes every packet. */ + int max_idle; + uint64_t datapath_id; time_t last_features_request; struct mac_learning *ml; /* NULL to act as hub instead of switch. */ @@ -63,24 +68,27 @@ static void queue_tx(struct lswitch *, struct rconn *, struct buffer *); static void send_features_request(struct lswitch *, struct rconn *); static void process_packet_in(struct lswitch *, struct rconn *, struct ofp_packet_in *); +static void process_echo_request(struct lswitch *, struct rconn *, + struct ofp_header *); /* Creates and returns a new learning switch. * * If 'learn_macs' is true, the new switch will learn the ports on which MAC * addresses appear. Otherwise, the new switch will flood all packets. * - * If 'setup_flows' is true, the new switch will set up flows. Otherwise, the - * new switch will process every packet. + * If 'max_idle' is nonnegative, the new switch will set up flows that expire + * after the given number of seconds (or never expire, if 'max_idle' is + * OFP_FLOW_PERMANENT). Otherwise, the new switch will process every packet. * * 'rconn' is used to send out an OpenFlow features request. */ struct lswitch * -lswitch_create(struct rconn *rconn, bool learn_macs, bool setup_flows) +lswitch_create(struct rconn *rconn, bool learn_macs, int max_idle) { struct lswitch *sw = xmalloc(sizeof *sw); memset(sw, 0, sizeof *sw); - sw->setup_flows = setup_flows; + sw->max_idle = max_idle; sw->datapath_id = 0; - sw->last_features_request = 0; + sw->last_features_request = time(0) - 1; sw->ml = learn_macs ? mac_learning_create() : NULL; send_features_request(sw, rconn); return sw; @@ -119,7 +127,9 @@ lswitch_process_packet(struct lswitch *sw, struct rconn *rconn, return; } - if (oh->type == OFPT_FEATURES_REPLY) { + if (oh->type == OFPT_ECHO_REQUEST) { + process_echo_request(sw, rconn, msg->data); + } else if (oh->type == OFPT_FEATURES_REPLY) { struct ofp_switch_features *osf = msg->data; sw->datapath_id = osf->datapath_id; } else if (sw->datapath_id == 0) { @@ -213,11 +223,15 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, out_port = mac_learning_lookup(sw->ml, flow.dl_dst); } - if (sw->setup_flows && (!sw->ml || out_port != OFPP_FLOOD)) { + if (in_port == out_port) { + /* The input port and output port match, so just drop the packet + * by returning. */ + return; + } else if (sw->max_idle >= 0 && (!sw->ml || out_port != OFPP_FLOOD)) { /* The output port is known, or we always flood everything, so add a * new flow. */ queue_tx(sw, rconn, make_add_simple_flow(&flow, ntohl(opi->buffer_id), - out_port)); + out_port, sw->max_idle)); /* If the switch didn't buffer the packet, we need to send a copy. */ if (ntohl(opi->buffer_id) == UINT32_MAX) { @@ -237,3 +251,10 @@ process_packet_in(struct lswitch *sw, struct rconn *rconn, queue_tx(sw, rconn, b); } } + +static void +process_echo_request(struct lswitch *sw, struct rconn *rconn, + struct ofp_header *rq) +{ + queue_tx(sw, rconn, make_echo_reply(rq)); +} diff --git a/lib/list.c b/lib/list.c index c35ef10..8eaf9fe 100644 --- a/lib/list.c +++ b/lib/list.c @@ -30,6 +30,7 @@ * advertising or publicity pertaining to the Software or any * derivatives without specific, written prior permission. */ +#include #include "list.h" #include diff --git a/lib/mac-learning.c b/lib/mac-learning.c index 5311ccf..29a66f1 100644 --- a/lib/mac-learning.c +++ b/lib/mac-learning.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "mac-learning.h" #include diff --git a/lib/netdev.c b/lib/netdev.c index c3a4bd7..7532eb1 100644 --- a/lib/netdev.c +++ b/lib/netdev.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "netdev.h" #include @@ -64,6 +65,7 @@ #include #include #include +#include #include #include #include @@ -75,6 +77,7 @@ #include "openflow.h" #include "packets.h" #include "poll-loop.h" +#include "socket-util.h" /* This doesn't seem to be defined in the linux/ethtool.h for linux 2.4 */ #ifndef SPEED_2500 @@ -88,6 +91,7 @@ struct netdev { struct list node; char *name; + int ifindex; int fd; uint8_t etheraddr[ETH_ADDR_LEN]; int speed; @@ -95,11 +99,15 @@ struct netdev { uint32_t features; struct in_addr in4; struct in6_addr in6; - int save_flags; + int save_flags; /* Initial device flags. */ + int changed_flags; /* Flags that we changed. */ }; static struct list netdev_list = LIST_INITIALIZER(&netdev_list); +/* An AF_INET socket (used for ioctl operations). */ +static int af_inet_sock = -1; + static void init_netdev(void); static int restore_flags(struct netdev *netdev); static int get_flags(const struct netdev *, int *flagsp); @@ -110,25 +118,17 @@ static int set_flags(struct netdev *, int flags); static bool get_ipv4_address(const char *name, struct in_addr *in4) { - int sock; struct ifreq ifr; - sock = socket(AF_INET, SOCK_DGRAM, 0); - if (sock < 0) { - VLOG_WARN("socket(AF_INET): %s", strerror(errno)); - return false; - } - strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name); ifr.ifr_addr.sa_family = AF_INET; - if (ioctl(sock, SIOCGIFADDR, &ifr) == 0) { + if (ioctl(af_inet_sock, SIOCGIFADDR, &ifr) == 0) { struct sockaddr_in *sin = (struct sockaddr_in *) &ifr.ifr_addr; *in4 = sin->sin_addr; } else { in4->s_addr = INADDR_ANY; } - close(sock); return true; } @@ -236,16 +236,19 @@ do_ethtool(struct netdev *netdev) /* Opens the network device named 'name' (e.g. "eth0") and returns zero if * successful, otherwise a positive errno value. On success, sets '*netdev' - * to the new network device, otherwise to null. */ + * to the new network device, otherwise to null. + * + * 'ethertype' may be a 16-bit Ethernet protocol value in host byte order to + * capture frames of that type received on the device. It may also be one of + * the 'enum netdev_pseudo_ethertype' values to receive frames in one of those + * categories. */ int -netdev_open(const char *name, struct netdev **netdev_) +netdev_open(const char *name, int ethertype, struct netdev **netdev_) { int fd; - struct sockaddr sa; + struct sockaddr_ll sll; struct ifreq ifr; unsigned int ifindex; - socklen_t rcvbuf_len; - size_t rcvbuf; uint8_t etheraddr[ETH_ADDR_LEN]; struct in_addr in4; struct in6_addr in6; @@ -256,47 +259,16 @@ netdev_open(const char *name, struct netdev **netdev_) *netdev_ = NULL; init_netdev(); - /* Create raw socket. - * - * We have to use SOCK_PACKET, despite its deprecation, because only - * SOCK_PACKET lets us set the hardware source address of outgoing - * packets. */ - fd = socket(PF_PACKET, SOCK_PACKET, htons(ETH_P_ALL)); + /* Create raw socket. */ + fd = socket(PF_PACKET, SOCK_RAW, + htons(ethertype == NETDEV_ETH_TYPE_NONE ? 0 + : ethertype == NETDEV_ETH_TYPE_ANY ? ETH_P_ALL + : ethertype == NETDEV_ETH_TYPE_802_2 ? ETH_P_802_2 + : ethertype)); if (fd < 0) { return errno; } - /* Bind to specific ethernet device. */ - memset(&sa, 0, sizeof sa); - sa.sa_family = AF_UNSPEC; - strncpy((char *) sa.sa_data, name, sizeof sa.sa_data); - if (bind(fd, &sa, sizeof sa) < 0) { - VLOG_ERR("bind to %s failed: %s", name, strerror(errno)); - goto error; - } - - /* Between the socket() and bind() calls above, the socket receives all - * packets on all system interfaces. We do not want to receive that - * data, but there is no way to avoid it. So we must now drain out the - * receive queue. There is no way to know how long the receive queue is, - * but we know that the total number of bytes queued does not exceed the - * receive buffer size, so we pull packets until none are left or we've - * read that many bytes. */ - rcvbuf_len = sizeof rcvbuf; - if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &rcvbuf_len) < 0) { - VLOG_ERR("getsockopt(SO_RCVBUF) on %s device failed: %s", - name, strerror(errno)); - goto error; - } - while (rcvbuf > 0) { - char buffer; - ssize_t n_bytes = recv(fd, &buffer, 1, MSG_TRUNC | MSG_DONTWAIT); - if (n_bytes <= 0) { - break; - } - rcvbuf -= n_bytes; - } - /* Get ethernet device index. */ strncpy(ifr.ifr_name, name, sizeof ifr.ifr_name); if (ioctl(fd, SIOCGIFINDEX, &ifr) < 0) { @@ -306,6 +278,26 @@ netdev_open(const char *name, struct netdev **netdev_) } ifindex = ifr.ifr_ifindex; + /* Bind to specific ethernet device. */ + memset(&sll, 0, sizeof sll); + sll.sll_family = AF_PACKET; + sll.sll_ifindex = ifindex; + if (bind(fd, (struct sockaddr *) &sll, sizeof sll) < 0) { + VLOG_ERR("bind to %s failed: %s", name, strerror(errno)); + goto error; + } + + if (ethertype != NETDEV_ETH_TYPE_NONE) { + /* Between the socket() and bind() calls above, the socket receives all + * packets of the requested type on all system interfaces. We do not + * want to receive that data, but there is no way to avoid it. So we + * must now drain out the receive queue. */ + error = drain_rcvbuf(fd); + if (error) { + goto error; + } + } + /* Get MAC address. */ if (ioctl(fd, SIOCGIFHWADDR, &ifr) < 0) { VLOG_ERR("ioctl(SIOCGIFHWADDR) on %s device failed: %s", @@ -335,6 +327,7 @@ netdev_open(const char *name, struct netdev **netdev_) /* Allocate network device. */ netdev = xmalloc(sizeof *netdev); netdev->name = xstrdup(name); + netdev->ifindex = ifindex; netdev->fd = fd; memcpy(netdev->etheraddr, etheraddr, sizeof etheraddr); netdev->mtu = mtu; @@ -349,6 +342,7 @@ netdev_open(const char *name, struct netdev **netdev_) if (error) { goto preset_error; } + netdev->changed_flags = 0; fatal_signal_block(); list_push_back(&netdev_list, &netdev->node); fatal_signal_unblock(); @@ -450,37 +444,38 @@ netdev_recv_wait(struct netdev *netdev) poll_fd_wait(netdev->fd, POLLIN); } +/* Discards all packets waiting to be received from 'netdev'. */ +void +netdev_drain(struct netdev *netdev) +{ + drain_rcvbuf(netdev->fd); +} + /* Sends 'buffer' on 'netdev'. Returns 0 if successful, otherwise a positive * errno value. Returns EAGAIN without blocking if the packet cannot be queued * immediately. Returns EMSGSIZE if a partial packet was transmitted or if - * the packet is too big to transmit on the device. + * the packet is too big or too small to transmit on the device. + * + * The caller retains ownership of 'buffer' in all cases. * * The kernel maintains a packet transmission queue, so the caller is not * expected to do additional queuing of packets. */ int -netdev_send(struct netdev *netdev, struct buffer *buffer) +netdev_send(struct netdev *netdev, const struct buffer *buffer) { ssize_t n_bytes; const struct eth_header *eh; - struct sockaddr_pkt spkt; - /* Ensure packet is long enough. (Although all incoming packets are at - * least ETH_TOTAL_MIN bytes long, we could have trimmed some data off a - * minimum-size packet, e.g. by dropping a vlan header.) - * - * The kernel does not require this, but it ensures that we always access - * valid memory in grabbing the sockaddr below. */ - pad_to_minimum_length(buffer); - - /* Construct packet sockaddr, which SOCK_PACKET requires. */ - spkt.spkt_family = AF_PACKET; - strncpy((char *) spkt.spkt_device, netdev->name, sizeof spkt.spkt_device); + /* Pull out the Ethernet header. */ + if (buffer->size < ETH_HEADER_LEN) { + VLOG_WARN("cannot send %zu-byte frame on %s", + buffer->size, netdev->name); + return EMSGSIZE; + } eh = buffer_at_assert(buffer, 0, sizeof *eh); - spkt.spkt_protocol = eh->eth_type; do { - n_bytes = sendto(netdev->fd, buffer->data, buffer->size, 0, - (const struct sockaddr *) &spkt, sizeof spkt); + n_bytes = sendto(netdev->fd, buffer->data, buffer->size, 0, NULL, 0); } while (n_bytes < 0 && errno == EINTR); if (n_bytes < 0) { @@ -557,22 +552,95 @@ netdev_get_features(const struct netdev *netdev) return netdev->features; } -/* If 'netdev' has an assigned IPv4 address, sets '*in4' to that address and - * returns true. Otherwise, returns false. */ +/* If 'netdev' has an assigned IPv4 address, sets '*in4' to that address (if + * 'in4' is non-null) and returns true. Otherwise, returns false. */ bool netdev_get_in4(const struct netdev *netdev, struct in_addr *in4) { - *in4 = netdev->in4; - return in4->s_addr != INADDR_ANY; + if (in4) { + *in4 = netdev->in4; + } + return netdev->in4.s_addr != INADDR_ANY; } -/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address and - * returns true. Otherwise, returns false. */ +static void +make_in4_sockaddr(struct sockaddr *sa, struct in_addr addr) +{ + struct sockaddr_in sin; + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + sin.sin_addr = addr; + sin.sin_port = 0; + + memset(sa, 0, sizeof *sa); + memcpy(sa, &sin, sizeof sin); +} + +static int +do_set_addr(struct netdev *netdev, int sock, + int ioctl_nr, const char *ioctl_name, struct in_addr addr) +{ + struct ifreq ifr; + int error; + + strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name); + make_in4_sockaddr(&ifr.ifr_addr, addr); + error = ioctl(sock, ioctl_nr, &ifr) < 0 ? errno : 0; + if (error) { + VLOG_WARN("ioctl(%s): %s", ioctl_name, strerror(error)); + } + return error; +} + +/* Assigns 'addr' as 'netdev''s IPv4 address and 'mask' as its netmask. If + * 'addr' is INADDR_ANY, 'netdev''s IPv4 address is cleared. Returns a + * positive errno value. */ +int +netdev_set_in4(struct netdev *netdev, struct in_addr addr, struct in_addr mask) +{ + int error; + + error = do_set_addr(netdev, af_inet_sock, + SIOCSIFADDR, "SIOCSIFADDR", addr); + if (!error) { + netdev->in4 = addr; + if (addr.s_addr != INADDR_ANY) { + error = do_set_addr(netdev, af_inet_sock, + SIOCSIFNETMASK, "SIOCSIFNETMASK", mask); + } + } + return error; +} + +/* Adds 'router' as a default gateway for 'netdev''s IP address. */ +int +netdev_add_router(struct netdev *netdev, struct in_addr router) +{ + struct in_addr any = { INADDR_ANY }; + struct rtentry rt; + int error; + + memset(&rt, 0, sizeof rt); + make_in4_sockaddr(&rt.rt_dst, any); + make_in4_sockaddr(&rt.rt_gateway, router); + make_in4_sockaddr(&rt.rt_genmask, any); + rt.rt_flags = RTF_UP | RTF_GATEWAY; + error = ioctl(af_inet_sock, SIOCADDRT, &rt) < 0 ? errno : 0; + if (error) { + VLOG_WARN("ioctl(SIOCADDRT): %s", strerror(error)); + } + return error; +} + +/* If 'netdev' has an assigned IPv6 address, sets '*in6' to that address (if + * 'in6' is non-null) and returns true. Otherwise, returns false. */ bool netdev_get_in6(const struct netdev *netdev, struct in6_addr *in6) { - *in6 = netdev->in6; - return memcmp(in6, &in6addr_any, sizeof *in6) != 0; + if (in6) { + *in6 = netdev->in6; + } + return memcmp(&netdev->in6, &in6addr_any, sizeof netdev->in6) != 0; } /* Obtains the current flags for 'netdev' and stores them into '*flagsp'. @@ -597,10 +665,26 @@ netdev_get_flags(const struct netdev *netdev, enum netdev_flags *flagsp) return 0; } -/* Sets the flags for 'netdev' to 'nd_flags'. - * Returns 0 if successful, otherwise a positive errno value. */ -int -netdev_set_flags(struct netdev *netdev, enum netdev_flags nd_flags) +static int +nd_to_iff_flags(enum netdev_flags nd) +{ + int iff = 0; + if (nd & NETDEV_UP) { + iff |= IFF_UP; + } + if (nd & NETDEV_PROMISC) { + iff |= IFF_PROMISC; + } + return iff; +} + +/* On 'netdev', turns off the flags in 'off' and then turns on the flags in + * 'on'. If 'permanent' is true, the changes will persist; otherwise, they + * will be reverted when 'netdev' is closed or the program exits. Returns 0 if + * successful, otherwise a positive errno value. */ +static int +do_update_flags(struct netdev *netdev, enum netdev_flags off, + enum netdev_flags on, bool permanent) { int old_flags, new_flags; int error; @@ -610,18 +694,78 @@ netdev_set_flags(struct netdev *netdev, enum netdev_flags nd_flags) return error; } - new_flags = old_flags & ~(IFF_UP | IFF_PROMISC); - if (nd_flags & NETDEV_UP) { - new_flags |= IFF_UP; - } - if (nd_flags & NETDEV_PROMISC) { - new_flags |= IFF_PROMISC; + new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on); + if (!permanent) { + netdev->changed_flags |= new_flags ^ old_flags; } if (new_flags != old_flags) { error = set_flags(netdev, new_flags); } return error; } + +/* Sets the flags for 'netdev' to 'flags'. + * If 'permanent' is true, the changes will persist; otherwise, they + * will be reverted when 'netdev' is closed or the program exits. + * Returns 0 if successful, otherwise a positive errno value. */ +int +netdev_set_flags(struct netdev *netdev, enum netdev_flags flags, + bool permanent) +{ + return do_update_flags(netdev, -1, flags, permanent); +} + +/* Turns on the specified 'flags' on 'netdev'. + * If 'permanent' is true, the changes will persist; otherwise, they + * will be reverted when 'netdev' is closed or the program exits. + * Returns 0 if successful, otherwise a positive errno value. */ +int +netdev_turn_flags_on(struct netdev *netdev, enum netdev_flags flags, + bool permanent) +{ + return do_update_flags(netdev, 0, flags, permanent); +} + +/* Turns off the specified 'flags' on 'netdev'. + * If 'permanent' is true, the changes will persist; otherwise, they + * will be reverted when 'netdev' is closed or the program exits. + * Returns 0 if successful, otherwise a positive errno value. */ +int +netdev_turn_flags_off(struct netdev *netdev, enum netdev_flags flags, + bool permanent) +{ + return do_update_flags(netdev, flags, 0, permanent); +} + +/* Looks up the ARP table entry for 'ip' on 'netdev'. If one exists and can be + * successfully retrieved, it stores the corresponding MAC address in 'mac' and + * returns 0. Otherwise, it returns a positive errno value; in particular, + * ENXIO indicates that there is not ARP table entry for 'ip' on 'netdev'. */ +int +netdev_arp_lookup(const struct netdev *netdev, + uint32_t ip, uint8_t mac[ETH_ADDR_LEN]) +{ + struct arpreq r; + struct sockaddr_in *pa; + int retval; + + memset(&r, 0, sizeof r); + pa = (struct sockaddr_in *) &r.arp_pa; + pa->sin_family = AF_INET; + pa->sin_addr.s_addr = ip; + pa->sin_port = 0; + r.arp_ha.sa_family = ARPHRD_ETHER; + r.arp_flags = 0; + strncpy(r.arp_dev, netdev->name, sizeof r.arp_dev); + retval = ioctl(af_inet_sock, SIOCGARP, &r) < 0 ? errno : 0; + if (!retval) { + memcpy(mac, r.arp_ha.sa_data, ETH_ADDR_LEN); + } else if (retval != ENXIO) { + VLOG_WARN("%s: could not look up ARP entry for "IP_FMT": %s", + netdev->name, IP_ARGS(&ip), strerror(retval)); + } + return retval; +} static void restore_all_flags(void *aux); @@ -634,6 +778,10 @@ init_netdev(void) if (!inited) { inited = true; fatal_signal_add_hook(restore_all_flags, NULL); + af_inet_sock = socket(AF_INET, SOCK_DGRAM, 0); + if (af_inet_sock < 0) { + fatal(errno, "socket(AF_INET)"); + } } } @@ -646,6 +794,7 @@ static int restore_flags(struct netdev *netdev) { struct ifreq ifr; + int restore_flags; /* Get current flags. */ strncpy(ifr.ifr_name, netdev->name, sizeof ifr.ifr_name); @@ -654,9 +803,10 @@ restore_flags(struct netdev *netdev) } /* Restore flags that we might have changed, if necessary. */ - if ((ifr.ifr_flags ^ netdev->save_flags) & (IFF_PROMISC | IFF_UP)) { - ifr.ifr_flags &= ~(IFF_PROMISC | IFF_UP); - ifr.ifr_flags |= netdev->save_flags & (IFF_PROMISC | IFF_UP); + restore_flags = netdev->changed_flags & (IFF_PROMISC | IFF_UP); + if ((ifr.ifr_flags ^ netdev->save_flags) & restore_flags) { + ifr.ifr_flags &= ~restore_flags; + ifr.ifr_flags |= netdev->save_flags & restore_flags; if (ioctl(netdev->fd, SIOCSIFFLAGS, &ifr) < 0) { return errno; } diff --git a/lib/netlink.c b/lib/netlink.c index 0480788..d9dd435 100644 --- a/lib/netlink.c +++ b/lib/netlink.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "netlink.h" #include #include @@ -470,12 +471,12 @@ nl_msg_nlmsgerr(const struct buffer *msg, int *errorp) } } -/* Ensures that 'b' has room for at least 'size' bytes plus netlink pading at +/* Ensures that 'b' has room for at least 'size' bytes plus netlink padding at * its tail end, reallocating and copying its data if necessary. */ void nl_msg_reserve(struct buffer *msg, size_t size) { - buffer_reserve_tailroom(msg, NLMSG_ALIGN(size)); + buffer_prealloc_tailroom(msg, NLMSG_ALIGN(size)); } /* Puts a nlmsghdr at the beginning of 'msg', which must be initially empty. diff --git a/lib/ofp-print.c b/lib/ofp-print.c index e2b9f6d..4790589 100644 --- a/lib/ofp-print.c +++ b/lib/ofp-print.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "ofp-print.h" #include "xtoxll.h" @@ -218,16 +219,62 @@ ofp_print_action(struct ds *string, const struct ofp_action *a) { switch (ntohs(a->type)) { case OFPAT_OUTPUT: - ds_put_cstr(string, "output("); - ofp_print_port_name(string, ntohs(a->arg.output.port)); - if (a->arg.output.port == htons(OFPP_CONTROLLER)) { - ds_put_format(string, ", max %"PRIu16" bytes", ntohs(a->arg.output.max_len)); + { + uint16_t port = ntohs(a->arg.output.port); + if (port < OFPP_MAX) { + ds_put_format(string, "output:%"PRIu16, port); + } else { + ofp_print_port_name(string, port); + if (port == OFPP_CONTROLLER) { + if (a->arg.output.max_len) { + ds_put_format(string, ":%"PRIu16, + ntohs(a->arg.output.max_len)); + } else { + ds_put_cstr(string, ":all"); + } + } + } } - ds_put_cstr(string, ")"); + break; + + case OFPAT_SET_DL_VLAN: + ds_put_cstr(string, "mod_vlan:"); + if (ntohs(a->arg.vlan_id) == OFP_VLAN_NONE) { + ds_put_cstr(string, "strip"); + } else { + ds_put_format(string, "%"PRIu16, ntohs(a->arg.vlan_id)); + } + break; + + case OFPAT_SET_DL_SRC: + ds_put_format(string, "mod_dl_src:"ETH_ADDR_FMT, + ETH_ADDR_ARGS(a->arg.dl_addr)); + break; + + case OFPAT_SET_DL_DST: + ds_put_format(string, "mod_dl_dst:"ETH_ADDR_FMT, + ETH_ADDR_ARGS(a->arg.dl_addr)); + break; + + case OFPAT_SET_NW_SRC: + ds_put_format(string, "mod_nw_src:"IP_FMT, IP_ARGS(&a->arg.nw_addr)); + break; + + case OFPAT_SET_NW_DST: + ds_put_format(string, "mod_nw_dst:"IP_FMT, IP_ARGS(&a->arg.nw_addr)); + break; + + case OFPAT_SET_TP_SRC: + ds_put_format(string, "mod_tp_src:%d", ntohs(a->arg.tp)); + break; + + case OFPAT_SET_TP_DST: + ds_put_format(string, "mod_tp_dst:%d", ntohs(a->arg.tp)); break; default: - ds_put_format(string, "(decoder %"PRIu16" not implemented)", ntohs(a->type)); + ds_put_format(string, "(decoder %"PRIu16" not implemented)", + ntohs(a->type)); break; } } @@ -237,21 +284,21 @@ static void ofp_print_actions(struct ds *string, size_t n_bytes) { size_t i; + int n_actions = n_bytes / sizeof *actions; - ds_put_cstr(string, " actions["); - for (i = 0; i < n_bytes / sizeof *actions; i++) { + ds_put_format(string, "action%s=", n_actions == 1 ? "" : "s"); + for (i = 0; i < n_actions; i++) { if (i) { - ds_put_cstr(string, "; "); + ds_put_cstr(string, ","); } ofp_print_action(string, &actions[i]); } if (n_bytes % sizeof *actions) { if (i) { - ds_put_cstr(string, "; "); + ds_put_cstr(string, ","); } - ds_put_cstr(string, "; ***trailing garbage***"); + ds_put_cstr(string, ", ***trailing garbage***"); } - ds_put_cstr(string, "]"); } /* Pretty-print the OFPT_PACKET_OUT packet of 'len' bytes at 'oh' to 'string' @@ -369,11 +416,15 @@ ofp_print_switch_config(struct ds *string, const void *oh, size_t len, } static void print_wild(struct ds *string, const char *leader, int is_wild, - const char *format, ...) __attribute__((format(printf, 4, 5))); + int verbosity, const char *format, ...) + __attribute__((format(printf, 5, 6))); static void print_wild(struct ds *string, const char *leader, int is_wild, - const char *format, ...) + int verbosity, const char *format, ...) { + if (is_wild && verbosity < 2) { + return; + } ds_put_cstr(string, leader); if (!is_wild) { va_list args; @@ -382,28 +433,36 @@ static void print_wild(struct ds *string, const char *leader, int is_wild, ds_put_format_valist(string, format, args); va_end(args); } else { - ds_put_char(string, '?'); + ds_put_char(string, '*'); } } /* Pretty-print the ofp_match structure */ -static void ofp_print_match(struct ds *f, const struct ofp_match *om) +static void ofp_print_match(struct ds *f, const struct ofp_match *om, + int verbosity) { uint16_t w = ntohs(om->wildcards); - print_wild(f, " inport", w & OFPFW_IN_PORT, "%d", ntohs(om->in_port)); - print_wild(f, ":vlan", w & OFPFW_DL_VLAN, "%04x", ntohs(om->dl_vlan)); - print_wild(f, " mac[", w & OFPFW_DL_SRC, - ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_src)); - print_wild(f, "->", w & OFPFW_DL_DST, - ETH_ADDR_FMT, ETH_ADDR_ARGS(om->dl_dst)); - print_wild(f, "] type", w & OFPFW_DL_TYPE, "%04x", ntohs(om->dl_type)); - print_wild(f, " ip[", w & OFPFW_NW_SRC, IP_FMT, IP_ARGS(&om->nw_src)); - print_wild(f, "->", w & OFPFW_NW_DST, IP_FMT, IP_ARGS(&om->nw_dst)); - print_wild(f, "] proto", w & OFPFW_NW_PROTO, "%u", om->nw_proto); - print_wild(f, " tport[", w & OFPFW_TP_SRC, "%d", ntohs(om->tp_src)); - print_wild(f, "->", w & OFPFW_TP_DST, "%d", ntohs(om->tp_dst)); - ds_put_cstr(f, "]"); + print_wild(f, "in_port=", w & OFPFW_IN_PORT, verbosity, + "%d,", ntohs(om->in_port)); + print_wild(f, "dl_vlan=", w & OFPFW_DL_VLAN, verbosity, + "%04x,", ntohs(om->dl_vlan)); + print_wild(f, "dl_src=", w & OFPFW_DL_SRC, verbosity, + ETH_ADDR_FMT",", ETH_ADDR_ARGS(om->dl_src)); + print_wild(f, "dl_dst=", w & OFPFW_DL_DST, verbosity, + ETH_ADDR_FMT",", ETH_ADDR_ARGS(om->dl_dst)); + print_wild(f, "dl_type=", w & OFPFW_DL_TYPE, verbosity, + "%04x,", ntohs(om->dl_type)); + print_wild(f, "nw_src=", w & OFPFW_NW_SRC, verbosity, + IP_FMT",", IP_ARGS(&om->nw_src)); + print_wild(f, "nw_dst=", w & OFPFW_NW_DST, verbosity, + IP_FMT",", IP_ARGS(&om->nw_dst)); + print_wild(f, "nw_proto=", w & OFPFW_NW_PROTO, verbosity, + "%u,", om->nw_proto); + print_wild(f, "tp_src=", w & OFPFW_TP_SRC, verbosity, + "%d,", ntohs(om->tp_src)); + print_wild(f, "tp_dst=", w & OFPFW_TP_DST, verbosity, + "%d,", ntohs(om->tp_dst)); } /* Pretty-print the OFPT_FLOW_MOD packet of 'len' bytes at 'oh' to 'string' @@ -414,7 +473,7 @@ ofp_print_flow_mod(struct ds *string, const void *oh, size_t len, { const struct ofp_flow_mod *ofm = oh; - ofp_print_match(string, &ofm->match); + ofp_print_match(string, &ofm->match, verbosity); ds_put_format(string, " cmd:%d idle:%d pri:%d buf:%#x", ntohs(ofm->command), ntohs(ofm->max_idle), ofm->match.wildcards ? ntohs(ofm->priority) : (uint16_t)-1, @@ -432,7 +491,7 @@ ofp_print_flow_expired(struct ds *string, const void *oh, size_t len, { const struct ofp_flow_expired *ofe = oh; - ofp_print_match(string, &ofe->match); + ofp_print_match(string, &ofe->match, verbosity); ds_put_format(string, " pri%"PRIu16" secs%"PRIu32" pkts%"PRIu64" bytes%"PRIu64"\n", ofe->match.wildcards ? ntohs(ofe->priority) : (uint16_t)-1, @@ -485,7 +544,7 @@ ofp_flow_stats_request(struct ds *string, const void *oh, size_t len, ds_put_format(string, " table_id=%"PRIu8", ", fsr->table_id); } - ofp_print_match(string, &fsr->match); + ofp_print_match(string, &fsr->match, verbosity); } static void @@ -535,7 +594,7 @@ ofp_flow_stats_reply(struct ds *string, const void *body_, size_t len, ntohll(fs->packet_count)); ds_put_format(string, "n_bytes=%"PRIu64", ", ntohll(fs->byte_count)); ds_put_format(string, "max_idle=%"PRIu16",", ntohs(fs->max_idle)); - ofp_print_match(string, &fs->match); + ofp_print_match(string, &fs->match, verbosity); ofp_print_actions(string, fs->actions, length - sizeof *fs); ds_put_char(string, '\n'); @@ -555,7 +614,7 @@ ofp_aggregate_stats_request(struct ds *string, const void *oh, size_t len, ds_put_format(string, " table_id=%"PRIu8", ", asr->table_id); } - ofp_print_match(string, &asr->match); + ofp_print_match(string, &asr->match, verbosity); } static void @@ -722,6 +781,17 @@ ofp_stats_reply(struct ds *string, const void *oh, size_t len, int verbosity) verbosity, REPLY); } +static void +ofp_echo(struct ds *string, const void *oh, size_t len, int verbosity) +{ + const struct ofp_header *hdr = oh; + + ds_put_format(string, " %zu bytes of payload\n", len - sizeof *hdr); + if (verbosity > 1) { + ds_put_hex_dump(string, hdr, len - sizeof *hdr, 0, true); + } +} + struct openflow_packet { const char *name; size_t min_size; @@ -799,6 +869,16 @@ static const struct openflow_packet packets[] = { sizeof (struct ofp_stats_reply), ofp_stats_reply, }, + [OFPT_ECHO_REQUEST] = { + "echo_request", + sizeof (struct ofp_header), + ofp_echo, + }, + [OFPT_ECHO_REPLY] = { + "echo_reply", + sizeof (struct ofp_header), + ofp_echo, + }, }; /* Composes and returns a string representing the OpenFlow packet of 'len' diff --git a/lib/poll-loop.c b/lib/poll-loop.c index 556521a..71f1e6f 100644 --- a/lib/poll-loop.c +++ b/lib/poll-loop.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "poll-loop.h" #include #include diff --git a/lib/queue.c b/lib/queue.c index 586ae52..635e144 100644 --- a/lib/queue.c +++ b/lib/queue.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "queue.h" #include #include "buffer.h" diff --git a/lib/random.c b/lib/random.c new file mode 100644 index 0000000..c7a3edf --- /dev/null +++ b/lib/random.c @@ -0,0 +1,79 @@ +/* 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 +#include "random.h" + +#include +#include +#include + +#include "util.h" + +void +random_init(void) +{ + static bool inited = false; + if (!inited) { + struct timeval tv; + inited = true; + if (gettimeofday(&tv, NULL) < 0) { + fatal(errno, "gettimeofday"); + } + srand(tv.tv_sec ^ tv.tv_usec); + } +} + +void +random_bytes(void *p_, size_t n) +{ + uint8_t *p = p_; + random_init(); + while (n--) { + *p++ = rand(); + } +} + +uint32_t +random_uint32(void) +{ + uint32_t x; + random_bytes(&x, sizeof x); + return x; +} + +int +random_range(int max) +{ + return random_uint32() % max; +} diff --git a/lib/rconn.c b/lib/rconn.c index 5a5f4b1..9bb2b5c 100644 --- a/lib/rconn.c +++ b/lib/rconn.c @@ -1,6 +1,6 @@ /* 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 @@ -13,10 +13,10 @@ * 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 @@ -25,67 +25,207 @@ * 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 #include "rconn.h" #include #include +#include #include #include #include "buffer.h" #include "poll-loop.h" #include "ofp-print.h" +#include "timeval.h" #include "util.h" #include "vconn.h" #define THIS_MODULE VLM_rconn #include "vlog.h" +#define STATES \ + STATE(VOID, 1 << 0) \ + STATE(BACKOFF, 1 << 1) \ + STATE(CONNECTING, 1 << 2) \ + STATE(ACTIVE, 1 << 3) \ + STATE(IDLE, 1 << 4) +enum state { +#define STATE(NAME, VALUE) S_##NAME = VALUE, + STATES +#undef STATE +}; + +static const char * +state_name(enum state state) +{ + switch (state) { +#define STATE(NAME, VALUE) case S_##NAME: return #NAME; + STATES +#undef STATE + } + return "***ERROR***"; +} + /* A reliable connection to an OpenFlow switch or controller. * * See the large comment in rconn.h for more information. */ struct rconn { - bool reliable; - char *name; + enum state state; + time_t state_entered; + struct vconn *vconn; - bool connected; + char *name; + bool reliable; + struct queue txq; int txq_limit; - time_t backoff_deadline; + int backoff; + int max_backoff; + time_t backoff_deadline; + time_t last_received; time_t last_connected; + unsigned int packets_sent; + + /* If we can't connect to the peer, it could be for any number of reasons. + * Usually, one would assume it is because the peer is not running or + * because the network is partitioned. But it could also be because the + * network topology has changed, in which case the upper layer will need to + * reassess it (in particular, obtain a new IP address via DHCP and find + * the new location of the controller). We set this flag when we suspect + * that this could be the case. */ + bool questionable_connectivity; + time_t last_questioned; + + /* Throughout this file, "probe" is shorthand for "inactivity probe". + * When nothing has been received from the peer for a while, we send out + * an echo request as an inactivity probe packet. We should receive back + * a response. */ + int probe_interval; /* Secs of inactivity before sending probe. */ }; -static struct rconn *create_rconn(const char *name, int txq_limit, - struct vconn *); +static unsigned int sat_add(unsigned int x, unsigned int y); +static unsigned int sat_mul(unsigned int x, unsigned int y); +static unsigned int elapsed_in_this_state(const struct rconn *); +static unsigned int timeout(const struct rconn *); +static bool timed_out(const struct rconn *); +static void state_transition(struct rconn *, enum state); static int try_send(struct rconn *); +static int reconnect(struct rconn *); static void disconnect(struct rconn *, int error); +static void question_connectivity(struct rconn *); -/* Creates and returns a new rconn that connects (and re-connects as necessary) - * to the vconn named 'name'. - * - * 'txq_limit' is the maximum length of the send queue, in packets. */ +/* Creates a new rconn, connects it (reliably) to 'name', and returns it. */ struct rconn * -rconn_new(const char *name, int txq_limit) +rconn_new(const char *name, int txq_limit, int inactivity_probe_interval, + int max_backoff) { - return create_rconn(name, txq_limit, NULL); + struct rconn *rc = rconn_create(txq_limit, inactivity_probe_interval, + max_backoff); + rconn_connect(rc, name); + return rc; } -/* Creates and returns a new rconn that is initially connected to 'vconn' and - * has the given 'name'. The rconn will not re-connect after the connection - * drops. - * - * 'txq_limit' is the maximum length of the send queue, in packets. */ +/* Creates a new rconn, connects it (unreliably) to 'vconn', and returns it. */ struct rconn * -rconn_new_from_vconn(const char *name, int txq_limit, struct vconn *vconn) +rconn_new_from_vconn(const char *name, int txq_limit, struct vconn *vconn) +{ + struct rconn *rc = rconn_create(txq_limit, 60, 0); + rconn_connect_unreliably(rc, name, vconn); + return rc; +} + +/* Creates and returns a new rconn. + * + * 'txq_limit' is the maximum length of the send queue, in packets. + * + * 'probe_interval' is a number of seconds. If the interval passes once + * without an OpenFlow message being received from the peer, the rconn sends + * out an "echo request" message. If the interval passes again without a + * message being received, the rconn disconnects and re-connects to the peer. + * Setting 'probe_interval' to 0 disables this behavior. + * + * 'max_backoff' is the maximum number of seconds between attempts to connect + * to the peer. The actual interval starts at 1 second and doubles on each + * failure until it reaches 'max_backoff'. If 0 is specified, the default of + * 60 seconds is used. */ +struct rconn * +rconn_create(int txq_limit, int probe_interval, int max_backoff) +{ + struct rconn *rc = xcalloc(1, sizeof *rc); + + rc->state = S_VOID; + rc->state_entered = time(0); + + rc->vconn = NULL; + rc->name = xstrdup("void"); + rc->reliable = false; + + queue_init(&rc->txq); + assert(txq_limit > 0); + rc->txq_limit = txq_limit; + + rc->backoff = 0; + rc->max_backoff = max_backoff ? max_backoff : 60; + rc->backoff_deadline = TIME_MIN; + rc->last_received = time(0); + rc->last_connected = time(0); + + rc->packets_sent = 0; + + rc->questionable_connectivity = false; + rc->last_questioned = time(0); + + rc->probe_interval = probe_interval ? MAX(5, probe_interval) : 0; + + return rc; +} + +int +rconn_connect(struct rconn *rc, const char *name) +{ + rconn_disconnect(rc); + free(rc->name); + rc->name = xstrdup(name); + rc->reliable = true; + return reconnect(rc); +} + +void +rconn_connect_unreliably(struct rconn *rc, + const char *name, struct vconn *vconn) { assert(vconn != NULL); - return create_rconn(name, txq_limit, vconn); + rconn_disconnect(rc); + free(rc->name); + rc->name = xstrdup(name); + rc->reliable = false; + rc->vconn = vconn; + rc->last_connected = time(0); + state_transition(rc, S_ACTIVE); +} + +void +rconn_disconnect(struct rconn *rc) +{ + if (rc->vconn) { + vconn_close(rc->vconn); + rc->vconn = NULL; + } + free(rc->name); + rc->name = xstrdup("void"); + rc->reliable = false; + + rc->backoff = 0; + rc->backoff_deadline = TIME_MIN; + + state_transition(rc, S_VOID); } /* Disconnects 'rc' and frees the underlying storage. */ @@ -100,63 +240,166 @@ rconn_destroy(struct rconn *rc) } } +static unsigned int +timeout_VOID(const struct rconn *rc) +{ + return UINT_MAX; +} + +static void +run_VOID(struct rconn *rc) +{ + /* Nothing to do. */ +} + +static int +reconnect(struct rconn *rc) +{ + int retval; + + VLOG_WARN("%s: connecting...", rc->name); + retval = vconn_open(rc->name, &rc->vconn); + if (!retval) { + rc->backoff_deadline = time(0) + rc->backoff; + state_transition(rc, S_CONNECTING); + } else { + VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(retval)); + disconnect(rc, 0); + } + return retval; +} + +static unsigned int +timeout_BACKOFF(const struct rconn *rc) +{ + return rc->backoff; +} + +static void +run_BACKOFF(struct rconn *rc) +{ + if (timed_out(rc)) { + reconnect(rc); + } +} + +static unsigned int +timeout_CONNECTING(const struct rconn *rc) +{ + return MAX(1, rc->backoff); +} + +static void +run_CONNECTING(struct rconn *rc) +{ + int retval = vconn_connect(rc->vconn); + if (!retval) { + VLOG_WARN("%s: connected", rc->name); + if (vconn_is_passive(rc->vconn)) { + error(0, "%s: passive vconn not supported", rc->name); + state_transition(rc, S_VOID); + } else { + state_transition(rc, S_ACTIVE); + rc->last_connected = rc->state_entered; + } + } else if (retval != EAGAIN) { + VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(retval)); + disconnect(rc, retval); + } else if (timed_out(rc)) { + VLOG_WARN("%s: connection timed out", rc->name); + rc->backoff_deadline = TIME_MAX; /* Prevent resetting backoff. */ + disconnect(rc, 0); + } +} + +static void +do_tx_work(struct rconn *rc) +{ + while (rc->txq.n > 0) { + int error = try_send(rc); + if (error) { + break; + } + } +} + +static unsigned int +timeout_ACTIVE(const struct rconn *rc) +{ + if (rc->probe_interval) { + unsigned int base = MAX(rc->last_received, rc->state_entered); + unsigned int arg = base + rc->probe_interval - rc->state_entered; + return arg; + } + return UINT_MAX; +} + +static void +run_ACTIVE(struct rconn *rc) +{ + if (timed_out(rc)) { + unsigned int base = MAX(rc->last_received, rc->state_entered); + queue_push_tail(&rc->txq, make_echo_request()); + VLOG_DBG("%s: idle %u seconds, sending inactivity probe", + rc->name, (unsigned int) (time(0) - base)); + state_transition(rc, S_IDLE); + return; + } + + do_tx_work(rc); +} + +static unsigned int +timeout_IDLE(const struct rconn *rc) +{ + return rc->probe_interval; +} + +static void +run_IDLE(struct rconn *rc) +{ + if (timed_out(rc)) { + question_connectivity(rc); + VLOG_ERR("%s: no response to inactivity probe after %u " + "seconds, disconnecting", + rc->name, elapsed_in_this_state(rc)); + disconnect(rc, 0); + } else { + do_tx_work(rc); + } +} + /* Performs whatever activities are necessary to maintain 'rc': if 'rc' is * disconnected, attempts to (re)connect, backing off as necessary; if 'rc' is * connected, attempts to send packets in the send queue, if any. */ void rconn_run(struct rconn *rc) { - if (!rc->vconn) { - if (rc->reliable && time(0) >= rc->backoff_deadline) { - int retval; - - retval = vconn_open(rc->name, &rc->vconn); - if (!retval) { - rc->backoff_deadline = time(0) + rc->backoff; - rc->connected = false; - } else { - VLOG_WARN("%s: connection failed (%s)", - rc->name, strerror(retval)); - disconnect(rc, 0); - } + int old_state; + do { + old_state = rc->state; + switch (rc->state) { +#define STATE(NAME, VALUE) case S_##NAME: run_##NAME(rc); break; + STATES +#undef STATE + default: + NOT_REACHED(); } - } else if (!rc->connected) { - int error = vconn_connect(rc->vconn); - if (!error) { - VLOG_WARN("%s: connected", rc->name); - if (vconn_is_passive(rc->vconn)) { - fatal(0, "%s: passive vconn not supported in switch", - rc->name); - } - rc->connected = true; - } else if (error != EAGAIN) { - VLOG_WARN("%s: connection failed (%s)", rc->name, strerror(error)); - disconnect(rc, 0); - } - } else { - while (rc->txq.n > 0) { - int error = try_send(rc); - if (error == EAGAIN) { - break; - } else if (error) { - disconnect(rc, error); - return; - } - } - } + } while (rc->state != old_state); } /* Causes the next call to poll_block() to wake up when rconn_run() should be * called on 'rc'. */ void -rconn_run_wait(struct rconn *rc) +rconn_run_wait(struct rconn *rc) { - if (rc->vconn) { - if (rc->txq.n) { - vconn_wait(rc->vconn, WAIT_SEND); - } - } else { - poll_timer_wait((rc->backoff_deadline - time(0)) * 1000); + unsigned int timeo = timeout(rc); + if (timeo != UINT_MAX) { + poll_timer_wait(sat_mul(timeo, 1000)); + } + + if ((rc->state & (S_ACTIVE | S_IDLE)) && rc->txq.n) { + vconn_wait(rc->vconn, WAIT_SEND); } } @@ -166,14 +409,17 @@ rconn_run_wait(struct rconn *rc) struct buffer * rconn_recv(struct rconn *rc) { - if (rc->vconn && rc->connected) { + if (rc->state & (S_ACTIVE | S_IDLE)) { struct buffer *buffer; int error = vconn_recv(rc->vconn, &buffer); if (!error) { - rc->last_connected = time(0); + rc->last_received = time(0); + if (rc->state == S_IDLE) { + state_transition(rc, S_ACTIVE); + } return buffer; } else if (error != EAGAIN) { - disconnect(rc, error); + disconnect(rc, error); } } return NULL; @@ -182,7 +428,7 @@ rconn_recv(struct rconn *rc) /* Causes the next call to poll_block() to wake up when a packet may be ready * to be received by vconn_recv() on 'rc'. */ void -rconn_recv_wait(struct rconn *rc) +rconn_recv_wait(struct rconn *rc) { if (rc->vconn) { vconn_wait(rc->vconn, WAIT_RECV); @@ -213,7 +459,7 @@ do_send(struct rconn *rc, struct buffer *b, int txq_limit) * full, or ENOTCONN if 'rc' is not currently connected. * * There is no rconn_send_wait() function: an rconn has a send queue that it - * takes care of sending if you call rconn_wait(), which will have the side + * takes care of sending if you call rconn_run(), which will have the side * effect of waking up poll_block(). */ int rconn_send(struct rconn *rc, struct buffer *b) @@ -235,7 +481,7 @@ rconn_force_send(struct rconn *rc, struct buffer *b) /* Returns true if 'rc''s send buffer is full, * false if it has room for at least one more packet. */ bool -rconn_is_full(const struct rconn *rc) +rconn_is_full(const struct rconn *rc) { return rc->txq.n >= rc->txq_limit; } @@ -244,59 +490,65 @@ rconn_is_full(const struct rconn *rc) * vconn. A packet is not counted as sent while it is still queued in the * rconn, only when it has been successfuly passed to the vconn. */ unsigned int -rconn_packets_sent(const struct rconn *rc) +rconn_packets_sent(const struct rconn *rc) { return rc->packets_sent; } /* Returns 'rc''s name (the 'name' argument passed to rconn_new()). */ const char * -rconn_get_name(const struct rconn *rc) +rconn_get_name(const struct rconn *rc) { return rc->name; } /* Returns true if 'rconn' is connected or in the process of reconnecting, - * false if 'rconn' is disconnected and will not be reconnected. */ + * false if 'rconn' is disconnected and will not reconnect on its own. */ bool -rconn_is_alive(const struct rconn *rconn) +rconn_is_alive(const struct rconn *rconn) { - return rconn->reliable || rconn->vconn; + return rconn->state != S_VOID; } /* Returns true if 'rconn' is connected, false otherwise. */ bool rconn_is_connected(const struct rconn *rconn) { - return rconn->vconn && !vconn_connect(rconn->vconn); + return rconn->state & (S_ACTIVE | S_IDLE); } /* Returns 0 if 'rconn' is connected, otherwise the number of seconds that it * has been disconnected. */ int -rconn_disconnected_duration(const struct rconn *rconn) +rconn_disconnected_duration(const struct rconn *rconn) { - return rconn_is_connected(rconn) ? 0 : time(0) - rconn->last_connected; -} - -static struct rconn * -create_rconn(const char *name, int txq_limit, struct vconn *vconn) -{ - struct rconn *rc = xmalloc(sizeof *rc); - assert(txq_limit > 0); - rc->reliable = vconn == NULL; - rc->connected = vconn != NULL; - rc->name = xstrdup(name); - rc->vconn = vconn; - queue_init(&rc->txq); - rc->txq_limit = txq_limit; - rc->backoff_deadline = 0; - rc->backoff = 0; - rc->last_connected = time(0); - rc->packets_sent = 0; - return rc; + return rconn_is_connected(rconn) ? 0 : time(0) - rconn->last_received; } +/* Returns the IP address of the peer, or 0 if the peer is not connected over + * an IP-based protocol or if its IP address is not known. */ +uint32_t +rconn_get_ip(const struct rconn *rconn) +{ + return rconn->vconn ? vconn_get_ip(rconn->vconn) : 0; +} + +/* If 'rconn' can't connect to the peer, it could be for any number of reasons. + * Usually, one would assume it is because the peer is not running or because + * the network is partitioned. But it could also be because the network + * topology has changed, in which case the upper layer will need to reassess it + * (in particular, obtain a new IP address via DHCP and find the new location + * of the controller). When this appears that this might be the case, this + * function returns true. It also clears the questionability flag and prevents + * it from being set again for some time. */ +bool +rconn_is_connectivity_questionable(struct rconn *rconn) +{ + bool questionable = rconn->questionable_connectivity; + rconn->questionable_connectivity = false; + return questionable; +} + /* Tries to send a packet from 'rc''s send buffer. Returns 0 if successful, * otherwise a positive errno value. */ static int @@ -306,6 +558,9 @@ try_send(struct rconn *rc) struct buffer *next = rc->txq.head->next; retval = vconn_send(rc->vconn, rc->txq.head); if (retval) { + if (retval != EAGAIN) { + disconnect(rc, retval); + } return retval; } rc->packets_sent++; @@ -317,32 +572,95 @@ try_send(struct rconn *rc) * nonzero, then it should be EOF to indicate the connection was closed by the * peer in a normal fashion or a positive errno value. */ static void -disconnect(struct rconn *rc, int error) +disconnect(struct rconn *rc, int error) +{ + if (rc->reliable) { + time_t now = time(0); + + if (rc->state & (S_CONNECTING | S_ACTIVE | S_IDLE)) { + if (error > 0) { + VLOG_WARN("%s: connection dropped (%s)", + rc->name, strerror(error)); + } else if (error == EOF) { + if (rc->reliable) { + VLOG_WARN("%s: connection closed", rc->name); + } + } else { + VLOG_WARN("%s: connection dropped", rc->name); + } + vconn_close(rc->vconn); + rc->vconn = NULL; + queue_clear(&rc->txq); + } + + if (now >= rc->backoff_deadline) { + rc->backoff = 1; + } else { + rc->backoff = MIN(rc->max_backoff, MAX(1, 2 * rc->backoff)); + VLOG_WARN("%s: waiting %d seconds before reconnect\n", + rc->name, rc->backoff); + } + rc->backoff_deadline = now + rc->backoff; + state_transition(rc, S_BACKOFF); + if (now - rc->last_connected > 60) { + question_connectivity(rc); + } + } else { + rconn_disconnect(rc); + } +} + +static unsigned int +elapsed_in_this_state(const struct rconn *rc) +{ + return time(0) - rc->state_entered; +} + +static unsigned int +timeout(const struct rconn *rc) +{ + switch (rc->state) { +#define STATE(NAME, VALUE) case S_##NAME: return timeout_##NAME(rc); + STATES +#undef STATE + default: + NOT_REACHED(); + } +} + +static bool +timed_out(const struct rconn *rc) +{ + return time(0) >= sat_add(rc->state_entered, timeout(rc)); +} + +static void +state_transition(struct rconn *rc, enum state state) +{ + VLOG_DBG("%s: entering %s", rc->name, state_name(state)); + rc->state = state; + rc->state_entered = time(0); +} + +static unsigned int +sat_add(unsigned int x, unsigned int y) +{ + return x + y >= x ? x + y : UINT_MAX; +} + +static unsigned int +sat_mul(unsigned int x, unsigned int y) +{ + assert(y); + return x <= UINT_MAX / y ? x * y : UINT_MAX; +} + +static void +question_connectivity(struct rconn *rc) { time_t now = time(0); - - if (rc->vconn) { - if (error > 0) { - VLOG_WARN("%s: connection dropped (%s)", - rc->name, strerror(error)); - } else if (error == EOF) { - if (rc->reliable) { - VLOG_WARN("%s: connection closed", rc->name); - } - } else { - VLOG_WARN("%s: connection dropped", rc->name); - } - vconn_close(rc->vconn); - rc->vconn = NULL; - queue_clear(&rc->txq); + if (now - rc->last_questioned > 60) { + rc->questionable_connectivity = true; + rc->last_questioned = now; } - - if (now >= rc->backoff_deadline) { - rc->backoff = 1; - } else { - rc->backoff = MIN(60, MAX(1, 2 * rc->backoff)); - VLOG_WARN("%s: waiting %d seconds before reconnect\n", - rc->name, rc->backoff); - } - rc->backoff_deadline = now + rc->backoff; } diff --git a/lib/socket-util.c b/lib/socket-util.c index 6061d26..35dbcd6 100644 --- a/lib/socket-util.c +++ b/lib/socket-util.c @@ -31,14 +31,20 @@ * derivatives without specific, written prior permission. */ +#include #include "socket-util.h" #include #include #include #include #include +#include #include #include +#include +#include +#include "fatal-signal.h" +#include "util.h" #include "vlog.h" #define THIS_MODULE VLM_socket_util @@ -118,3 +124,129 @@ check_connection_completion(int fd) return EAGAIN; } } + +/* Drain all the data currently in the receive queue of a datagram socket (and + * possibly additional data). There is no way to know how many packets are in + * the receive queue, but we do know that the total number of bytes queued does + * not exceed the receive buffer size, so we pull packets until none are left + * or we've read that many bytes. */ +int +drain_rcvbuf(int fd) +{ + socklen_t rcvbuf_len; + size_t rcvbuf; + + rcvbuf_len = sizeof rcvbuf; + if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &rcvbuf, &rcvbuf_len) < 0) { + VLOG_ERR("getsockopt(SO_RCVBUF) failed: %s", strerror(errno)); + return errno; + } + while (rcvbuf > 0) { + /* In Linux, specifying MSG_TRUNC in the flags argument causes the + * datagram length to be returned, even if that is longer than the + * buffer provided. Thus, we can use a 1-byte buffer to discard the + * incoming datagram and still be able to account how many bytes were + * removed from the receive buffer. + * + * On other Unix-like OSes, MSG_TRUNC has no effect in the flags + * argument. */ +#ifdef __linux__ +#define BUFFER_SIZE 1 +#else +#define BUFFER_SIZE 2048 +#endif + char buffer[BUFFER_SIZE]; + ssize_t n_bytes = recv(fd, buffer, sizeof buffer, + MSG_TRUNC | MSG_DONTWAIT); + if (n_bytes <= 0 || n_bytes >= rcvbuf) { + break; + } + rcvbuf -= n_bytes; + } + return 0; +} + +/* Stores in '*un' a sockaddr_un that refers to file 'name'. Stores in + * '*un_len' the size of the sockaddr_un. */ +static void +make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len) +{ + un->sun_family = AF_UNIX; + strncpy(un->sun_path, name, sizeof un->sun_path); + un->sun_path[sizeof un->sun_path - 1] = '\0'; + *un_len = (offsetof(struct sockaddr_un, sun_path) + + strlen (un->sun_path) + 1); +} + +/* Creates a Unix domain socket in the given 'style' (either SOCK_DGRAM or + * SOCK_STREAM) that is bound to '*bind_path' (if 'bind_path' is non-null) and + * connected to '*connect_path' (if 'connect_path' is non-null). If 'nonblock' + * is true, the socket is made non-blocking. If 'passcred' is true, the socket + * is configured to receive SCM_CREDENTIALS control messages. + * + * Returns the socket's fd if successful, otherwise a negative errno value. */ +int +make_unix_socket(int style, bool nonblock, bool passcred UNUSED, + const char *bind_path, const char *connect_path) +{ + int error; + int fd; + + fd = socket(PF_UNIX, style, 0); + if (fd < 0) { + return -errno; + } + + if (nonblock) { + int flags = fcntl(fd, F_GETFL, 0); + if (flags == -1) { + goto error; + } + if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { + goto error; + } + } + + if (bind_path) { + struct sockaddr_un un; + socklen_t un_len; + make_sockaddr_un(bind_path, &un, &un_len); + if (unlink(un.sun_path) && errno != ENOENT) { + VLOG_WARN("unlinking \"%s\": %s\n", un.sun_path, strerror(errno)); + } + fatal_signal_add_file_to_unlink(bind_path); + if (bind(fd, (struct sockaddr*) &un, un_len) + || fchmod(fd, S_IRWXU)) { + goto error; + } + } + + if (connect_path) { + struct sockaddr_un un; + socklen_t un_len; + make_sockaddr_un(connect_path, &un, &un_len); + if (connect(fd, (struct sockaddr*) &un, un_len) + && errno != EINPROGRESS) { + goto error; + } + } + +#ifdef SCM_CREDENTIALS + if (passcred) { + int enable = 1; + if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable))) { + goto error; + } + } +#endif + + return fd; + +error: + if (bind_path) { + fatal_signal_remove_file_to_unlink(bind_path); + } + error = errno; + close(fd); + return -error; +} diff --git a/lib/util.c b/lib/util.c index 04fcd90..59c7806 100644 --- a/lib/util.c +++ b/lib/util.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "util.h" #include #include @@ -83,10 +84,19 @@ xmemdup(const void *p_, size_t size) return p; } +char * +xmemdup0(const char *p_, size_t length) +{ + char *p = xmalloc(length + 1); + memcpy(p, p_, length); + p[length] = '\0'; + return p; +} + char * xstrdup(const char *s) { - return xmemdup(s, strlen(s) + 1); + return xmemdup0(s, strlen(s)); } char * diff --git a/lib/vconn-netlink.c b/lib/vconn-netlink.c index ca1f93d..403f3fc 100644 --- a/lib/vconn-netlink.c +++ b/lib/vconn-netlink.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "vconn.h" #include #include @@ -77,12 +78,14 @@ netlink_open(const char *name, char *suffix, struct vconn **vconnp) subscribe = 1; if (sscanf(suffix, "%d:%d", &dp_idx, &subscribe) < 1) { - fatal(0, "%s: syntax error", name); + error(0, "%s: syntax error", name); + return EAFNOSUPPORT; } netlink = xmalloc(sizeof *netlink); netlink->vconn.class = &netlink_vconn_class; netlink->vconn.connect_status = 0; + netlink->vconn.ip = 0; retval = dpif_open(dp_idx, subscribe, &netlink->dp); if (retval) { free(netlink); diff --git a/lib/vconn-ssl.c b/lib/vconn-ssl.c index 99b8269..d109cfd 100644 --- a/lib/vconn-ssl.c +++ b/lib/vconn-ssl.c @@ -31,10 +31,12 @@ * derivatives without specific, written prior permission. */ +#include #include "vconn-ssl.h" #include "dhparams.h" #include #include +#include #include #include #include @@ -45,6 +47,7 @@ #include "socket-util.h" #include "util.h" #include "openflow.h" +#include "packets.h" #include "poll-loop.h" #include "ofp-print.h" #include "socket-util.h" @@ -171,7 +174,8 @@ want_to_poll_events(int want) static int new_ssl_vconn(const char *name, int fd, enum session_type type, - enum ssl_state state, struct vconn **vconnp) + enum ssl_state state, const struct sockaddr_in *sin, + struct vconn **vconnp) { struct ssl_vconn *sslv; SSL *ssl = NULL; @@ -220,6 +224,7 @@ new_ssl_vconn(const char *name, int fd, enum session_type type, sslv = xmalloc(sizeof *sslv); sslv->vconn.class = &ssl_vconn_class; sslv->vconn.connect_status = EAGAIN; + sslv->vconn.ip = sin->sin_addr.s_addr; sslv->state = state; sslv->type = type; sslv->fd = fd; @@ -266,7 +271,8 @@ ssl_open(const char *name, char *suffix, struct vconn **vconnp) host_name = strtok_r(suffix, "::", &save_ptr); port_string = strtok_r(NULL, "::", &save_ptr); if (!host_name) { - fatal(0, "%s: bad peer name format", name); + error(0, "%s: bad peer name format", name); + return EAFNOSUPPORT; } memset(&sin, 0, sizeof sin); @@ -294,7 +300,7 @@ ssl_open(const char *name, char *suffix, struct vconn **vconnp) if (retval < 0) { if (errno == EINPROGRESS) { return new_ssl_vconn(name, fd, CLIENT, STATE_TCP_CONNECTING, - vconnp); + &sin, vconnp); } else { int error = errno; VLOG_ERR("%s: connect: %s", name, strerror(error)); @@ -303,7 +309,7 @@ ssl_open(const char *name, char *suffix, struct vconn **vconnp) } } else { return new_ssl_vconn(name, fd, CLIENT, STATE_SSL_CONNECTING, - vconnp); + &sin, vconnp); } } @@ -330,8 +336,9 @@ ssl_connect(struct vconn *vconn) if (retval < 0 && ssl_wants_io(error)) { return EAGAIN; } else { + int unused; interpret_ssl_error((sslv->type == CLIENT ? "SSL_connect" - : "SSL_accept"), retval, error, NULL); + : "SSL_accept"), retval, error, &unused); shutdown(sslv->fd, SHUT_RDWR); return EPROTO; } @@ -453,7 +460,7 @@ again: return 0; } } - buffer_reserve_tailroom(rx, want_bytes); + buffer_prealloc_tailroom(rx, want_bytes); /* Behavior of zero-byte SSL_read is poorly defined. */ assert(want_bytes > 0); @@ -732,10 +739,13 @@ static int pssl_accept(struct vconn *vconn, struct vconn **new_vconnp) { struct pssl_vconn *pssl = pssl_vconn_cast(vconn); + struct sockaddr_in sin; + socklen_t sin_len = sizeof sin; + char name[128]; int new_fd; int error; - new_fd = accept(pssl->fd, NULL, NULL); + new_fd = accept(pssl->fd, &sin, &sin_len); if (new_fd < 0) { int error = errno; if (error != EAGAIN) { @@ -750,8 +760,12 @@ pssl_accept(struct vconn *vconn, struct vconn **new_vconnp) return error; } - return new_ssl_vconn("ssl" /* FIXME */, new_fd, - SERVER, STATE_SSL_CONNECTING, new_vconnp); + sprintf(name, "ssl:"IP_FMT, IP_ARGS(&sin.sin_addr)); + if (sin.sin_port != htons(OFP_SSL_PORT)) { + sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin.sin_port)); + } + return new_ssl_vconn(name, new_fd, SERVER, STATE_SSL_CONNECTING, &sin, + new_vconnp); } static void @@ -855,6 +869,13 @@ tmp_dh_callback(SSL *ssl, int is_export UNUSED, int keylength) return NULL; } +/* Returns true if SSL is at least partially configured. */ +bool +vconn_ssl_is_configured(void) +{ + return has_private_key || has_certificate || has_ca_cert; +} + void vconn_ssl_set_private_key_file(const char *file_name) { diff --git a/lib/vconn-stream.c b/lib/vconn-stream.c new file mode 100644 index 0000000..28d9635 --- /dev/null +++ b/lib/vconn-stream.c @@ -0,0 +1,354 @@ +/* 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 +#include "vconn-stream.h" +#include +#include +#include +#include +#include +#include +#include +#include "buffer.h" +#include "util.h" +#include "openflow.h" +#include "poll-loop.h" +#include "socket-util.h" +#include "vconn.h" + +#include "vlog.h" +#define THIS_MODULE VLM_vconn_stream + +/* Active stream socket vconn. */ + +struct stream_vconn +{ + struct vconn vconn; + int fd; + struct buffer *rxbuf; + struct buffer *txbuf; + struct poll_waiter *tx_waiter; +}; + +static struct vconn_class stream_vconn_class; + +int +new_stream_vconn(const char *name, int fd, int connect_status, + uint32_t ip, struct vconn **vconnp) +{ + struct stream_vconn *s; + + s = xmalloc(sizeof *s); + s->vconn.class = &stream_vconn_class; + s->vconn.connect_status = connect_status; + s->vconn.ip = ip; + s->fd = fd; + s->txbuf = NULL; + s->tx_waiter = NULL; + s->rxbuf = NULL; + *vconnp = &s->vconn; + return 0; +} + +static struct stream_vconn * +stream_vconn_cast(struct vconn *vconn) +{ + assert(vconn->class == &stream_vconn_class); + return CONTAINER_OF(vconn, struct stream_vconn, vconn); +} + +static void +stream_close(struct vconn *vconn) +{ + struct stream_vconn *s = stream_vconn_cast(vconn); + poll_cancel(s->tx_waiter); + close(s->fd); + free(s); +} + +static int +stream_connect(struct vconn *vconn) +{ + struct stream_vconn *s = stream_vconn_cast(vconn); + return check_connection_completion(s->fd); +} + +static int +stream_recv(struct vconn *vconn, struct buffer **bufferp) +{ + struct stream_vconn *s = stream_vconn_cast(vconn); + struct buffer *rx; + size_t want_bytes; + ssize_t retval; + + if (s->rxbuf == NULL) { + s->rxbuf = buffer_new(1564); + } + rx = s->rxbuf; + +again: + if (sizeof(struct ofp_header) > rx->size) { + want_bytes = sizeof(struct ofp_header) - rx->size; + } else { + struct ofp_header *oh = rx->data; + size_t length = ntohs(oh->length); + if (length < sizeof(struct ofp_header)) { + VLOG_ERR("received too-short ofp_header (%zu bytes)", length); + return EPROTO; + } + want_bytes = length - rx->size; + if (!want_bytes) { + *bufferp = rx; + s->rxbuf = NULL; + return 0; + } + } + buffer_prealloc_tailroom(rx, want_bytes); + + retval = read(s->fd, buffer_tail(rx), want_bytes); + if (retval > 0) { + rx->size += retval; + if (retval == want_bytes) { + if (rx->size > sizeof(struct ofp_header)) { + *bufferp = rx; + s->rxbuf = NULL; + return 0; + } else { + goto again; + } + } + return EAGAIN; + } else if (retval == 0) { + if (rx->size) { + VLOG_ERR("connection dropped mid-packet"); + return EPROTO; + } else { + return EOF; + } + } else { + return retval ? errno : EAGAIN; + } +} + +static void +stream_clear_txbuf(struct stream_vconn *s) +{ + buffer_delete(s->txbuf); + s->txbuf = NULL; + s->tx_waiter = NULL; +} + +static void +stream_do_tx(int fd UNUSED, short int revents UNUSED, void *vconn_) +{ + struct vconn *vconn = vconn_; + struct stream_vconn *s = stream_vconn_cast(vconn); + ssize_t n = write(s->fd, s->txbuf->data, s->txbuf->size); + if (n < 0) { + if (errno != EAGAIN) { + VLOG_ERR("send: %s", strerror(errno)); + stream_clear_txbuf(s); + return; + } + } else if (n > 0) { + buffer_pull(s->txbuf, n); + if (!s->txbuf->size) { + stream_clear_txbuf(s); + return; + } + } + s->tx_waiter = poll_fd_callback(s->fd, POLLOUT, stream_do_tx, vconn); +} + +static int +stream_send(struct vconn *vconn, struct buffer *buffer) +{ + struct stream_vconn *s = stream_vconn_cast(vconn); + ssize_t retval; + + if (s->txbuf) { + return EAGAIN; + } + + retval = write(s->fd, buffer->data, buffer->size); + if (retval == buffer->size) { + buffer_delete(buffer); + return 0; + } else if (retval >= 0 || errno == EAGAIN) { + s->txbuf = buffer; + if (retval > 0) { + buffer_pull(buffer, retval); + } + s->tx_waiter = poll_fd_callback(s->fd, POLLOUT, stream_do_tx, vconn); + return 0; + } else { + return errno; + } +} + +static void +stream_wait(struct vconn *vconn, enum vconn_wait_type wait) +{ + struct stream_vconn *s = stream_vconn_cast(vconn); + switch (wait) { + case WAIT_CONNECT: + poll_fd_wait(s->fd, POLLOUT); + break; + + case WAIT_SEND: + if (!s->txbuf) { + poll_fd_wait(s->fd, POLLOUT); + } else { + /* Nothing to do: need to drain txbuf first. */ + } + break; + + case WAIT_RECV: + poll_fd_wait(s->fd, POLLIN); + break; + + default: + NOT_REACHED(); + } +} + +static struct vconn_class stream_vconn_class = { + .name = "stream", + .close = stream_close, + .connect = stream_connect, + .recv = stream_recv, + .send = stream_send, + .wait = stream_wait, +}; + +/* Passive stream socket vconn. */ + +struct pstream_vconn +{ + struct vconn vconn; + int fd; + int (*accept_cb)(int fd, const struct sockaddr *, size_t sa_len, + struct vconn **); +}; + +static struct vconn_class pstream_vconn_class; + +static struct pstream_vconn * +pstream_vconn_cast(struct vconn *vconn) +{ + assert(vconn->class == &pstream_vconn_class); + return CONTAINER_OF(vconn, struct pstream_vconn, vconn); +} + +int +new_pstream_vconn(const char *name, int fd, + int (*accept_cb)(int fd, const struct sockaddr *, + size_t sa_len, struct vconn **), + struct vconn **vconnp) +{ + struct pstream_vconn *ps; + int retval; + + retval = set_nonblocking(fd); + if (retval) { + close(fd); + return retval; + } + + if (listen(fd, 10) < 0) { + int error = errno; + VLOG_ERR("%s: listen: %s", name, strerror(error)); + close(fd); + return error; + } + + ps = xmalloc(sizeof *ps); + ps->vconn.class = &pstream_vconn_class; + ps->vconn.connect_status = 0; + ps->fd = fd; + ps->accept_cb = accept_cb; + *vconnp = &ps->vconn; + return 0; +} + +static void +pstream_close(struct vconn *vconn) +{ + struct pstream_vconn *ps = pstream_vconn_cast(vconn); + close(ps->fd); + free(ps); +} + +static int +pstream_accept(struct vconn *vconn, struct vconn **new_vconnp) +{ + struct pstream_vconn *ps = pstream_vconn_cast(vconn); + struct sockaddr_storage ss; + socklen_t ss_len = sizeof ss; + int new_fd; + int retval; + + new_fd = accept(ps->fd, (struct sockaddr *) &ss, &ss_len); + if (new_fd < 0) { + int retval = errno; + if (retval != EAGAIN) { + VLOG_DBG("accept: %s", strerror(retval)); + } + return retval; + } + + retval = set_nonblocking(new_fd); + if (retval) { + close(new_fd); + return retval; + } + + return ps->accept_cb(new_fd, (const struct sockaddr *) &ss, ss_len, + new_vconnp); +} + +static void +pstream_wait(struct vconn *vconn, enum vconn_wait_type wait) +{ + struct pstream_vconn *ps = pstream_vconn_cast(vconn); + assert(wait == WAIT_ACCEPT); + poll_fd_wait(ps->fd, POLLIN); +} + +static struct vconn_class pstream_vconn_class = { + .name = "pstream", + .close = pstream_close, + .accept = pstream_accept, + .wait = pstream_wait +}; diff --git a/lib/vconn-tcp.c b/lib/vconn-tcp.c index ea70260..be4cd95 100644 --- a/lib/vconn-tcp.c +++ b/lib/vconn-tcp.c @@ -31,43 +31,31 @@ * derivatives without specific, written prior permission. */ +#include #include "vconn.h" -#include #include -#include -#include +#include #include #include #include #include #include #include -#include "buffer.h" +#include "packets.h" #include "socket-util.h" #include "util.h" #include "openflow.h" -#include "ofp-print.h" -#include "poll-loop.h" +#include "vconn-stream.h" #include "vlog.h" #define THIS_MODULE VLM_vconn_tcp /* Active TCP. */ -struct tcp_vconn -{ - struct vconn vconn; - int fd; - struct buffer *rxbuf; - struct buffer *txbuf; - struct poll_waiter *tx_waiter; -}; - static int new_tcp_vconn(const char *name, int fd, int connect_status, - struct vconn **vconnp) + const struct sockaddr_in *sin, struct vconn **vconnp) { - struct tcp_vconn *tcp; int on = 1; int retval; @@ -78,25 +66,10 @@ new_tcp_vconn(const char *name, int fd, int connect_status, return errno; } - tcp = xmalloc(sizeof *tcp); - tcp->vconn.class = &tcp_vconn_class; - tcp->vconn.connect_status = connect_status; - tcp->fd = fd; - tcp->txbuf = NULL; - tcp->tx_waiter = NULL; - tcp->rxbuf = NULL; - *vconnp = &tcp->vconn; - return 0; + return new_stream_vconn(name, fd, connect_status, sin->sin_addr.s_addr, + vconnp); } -static struct tcp_vconn * -tcp_vconn_cast(struct vconn *vconn) -{ - assert(vconn->class == &tcp_vconn_class); - return CONTAINER_OF(vconn, struct tcp_vconn, vconn); -} - - static int tcp_open(const char *name, char *suffix, struct vconn **vconnp) { @@ -114,7 +87,8 @@ tcp_open(const char *name, char *suffix, struct vconn **vconnp) host_name = strtok_r(suffix, "::", &save_ptr); port_string = strtok_r(NULL, "::", &save_ptr); if (!host_name) { - fatal(0, "%s: bad peer name format", name); + error(0, "%s: bad peer name format", name); + return EAFNOSUPPORT; } memset(&sin, 0, sizeof sin); @@ -139,7 +113,7 @@ tcp_open(const char *name, char *suffix, struct vconn **vconnp) retval = connect(fd, (struct sockaddr *) &sin, sizeof sin); if (retval < 0) { if (errno == EINPROGRESS) { - return new_tcp_vconn(name, fd, EAGAIN, vconnp); + return new_tcp_vconn(name, fd, EAGAIN, &sin, vconnp); } else { int error = errno; VLOG_ERR("%s: connect: %s", name, strerror(error)); @@ -147,195 +121,24 @@ tcp_open(const char *name, char *suffix, struct vconn **vconnp) return error; } } else { - return new_tcp_vconn(name, fd, 0, vconnp); - } -} - -static void -tcp_close(struct vconn *vconn) -{ - struct tcp_vconn *tcp = tcp_vconn_cast(vconn); - poll_cancel(tcp->tx_waiter); - close(tcp->fd); - free(tcp); -} - -static int -tcp_connect(struct vconn *vconn) -{ - struct tcp_vconn *tcp = tcp_vconn_cast(vconn); - return check_connection_completion(tcp->fd); -} - -static int -tcp_recv(struct vconn *vconn, struct buffer **bufferp) -{ - struct tcp_vconn *tcp = tcp_vconn_cast(vconn); - struct buffer *rx; - size_t want_bytes; - ssize_t retval; - - if (tcp->rxbuf == NULL) { - tcp->rxbuf = buffer_new(1564); - } - rx = tcp->rxbuf; - -again: - if (sizeof(struct ofp_header) > rx->size) { - want_bytes = sizeof(struct ofp_header) - rx->size; - } else { - struct ofp_header *oh = rx->data; - size_t length = ntohs(oh->length); - if (length < sizeof(struct ofp_header)) { - VLOG_ERR("received too-short ofp_header (%zu bytes)", length); - return EPROTO; - } - want_bytes = length - rx->size; - if (!want_bytes) { - *bufferp = rx; - tcp->rxbuf = NULL; - return 0; - } - } - buffer_reserve_tailroom(rx, want_bytes); - - retval = read(tcp->fd, buffer_tail(rx), want_bytes); - if (retval > 0) { - rx->size += retval; - if (retval == want_bytes) { - if (rx->size > sizeof(struct ofp_header)) { - *bufferp = rx; - tcp->rxbuf = NULL; - return 0; - } else { - goto again; - } - } - return EAGAIN; - } else if (retval == 0) { - if (rx->size) { - VLOG_ERR("connection dropped mid-packet"); - return EPROTO; - } else { - return EOF; - } - } else { - return retval ? errno : EAGAIN; - } -} - -static void -tcp_clear_txbuf(struct tcp_vconn *tcp) -{ - buffer_delete(tcp->txbuf); - tcp->txbuf = NULL; - tcp->tx_waiter = NULL; -} - -static void -tcp_do_tx(int fd UNUSED, short int revents UNUSED, void *vconn_) -{ - struct vconn *vconn = vconn_; - struct tcp_vconn *tcp = tcp_vconn_cast(vconn); - ssize_t n = write(tcp->fd, tcp->txbuf->data, tcp->txbuf->size); - if (n < 0) { - if (errno != EAGAIN) { - VLOG_ERR("send: %s", strerror(errno)); - tcp_clear_txbuf(tcp); - return; - } - } else if (n > 0) { - buffer_pull(tcp->txbuf, n); - if (!tcp->txbuf->size) { - tcp_clear_txbuf(tcp); - return; - } - } - tcp->tx_waiter = poll_fd_callback(tcp->fd, POLLOUT, tcp_do_tx, vconn); -} - -static int -tcp_send(struct vconn *vconn, struct buffer *buffer) -{ - struct tcp_vconn *tcp = tcp_vconn_cast(vconn); - ssize_t retval; - - if (tcp->txbuf) { - return EAGAIN; - } - - retval = write(tcp->fd, buffer->data, buffer->size); - if (retval == buffer->size) { - buffer_delete(buffer); - return 0; - } else if (retval >= 0 || errno == EAGAIN) { - tcp->txbuf = buffer; - if (retval > 0) { - buffer_pull(buffer, retval); - } - tcp->tx_waiter = poll_fd_callback(tcp->fd, POLLOUT, tcp_do_tx, vconn); - return 0; - } else { - return errno; - } -} - -static void -tcp_wait(struct vconn *vconn, enum vconn_wait_type wait) -{ - struct tcp_vconn *tcp = tcp_vconn_cast(vconn); - switch (wait) { - case WAIT_CONNECT: - poll_fd_wait(tcp->fd, POLLOUT); - break; - - case WAIT_SEND: - if (!tcp->txbuf) { - poll_fd_wait(tcp->fd, POLLOUT); - } else { - /* Nothing to do: need to drain txbuf first. */ - } - break; - - case WAIT_RECV: - poll_fd_wait(tcp->fd, POLLIN); - break; - - default: - NOT_REACHED(); + return new_tcp_vconn(name, fd, 0, &sin, vconnp); } } struct vconn_class tcp_vconn_class = { .name = "tcp", .open = tcp_open, - .close = tcp_close, - .connect = tcp_connect, - .recv = tcp_recv, - .send = tcp_send, - .wait = tcp_wait, }; /* Passive TCP. */ -struct ptcp_vconn -{ - struct vconn vconn; - int fd; -}; - -static struct ptcp_vconn * -ptcp_vconn_cast(struct vconn *vconn) -{ - assert(vconn->class == &ptcp_vconn_class); - return CONTAINER_OF(vconn, struct ptcp_vconn, vconn); -} +static int ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct vconn **vconnp); static int ptcp_open(const char *name, char *suffix, struct vconn **vconnp) { struct sockaddr_in sin; - struct ptcp_vconn *ptcp; int retval; int fd; unsigned int yes = 1; @@ -346,12 +149,11 @@ ptcp_open(const char *name, char *suffix, struct vconn **vconnp) return errno; } - if ( setsockopt(fd, SOL_SOCKET, SO_REUSEADDR,&yes,sizeof(yes)) < 0) { - VLOG_ERR("%s: setsockopt::SO_REUSEADDR: %s", name, strerror(errno)); + if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) < 0) { + VLOG_ERR("%s: setsockopt(SO_REUSEADDR): %s", name, strerror(errno)); return errno; } - memset(&sin, 0, sizeof sin); sin.sin_family = AF_INET; sin.sin_addr.s_addr = htonl(INADDR_ANY); @@ -364,74 +166,29 @@ ptcp_open(const char *name, char *suffix, struct vconn **vconnp) return error; } - retval = listen(fd, 10); - if (retval < 0) { - int error = errno; - VLOG_ERR("%s: listen: %s", name, strerror(error)); - close(fd); - return error; - } - - retval = set_nonblocking(fd); - if (retval) { - close(fd); - return retval; - } - - ptcp = xmalloc(sizeof *ptcp); - ptcp->vconn.class = &ptcp_vconn_class; - ptcp->vconn.connect_status = 0; - ptcp->fd = fd; - *vconnp = &ptcp->vconn; - return 0; -} - -static void -ptcp_close(struct vconn *vconn) -{ - struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn); - close(ptcp->fd); - free(ptcp); + return new_pstream_vconn("ptcp", fd, ptcp_accept, vconnp); } static int -ptcp_accept(struct vconn *vconn, struct vconn **new_vconnp) +ptcp_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct vconn **vconnp) { - struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn); - int new_fd; - int error; + const struct sockaddr_in *sin = (const struct sockaddr_in *) sa; + char name[128]; - new_fd = accept(ptcp->fd, NULL, NULL); - if (new_fd < 0) { - int error = errno; - if (error != EAGAIN) { - VLOG_DBG("accept: %s", strerror(error)); + if (sa_len == sizeof(struct sockaddr_in) && sin->sin_family == AF_INET) { + sprintf(name, "tcp:"IP_FMT, IP_ARGS(&sin->sin_addr)); + if (sin->sin_port != htons(OFP_TCP_PORT)) { + sprintf(strchr(name, '\0'), ":%"PRIu16, ntohs(sin->sin_port)); } - return error; + } else { + strcpy(name, "tcp"); } - - error = set_nonblocking(new_fd); - if (error) { - close(new_fd); - return error; - } - - return new_tcp_vconn("tcp" /* FIXME */, new_fd, 0, new_vconnp); -} - -static void -ptcp_wait(struct vconn *vconn, enum vconn_wait_type wait) -{ - struct ptcp_vconn *ptcp = ptcp_vconn_cast(vconn); - assert(wait == WAIT_ACCEPT); - poll_fd_wait(ptcp->fd, POLLIN); + return new_tcp_vconn(name, fd, 0, sin, vconnp); } struct vconn_class ptcp_vconn_class = { .name = "ptcp", .open = ptcp_open, - .close = ptcp_close, - .accept = ptcp_accept, - .wait = ptcp_wait }; diff --git a/lib/vconn-unix.c b/lib/vconn-unix.c new file mode 100644 index 0000000..f8b6cbc --- /dev/null +++ b/lib/vconn-unix.c @@ -0,0 +1,128 @@ +/* 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 +#include "vconn.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "buffer.h" +#include "socket-util.h" +#include "util.h" +#include "openflow.h" +#include "ofp-print.h" +#include "packets.h" +#include "poll-loop.h" +#include "vconn-stream.h" + +#include "vlog.h" +#define THIS_MODULE VLM_vconn_unix + +/* Active UNIX socket. */ + +/* Number of unix sockets created so far, to ensure binding path uniqueness. */ +static int n_unix_sockets; + +static int +unix_open(const char *name, char *suffix, struct vconn **vconnp) +{ + const char *connect_path = suffix; + char bind_path[128]; + int fd; + + sprintf(bind_path, "/tmp/vconn-unix.%ld.%d", + (long int) getpid(), n_unix_sockets++); + fd = make_unix_socket(SOCK_STREAM, true, false, bind_path, connect_path); + if (fd < 0) { + VLOG_ERR("%s: connection to %s failed: %s", + bind_path, connect_path, strerror(-fd)); + return -fd; + } + + return new_stream_vconn(name, fd, check_connection_completion(fd), + 0, vconnp); +} + +struct vconn_class unix_vconn_class = { + .name = "unix", + .open = unix_open, +}; + +/* Passive UNIX socket. */ + +static int punix_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct vconn **vconnp); + +static int +punix_open(const char *name, char *suffix, struct vconn **vconnp) +{ + int fd; + + fd = make_unix_socket(SOCK_STREAM, true, true, suffix, NULL); + if (fd < 0) { + VLOG_ERR("%s: binding failed: %s", suffix, strerror(errno)); + return errno; + } + + return new_pstream_vconn("punix", fd, punix_accept, vconnp); +} + +static int +punix_accept(int fd, const struct sockaddr *sa, size_t sa_len, + struct vconn **vconnp) +{ + const struct sockaddr_un *sun = (const struct sockaddr_un *) sa; + char name[128]; + + if (sa_len >= offsetof(struct sockaddr_un, sun_path)) { + snprintf(name, sizeof name, "unix:%.*s", + (int) (sa_len - offsetof(struct sockaddr_un, sun_path)), + sun->sun_path); + } else { + strcpy(name, "unix"); + } + return new_stream_vconn(name, fd, 0, 0, vconnp); +} + +struct vconn_class punix_vconn_class = { + .name = "punix", + .open = punix_open, +}; + diff --git a/lib/vconn.c b/lib/vconn.c index 4400584..7af59f3 100644 --- a/lib/vconn.c +++ b/lib/vconn.c @@ -31,9 +31,11 @@ * derivatives without specific, written prior permission. */ +#include #include "vconn.h" #include #include +#include #include #include #include @@ -43,6 +45,7 @@ #include "ofp-print.h" #include "openflow.h" #include "poll-loop.h" +#include "random.h" #include "util.h" #define THIS_MODULE VLM_vconn @@ -58,6 +61,8 @@ static struct vconn_class *vconn_classes[] = { &ssl_vconn_class, &pssl_vconn_class, #endif + &unix_vconn_class, + &punix_vconn_class, }; /* Check the validity of the vconn class structures. */ @@ -71,11 +76,16 @@ check_vconn_classes(void) struct vconn_class *class = vconn_classes[i]; assert(class->name != NULL); assert(class->open != NULL); - assert(class->close != NULL); - assert(class->accept - ? !class->recv && !class->send - : class->recv && class->send); - assert(class->wait != NULL); + if (class->close || class->accept || class->recv || class->send + || class->wait) { + assert(class->close != NULL); + assert(class->accept + ? !class->recv && !class->send + : class->recv && class->send); + assert(class->wait != NULL); + } else { + /* This class delegates to another one. */ + } } #endif } @@ -102,6 +112,7 @@ vconn_usage(bool active, bool passive) printf(" ssl:HOST[:PORT] " "SSL PORT (default: %d) on remote HOST\n", OFP_SSL_PORT); #endif + printf(" unix:FILE Unix domain socket named FILE\n"); } if (passive) { @@ -114,6 +125,8 @@ vconn_usage(bool active, bool passive) "listen for SSL on PORT (default: %d)\n", OFP_SSL_PORT); #endif + printf(" punix:FILE " + "listen on Unix domain socket FILE\n"); } #ifdef HAVE_OPENSSL @@ -141,7 +154,8 @@ vconn_open(const char *name, struct vconn **vconnp) prefix_len = strcspn(name, ":"); if (prefix_len == strlen(name)) { - fatal(0, "`%s' not correct format for peer name", name); + error(0, "`%s' not correct format for peer name", name); + return EAFNOSUPPORT; } for (i = 0; i < ARRAY_SIZE(vconn_classes); i++) { struct vconn_class *class = vconn_classes[i]; @@ -159,8 +173,8 @@ vconn_open(const char *name, struct vconn **vconnp) return retval; } } - fatal(0, "unknown peer type `%.*s'", (int) prefix_len, name); - abort(); + error(0, "unknown peer type `%.*s'", (int) prefix_len, name); + return EAFNOSUPPORT; } int @@ -204,6 +218,14 @@ vconn_is_passive(const struct vconn *vconn) return vconn->class->accept != NULL; } +/* Returns the IP address of the peer, or 0 if the peer is not connected over + * an IP-based protocol or if its IP address is not yet known. */ +uint32_t +vconn_get_ip(const struct vconn *vconn) +{ + return vconn->ip; +} + /* Tries to complete the connection on 'vconn', which must be an active * vconn. If 'vconn''s connection is complete, returns 0 if the connection * was successful or a positive errno value if it failed. If the @@ -254,10 +276,24 @@ vconn_recv(struct vconn *vconn, struct buffer **msgp) int retval = vconn_connect(vconn); if (!retval) { retval = (vconn->class->recv)(vconn, msgp); - if (VLOG_IS_DBG_ENABLED() && !retval) { - char *s = ofp_to_string((*msgp)->data, (*msgp)->size, 1); - VLOG_DBG("received: %s", s); - free(s); + if (!retval) { + struct ofp_header *oh; + + if (VLOG_IS_DBG_ENABLED()) { + char *s = ofp_to_string((*msgp)->data, (*msgp)->size, 1); + VLOG_DBG("received: %s", s); + free(s); + } + + oh = buffer_at_assert(*msgp, 0, sizeof *oh); + if (oh->version != OFP_VERSION) { + VLOG_ERR("received OpenFlow version %02"PRIx8" " + "!= expected %02x", + oh->version, OFP_VERSION); + buffer_delete(*msgp); + *msgp = NULL; + return EPROTO; + } } } if (retval) { @@ -282,6 +318,8 @@ vconn_send(struct vconn *vconn, struct buffer *msg) { int retval = vconn_connect(vconn); if (!retval) { + assert(msg->size >= sizeof(struct ofp_header)); + assert(((struct ofp_header *) msg->data)->length == htons(msg->size)); if (!VLOG_IS_DBG_ENABLED()) { retval = (vconn->class->send)(vconn, msg); } else { @@ -322,6 +360,45 @@ vconn_recv_block(struct vconn *vconn, struct buffer **msgp) return retval; } +/* Sends 'request' to 'vconn' and blocks until it receives a reply with a + * matching transaction ID. Returns 0 if successful, in which case the reply + * is stored in '*replyp' for the caller to examine and free. Otherwise + * returns a positive errno value, or EOF, and sets '*replyp' to null. + * + * 'request' is always destroyed, regardless of the return value. */ +int +vconn_transact(struct vconn *vconn, struct buffer *request, + struct buffer **replyp) +{ + uint32_t send_xid = ((struct ofp_header *) request->data)->xid; + int error; + + *replyp = NULL; + error = vconn_send_block(vconn, request); + if (error) { + buffer_delete(request); + return error; + } + for (;;) { + uint32_t recv_xid; + struct buffer *reply; + + error = vconn_recv_block(vconn, &reply); + if (error) { + return error; + } + recv_xid = ((struct ofp_header *) reply->data)->xid; + if (send_xid == recv_xid) { + *replyp = reply; + return 0; + } + + VLOG_DBG("received reply with xid %08"PRIx32" != expected %08"PRIx32, + recv_xid, send_xid); + buffer_delete(reply); + } +} + void vconn_wait(struct vconn *vconn, enum vconn_wait_type wait) { @@ -368,9 +445,51 @@ vconn_send_wait(struct vconn *vconn) vconn_wait(vconn, WAIT_SEND); } +/* Allocates and returns the first byte of a buffer 'openflow_len' bytes long, + * containing an OpenFlow header with the given 'type' and a random transaction + * id. Stores the new buffer in '*bufferp'. The caller must free the buffer + * when it is no longer needed. */ +void * +make_openflow(size_t openflow_len, uint8_t type, struct buffer **bufferp) +{ + return make_openflow_xid(openflow_len, type, random_uint32(), bufferp); +} + +/* Allocates and returns the first byte of a buffer 'openflow_len' bytes long, + * containing an OpenFlow header with the given 'type' and transaction id + * 'xid'. Stores the new buffer in '*bufferp'. The caller must free the + * buffer when it is no longer needed. */ +void * +make_openflow_xid(size_t openflow_len, uint8_t type, uint32_t xid, + struct buffer **bufferp) +{ + struct buffer *buffer; + struct ofp_header *oh; + + assert(openflow_len >= sizeof *oh); + assert(openflow_len <= UINT16_MAX); + buffer = *bufferp = buffer_new(openflow_len); + oh = buffer_put_uninit(buffer, openflow_len); + memset(oh, 0, openflow_len); + oh->version = OFP_VERSION; + oh->type = type; + oh->length = htons(openflow_len); + oh->xid = xid; + return oh; +} + +/* Updates the 'length' field of the OpenFlow message in 'buffer' to + * 'buffer->size'. */ +void +update_openflow_length(struct buffer *buffer) +{ + struct ofp_header *oh = buffer_at_assert(buffer, 0, sizeof *oh); + oh->length = htons(buffer->size); +} + struct buffer * make_add_simple_flow(const struct flow *flow, - uint32_t buffer_id, uint16_t out_port) + uint32_t buffer_id, uint16_t out_port, uint16_t max_idle) { struct ofp_flow_mod *ofm; size_t size = sizeof *ofm + sizeof ofm->actions[0]; @@ -392,7 +511,7 @@ make_add_simple_flow(const struct flow *flow, ofm->match.tp_src = flow->tp_src; ofm->match.tp_dst = flow->tp_dst; ofm->command = htons(OFPFC_ADD); - ofm->max_idle = htons(60); + ofm->max_idle = htons(max_idle); ofm->buffer_id = htonl(buffer_id); ofm->actions[0].type = htons(OFPAT_OUTPUT); ofm->actions[0].arg.output.max_len = htons(0); @@ -440,3 +559,28 @@ make_buffered_packet_out(uint32_t buffer_id, return out; } +/* Creates and returns an OFPT_ECHO_REQUEST message with an empty payload. */ +struct buffer * +make_echo_request(void) +{ + struct ofp_header *rq; + struct buffer *out = buffer_new(sizeof *rq); + rq = buffer_put_uninit(out, sizeof *rq); + rq->version = OFP_VERSION; + rq->type = OFPT_ECHO_REQUEST; + rq->length = htons(sizeof *rq); + rq->xid = 0; + return out; +} + +/* Creates and returns an OFPT_ECHO_REPLY message matching the + * OFPT_ECHO_REQUEST message in 'rq'. */ +struct buffer * +make_echo_reply(const struct ofp_header *rq) +{ + size_t size = ntohs(rq->length); + struct buffer *out = buffer_new(size); + struct ofp_header *reply = buffer_put(out, rq, size); + reply->type = OFPT_ECHO_REPLY; + return out; +} diff --git a/lib/vlog-socket.c b/lib/vlog-socket.c index 05b9186..a9f8a52 100644 --- a/lib/vlog-socket.c +++ b/lib/vlog-socket.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "vlog-socket.h" #include #include @@ -44,15 +45,13 @@ #include #include "fatal-signal.h" #include "poll-loop.h" +#include "socket-util.h" #include "util.h" #include "vlog.h" #ifndef SCM_CREDENTIALS #include #endif - -static int make_unix_socket(bool nonblock, bool passcred, - const char *bind_path, const char *connect_path); /* Server for Vlog control connection. */ struct vlog_server { @@ -89,7 +88,7 @@ vlog_server_listen(const char *path, struct vlog_server **serverp) (long int) getpid(), path ? path : ""); } - server->fd = make_unix_socket(true, true, server->path, NULL); + server->fd = make_unix_socket(SOCK_DGRAM, true, true, server->path, NULL); if (server->fd < 0) { int fd = server->fd; free(server->path); @@ -294,7 +293,7 @@ vlog_client_connect(const char *path, struct vlog_client **clientp) : xasprintf("/tmp/vlogs.%s", path)); client->bind_path = xasprintf("/tmp/vlog.%ld", (long int) getpid()); - fd = make_unix_socket(false, false, + fd = make_unix_socket(SOCK_DGRAM, false, false, client->bind_path, client->connect_path); if (fd >= 0) { @@ -434,89 +433,3 @@ vlog_client_target(const struct vlog_client *client) return client->connect_path; } -/* Helper functions. */ - -/* Stores in '*un' a sockaddr_un that refers to file 'name'. Stores in - * '*un_len' the size of the sockaddr_un. */ -static void -make_sockaddr_un(const char *name, struct sockaddr_un* un, socklen_t *un_len) -{ - un->sun_family = AF_UNIX; - strncpy(un->sun_path, name, sizeof un->sun_path); - un->sun_path[sizeof un->sun_path - 1] = '\0'; - *un_len = (offsetof(struct sockaddr_un, sun_path) - + strlen (un->sun_path) + 1); -} - -/* Creates a Unix domain datagram socket that is bound to '*bind_path' (if - * 'bind_path' is non-null) and connected to '*connect_path' (if 'connect_path' - * is non-null). If 'nonblock' is true, the socket is made non-blocking. If - * 'passcred' is true, the socket is configured to receive SCM_CREDENTIALS - * control messages. - * - * Returns the socket's fd if successful, otherwise a negative errno value. */ -static int -make_unix_socket(bool nonblock, bool passcred UNUSED, - const char *bind_path, const char *connect_path) -{ - int error; - int fd; - - fd = socket(PF_UNIX, SOCK_DGRAM, 0); - if (fd < 0) { - return -errno; - } - - if (nonblock) { - int flags = fcntl(fd, F_GETFL, 0); - if (flags == -1) { - goto error; - } - if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) { - goto error; - } - } - - if (bind_path) { - struct sockaddr_un un; - socklen_t un_len; - make_sockaddr_un(bind_path, &un, &un_len); - if (unlink(un.sun_path) && errno != ENOENT) { - fprintf(stderr, "unlinking \"%s\": %s\n", - un.sun_path, strerror(errno)); - } - fatal_signal_add_file_to_unlink(bind_path); - if (bind(fd, (struct sockaddr*) &un, un_len) - || fchmod(fd, S_IRWXU)) { - goto error; - } - } - - if (connect_path) { - struct sockaddr_un un; - socklen_t un_len; - make_sockaddr_un(connect_path, &un, &un_len); - if (connect(fd, (struct sockaddr*) &un, un_len)) { - goto error; - } - } - -#ifdef SCM_CREDENTIALS - if (passcred) { - int enable = 1; - if (setsockopt(fd, SOL_SOCKET, SO_PASSCRED, &enable, sizeof(enable))) { - goto error; - } - } -#endif - - return fd; - -error: - if (bind_path) { - fatal_signal_remove_file_to_unlink(bind_path); - } - error = errno; - close(fd); - return -error; -} diff --git a/lib/vlog.c b/lib/vlog.c index 6316272..6e098ef 100644 --- a/lib/vlog.c +++ b/lib/vlog.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "vlog.h" #include #include @@ -40,9 +41,12 @@ #include #include #include +#include #include "dynamic-string.h" #include "util.h" +#define THIS_MODULE VLM_vlog + /* Name for each logging level. */ static const char *level_names[VLL_N_LEVELS] = { [VLL_EMER] = "EMER", @@ -171,7 +175,7 @@ vlog_set_levels(enum vlog_module module, enum vlog_facility facility, /* Set debugging levels: * - * mod:facility:level mod2:facility:level ... + * mod[:facility[:level]] mod2[:facility[:level]] ... * * Return null if successful, otherwise an error message that the caller must * free(). @@ -191,10 +195,6 @@ vlog_set_levels_from_string(const char *s_) facility = strtok_r(NULL, ":", &save_ptr); level = strtok_r(NULL, ":", &save_ptr); - if (level == NULL || facility == NULL) { - free(s); - return xstrdup("syntax error in level string"); - } if (!strcmp(module, "ANY")) { e_module = VLM_ANY_MODULE; @@ -207,7 +207,7 @@ vlog_set_levels_from_string(const char *s_) } } - if (!strcmp(facility, "ANY")) { + if (!facility || !strcmp(facility, "ANY")) { e_facility = VLF_ANY_FACILITY; } else { e_facility = vlog_get_facility_val(facility); @@ -218,7 +218,7 @@ vlog_set_levels_from_string(const char *s_) } } - e_level = vlog_get_level_val(level); + e_level = level ? vlog_get_level_val(level) : VLL_DBG; if (e_level >= VLL_N_LEVELS) { char *msg = xasprintf("unknown level \"%s\"", level); free(s); @@ -232,13 +232,17 @@ vlog_set_levels_from_string(const char *s_) } /* If 'arg' is null, configure maximum verbosity. Otherwise, sets - * configuration according to 'arg' (see vlog_set_levels_from_string()). If - * parsing fails, default to maximum verbosity. */ + * configuration according to 'arg' (see vlog_set_levels_from_string()). */ void vlog_set_verbosity(const char *arg) { - if (arg == NULL || !vlog_set_levels_from_string(arg)) { - vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_DBG); + if (arg) { + char *msg = vlog_set_levels_from_string(arg); + if (msg) { + fatal(0, "processing \"%s\": %s", arg, msg); + } + } else { + vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_DBG); } } @@ -246,8 +250,20 @@ vlog_set_verbosity(const char *arg) void vlog_init(void) { + time_t now; + openlog(program_name, LOG_NDELAY, LOG_DAEMON); - vlog_set_levels(VLM_ANY_MODULE, VLF_CONSOLE, VLL_WARN); + vlog_set_levels(VLM_ANY_MODULE, VLF_ANY_FACILITY, VLL_WARN); + + now = time(0); + if (now < 0) { + struct tm tm; + char s[128]; + + localtime_r(&now, &tm); + strftime(s, sizeof s, "%a, %d %b %Y %H:%M:%S %z", &tm); + VLOG_ERR("current time is negative: %s (%ld)", s, (long int) now); + } } /* Closes the logging subsystem. */ @@ -301,11 +317,18 @@ vlog(enum vlog_module module, enum vlog_level level, const char *message, ...) static int msg_num; const char *module_name = vlog_get_module_name(module); const char *level_name = vlog_get_level_name(level); + time_t now; + struct tm tm; va_list args; char s[1024]; - size_t len; + size_t len, time_len; - len = sprintf(s, "%05d|%s|%s:", ++msg_num, module_name, level_name); + now = time(0); + localtime_r(&now, &tm); + + len = time_len = strftime(s, sizeof s, "%b %d %H:%M:%S|", &tm); + len += sprintf(s + len, "%05d|%s|%s:", + ++msg_num, module_name, level_name); va_start(args, message); len += vsnprintf(s + len, sizeof s - len, message, args); va_end(args); @@ -322,13 +345,18 @@ vlog(enum vlog_module module, enum vlog_level level, const char *message, ...) if (log_syslog) { static const int syslog_levels[VLL_N_LEVELS] = { - [VLL_EMER] = LOG_EMERG, + [VLL_EMER] = LOG_ALERT, [VLL_ERR] = LOG_ERR, [VLL_WARN] = LOG_WARNING, [VLL_DBG] = LOG_DEBUG, }; + char *save_ptr = NULL; + char *line; - syslog(syslog_levels[level], "%s", s); + for (line = strtok_r(s + time_len, "\n", &save_ptr); line != NULL; + line = strtok_r(NULL, "\n", &save_ptr)) { + syslog(syslog_levels[level], "%s", line); + } } errno = save_errno; } diff --git a/secchan/Makefile.am b/secchan/Makefile.am index c575b47..bedb286 100644 --- a/secchan/Makefile.am +++ b/secchan/Makefile.am @@ -9,7 +9,7 @@ man_MANS = endif secchan_SOURCES = secchan.c -secchan_LDADD = ../lib/libopenflow.la -ldl +secchan_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS) EXTRA_DIST = secchan.8.in DISTCLEANFILES = secchan.8 diff --git a/secchan/secchan.8.in b/secchan/secchan.8.in index 1b9d1ac..12f390b 100644 --- a/secchan/secchan.8.in +++ b/secchan/secchan.8.in @@ -5,7 +5,7 @@ secchan \- secure channel connecting an OpenFlow datapath to a controller .SH SYNOPSIS .B secchan -[\fIoptions\fR] \fBnl:\fIdp_idx\fR \fIremote\fR +[\fIoptions\fR] \fBnl:\fIdp_idx\fR [\fIremote\fR] .SH DESCRIPTION The \fBsecchan\fR program sets up a secure channel between a local @@ -18,7 +18,7 @@ to relay. Within this argument, \fIdp_idx\fR is the number of a datapath that has been created with .BR dpctl (8). -The mandatory \fIcontroller\fR argument specifies how to connect to +The optional \fIcontroller\fR argument specifies how to connect to the OpenFlow controller. It takes one of the following forms: .TP @@ -32,6 +32,13 @@ The specified SSL \fIport\fR (default: 976) on the given remote The specified TCP \fIport\fR (default: 975) on the given remote \fIhost\fR. +.TP +\fBunix:\fIfile\fR +The Unix domain server socket named \fIfile\fR. + +If \fIcontroller\fR is omitted, \fBsecchan\fR attempts to discover the +location of the controller automatically (see below). + .SH "CONTACTING THE CONTROLLER" The OpenFlow switch must be able to contact the OpenFlow controller over the network. It can do so in one of two ways: @@ -56,7 +63,81 @@ addif\fR. This configuration is often more convenient than out-of-band control, because it is not necessary to maintain two independent networks. -Using \fBsecchan\fR in a network with in-band control requires +With in-band control, the location of the controller can be configured +manually or discovered automatically: + +.RS +.IP "controller discovery" +To make \fBsecchan\fR discover the location of the controller +automatically, do not specify the location of the controller on the +\fBsecchan\fR command line. + +In this mode, \fBsecchan\fR will broadcast a DHCP request with vendor +class identifier \fBOpenFlow\fR across the network devices added to +the datapath with \fBdpctl addif\fR. It will accept any valid DHCP +reply that has the same vendor class identifier and includes a +vendor-specific option with code 1 whose contents are a string +specifying the location of the controller in the same format used on +the \fBsecchan\fR command line (e.g. \fBssl:192.168.0.1\fR). + +The DHCP reply may also, optionally, include a vendor-specific option +with code 2 whose contents are a string specifying the URI to the base +of the OpenFlow PKI (e.g. \fBhttp://192.168.0.1/openflow/pki\fR). +This URI is used only for bootstrapping the OpenFlow PKI at initial +switch setup; \fBsecchan\fR does not use it at all. + +The following ISC DHCP server configuration file assigns the IP +address range 192.168.0.20 through 192.168.0.30 to OpenFlow switches +that follow the switch protocol and addresses 192.168.0.1 through +192.168.0.10 to all other DHCP clients: + +default-lease-time 600; +.br +max-lease-time 7200; +.br +option space openflow; +.br +option openflow.controller-vconn code 1 = text; +.br +option openflow.pki-uri code 2 = text; +.br +class "OpenFlow" { +.br + match if option vendor-class-identifier = "OpenFlow"; +.br + vendor-option-space openflow; +.br + option openflow.controller-vconn "tcp:192.168.0.10"; +.br + option openflow.pki-uri "http://192.168.0.10/openflow/pki"; +.br + option vendor-class-identifier "OpenFlow"; +.br +} +.br +subnet 192.168.0.0 netmask 255.255.255.0 { +.br + pool { +.br + allow members of "OpenFlow"; +.br + range 192.168.0.20 192.168.0.30; +.br + } +.br + pool { +.br + deny members of "OpenFlow"; +.br + range 192.168.0.1 192.168.0.10; +.br + } +.br +} +.br + +.IP "manual configuration" +Configuring in-band control manually requires additional setup before starting \fBsecchan\fR. At a minimum, the special OpenFlow network device \fBof\fIn\fR, where \fIn\fR is same as the \fIdp_idx\fR specified on the \fBsecchan\fR command line, must be @@ -85,8 +166,46 @@ e.g. it obtains its IP address dynamically via DHCP, the DHCP client will not be able to contact the DHCP server until the secure channel has started up. Thus, start \fBsecchan\fR without configuring \fBof\fIn\fR further, and start the DHCP client afterward. +.RE .SH OPTIONS +.TP +\fB--accept-vconn=\fIregex\fR +When \fBsecchan\fR performs controller discovery (see \fBCONTACTING +THE CONTROLLER\fR, above, for more information about controller +discovery), it validates the controller location obtained via DHCP +with a POSIX extended regular expression. Only controllers whose +names match the regular expression will be accepted. + +The default regular expression is \fBssl:.*\fR (meaning that only SSL +controller connections will be accepted) when any of the SSL +configuration options \fB--private-key\fR, \fB--certificate\fR, or +\fB--ca-cert\fR is specified. The default is \fB.*\fR otherwise +(meaning that any controller will be accepted). + +The \fIregex\fR is implicitly anchored at the beginning of the +controller location string, as if it begins with \fB^\fR. + +When controller discovery is not performed, this option has no effect. + +.TP +\fB--no-resolv-conf\fR +When \fBsecchan\fR performs controller discovery (see \fBCONTACTING +THE CONTROLLER\fR, above, for more information about controller +discovery), by default it overwrites the system's +\fB/etc/resolv.conf\fR with domain information and DNS servers +obtained via DHCP. If the location of the controller is specified +using a hostname, rather than an IP address, and the network's DNS +servers ever change, this behavior is essential. But because it also +interferes with any administrator or process that manages +\fB/etc/resolv.conf\fR, when this option is specified, \fBsecchan\fR +will not modify \fB/etc/resolv.conf\fR. + +\fBsecchan\fR will only modify \fBresolv.conf\fR if the DHCP response +that it receives specifies one or more DNS servers. + +When controller discovery is not performed, this option has no effect. + .TP \fB-f\fR, \fB--fail=\fR[\fBopen\fR|\fBclosed\fR] The controller is, ordinarily, responsible for setting up all flows on @@ -97,25 +216,69 @@ switch at all. If this option is set to \fBopen\fR (the default), \fBsecchan\fR will take over responsibility for setting up flows in the local datapath -when the controller connection stays down for long enough. In this -``fail open'' mode, \fBsecchan\fR causes the datapath to act like an -ordinary MAC-learning switch. \fBsecchan\fR will continue to retry -connection to the controller in the background and, when the -connection succeeds, it discontinues its fail-open behavior. +when no message has been received from the controller for three times +the inactivity probe interval (see below), or 45 seconds by default. +In this ``fail open'' mode, \fBsecchan\fR causes the datapath to act +like an ordinary MAC-learning switch. \fBsecchan\fR will continue to +retry connection to the controller in the background and, when the +connection succeeds, it discontinues its fail-open behavior. The +secure channel enters the fail-open mode when If this option is set to \fBclosed\fR, then \fBsecchan\fR will not set up flows on its own when the controller connection fails. .TP -\fB-d\fR, \fB--fail-open-delay=\fIsecs\fR -Sets the number of seconds of failed controller connection attempts -after which the switch enters fail-open mode. The default is 30 -seconds. +\fB--inactivity-probe=\fIsecs\fR +When the secure channel is connected to the controller, the secure +channel waits for a message to be received from the controller for +\fIsecs\fR seconds before it sends a inactivity probe to the +controller. After sending the inactivity probe, if no response is +received for an additional \fIsecs\fR seconds, the secure channel +assumes that the connection has been broken and attempts to reconnect. +The default is 15 seconds, and the minimum value is 5 seconds. -This option has no effect when \fB--fail=closed\fR is specified. +When fail-open mode is configured, changing the inactivity probe +interval also changes the interval before entering fail-open mode (see +above). .TP -\fB-l\Fr, \Fb--listen=\fImethod\fR +\fB--max-idle=\fIsecs\fR|\fBpermanent\fR +Sets \fIsecs\fR as the number of seconds that a flow set up by the +secure channel will remain in the switch's flow table without any +matching packets being seen. If \fBpermanent\fR is specified, which +is not recommended, flows set up by the secure channel will never +expire. The default is 15 seconds. + +Most flows are set up by the OpenFlow controller, not by the secure +channel. This option affects only the following flows, which the +secure channel sets up itself: + +.RS +.IP \(bu +When \fB--fail=open\fR is specified, flows set up when the secure +channel has not been able to contact the controller for the configured +fail-open delay. + +.IP \(bu +When in-band control is in use, flows set up to bootstrap contacting +the controller (see \fBCONTACTING THE CONTROLLER\fR, above, for +more information about in-band control). +.RE + +.IP +As a result, when both \fB--fail=open\fR and in-band control are not +in use, this option has no effect. + +.TP +\fB--max-backoff=\fIsecs\fR +Sets the maximum time between attempts to connect to the controller to +\fIsecs\fR, which must be at least 1. The actual interval between +connection attempts starts at 1 second and doubles on each failing +attempt until it reaches the maximum. The default maximum backoff +time is 15 seconds. + +.TP +\fB-l\fR, \fB--listen=\fImethod\fR Configures the switch to additionally listen for incoming OpenFlow connections for switch management with \fBdpctl\fR. The \fImethod\fR must be given as one of the following passive OpenFlow connection @@ -133,6 +296,10 @@ are mandatory when this form is used. Listens for TCP connections on \fIport\fR (default: 975). .RE +.TP +\fBpunix:\fIfile\fR +Listens for connections on Unix domain server socket named \fIfile\fR. + .TP \fB-p\fR, \fB--private-key=\fIprivkey.pem\fR Specifies a PEM file containing the private key used as the switch's @@ -166,17 +333,18 @@ run as a background process. Prints a brief help message to the console. .TP -\fB-v\fR \fImodule\fB:\fIfacility\fB:\fIlevel\fR, \fB--verbose=\fImodule\fB:\fIfacility\fB:\fIlevel\fR +\fB-v\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] Sets the logging level for \fImodule\fR in \fIfacility\fR to \fIlevel\fR. The \fImodule\fR may be any valid module name (as displayed by the \fB--list\fR action on \fBvlogconf\fR(8)), or the special name \fBANY\fR to set the logging levels for all modules. The \fIfacility\fR may be \fBsyslog\fR or \fBconsole\fR to set the levels for logging to the system log or to the console, respectively, or -\fBANY\fR to set the logging levels for both facilities. The -\fIlevel\fR must be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or -\fBdbg\fR, designating the minimum severity of a message for it to be -logged. +\fBANY\fR to set the logging levels for both facilities. If it is +omitted, \fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must +be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating +the minimum severity of a message for it to be logged. If it is +omitted, \fIlevel\fR defaults to \fBdbg\fR. .TP \fB-v\fR, \fB--verbose\fR @@ -190,6 +358,7 @@ Prints version information to the console. .SH "SEE ALSO" .BR dpctl (8), +.BR ofp-discover (8), .BR controller (8), .BR ofp-pki (8), .BR vlogconf (8), diff --git a/secchan/secchan.c b/secchan/secchan.c index 66993ba..50ebebb 100644 --- a/secchan/secchan.c +++ b/secchan/secchan.c @@ -1,6 +1,6 @@ - /* Copyright (c) 2008 The Board of Trustees of The Leland Stanford +/* 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 @@ -13,10 +13,10 @@ * 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 @@ -25,17 +25,20 @@ * 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 +#include #include #include #include #include #include +#include #include #include #include @@ -45,6 +48,9 @@ #include "command-line.h" #include "compiler.h" #include "daemon.h" +#include "dhcp.h" +#include "dhcp-client.h" +#include "dynamic-string.h" #include "fault.h" #include "flow.h" #include "learning-switch.h" @@ -63,21 +69,41 @@ #include "vlog.h" #define THIS_MODULE VLM_secchan -#include "ofp-print.h" - -static const char *listen_vconn_name; - -struct half { - struct rconn *rconn; - struct buffer *rxbuf; -}; - /* Behavior when the connection to the controller fails. */ enum fail_mode { FAIL_OPEN, /* Act as learning switch. */ FAIL_CLOSED /* Drop all packets. */ }; +/* Settings that may be configured by the user. */ +struct settings { + /* Overall mode of operation. */ + bool discovery; /* Discover the controller automatically? */ + bool in_band; /* Connect to controller in-band? */ + + /* Related vconns and network devices. */ + const char *nl_name; /* Local datapath (must be "nl:" vconn). */ + char *of_name; /* ofX network device name. */ + const char *controller_name; /* Controller (if not discovery mode). */ + const char *listen_vconn_name; /* Listens for mgmt connections. */ + + /* Failure behavior. */ + enum fail_mode fail_mode; /* Act as learning switch if no controller? */ + int max_idle; /* Idle time for flows in fail-open mode. */ + int probe_interval; /* # seconds idle before sending echo request. */ + int max_backoff; /* Max # seconds between connection attempts. */ + + /* Discovery behavior. */ + regex_t accept_controller_regex; /* Controller vconns to accept. */ + const char *accept_controller_re; /* String version of regex. */ + bool update_resolv_conf; /* Update /etc/resolv.conf? */ +}; + +struct half { + struct rconn *rconn; + struct buffer *rxbuf; +}; + struct relay { struct list node; @@ -86,103 +112,79 @@ struct relay { struct half halves[2]; bool is_mgmt_conn; - struct lswitch *lswitch; }; -static struct list relays = LIST_INITIALIZER(&relays); +struct hook { + bool (*packet_cb)(struct relay *, int half, void *aux); + void (*periodic_cb)(void *aux); + void *aux; +}; -/* Enable the local port? */ -static int local_port; - -/* MAC address of local port. */ -static uint8_t local_mac[ETH_ADDR_LEN]; - -/* MAC learning table for local port. */ -static struct mac_learning *local_ml; - -/* -f, --fail: Behavior when the connection to the controller fails. */ -static enum fail_mode fail_mode = FAIL_OPEN; - -/* -d, --fail-open-delay: Number of seconds after which to fail open, when - * fail_mode is FAIL_OPEN. */ -static int fail_open_delay = 30; - -static void parse_options(int argc, char *argv[]); +static void parse_options(int argc, char *argv[], struct settings *); static void usage(void) NO_RETURN; -static void new_management_connection(const char *nl_name, struct vconn *new_remote); static struct relay *relay_create(struct rconn *local, struct rconn *remote, bool is_mgmt_conn); -static void relay_run(struct relay *); +static struct relay *relay_accept(const struct settings *, struct vconn *); +static void relay_run(struct relay *, const struct hook[], size_t n_hooks); static void relay_wait(struct relay *); static void relay_destroy(struct relay *); -static bool local_hook(struct relay *r); -static bool fail_open_hook(struct relay *r); +static struct hook make_hook(bool (*packet_cb)(struct relay *, int, void *), + void (*periodic_cb)(void *), + void *aux); + +static struct discovery *discovery_init(const struct settings *); +static void discovery_question_connectivity(struct discovery *); +static bool discovery_run(struct discovery *, char **controller_name); +static void discovery_wait(struct discovery *); + +static struct hook in_band_hook_create(const struct settings *); +static struct hook fail_open_hook_create(const struct settings *, + struct rconn *local, + struct rconn *remote); + +static void modify_dhcp_request(struct dhcp_msg *, void *aux); +static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux); int main(int argc, char *argv[]) { + struct settings s; + + struct list relays = LIST_INITIALIZER(&relays); + + struct hook hooks[3]; + size_t n_hooks; + + struct rconn *local_rconn, *remote_rconn; struct vconn *listen_vconn; - struct netdev *of_device; - const char *nl_name; - char of_name[16]; + struct relay *controller_relay; + struct discovery *discovery; int retval; set_program_name(argv[0]); register_fault_handlers(); vlog_init(); - parse_options(argc, argv); + parse_options(argc, argv, &s); - if (argc - optind != 2) { - fatal(0, - "need exactly two non-option arguments; use --help for usage"); - } - nl_name = argv[optind]; - if (strncmp(nl_name, "nl:", 3) - || strlen(nl_name) < 4 - || nl_name[strspn(nl_name + 3, "0123456789") + 3]) { - fatal(0, "%s: argument is not of the form \"nl:DP_ID\"", nl_name); - } - - if (listen_vconn_name) { - retval = vconn_open(listen_vconn_name, &listen_vconn); + /* Start listening for management connections. */ + if (s.listen_vconn_name) { + retval = vconn_open(s.listen_vconn_name, &listen_vconn); if (retval && retval != EAGAIN) { - fatal(retval, "opening %s", listen_vconn_name); + fatal(retval, "opening %s", s.listen_vconn_name); } if (!vconn_is_passive(listen_vconn)) { - fatal(0, "%s is not a passive vconn", listen_vconn_name); + fatal(0, "%s is not a passive vconn", s.listen_vconn_name); } } else { listen_vconn = NULL; } - snprintf(of_name, sizeof of_name, "of%s", nl_name + 3); - retval = netdev_open(of_name, &of_device); - if (!retval) { - enum netdev_flags flags; - retval = netdev_get_flags(of_device, &flags); - if (!retval) { - if (flags & NETDEV_UP) { - struct in6_addr in6; - - local_port = true; - memcpy(local_mac, netdev_get_etheraddr(of_device), - ETH_ADDR_LEN); - if (netdev_get_in6(of_device, &in6)) { - VLOG_WARN("Ignoring IPv6 address on %s device: " - "IPv6 not supported", of_name); - } - local_ml = mac_learning_create(); - } - } else { - error(retval, "Could not get flags for %s device", of_name); - } - netdev_close(of_device); - } else { - error(retval, "Could not open %s device", of_name); - } + /* Start controller discovery. */ + discovery = s.discovery ? discovery_init(&s) : NULL; + /* Start listening for vlogconf requests. */ retval = vlog_server_listen(NULL, NULL); if (retval) { fatal(retval, "Could not listen for vlog connections"); @@ -190,26 +192,67 @@ main(int argc, char *argv[]) daemonize(); - relay_create(rconn_new(argv[optind], 1), rconn_new(argv[optind + 1], 1), - false); + /* Connect to datapath. */ + local_rconn = rconn_create(1, 0, s.max_backoff); + rconn_connect(local_rconn, s.nl_name); + + /* Connect to controller. */ + remote_rconn = rconn_create(1, s.probe_interval, s.max_backoff); + if (s.controller_name) { + retval = rconn_connect(remote_rconn, s.controller_name); + if (retval == EAFNOSUPPORT) { + fatal(0, "No support for %s vconn", s.controller_name); + } + } + + /* Start relaying. */ + controller_relay = relay_create(local_rconn, remote_rconn, false); + list_push_back(&relays, &controller_relay->node); + + /* Set up hooks. */ + n_hooks = 0; + if (s.in_band) { + hooks[n_hooks++] = in_band_hook_create(&s); + } + if (s.fail_mode == FAIL_OPEN) { + hooks[n_hooks++] = fail_open_hook_create(&s, + local_rconn, remote_rconn); + } + assert(n_hooks <= ARRAY_SIZE(hooks)); + for (;;) { struct relay *r, *n; + size_t i; /* Do work. */ LIST_FOR_EACH_SAFE (r, n, struct relay, node, &relays) { - relay_run(r); + relay_run(r, hooks, n_hooks); } if (listen_vconn) { for (;;) { - struct vconn *new_remote; - retval = vconn_accept(listen_vconn, &new_remote); - if (retval) { - if (retval != EAGAIN) { - VLOG_WARN("accept failed (%s)", strerror(retval)); - } + struct relay *r = relay_accept(&s, listen_vconn); + if (!r) { break; } - new_management_connection(nl_name, new_remote); + list_push_back(&relays, &r->node); + } + } + for (i = 0; i < n_hooks; i++) { + if (hooks[i].periodic_cb) { + hooks[i].periodic_cb(hooks[i].aux); + } + } + if (s.discovery) { + char *controller_name; + if (rconn_is_connectivity_questionable(remote_rconn)) { + discovery_question_connectivity(discovery); + } + if (discovery_run(discovery, &controller_name)) { + if (controller_name) { + rconn_connect(remote_rconn, controller_name); + } else { + rconn_disconnect(remote_rconn); + } } } @@ -220,42 +263,70 @@ main(int argc, char *argv[]) if (listen_vconn) { vconn_accept_wait(listen_vconn); } + if (discovery) { + discovery_wait(discovery); + } poll_block(); } return 0; } -static void -new_management_connection(const char *nl_name, struct vconn *new_remote) +static struct hook +make_hook(bool (*packet_cb)(struct relay *, int half, void *aux), + void (*periodic_cb)(void *aux), + void *aux) { + struct hook h; + h.packet_cb = packet_cb; + h.periodic_cb = periodic_cb; + h.aux = aux; + return h; +} + +/* OpenFlow message relaying. */ + +static struct relay * +relay_accept(const struct settings *s, struct vconn *listen_vconn) +{ + struct vconn *new_remote, *new_local; char *nl_name_without_subscription; - struct vconn *new_local; struct rconn *r1, *r2; int retval; + retval = vconn_accept(listen_vconn, &new_remote); + if (retval) { + if (retval != EAGAIN) { + VLOG_WARN("accept failed (%s)", strerror(retval)); + } + return NULL; + } + /* nl:123 or nl:123:1 opens a netlink connection to local datapath 123. We * only accept the former syntax in main(). * * nl:123:0 opens a netlink connection to local datapath 123 without * obtaining a subscription for ofp_packet_in or ofp_flow_expired * messages.*/ - nl_name_without_subscription = xasprintf("%s:0", nl_name); + nl_name_without_subscription = xasprintf("%s:0", s->nl_name); retval = vconn_open(nl_name_without_subscription, &new_local); if (retval) { VLOG_ERR("could not connect to %s (%s)", nl_name_without_subscription, strerror(retval)); vconn_close(new_remote); free(nl_name_without_subscription); - return; + return NULL; } - /* Add it to the relay list. */ - r1 = rconn_new_from_vconn(nl_name_without_subscription, 1, new_local); - r2 = rconn_new_from_vconn("passive", 1, new_remote); - relay_create(r1, r2, true); - + /* Create and return relay. */ + r1 = rconn_create(1, 0, 0); + rconn_connect_unreliably(r1, nl_name_without_subscription, new_local); free(nl_name_without_subscription); + + r2 = rconn_create(1, 0, 0); + rconn_connect_unreliably(r2, "passive", new_remote); + + return relay_create(r1, r2, true); } static struct relay * @@ -271,13 +342,11 @@ relay_create(struct rconn *local, struct rconn *remote, bool is_mgmt_conn) r->halves[i].rxbuf = NULL; } r->is_mgmt_conn = is_mgmt_conn; - r->lswitch = NULL; - list_push_back(&relays, &r->node); return r; } static void -relay_run(struct relay *r) +relay_run(struct relay *r, const struct hook hooks[], size_t n_hooks) { int iteration; int i; @@ -295,10 +364,16 @@ relay_run(struct relay *r) if (!this->rxbuf) { this->rxbuf = rconn_recv(this->rconn); - if (this->rxbuf && !r->is_mgmt_conn && i == HALF_LOCAL - && (local_hook(r) || fail_open_hook(r))) { - buffer_delete(this->rxbuf); - this->rxbuf = NULL; + if (this->rxbuf) { + const struct hook *h; + for (h = hooks; h < &hooks[n_hooks]; h++) { + if (h->packet_cb(r, i, h->aux)) { + buffer_delete(this->rxbuf); + this->rxbuf = NULL; + progress = true; + break; + } + } } } @@ -319,11 +394,13 @@ relay_run(struct relay *r) } } - for (i = 0; i < 2; i++) { - struct half *this = &r->halves[i]; - if (!rconn_is_alive(this->rconn)) { - relay_destroy(r); - return; + if (r->is_mgmt_conn) { + for (i = 0; i < 2; i++) { + struct half *this = &r->halves[i]; + if (!rconn_is_alive(this->rconn)) { + relay_destroy(r); + return; + } } } } @@ -356,20 +433,88 @@ relay_destroy(struct relay *r) } free(r); } + +/* In-band control. */ + +struct in_band_data { + const struct settings *s; + struct mac_learning *ml; + struct netdev *of_device; + uint8_t mac[ETH_ADDR_LEN]; +}; + +static void +queue_tx(struct rconn *rc, struct buffer *b) +{ + if (rconn_force_send(rc, b)) { + buffer_delete(b); + } +} static bool -local_hook(struct relay *r) +is_controller_mac(const uint8_t dl_addr[ETH_ADDR_LEN], struct netdev *netdev, + struct rconn *controller) { + static uint32_t ip, last_nonzero_ip; + static uint8_t mac[ETH_ADDR_LEN], last_nonzero_mac[ETH_ADDR_LEN]; + static time_t next_refresh = 0; + + uint32_t last_ip = ip; + + time_t now = time(0); + + ip = rconn_get_ip(controller); + if (last_ip != ip || !next_refresh || now >= next_refresh) { + bool have_mac; + + /* Look up MAC address. */ + memset(mac, 0, sizeof mac); + if (ip) { + int retval = netdev_arp_lookup(netdev, ip, mac); + if (retval) { + VLOG_DBG("cannot look up controller hw address ("IP_FMT"): %s", + IP_ARGS(&ip), strerror(retval)); + } + } + have_mac = !eth_addr_is_zero(mac); + + /* Log changes in IP, MAC addresses. */ + if (ip && ip != last_nonzero_ip) { + VLOG_DBG("controller IP address changed from "IP_FMT + " to "IP_FMT, IP_ARGS(&last_nonzero_ip), IP_ARGS(&ip)); + last_nonzero_ip = ip; + } + if (have_mac && memcmp(last_nonzero_mac, mac, ETH_ADDR_LEN)) { + VLOG_DBG("controller MAC address changed from "ETH_ADDR_FMT" to " + ETH_ADDR_FMT, + ETH_ADDR_ARGS(last_nonzero_mac), ETH_ADDR_ARGS(mac)); + memcpy(last_nonzero_mac, mac, ETH_ADDR_LEN); + } + + /* Schedule next refresh. + * + * If we have an IP address but not a MAC address, then refresh + * quickly, since we probably will get a MAC address soon (via ARP). + * Otherwise, we can afford to wait a little while. */ + next_refresh = now + (!ip || have_mac ? 10 : 1); + } + return !eth_addr_is_zero(mac) && eth_addr_equals(mac, dl_addr); +} + +static bool +in_band_packet_cb(struct relay *r, int half, void *in_band_) +{ + struct in_band_data *in_band = in_band_; struct rconn *rc = r->halves[HALF_LOCAL].rconn; struct buffer *msg = r->halves[HALF_LOCAL].rxbuf; struct ofp_packet_in *opi; struct ofp_header *oh; size_t pkt_ofs, pkt_len; - struct buffer pkt, *b; + struct buffer pkt; struct flow flow; uint16_t in_port, out_port; - if (!local_port) { + if (half != HALF_LOCAL || r->is_mgmt_conn) { return false; } @@ -392,82 +537,260 @@ local_hook(struct relay *r) flow_extract(&pkt, in_port, &flow); /* Deal with local stuff. */ - if (!rconn_is_connected(r->halves[HALF_REMOTE].rconn) - && eth_addr_is_broadcast(flow.dl_dst)) { - out_port = OFPP_FLOOD; - } else if (in_port == OFPP_LOCAL) { - out_port = mac_learning_lookup(local_ml, flow.dl_dst); - } else if (eth_addr_equals(flow.dl_dst, local_mac)) { + if (in_port == OFPP_LOCAL) { + out_port = mac_learning_lookup(in_band->ml, flow.dl_dst); + } else if (eth_addr_equals(flow.dl_dst, in_band->mac)) { out_port = OFPP_LOCAL; - if (mac_learning_learn(local_ml, flow.dl_src, in_port)) { + if (mac_learning_learn(in_band->ml, flow.dl_src, in_port)) { VLOG_DBG("learned that "ETH_ADDR_FMT" is on port %"PRIu16, ETH_ADDR_ARGS(flow.dl_src), in_port); } + } else if (flow.dl_type == htons(ETH_TYPE_ARP) + && eth_addr_is_broadcast(flow.dl_dst) + && is_controller_mac(flow.dl_src, in_band->of_device, + r->halves[HALF_REMOTE].rconn)) { + out_port = OFPP_FLOOD; } else { return false; } - /* Add new flow. */ if (out_port != OFPP_FLOOD) { - b = make_add_simple_flow(&flow, ntohl(opi->buffer_id), out_port); - if (rconn_force_send(rc, b)) { - buffer_delete(b); - } - } + /* The output port is known, so add a new flow. */ + queue_tx(rc, make_add_simple_flow(&flow, ntohl(opi->buffer_id), + out_port, in_band->s->max_idle)); - /* If the switch didn't buffer the packet, we need to send a copy. */ - if (out_port == OFPP_FLOOD || ntohl(opi->buffer_id) == UINT32_MAX) { - b = make_unbuffered_packet_out(&pkt, in_port, out_port); - if (rconn_force_send(rc, b)) { - buffer_delete(b); + /* If the switch didn't buffer the packet, we need to send a copy. */ + if (ntohl(opi->buffer_id) == UINT32_MAX) { + queue_tx(rc, make_unbuffered_packet_out(&pkt, in_port, out_port)); } + } else { + /* We don't know that MAC. Send along the packet without setting up a + * flow. */ + struct buffer *b; + if (ntohl(opi->buffer_id) == UINT32_MAX) { + b = make_unbuffered_packet_out(&pkt, in_port, out_port); + } else { + b = make_buffered_packet_out(ntohl(opi->buffer_id), + in_port, out_port); + } + queue_tx(rc, b); } return true; } -static bool -fail_open_hook(struct relay *r) +static struct hook +in_band_hook_create(const struct settings *s) { - struct buffer *msg = r->halves[HALF_LOCAL].rxbuf; - struct rconn *local = r->halves[HALF_LOCAL].rconn; - struct rconn *remote = r->halves[HALF_REMOTE].rconn; - int disconnected_duration; + struct in_band_data *in_band; + int retval; - if (fail_mode == FAIL_CLOSED) { - /* We fail closed, so there's never anything to do. */ - return false; + in_band = xmalloc(sizeof *in_band); + in_band->s = s; + in_band->ml = mac_learning_create(); + retval = netdev_open(s->of_name, NETDEV_ETH_TYPE_NONE, + &in_band->of_device); + if (retval) { + fatal(retval, "Could not open %s device", s->of_name); } + memcpy(in_band->mac, netdev_get_etheraddr(in_band->of_device), + ETH_ADDR_LEN); - disconnected_duration = rconn_disconnected_duration(remote); - if (disconnected_duration < fail_open_delay) { - /* It's not time to fail open yet. */ - if (r->lswitch && rconn_is_connected(remote)) { - /* We're connected, so drop the learning switch. */ + return make_hook(in_band_packet_cb, NULL, in_band); +} + +/* Fail open support. */ + +struct fail_open_data { + const struct settings *s; + struct rconn *local_rconn; + struct rconn *remote_rconn; + struct lswitch *lswitch; + int last_disconn_secs; +}; + +/* Causes 'r' to enter or leave fail-open mode, if appropriate. */ +static void +fail_open_periodic_cb(void *fail_open_) +{ + struct fail_open_data *fail_open = fail_open_; + int disconn_secs; + bool open; + + disconn_secs = rconn_disconnected_duration(fail_open->remote_rconn); + open = disconn_secs >= fail_open->s->probe_interval * 3; + if (open != (fail_open->lswitch != NULL)) { + if (!open) { VLOG_WARN("No longer in fail-open mode"); - lswitch_destroy(r->lswitch); - r->lswitch = NULL; + lswitch_destroy(fail_open->lswitch); + fail_open->lswitch = NULL; + } else { + VLOG_WARN("Could not connect to controller for %d seconds, " + "failing open", disconn_secs); + fail_open->lswitch = lswitch_create(fail_open->local_rconn, true, + fail_open->s->max_idle); + fail_open->last_disconn_secs = disconn_secs; } + } else if (open && disconn_secs > fail_open->last_disconn_secs + 60) { + VLOG_WARN("Still in fail-open mode after %d seconds disconnected " + "from controller", disconn_secs); + fail_open->last_disconn_secs = disconn_secs; + } +} + +static bool +fail_open_packet_cb(struct relay *r, int half, void *fail_open_) +{ + struct fail_open_data *fail_open = fail_open_; + if (half != HALF_LOCAL || r->is_mgmt_conn || !fail_open->lswitch) { + return false; + } else { + lswitch_process_packet(fail_open->lswitch, fail_open->local_rconn, + r->halves[HALF_LOCAL].rxbuf); + rconn_run(fail_open->local_rconn); + return true; + } +} + +static struct hook +fail_open_hook_create(const struct settings *s, struct rconn *local_rconn, + struct rconn *remote_rconn) +{ + struct fail_open_data *fail_open = xmalloc(sizeof *fail_open); + fail_open->s = s; + fail_open->local_rconn = local_rconn; + fail_open->remote_rconn = remote_rconn; + fail_open->lswitch = NULL; + return make_hook(fail_open_packet_cb, fail_open_periodic_cb, fail_open); +} + +/* Controller discovery. */ + +struct discovery +{ + const struct settings *s; + struct dhclient *dhcp; + bool ever_successful; +}; + +static struct discovery * +discovery_init(const struct settings *s) +{ + struct netdev *netdev; + struct discovery *d; + struct dhclient *dhcp; + int retval; + + /* Bring ofX network device up. */ + retval = netdev_open(s->of_name, NETDEV_ETH_TYPE_NONE, &netdev); + if (retval) { + fatal(retval, "Could not open %s device", s->of_name); + } + retval = netdev_turn_flags_on(netdev, NETDEV_UP, true); + if (retval) { + fatal(retval, "Could not bring %s device up", s->of_name); + } + netdev_close(netdev); + + /* Initialize DHCP client. */ + retval = dhclient_create(s->of_name, modify_dhcp_request, + validate_dhcp_offer, (void *) s, &dhcp); + if (retval) { + fatal(retval, "Failed to initialize DHCP client"); + } + dhclient_init(dhcp, 0); + + d = xmalloc(sizeof *d); + d->s = s; + d->dhcp = dhcp; + d->ever_successful = false; + return d; +} + +static void +discovery_question_connectivity(struct discovery *d) +{ + dhclient_force_renew(d->dhcp, 15); +} + +static bool +discovery_run(struct discovery *d, char **controller_name) +{ + dhclient_run(d->dhcp); + if (!dhclient_changed(d->dhcp)) { return false; } - if (!r->lswitch) { - VLOG_WARN("Could not connect to controller for %d seconds, " - "failing open", disconnected_duration); - r->lswitch = lswitch_create(local, true, true); + dhclient_configure_netdev(d->dhcp); + if (d->s->update_resolv_conf) { + dhclient_update_resolv_conf(d->dhcp); } - /* Do switching. */ - lswitch_process_packet(r->lswitch, local, msg); - rconn_run(local); + if (dhclient_is_bound(d->dhcp)) { + *controller_name = dhcp_msg_get_string(dhclient_get_config(d->dhcp), + DHCP_CODE_OFP_CONTROLLER_VCONN); + VLOG_WARN("%s: discovered controller", *controller_name); + d->ever_successful = true; + } else if (controller_name) { + *controller_name = NULL; + if (d->ever_successful) { + VLOG_WARN("discovered controller no longer available"); + } + } return true; } static void -parse_options(int argc, char *argv[]) +discovery_wait(struct discovery *d) { + dhclient_wait(d->dhcp); +} + +static void +modify_dhcp_request(struct dhcp_msg *msg, void *aux) +{ + dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow"); +} + +static bool +validate_dhcp_offer(const struct dhcp_msg *msg, void *s_) +{ + const struct settings *s = s_; + char *vconn_name; + bool accept; + + vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN); + if (!vconn_name) { + VLOG_WARN("rejecting DHCP offer missing controller vconn"); + return false; + } + accept = !regexec(&s->accept_controller_regex, vconn_name, 0, NULL, 0); + if (!accept) { + VLOG_WARN("rejecting controller vconn that fails to match %s", + s->accept_controller_re); + } + free(vconn_name); + return accept; +} + +/* User interface. */ + +static void +parse_options(int argc, char *argv[], struct settings *s) +{ + enum { + OPT_ACCEPT_VCONN = UCHAR_MAX + 1, + OPT_NO_RESOLV_CONF, + OPT_INACTIVITY_PROBE, + OPT_MAX_IDLE, + OPT_MAX_BACKOFF + }; static struct option long_options[] = { + {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN}, + {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF}, {"fail", required_argument, 0, 'f'}, - {"fail-open-delay", required_argument, 0, 'd'}, + {"inactivity-probe", required_argument, 0, OPT_INACTIVITY_PROBE}, + {"max-idle", required_argument, 0, OPT_MAX_IDLE}, + {"max-backoff", required_argument, 0, OPT_MAX_BACKOFF}, {"listen", required_argument, 0, 'l'}, {"detach", no_argument, 0, 'D'}, {"pidfile", optional_argument, 0, 'P'}, @@ -478,7 +801,16 @@ parse_options(int argc, char *argv[]) {0, 0, 0, 0}, }; char *short_options = long_options_to_short_options(long_options); - + char *accept_re = NULL; + int retval; + + /* Set defaults that we can figure out before parsing options. */ + s->listen_vconn_name = NULL; + s->fail_mode = FAIL_OPEN; + s->max_idle = 15; + s->probe_interval = 15; + s->max_backoff = 15; + s->update_resolv_conf = true; for (;;) { int c; @@ -488,22 +820,50 @@ parse_options(int argc, char *argv[]) } switch (c) { + case OPT_ACCEPT_VCONN: + accept_re = optarg[0] == '^' ? optarg : xasprintf("^%s", optarg); + break; + + case OPT_NO_RESOLV_CONF: + s->update_resolv_conf = false; + break; + case 'f': if (!strcmp(optarg, "open")) { - fail_mode = FAIL_OPEN; + s->fail_mode = FAIL_OPEN; } else if (!strcmp(optarg, "closed")) { - fail_mode = FAIL_CLOSED; + s->fail_mode = FAIL_CLOSED; } else { fatal(0, "-f or --fail argument must be \"open\" or \"closed\""); } break; - case 'd': - fail_open_delay = atoi(optarg); - if (fail_open_delay < 1) { - fatal(0, - "-d or --fail-open-delay argument must be at least 1"); + case OPT_INACTIVITY_PROBE: + s->probe_interval = atoi(optarg); + if (s->probe_interval < 5) { + fatal(0, "--inactivity-probe argument must be at least 5"); + } + break; + + case OPT_MAX_IDLE: + if (!strcmp(optarg, "permanent")) { + s->max_idle = OFP_FLOW_PERMANENT; + } else { + s->max_idle = atoi(optarg); + if (s->max_idle < 1 || s->max_idle > 65535) { + fatal(0, "--max-idle argument must be between 1 and " + "65535 or the word 'permanent'"); + } + } + break; + + case OPT_MAX_BACKOFF: + s->max_backoff = atoi(optarg); + if (s->max_backoff < 1) { + fatal(0, "--max-backoff argument must be at least 1"); + } else if (s->max_backoff > 3600) { + s->max_backoff = 3600; } break; @@ -512,14 +872,14 @@ parse_options(int argc, char *argv[]) break; case 'P': - set_pidfile(optarg ? optarg : "secchan.pid"); + set_pidfile(optarg); break; case 'l': - if (listen_vconn_name) { + if (s->listen_vconn_name) { fatal(0, "-l or --listen may be only specified once"); } - listen_vconn_name = optarg; + s->listen_vconn_name = optarg; break; case 'h': @@ -543,29 +903,92 @@ parse_options(int argc, char *argv[]) } } free(short_options); + + argc -= optind; + argv += optind; + if (argc < 1 || argc > 2) { + fatal(0, "need one or two non-option arguments; use --help for usage"); + } + + /* Local and remote vconns. */ + s->nl_name = argv[0]; + if (strncmp(s->nl_name, "nl:", 3) + || strlen(s->nl_name) < 4 + || s->nl_name[strspn(s->nl_name + 3, "0123456789") + 3]) { + fatal(0, "%s: argument is not of the form \"nl:DP_IDX\"", s->nl_name); + } + s->of_name = xasprintf("of%s", s->nl_name + 3); + s->controller_name = argc > 1 ? xstrdup(argv[1]) : NULL; + + /* Set accept_controller_regex. */ + if (!accept_re) { + accept_re = vconn_ssl_is_configured() ? "^ssl:.*" : ".*"; + } + retval = regcomp(&s->accept_controller_regex, accept_re, + REG_NOSUB | REG_EXTENDED); + if (retval) { + size_t length = regerror(retval, &s->accept_controller_regex, NULL, 0); + char *buffer = xmalloc(length); + regerror(retval, &s->accept_controller_regex, buffer, length); + fatal(0, "%s: %s", accept_re, buffer); + } + s->accept_controller_re = accept_re; + + /* Mode of operation. */ + s->discovery = s->controller_name == NULL; + if (s->discovery) { + s->in_band = true; + } else { + enum netdev_flags flags; + struct netdev *netdev; + + retval = netdev_open(s->of_name, NETDEV_ETH_TYPE_NONE, &netdev); + if (retval) { + fatal(retval, "Could not open %s device", s->of_name); + } + + retval = netdev_get_flags(netdev, &flags); + if (retval) { + fatal(retval, "Could not get flags for %s device", s->of_name); + } + + s->in_band = (flags & NETDEV_UP) != 0; + if (s->in_band && netdev_get_in6(netdev, NULL)) { + VLOG_WARN("Ignoring IPv6 address on %s device: IPv6 not supported", + s->of_name); + } + + netdev_close(netdev); + } } static void usage(void) { printf("%s: secure channel, a relay for OpenFlow messages.\n" - "usage: %s [OPTIONS] nl:DP_IDX CONTROLLER\n" - "where nl:DP_IDX is a datapath that has been added with dpctl\n" - "and CONTROLLER is an active OpenFlow connection method.\n", + "usage: %s [OPTIONS] nl:DP_IDX [CONTROLLER]\n" + "where nl:DP_IDX is a datapath that has been added with dpctl.\n" + "CONTROLLER is an active OpenFlow connection method; if it is\n" + "omitted, then secchan performs controller discovery.\n", program_name, program_name); vconn_usage(true, true); - printf("\nNetworking options:\n" + printf("\nController discovery options:\n" + " --accept-vconn=REGEX accept matching discovered controllers\n" + " --no-resolv-conf do not update /etc/resolv.conf\n" + "\nNetworking options:\n" " -f, --fail=open|closed when controller connection fails:\n" " closed: drop all packets\n" " open (default): act as learning switch\n" - " -d, --fail-open-delay=SECS number of seconds after which to\n" - " fail open if --fail=open (default: 30)\n" + " --inactivity-probe=SECS time between inactivity probes\n" + " --max-idle=SECS max idle for flows set up by secchan\n" + " --max-backoff=SECS max time between controller connection\n" + " attempts (default: 15 seconds)\n" " -l, --listen=METHOD allow management connections on METHOD\n" " (a passive OpenFlow connection method)\n" "\nOther options:\n" " -D, --detach run in background as daemon\n" " -P, --pidfile[=FILE] create pidfile (default: %s/secchan.pid)\n" - " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n" + " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n" " -v, --verbose set maximum verbosity level\n" " -h, --help display this help message\n" " -V, --version display version information\n", diff --git a/switch/Makefile.am b/switch/Makefile.am index bd1b06e..164c21f 100644 --- a/switch/Makefile.am +++ b/switch/Makefile.am @@ -17,7 +17,7 @@ switch_SOURCES = \ table-hash.c \ table-linear.c -switch_LDADD = ../lib/libopenflow.la -ldl +switch_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS) EXTRA_DIST = switch.8.in DISTCLEANFILES = switch.8 diff --git a/switch/chain.c b/switch/chain.c index a85a650..1e45a44 100644 --- a/switch/chain.c +++ b/switch/chain.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "chain.h" #include #include diff --git a/switch/crc32.c b/switch/crc32.c index 97c0961..f6c2c0b 100644 --- a/switch/crc32.c +++ b/switch/crc32.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "crc32.h" void diff --git a/switch/datapath.c b/switch/datapath.c index aa1cb80..505ebf3 100644 --- a/switch/datapath.c +++ b/switch/datapath.c @@ -40,6 +40,7 @@ #include #include "buffer.h" #include "chain.h" +#include "csum.h" #include "flow.h" #include "netdev.h" #include "packets.h" @@ -167,24 +168,13 @@ static int port_no(struct datapath *dp, struct sw_port *p) return p - dp->ports; } -/* Generates a unique datapath id. It incorporates the datapath index - * and a hardware address, if available. If not, it generates a random - * one. - */ +/* Generates and returns a random datapath id. */ static uint64_t gen_datapath_id(void) { - /* Choose a random datapath id. */ - uint64_t id = 0; - int i; - - srand(time(0)); - - for (i = 0; i < ETH_ADDR_LEN; i++) { - id |= (uint64_t)(rand() & 0xff) << (8*(ETH_ADDR_LEN-1 - i)); - } - - return id; + uint8_t ea[ETH_ADDR_LEN]; + eth_addr_random(ea); + return eth_addr_to_uint64(ea); } int @@ -225,11 +215,11 @@ dp_add_port(struct datapath *dp, const char *name) struct sw_port *p; int error; - error = netdev_open(name, &netdev); + error = netdev_open(name, NETDEV_ETH_TYPE_ANY, &netdev); if (error) { return error; } - error = netdev_set_flags(netdev, NETDEV_UP | NETDEV_PROMISC); + error = netdev_set_flags(netdev, NETDEV_UP | NETDEV_PROMISC, false); if (error) { VLOG_ERR("Couldn't set promiscuous mode on %s device", name); netdev_close(netdev); @@ -573,19 +563,11 @@ dp_output_port(struct datapath *dp, struct buffer *buffer, } static void * -alloc_openflow_buffer(struct datapath *dp, size_t openflow_len, uint8_t type, - const struct sender *sender, struct buffer **bufferp) +make_openflow_reply(size_t openflow_len, uint8_t type, + const struct sender *sender, struct buffer **bufferp) { - struct buffer *buffer; - struct ofp_header *oh; - - buffer = *bufferp = buffer_new(openflow_len); - oh = buffer_put_uninit(buffer, openflow_len); - oh->version = OFP_VERSION; - oh->type = type; - oh->length = 0; /* Filled in by send_openflow_buffer(). */ - oh->xid = sender ? sender->xid : 0; - return oh; + return make_openflow_xid(openflow_len, type, sender ? sender->xid : 0, + bufferp); } static int @@ -594,12 +576,9 @@ send_openflow_buffer(struct datapath *dp, struct buffer *buffer, { struct remote *remote = sender ? sender->remote : dp->controller; struct rconn *rconn = remote->rconn; - struct ofp_header *oh; int retval; - oh = buffer_at_assert(buffer, 0, sizeof *oh); - oh->length = htons(buffer->size); - + update_openflow_length(buffer); retval = rconn_send(rconn, buffer); if (retval) { VLOG_WARN("send to %s failed: %s", @@ -662,8 +641,8 @@ dp_send_features_reply(struct datapath *dp, const struct sender *sender) struct ofp_switch_features *ofr; struct sw_port *p; - ofr = alloc_openflow_buffer(dp, sizeof *ofr, OFPT_FEATURES_REPLY, - sender, &buffer); + ofr = make_openflow_reply(sizeof *ofr, OFPT_FEATURES_REPLY, + sender, &buffer); ofr->datapath_id = htonll(dp->id); ofr->n_exact = htonl(2 * TABLE_HASH_MAX_FLOWS); ofr->n_compression = 0; /* Not supported */ @@ -701,8 +680,7 @@ send_port_status(struct sw_port *p, uint8_t status) { struct buffer *buffer; struct ofp_port_status *ops; - ops = alloc_openflow_buffer(p->dp, sizeof *ops, OFPT_PORT_STATUS, NULL, - &buffer); + ops = make_openflow_xid(sizeof *ops, OFPT_PORT_STATUS, 0, &buffer); ops->reason = status; memset(ops->pad, 0, sizeof ops->pad); fill_port_desc(p->dp, p, &ops->desc); @@ -715,8 +693,7 @@ send_flow_expired(struct datapath *dp, struct sw_flow *flow) { struct buffer *buffer; struct ofp_flow_expired *ofe; - ofe = alloc_openflow_buffer(dp, sizeof *ofe, OFPT_FLOW_EXPIRED, NULL, - &buffer); + ofe = make_openflow_xid(sizeof *ofe, OFPT_FLOW_EXPIRED, 0, &buffer); flow_fill_match(&ofe->match, &flow->key); memset(ofe->pad, 0, sizeof ofe->pad); @@ -734,8 +711,8 @@ dp_send_error_msg(struct datapath *dp, const struct sender *sender, { struct buffer *buffer; struct ofp_error_msg *oem; - oem = alloc_openflow_buffer(dp, sizeof(*oem)+len, OFPT_ERROR_MSG, - sender, &buffer); + oem = make_openflow_reply(sizeof(*oem)+len, OFPT_ERROR_MSG, + sender, &buffer); oem->type = htons(type); oem->code = htons(code); memcpy(oem->data, data, len); @@ -869,34 +846,6 @@ execute_actions(struct datapath *dp, struct buffer *buffer, buffer_delete(buffer); } -/* Returns the new checksum for a packet in which the checksum field previously - * contained 'old_csum' and in which a field that contained 'old_u16' was - * changed to contain 'new_u16'. */ -static uint16_t -recalc_csum16(uint16_t old_csum, uint16_t old_u16, uint16_t new_u16) -{ - /* Ones-complement arithmetic is endian-independent, so this code does not - * use htons() or ntohs(). - * - * See RFC 1624 for formula and explanation. */ - uint16_t hc_complement = ~old_csum; - uint16_t m_complement = ~old_u16; - uint16_t m_prime = new_u16; - uint32_t sum = hc_complement + m_complement + m_prime; - uint16_t hc_prime_complement = sum + (sum >> 16); - return ~hc_prime_complement; -} - -/* Returns the new checksum for a packet in which the checksum field previously - * contained 'old_csum' and in which a field that contained 'old_u32' was - * changed to contain 'new_u32'. */ -static uint16_t -recalc_csum32(uint16_t old_csum, uint32_t old_u32, uint32_t new_u32) -{ - return recalc_csum16(recalc_csum16(old_csum, old_u32, new_u32), - old_u32 >> 16, new_u32 >> 16); -} - static void modify_nh(struct buffer *buffer, uint16_t eth_proto, uint8_t nw_proto, const struct ofp_action *a) { @@ -952,12 +901,12 @@ modify_vlan(struct buffer *buffer, uint16_t new_id = a->arg.vlan_id; struct vlan_eth_header *veh; - if (new_id != OFP_VLAN_NONE) { + if (new_id != htons(OFP_VLAN_NONE)) { if (key->flow.dl_vlan != htons(OFP_VLAN_NONE)) { /* Modify vlan id, but maintain other TCI values */ veh = buffer->l2; veh->veth_tci &= ~htons(VLAN_VID); - veh->veth_tci |= htons(new_id); + veh->veth_tci |= new_id; } else { /* Insert new vlan id. */ struct eth_header *eh = buffer->l2; @@ -1005,8 +954,8 @@ recv_get_config_request(struct datapath *dp, const struct sender *sender, struct buffer *buffer; struct ofp_switch_config *osc; - osc = alloc_openflow_buffer(dp, sizeof *osc, OFPT_GET_CONFIG_REPLY, - sender, &buffer); + osc = make_openflow_reply(sizeof *osc, OFPT_GET_CONFIG_REPLY, + sender, &buffer); assert(sizeof *osc == sizeof dp->config); memcpy(((char *)osc) + sizeof osc->header, @@ -1085,7 +1034,8 @@ add_flow(struct datapath *dp, const struct ofp_flow_mod *ofm) const struct ofp_action *a = &ofm->actions[i]; if (a->type == htons(OFPAT_OUTPUT) - && a->arg.output.port == htons(OFPP_TABLE)) { + && (a->arg.output.port == htons(OFPP_TABLE) + || a->arg.output.port == htons(OFPP_NONE))) { /* xxx Send fancy new error message? */ goto error; } @@ -1419,8 +1369,8 @@ stats_dump(struct datapath *dp, void *cb_) return 0; } - osr = alloc_openflow_buffer(dp, sizeof *osr, OFPT_STATS_REPLY, &cb->sender, - &buffer); + osr = make_openflow_reply(sizeof *osr, OFPT_STATS_REPLY, &cb->sender, + &buffer); osr->type = htons(cb->s - stats); osr->flags = 0; @@ -1504,6 +1454,20 @@ error: return err; } +static int +recv_echo_request(struct datapath *dp, const struct sender *sender, + const void *oh) +{ + return send_openflow_buffer(dp, make_echo_reply(oh), sender); +} + +static int +recv_echo_reply(struct datapath *dp UNUSED, const struct sender *sender UNUSED, + const void *oh UNUSED) +{ + return 0; +} + /* 'msg', which is 'length' bytes long, was received from the control path. * Apply it to 'chain'. */ int @@ -1544,14 +1508,22 @@ fwd_control_input(struct datapath *dp, const struct sender *sender, sizeof (struct ofp_stats_request), recv_stats_request, }, + [OFPT_ECHO_REQUEST] = { + sizeof (struct ofp_header), + recv_echo_request, + }, + [OFPT_ECHO_REPLY] = { + sizeof (struct ofp_header), + recv_echo_reply, + }, }; const struct openflow_packet *pkt; struct ofp_header *oh; oh = (struct ofp_header *) msg; - if (oh->version != OFP_VERSION || oh->type >= ARRAY_SIZE(packets) - || ntohs(oh->length) > length) + assert(oh->version == OFP_VERSION); + if (oh->type >= ARRAY_SIZE(packets) || ntohs(oh->length) > length) return -EINVAL; pkt = &packets[oh->type]; diff --git a/switch/switch-flow.c b/switch/switch-flow.c index 4e5d257..732cea5 100644 --- a/switch/switch-flow.c +++ b/switch/switch-flow.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "switch-flow.h" #include #include @@ -88,35 +89,40 @@ void flow_extract_match(struct sw_flow_key* to, const struct ofp_match* from) memcpy(to->flow.dl_dst, from->dl_dst, ETH_ADDR_LEN); to->flow.dl_type = from->dl_type; + to->flow.nw_src = to->flow.nw_dst = to->flow.nw_proto = 0; + to->flow.tp_src = to->flow.tp_dst = 0; - if (from->dl_type == htons(ETH_TYPE_IP)) { +#define OFPFW_TP (OFPFW_TP_SRC | OFPFW_TP_DST) +#define OFPFW_NW (OFPFW_NW_SRC | OFPFW_NW_DST | OFPFW_NW_PROTO) + if (to->wildcards & OFPFW_DL_TYPE) { + /* Can't sensibly match on network or transport headers if the + * data link type is unknown. */ + to->wildcards |= OFPFW_NW | OFPFW_TP; + } else if (from->dl_type == htons(ETH_TYPE_IP)) { to->flow.nw_src = from->nw_src; to->flow.nw_dst = from->nw_dst; to->flow.nw_proto = from->nw_proto; - if ((from->nw_proto != IPPROTO_TCP && from->nw_proto != IPPROTO_UDP)) { - goto no_th; + if (to->wildcards & OFPFW_NW_PROTO) { + /* Can't sensibly match on transport headers if the network + * protocol is unknown. */ + to->wildcards |= OFPFW_TP; + } else if (from->nw_proto == IPPROTO_TCP + || from->nw_proto == IPPROTO_UDP) { + to->flow.tp_src = from->tp_src; + to->flow.tp_dst = from->tp_dst; + } else { + /* Transport layer fields are undefined. Mark them as + * exact-match to allow such flows to reside in table-hash, + * instead of falling into table-linear. */ + to->wildcards &= ~OFPFW_TP; } - to->flow.tp_src = from->tp_src; - to->flow.tp_dst = from->tp_dst; - return; + } else { + /* Network and transport layer fields are undefined. Mark them + * as exact-match to allow such flows to reside in table-hash, + * instead of falling into table-linear. */ + to->wildcards &= ~(OFPFW_NW | OFPFW_TP); } - - /* dl_type is not IPv4, so the network layer fields are undefined. We set - * them to zero and mark them as exact-match to allow such flows to reside - * in table-hash, instead of falling into table-linear. */ - to->wildcards &= ~(OFPFW_NW_SRC | OFPFW_NW_DST | OFPFW_NW_PROTO); - to->flow.nw_src = 0; - to->flow.nw_dst = 0; - to->flow.nw_proto = 0; - -no_th: - /* nw_proto is not TCP or UDP, so the transport layer fields are undefined. - * We set them to zero and mark them as exact-match to allow such flows to - * reside in table-hash, instead of falling into table-linear. */ - to->wildcards &= ~(OFPFW_TP_SRC | OFPFW_TP_DST); - to->flow.tp_src = 0; - to->flow.tp_dst = 0; } void flow_fill_match(struct ofp_match* to, const struct sw_flow_key* from) diff --git a/switch/switch.8.in b/switch/switch.8.in index 93e6868..1bd0607 100644 --- a/switch/switch.8.in +++ b/switch/switch.8.in @@ -42,6 +42,10 @@ The specified SSL \fIport\fR (default: 976) on the given remote The specified TCP \fIport\fR (default: 975) on the given remote \fIhost\fR. +.TP +\fBunix:\fIfile\fR +The Unix domain server socket named \fIfile\fR. + .SH OPTIONS .TP \fB-i\fR, \fB--interfaces=\fR\fInetdev\fR[\fB,\fInetdev\fR]... @@ -56,6 +60,14 @@ Specifies the OpenFlow switch ID (a 48-bit number that uniquely identifies a controller) as \fIdpid\fR, which consists of exactly 12 hex digits. Without this option, \fBswitch\fR picks an ID randomly. +.TP +\fB--max-backoff=\fIsecs\fR +Sets the maximum time between attempts to connect to the controller to +\fIsecs\fR, which must be at least 1. The actual interval between +connection attempts starts at 1 second and doubles on each failing +attempt until it reaches the maximum. The default maximum backoff +time is 15 seconds. + .TP \fB-p\fR, \fB--private-key=\fIprivkey.pem\fR Specifies a PEM file containing the private key used as the switch's @@ -89,17 +101,18 @@ run as a background process. Prints a brief help message to the console. .TP -\fB-v\fR \fImodule\fB:\fIfacility\fB:\fIlevel\fR, \fB--verbose=\fImodule\fB:\fIfacility\fB:\fIlevel\fR +\fB-v\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] Sets the logging level for \fImodule\fR in \fIfacility\fR to \fIlevel\fR. The \fImodule\fR may be any valid module name (as displayed by the \fB--list\fR action on \fBvlogconf\fR(8)), or the special name \fBANY\fR to set the logging levels for all modules. The \fIfacility\fR may be \fBsyslog\fR or \fBconsole\fR to set the levels for logging to the system log or to the console, respectively, or -\fBANY\fR to set the logging levels for both facilities. The -\fIlevel\fR must be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or -\fBdbg\fR, designating the minimum severity of a message for it to be -logged. +\fBANY\fR to set the logging levels for both facilities. If it is +omitted, \fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must +be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating +the minimum severity of a message for it to be logged. If it is +omitted, \fIlevel\fR defaults to \fBdbg\fR. .TP \fB-v\fR, \fB--verbose\fR diff --git a/switch/switch.c b/switch/switch.c index 9432a02..d745bc8 100644 --- a/switch/switch.c +++ b/switch/switch.c @@ -31,8 +31,10 @@ * derivatives without specific, written prior permission. */ +#include #include #include +#include #include #include #include @@ -61,11 +63,16 @@ static struct datapath *dp; static uint64_t dpid = UINT64_MAX; static char *port_list; +/* --max-backoff: Maximum interval between controller connection attempts, in + * seconds. */ +static int max_backoff = 15; + static void add_ports(struct datapath *dp, char *port_list); int main(int argc, char *argv[]) { + struct rconn *rconn; int error; set_program_name(argv[0]); @@ -77,7 +84,12 @@ main(int argc, char *argv[]) fatal(0, "missing controller argument; use --help for usage"); } - error = dp_new(&dp, dpid, rconn_new(argv[optind], 128)); + rconn = rconn_create(128, 60, max_backoff); + error = rconn_connect(rconn, argv[optind]); + if (error == EAFNOSUPPORT) { + fatal(0, "no support for %s vconn", argv[optind]); + } + error = dp_new(&dp, dpid, rconn); if (listen_vconn_name) { struct vconn *listen_vconn; int retval; @@ -135,9 +147,14 @@ add_ports(struct datapath *dp, char *port_list) static void parse_options(int argc, char *argv[]) { + enum { + OPT_MAX_BACKOFF = UCHAR_MAX + 1 + }; + static struct option long_options[] = { {"interfaces", required_argument, 0, 'i'}, {"datapath-id", required_argument, 0, 'd'}, + {"max-backoff", required_argument, 0, OPT_MAX_BACKOFF}, {"listen", required_argument, 0, 'l'}, {"detach", no_argument, 0, 'D'}, {"pidfile", optional_argument, 0, 'P'}, @@ -183,7 +200,7 @@ parse_options(int argc, char *argv[]) break; case 'P': - set_pidfile(optarg ? optarg : "switch.pid"); + set_pidfile(optarg); break; case 'v': @@ -198,6 +215,15 @@ parse_options(int argc, char *argv[]) } break; + case OPT_MAX_BACKOFF: + max_backoff = atoi(optarg); + if (max_backoff < 1) { + fatal(0, "--max-backoff argument must be at least 1"); + } else if (max_backoff > 3600) { + max_backoff = 3600; + } + break; + case 'l': if (listen_vconn_name) { fatal(0, "-l or --listen may be only specified once"); @@ -230,12 +256,14 @@ usage(void) " add specified initial switch ports\n" " -d, --datapath-id=ID Use ID as the OpenFlow switch ID\n" " (ID must consist of 12 hex digits)\n" + " --max-backoff=SECS max time between controller connection\n" + " attempts (default: 15 seconds)\n" " -l, --listen=METHOD allow management connections on METHOD\n" " (a passive OpenFlow connection method)\n" "\nOther options:\n" " -D, --detach run in background as daemon\n" " -P, --pidfile[=FILE] create pidfile (default: %s/switch.pid)\n" - " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n" + " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n" " -v, --verbose set maximum verbosity level\n" " -h, --help display this help message\n" " -V, --version display version information\n", diff --git a/switch/table-hash.c b/switch/table-hash.c index aaec503..461a463 100644 --- a/switch/table-hash.c +++ b/switch/table-hash.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "table.h" #include #include diff --git a/switch/table-linear.c b/switch/table-linear.c index 6371bc2..ff9d60b 100644 --- a/switch/table-linear.c +++ b/switch/table-linear.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include "table.h" #include #include "flow.h" diff --git a/tests/Makefile.am b/tests/Makefile.am index 91e11a5..2548ea0 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -5,4 +5,13 @@ TESTS = test-list check_PROGRAMS = test-list test_list_SOURCES = test-list.c -test_list_LDADD = ../lib/libopenflow.la +test_list_LDADD = ../lib/libopenflow.a + +TESTS += test-type-props +check_PROGRAMS += test-type-props +test_type_props_SOURCES = test-type-props.c + +noinst_PROGRAMS = test-dhcp-client +test_dhcp_client_SOURCES = test-dhcp-client.c +test_dhcp_client_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) + diff --git a/tests/test-dhcp-client.c b/tests/test-dhcp-client.c new file mode 100644 index 0000000..285d99f --- /dev/null +++ b/tests/test-dhcp-client.c @@ -0,0 +1,205 @@ +/* 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 +#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; + +/* --no-resolv-conf: Update /etc/resolv.conf to match DHCP reply? */ +static bool update_resolv_conf = true; + +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); + if (dhclient_changed(cli)) { + dhclient_configure_netdev(cli); + if (update_resolv_conf) { + dhclient_update_resolv_conf(cli); + } + } + dhclient_wait(cli); + fatal_signal_unblock(); + poll_block(); + } +} + +static void +release(void *cli_) +{ + struct dhclient *cli = cli_; + dhclient_release(cli); + if (dhclient_changed(cli)) { + dhclient_configure_netdev(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, + OPT_NO_RESOLV_CONF + }; + static struct option long_options[] = { + {"request-ip", required_argument, 0, OPT_REQUEST_IP }, + {"vendor-class", required_argument, 0, OPT_VENDOR_CLASS }, + {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF}, + {"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 OPT_NO_RESOLV_CONF: + update_resolv_conf = false; + 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" + " --no-resolv-conf do not update /etc/resolv.conf\n" + "\nOther options:\n" + " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set 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); +} + diff --git a/tests/test-list.c b/tests/test-list.c index b9599db..62857be 100644 --- a/tests/test-list.c +++ b/tests/test-list.c @@ -1,6 +1,7 @@ /* A non-exhaustive test for some of the functions and macros declared in * list.h. */ +#include #include "list.h" #include diff --git a/tests/test-type-props.c b/tests/test-type-props.c new file mode 100644 index 0000000..67dabae --- /dev/null +++ b/tests/test-type-props.c @@ -0,0 +1,41 @@ +#include +#include "type-props.h" +#include +#include + +#define MUST_SUCCEED(EXPRESSION) \ + if (!(EXPRESSION)) { \ + fprintf(stderr, "%s:%d: %s failed\n", \ + __FILE__, __LINE__, #EXPRESSION); \ + exit(EXIT_FAILURE); \ + } + +#define TEST_TYPE(type, minimum, maximum, is_signed) \ + MUST_SUCCEED(TYPE_IS_INTEGER(type)); \ + MUST_SUCCEED(TYPE_IS_SIGNED(type) == is_signed); \ + MUST_SUCCEED(TYPE_MAXIMUM(type) == maximum); \ + MUST_SUCCEED(TYPE_MINIMUM(type) == minimum); + +int +main (void) +{ + TEST_TYPE(char, CHAR_MIN, CHAR_MAX, (CHAR_MIN < 0)); + + TEST_TYPE(signed char, SCHAR_MIN, SCHAR_MAX, 1); + TEST_TYPE(short int, SHRT_MIN, SHRT_MAX, 1); + TEST_TYPE(int, INT_MIN, INT_MAX, 1); + TEST_TYPE(long int, LONG_MIN, LONG_MAX, 1); + TEST_TYPE(long long int, LLONG_MIN, LLONG_MAX, 1); + + TEST_TYPE(unsigned char, 0, UCHAR_MAX, 0); + TEST_TYPE(unsigned short int, 0, USHRT_MAX, 0); + TEST_TYPE(unsigned int, 0, UINT_MAX, 0); + TEST_TYPE(unsigned long int, 0, ULONG_MAX, 0); + TEST_TYPE(unsigned long long int, 0, ULLONG_MAX, 0); + + MUST_SUCCEED(!(TYPE_IS_INTEGER(float))); + MUST_SUCCEED(!(TYPE_IS_INTEGER(double))); + MUST_SUCCEED(!(TYPE_IS_INTEGER(long double))); + + return 0; +} diff --git a/utilities/Makefile.am b/utilities/Makefile.am index 034978c..18beb4c 100644 --- a/utilities/Makefile.am +++ b/utilities/Makefile.am @@ -1,17 +1,18 @@ include ../Make.vars -bin_PROGRAMS = vlogconf dpctl +bin_PROGRAMS = vlogconf dpctl ofp-discover ofp-kill bin_SCRIPTS = ofp-pki noinst_SCRIPTS = ofp-pki-cgi -EXTRA_DIST = ofp-pki.in ofp-pki-cgi.in ofp-pki.8.in -DISTCLEANFILES = ofp-pki ofp-pki-cgi ofp-pki.8 +EXTRA_DIST = ofp-pki.in ofp-pki-cgi.in ofp-pki.8.in ofp-discover.8.in \ +ofp-kill.8.in +DISTCLEANFILES = ofp-pki ofp-pki-cgi ofp-pki.8 ofp-discover.8 ofp-kill.8 dist_man_MANS = vlogconf.8 dpctl.8 -man_MANS = ofp-pki.8 +man_MANS = ofp-pki.8 ofp-discover.8 ofp-kill.8 dpctl_SOURCES = dpctl.c -dpctl_LDADD = ../lib/libopenflow.la +dpctl_LDADD = ../lib/libopenflow.a $(FAULT_LIBS) $(SSL_LIBS) if HAVE_CURSES LIBS += -lncurses @@ -19,7 +20,13 @@ endif vlogconf_SOURCES = vlogconf.c -vlogconf_LDADD = ../lib/libopenflow.la +vlogconf_LDADD = ../lib/libopenflow.a + +ofp_discover_SOURCES = ofp-discover.c +ofp_discover_LDADD = ../lib/libopenflow.a + +ofp_kill_SOURCES = ofp-kill.c +ofp_kill_LDADD = ../lib/libopenflow.a pkidir = $(pkgdatadir)/pki @@ -31,3 +38,7 @@ ofp-pki-cgi: ofp-pki-cgi.in Makefile chmod +x ofp-pki-cgi ofp-pki.8: ofp-pki.8.in Makefile ($(do_subst) && $(ro_man)) < $(srcdir)/ofp-pki.8.in > ofp-pki.8 +ofp-discover.8: ofp-discover.8.in Makefile + ($(do_subst) && $(ro_man)) < $(srcdir)/ofp-discover.8.in > ofp-discover.8 +ofp-kill.8: ofp-kill.8.in Makefile + ($(do_subst) && $(ro_man)) < $(srcdir)/ofp-kill.8.in > ofp-kill.8 diff --git a/utilities/dpctl.8 b/utilities/dpctl.8 index dd5f775..fc4cd31 100644 --- a/utilities/dpctl.8 +++ b/utilities/dpctl.8 @@ -37,6 +37,10 @@ The specified SSL \fIport\fR (default: 976) on the given remote The specified TCP \fIport\fR (default: 975) on the given remote \fIhost\fR. +.TP +\fBunix:\fIfile\fR +The Unix domain server socket named \fIfile\fR. + .SH COMMANDS With the \fBdpctl\fR program, datapaths running in the kernel can be @@ -85,14 +89,9 @@ Prints to the console all OpenFlow packets sent by datapath \fIdp_idx\fR to its controller, where \fIdp_idx\fR is the ID of an existing datapath. -.TP -\fBbenchmark-nl nl:\fIdp_idx n size\fR -Checks the netlink performance between the kernel and userspace. -This is done by sending \fIN\fR packets of \fIsize\fR bytes from -the kernel module to dpctl. - .PP -The following commands can be used regardless of the connection method. +The following commands can be apply to OpenFlow switches regardless of +the connection method. .TP \fBshow \fIswitch\fR @@ -123,11 +122,17 @@ Prints to the console aggregate statistics for flows in datapath the statistics are aggregated across all flows in the datapath's flow tables. See \fBFLOW SYNTAX\fR, below, for the syntax of \fIflows\fR. +.TP +\fBadd-flow \fIswitch flow\fR +Add the flow entry as described by \fIflow\fR to the datapath \fIswitch\fR's +tables. The flow entry is in the format described in \fBFLOW SYNTAX\fR, +below. + .TP \fBadd-flows \fIswitch file\fR Add flow entries as described in \fIfile\fR to the datapath \fIswitch\fR's tables. Each line in \fIfile\fR is a flow entry in the format -described in \fBFLOW SYNTAX\fB, below. +described in \fBFLOW SYNTAX\fR, below. .TP \fBdel-flows \fIswitch \fR[\fIflow\fR] @@ -136,6 +141,32 @@ Deletes entries from the datapath \fIswitch\fR's tables that match tables are removed. See \fBFLOW SYNTAX\fR, below, for the syntax of \fIflows\fR. +.PP +The following commands can be used regardless of the connection +method. They apply to OpenFlow switches and controllers. + +.TP +\fBprobe \fIvconn\fR +Connects to \fIvconn\fR and sends a single OpenFlow echo-request +packet and waits for the response. With the \fB-t\fR or +\fB--timeout\fR option, this command can test whether an OpenFlow +switch or controller is up and running. + +.TP +\fBping \fIvconn \fR[\fIn\fR] +Sends a series of 10 echo request packets to \fIvconn\fR and times +each reply. The echo request packets consist of an OpenFlow header +plus \fIn\fR bytes (default: 64) of randomly generated payload. This +measures the latency of individual requests. + +.TP +\fBbenchmark \fIvconn n count\fR +Sends \fIcount\fR echo request packets that each consist of an +OpenFlow header plus \fIn\fR bytes of payload and waits for each +response. Reports the total time required. This is a measure of the +maximum bandwidth to \fIvconn\fR for round-trips of \fIn\fR-byte +messages. + .SH "FLOW SYNTAX" Some \fBdpctl\fR commands accept an argument that describes a flow or @@ -196,17 +227,21 @@ packets originating from a HTTP server. Matches UDP or TCP destination port \fIport\fR. .PP -The \fBadd-flow\fR command requires an additional field: +The \fBadd-flow\fR and \fBadd-flows\fR commands require an additional field: -.IP \fBaction=\fItarget\fR -Specifies the action to take on a packet when the flow entry matches. -The \fItarget\fR may be a decimal port number designating the physical -port on which to output the packet, or one of the following keywords: +.IP \fIactions\fB=\fItarget\fR[\fB,\fItarget\fR...]\fR +Specifies a comma-separated list of actions to take on a packet when the +flow entry matches. The \fItarget\fR may be a decimal port number +designating the physical port on which to output the packet, or one of +the following keywords: .RS +.IP \fBoutput\fR:\fIport\fR +Outputs the packet on the port specified by \fIport\fR. + .IP \fBnormal\fR -Subjects the packet to the device's normal L2/L3 processing. This -action is not implemented by all OpenFlow switches. +Subjects the packet to the device's normal L2/L3 processing. (This +action is not implemented by all OpenFlow switches.) .IP \fBflood\fR Outputs the packet on all switch physical ports other than the port on @@ -218,14 +253,23 @@ tree protocol). Outputs the packet on all switch physical ports other than the port on which it was received. -.IP \fBcontroller\fR +.IP \fBcontroller\fR:\fImax_len\fR Sends the packet to the OpenFlow controller as a ``packet in'' -message. +message. If \fImax_len\fR is a number, then it specifies the maximum +number of bytes that should be sent. If \fImax_len\fR is \fBALL\fR or +omitted, then the entire packet is sent. .IP \fBlocal\fR Outputs the packet on the ``local port,'' which corresponds to the \fBof\fIn\fR network device (see \fBCONTACTING THE CONTROLLER\fR in \fBsecchan\fR(8) for information on the \fBof\fIn\fR network device). + +.IP \fBmod_vlan\fR:\fIvlan_id\fR +Modifies the VLAN tag on a packet. If \fIvlan_id\fR is a number, then +the VLAN tag is added or modified as necessary to match the value +specified. If \fIvlan_id\fR is \fBSTRIP\fR, then the VLAN tag is +stripped from the packet if one is present. (This action is not +implemented by all OpenFlow switches.) .RE .IP @@ -254,6 +298,12 @@ If this field is not specified, or if \fInumber\fR is given as \fB255\fR, statistics are gathered about flows from all tables. .SH OPTIONS +.TP +\fB-t\fR, \fB--timeout=\fIsecs\fR +Limits \fBdpctl\fR runtime to approximately \fIsecs\fR seconds. If +the timeout expires, \fBdpctl\fR will exit with a \fBSIGALRM\fR +signal. + .TP \fB-p\fR, \fB--private-key=\fIprivkey.pem\fR Specifies a PEM file containing the private key used as the @@ -275,17 +325,18 @@ a switch is trustworthy. Prints a brief help message to the console. .TP -\fB-v\fR \fImodule\fB:\fIfacility\fB:\fIlevel\fR, \fB--verbose=\fImodule\fB:\fIfacility\fB:\fIlevel\fR +\fB-v\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] Sets the logging level for \fImodule\fR in \fIfacility\fR to \fIlevel\fR. The \fImodule\fR may be any valid module name (as displayed by the \fB--list\fR action on \fBvlogconf\fR(8)), or the special name \fBANY\fR to set the logging levels for all modules. The \fIfacility\fR may be \fBsyslog\fR or \fBconsole\fR to set the levels for logging to the system log or to the console, respectively, or -\fBANY\fR to set the logging levels for both facilities. The -\fIlevel\fR must be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or -\fBdbg\fR, designating the minimum severity of a message for it to be -logged. +\fBANY\fR to set the logging levels for both facilities. If it is +omitted, \fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must +be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating +the minimum severity of a message for it to be logged. If it is +omitted, \fIlevel\fR defaults to \fBdbg\fR. .TP \fB-v\fR, \fB--verbose\fR diff --git a/utilities/dpctl.c b/utilities/dpctl.c index b06c6bd..4f0e7d8 100644 --- a/utilities/dpctl.c +++ b/utilities/dpctl.c @@ -31,6 +31,7 @@ * derivatives without specific, written prior permission. */ +#include #include #include #include @@ -57,6 +58,8 @@ #ifdef HAVE_CURSES #include "ofp-printw.h" #endif +#include "random.h" +#include "signal.h" #include "vconn.h" #include "vconn-ssl.h" @@ -64,6 +67,9 @@ #define THIS_MODULE VLM_dpctl +#define DEFAULT_MAX_IDLE 60 +#define MAX_ADD_ACTS 5 + static const char* ifconfigbin = "/sbin/ifconfig"; struct command { @@ -115,6 +121,7 @@ static void parse_options(int argc, char *argv[]) { static struct option long_options[] = { + {"timeout", required_argument, 0, 't'}, {"verbose", optional_argument, 0, 'v'}, {"help", no_argument, 0, 'h'}, {"version", no_argument, 0, 'V'}, @@ -124,15 +131,30 @@ parse_options(int argc, char *argv[]) char *short_options = long_options_to_short_options(long_options); for (;;) { - int indexptr; + unsigned long int timeout; int c; - c = getopt_long(argc, argv, short_options, long_options, &indexptr); + c = getopt_long(argc, argv, short_options, long_options, NULL); if (c == -1) { break; } switch (c) { + case 't': + timeout = strtoul(optarg, NULL, 10); + if (timeout <= 0) { + fatal(0, "value %s on -t or --timeout is not at least 1", + optarg); + } else if (timeout < UINT_MAX) { + /* Add 1 because historical implementations allow an alarm to + * occur up to a second early. */ + alarm(timeout + 1); + } else { + alarm(UINT_MAX); + } + signal(SIGALRM, SIG_DFL); + break; + case 'h': usage(); @@ -162,15 +184,14 @@ usage(void) printf("%s: OpenFlow switch management utility\n" "usage: %s [OPTIONS] COMMAND [ARG...]\n" #ifdef HAVE_NETLINK - "\nCommands that apply to local datapaths only:\n" + "\nFor local datapaths only:\n" " adddp nl:DP_ID add a new local datapath DP_ID\n" " deldp nl:DP_ID delete local datapath DP_ID\n" " addif nl:DP_ID IFACE add IFACE as a port on DP_ID\n" " delif nl:DP_ID IFACE delete IFACE as a port on DP_ID\n" " monitor nl:DP_ID print packets received\n" - " benchmark-nl nl:DP_ID N SIZE send N packets of SIZE bytes\n" #endif - "\nCommands that apply to local datapaths and remote switches:\n" + "\nFor local datapaths and remote switches:\n" " show SWITCH show information\n" " dump-tables SWITCH print table stats\n" " dump-ports SWITCH print port statistics\n" @@ -178,17 +199,23 @@ usage(void) " dump-flows SWITCH FLOW print matching FLOWs\n" " dump-aggregate SWITCH print aggregate flow statistics\n" " dump-aggregate SWITCH FLOW print aggregate stats for FLOWs\n" + " add-flow SWITCH FLOW add flow described by FLOW\n" " add-flows SWITCH FILE add flows from FILE\n" " del-flows SWITCH FLOW delete matching FLOWs\n" #ifdef HAVE_CURSES " table-mtr SWITCH monitor table for FLOWs\n" " table-mtr SWITCH FLOW monitor table \n" #endif + "\nFor local datapaths, remote switches, and controllers:\n" + " probe VCONN probe whether VCONN is up\n" + " ping VCONN [N] latency of N-byte echos\n" + " benchmark VCONN N COUNT bandwidth of COUNT N-byte echos\n" "where each SWITCH is an active OpenFlow connection method.\n", program_name, program_name); vconn_usage(true, false); printf("\nOptions:\n" - " -v, --verbose=MODULE:FACILITY:LEVEL configure logging levels\n" + " -t, --timeout=SECS give up after SECS seconds\n" + " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set logging levels\n" " -v, --verbose set maximum verbosity level\n" " -h, --help display this help message\n" " -V, --version display version information\n"); @@ -318,86 +345,16 @@ static void do_monitor(int argc UNUSED, char *argv[], int indicator UNUSED) buffer_delete(b); } } - -#define BENCHMARK_INCR 100 - -static void do_benchmark_nl(int argc UNUSED, char *argv[], int indicator UNUSED) -{ - struct dpif dp; - uint32_t num_packets, i, milestone; - struct timeval start, end; - - open_nl_vconn(argv[1], false, &dp); - num_packets = atoi(argv[2]); - milestone = BENCHMARK_INCR; - run(dpif_benchmark_nl(&dp, num_packets, atoi(argv[3])), "benchmark_nl"); - if (gettimeofday(&start, NULL) == -1) { - run(errno, "gettimeofday"); - } - for (i = 0; i < num_packets;i++) { - struct buffer *b; - run(dpif_recv_openflow(&dp, &b, true), "dpif_recv_openflow"); - if (i == milestone) { - gettimeofday(&end, NULL); - printf("%u packets received in %f ms\n", - BENCHMARK_INCR, - (1000*(double)(end.tv_sec - start.tv_sec)) - + (.001*(end.tv_usec - start.tv_usec))); - milestone += BENCHMARK_INCR; - start = end; - } - buffer_delete(b); - } - gettimeofday(&end, NULL); - printf("%u packets received in %f ms\n", - i - (milestone - BENCHMARK_INCR), - (1000*(double)(end.tv_sec - start.tv_sec)) - + (.001*(end.tv_usec - start.tv_usec))); - - dpif_close(&dp); -} #endif /* HAVE_NETLINK */ - + /* Generic commands. */ -static uint32_t -random_xid(void) -{ - static bool inited = false; - if (!inited) { - struct timeval tv; - inited = true; - if (gettimeofday(&tv, NULL) < 0) { - fatal(errno, "gettimeofday"); - } - srand(tv.tv_sec ^ tv.tv_usec); - } - return rand(); -} - -static void * -alloc_openflow_buffer(size_t openflow_len, uint8_t type, - struct buffer **bufferp) -{ - struct buffer *buffer; - struct ofp_header *oh; - - buffer = *bufferp = buffer_new(openflow_len); - oh = buffer_put_uninit(buffer, openflow_len); - memset(oh, 0, openflow_len); - oh->version = OFP_VERSION; - oh->type = type; - oh->length = 0; - oh->xid = random_xid(); - return oh; -} - static void * alloc_stats_request(size_t body_len, uint16_t type, struct buffer **bufferp) { struct ofp_stats_request *rq; - rq = alloc_openflow_buffer((offsetof(struct ofp_stats_request, body) - + body_len), OFPT_STATS_REQUEST, bufferp); + rq = make_openflow((offsetof(struct ofp_stats_request, body) + + body_len), OFPT_STATS_REQUEST, bufferp); rq->type = htons(type); rq->flags = htons(0); return rq->body; @@ -406,57 +363,36 @@ alloc_stats_request(size_t body_len, uint16_t type, struct buffer **bufferp) static void send_openflow_buffer(struct vconn *vconn, struct buffer *buffer) { - struct ofp_header *oh; - - oh = buffer_at_assert(buffer, 0, sizeof *oh); - oh->length = htons(buffer->size); - + update_openflow_length(buffer); run(vconn_send_block(vconn, buffer), "failed to send packet to switch"); } -static struct buffer * -transact_openflow(struct vconn *vconn, struct buffer *request) -{ - uint32_t send_xid = ((struct ofp_header *) request->data)->xid; - - send_openflow_buffer(vconn, request); - for (;;) { - uint32_t recv_xid; - struct buffer *reply; - - run(vconn_recv_block(vconn, &reply), "OpenFlow packet receive failed"); - recv_xid = ((struct ofp_header *) reply->data)->xid; - if (send_xid == recv_xid) { - return reply; - } - - VLOG_DBG("received reply with xid %08"PRIx32" != expected %08"PRIx32, - recv_xid, send_xid); - buffer_delete(reply); - } -} - static void -dump_transaction(const char *vconn_name, struct buffer *request, int indicator) +dump_transaction(const char *vconn_name, struct buffer *request) { struct vconn *vconn; struct buffer *reply; + update_openflow_length(request); if (indicator) { #ifdef HAVE_CURSES - runw(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + runw(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + runw(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name); #endif } - else run(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + else { + run(vconn_open_block(vconn_name, &vconn), "connecting to %s", vconn_name); + run(vconn_transact(vconn, request, &reply), "talking to %s", vconn_name); + } - reply = transact_openflow(vconn, request); if (indicator){ #ifdef HAVE_CURSES - ofp_printw(stdout, reply->data, reply->size, 1); + ofp_printw(stdout, reply->data, reply->size, 1); #endif } - else - ofp_print(stdout, reply->data, reply->size, 1); + else { + ofp_print(stdout, reply->data, reply->size, 1); + } vconn_close(vconn); } @@ -464,8 +400,8 @@ static void dump_trivial_transaction(const char *vconn_name, uint8_t request_type, int indicator) { struct buffer *request; - alloc_openflow_buffer(sizeof(struct ofp_header), request_type, &request); - dump_transaction(vconn_name, request, indicator); + make_openflow(sizeof(struct ofp_header), request_type, &request); + dump_transaction(vconn_name, request); } static void @@ -573,33 +509,77 @@ str_to_ip(const char *str, uint32_t *ip) } static void -str_to_action(const char *str, struct ofp_action *action) +str_to_action(char *str, struct ofp_action *action, int *n_actions) { uint16_t port; + int i; + int max_actions = *n_actions; + char *act, *arg; + char *saveptr = NULL; + + memset(action, 0, sizeof(*action) * max_actions); + for (i=0, act = strtok_r(str, ", \t\r\n", &saveptr); + itype = OFPAT_OUTPUT; - action->arg.output.port = htons(port); + *n_actions = i; } static void -str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, - uint8_t *table_idx, uint16_t *priority) +str_to_flow(char *string, struct ofp_match *match, + struct ofp_action *action, int *n_actions, uint8_t *table_idx, + uint16_t *priority, uint16_t *max_idle) { struct field { const char *name; @@ -624,7 +604,7 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, char *name, *value; uint32_t wildcards; - bool got_action = false; + char *act_str; if (table_idx) { *table_idx = 0xff; @@ -632,6 +612,25 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, if (priority) { *priority = OFP_DEFAULT_PRIORITY; } + if (max_idle) { + *max_idle = DEFAULT_MAX_IDLE; + } + if (action) { + act_str = strstr(string, "action"); + if (!act_str) { + fatal(0, "must specify an action"); + } + *(act_str-1) = '\0'; + + act_str = strchr(act_str, '='); + if (!act_str) { + fatal(0, "must specify an action"); + } + + act_str++; + + str_to_action(act_str, action, n_actions); + } memset(match, 0, sizeof *match); wildcards = OFPFW_ALL; for (name = strtok(string, "="), value = strtok(NULL, " \t\r\n"); @@ -641,12 +640,6 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, const struct field *f; void *data; - if (action && !strcmp(name, "action")) { - got_action = true; - str_to_action(value, action); - continue; - } - if (table_idx && !strcmp(name, "table")) { *table_idx = atoi(value); continue; @@ -657,6 +650,11 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, continue; } + if (max_idle && !strcmp(name, "max_idle")) { + *max_idle = atoi(value); + continue; + } + for (f = fields; f < &fields[ARRAY_SIZE(fields)]; f++) { if (!strcmp(f->name, name)) { goto found; @@ -695,9 +693,6 @@ str_to_flow(char *string, struct ofp_match *match, struct ofp_action *action, if (name && !value) { fatal(0, "field %s missing value", name); } - if (action && !got_action) { - fatal(0, "must specify an action"); - } match->wildcards = htons(wildcards); } @@ -707,8 +702,8 @@ static void do_dump_flows(int argc, char *argv[], int indicator) struct buffer *request; req = alloc_stats_request(sizeof *req, OFPST_FLOW, &request); - str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL, &req->table_id, - NULL); + str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL, 0, + &req->table_id, NULL, NULL); memset(req->pad, 0, sizeof req->pad); dump_stats_transaction(argv[1], request, indicator); @@ -720,14 +715,43 @@ static void do_dump_aggregate(int argc, char *argv[], int indicator) struct buffer *request; req = alloc_stats_request(sizeof *req, OFPST_AGGREGATE, &request); - str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL, &req->table_id, - NULL); + str_to_flow(argc > 2 ? argv[2] : "", &req->match, NULL, 0, + &req->table_id, NULL, NULL); memset(req->pad, 0, sizeof req->pad); dump_stats_transaction(argv[1], request, indicator); } -static void do_add_flows(int argc, char *argv[], int indicator UNUSED) +static void do_add_flow(int argc, char *argv[]) +{ + struct vconn *vconn; + struct buffer *buffer; + struct ofp_flow_mod *ofm; + uint16_t priority, max_idle; + size_t size; + int n_actions = MAX_ADD_ACTS; + + run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + + /* Parse and send. */ + size = sizeof *ofm + (sizeof ofm->actions[0] * MAX_ADD_ACTS); + ofm = make_openflow(size, OFPT_FLOW_MOD, &buffer); + str_to_flow(argv[2], &ofm->match, &ofm->actions[0], &n_actions, + NULL, &priority, &max_idle); + ofm->command = htons(OFPFC_ADD); + ofm->max_idle = htons(max_idle); + ofm->buffer_id = htonl(UINT32_MAX); + ofm->priority = htons(priority); + ofm->reserved = htonl(0); + + /* xxx Should we use the buffer library? */ + buffer->size -= (MAX_ADD_ACTS - n_actions) * sizeof ofm->actions[0]; + + send_openflow_buffer(vconn, buffer); + vconn_close(vconn); +} + +static void do_add_flows(int argc, char *argv[]) { struct vconn *vconn; @@ -743,8 +767,9 @@ static void do_add_flows(int argc, char *argv[], int indicator UNUSED) while (fgets(line, sizeof line, file)) { struct buffer *buffer; struct ofp_flow_mod *ofm; - uint16_t priority; + uint16_t priority, max_idle; size_t size; + int n_actions = MAX_ADD_ACTS; char *comment; @@ -760,15 +785,19 @@ static void do_add_flows(int argc, char *argv[], int indicator UNUSED) } /* Parse and send. */ - size = sizeof *ofm + sizeof ofm->actions[0]; - ofm = alloc_openflow_buffer(size, OFPT_FLOW_MOD, &buffer); - str_to_flow(line, &ofm->match, &ofm->actions[0], NULL, &priority); + size = sizeof *ofm + (sizeof ofm->actions[0] * MAX_ADD_ACTS); + ofm = make_openflow(size, OFPT_FLOW_MOD, &buffer); + str_to_flow(line, &ofm->match, &ofm->actions[0], &n_actions, + NULL, &priority, &max_idle); ofm->command = htons(OFPFC_ADD); - ofm->max_idle = htons(50); + ofm->max_idle = htons(max_idle); ofm->buffer_id = htonl(UINT32_MAX); ofm->priority = htons(priority); ofm->reserved = htonl(0); + /* xxx Should we use the buffer library? */ + buffer->size -= (MAX_ADD_ACTS - n_actions) * sizeof ofm->actions[0]; + send_openflow_buffer(vconn, buffer); } vconn_close(vconn); @@ -787,8 +816,9 @@ static void do_del_flows(int argc, char *argv[], int indicator UNUSED) /* Parse and send. */ size = sizeof *ofm; - ofm = alloc_openflow_buffer(size, OFPT_FLOW_MOD, &buffer); - str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, NULL, &priority); + ofm = make_openflow(size, OFPT_FLOW_MOD, &buffer); + str_to_flow(argc > 2 ? argv[2] : "", &ofm->match, NULL, 0, NULL, + &priority, NULL); ofm->command = htons(OFPFC_DELETE); ofm->max_idle = htons(0); ofm->buffer_id = htonl(UINT32_MAX); @@ -807,7 +837,114 @@ do_dump_ports(int argc, char *argv[], int indicator) dump_trivial_stats_transaction(argv[1], OFPST_PORT, indicator); } -static void do_help(int argc UNUSED, char *argv[] UNUSED, int indicator UNUSED) +static void +do_probe(int argc, char *argv[]) +{ + struct buffer *request; + struct vconn *vconn; + struct buffer *reply; + + make_openflow(sizeof(struct ofp_header), OFPT_ECHO_REQUEST, &request); + run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + run(vconn_transact(vconn, request, &reply), "talking to %s", argv[1]); + if (reply->size != request->size) { + fatal(0, "reply does not match request"); + } + buffer_delete(reply); + vconn_close(vconn); +} + +static void +do_ping(int argc, char *argv[]) +{ + size_t max_payload = 65535 - sizeof(struct ofp_header); + unsigned int payload; + struct vconn *vconn; + int i; + + payload = argc > 2 ? atoi(argv[2]) : 64; + if (payload > max_payload) { + fatal(0, "payload must be between 0 and %zu bytes", max_payload); + } + + run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + for (i = 0; i < 10; i++) { + struct timeval start, end; + struct buffer *request, *reply; + struct ofp_header *rq_hdr, *rpy_hdr; + + rq_hdr = make_openflow(sizeof(struct ofp_header) + payload, + OFPT_ECHO_REQUEST, &request); + random_bytes(rq_hdr + 1, payload); + + gettimeofday(&start, NULL); + run(vconn_transact(vconn, buffer_clone(request), &reply), "transact"); + gettimeofday(&end, NULL); + + rpy_hdr = reply->data; + if (reply->size != request->size + || memcmp(rpy_hdr + 1, rq_hdr + 1, payload) + || rpy_hdr->xid != rq_hdr->xid + || rpy_hdr->type != OFPT_ECHO_REPLY) { + printf("Reply does not match request. Request:\n"); + ofp_print(stdout, request, request->size, 2); + printf("Reply:\n"); + ofp_print(stdout, reply, reply->size, 2); + } + printf("%d bytes from %s: xid=%08"PRIx32" time=%.1f ms\n", + reply->size - sizeof *rpy_hdr, argv[1], rpy_hdr->xid, + (1000*(double)(end.tv_sec - start.tv_sec)) + + (.001*(end.tv_usec - start.tv_usec))); + buffer_delete(request); + buffer_delete(reply); + } + vconn_close(vconn); +} + +static void +do_benchmark(int argc, char *argv[]) +{ + size_t max_payload = 65535 - sizeof(struct ofp_header); + struct timeval start, end; + unsigned int payload_size, message_size; + struct vconn *vconn; + double duration; + int count; + int i; + + payload_size = atoi(argv[2]); + if (payload_size > max_payload) { + fatal(0, "payload must be between 0 and %zu bytes", max_payload); + } + message_size = sizeof(struct ofp_header) + payload_size; + + count = atoi(argv[3]); + + printf("Sending %d packets * %u bytes (with header) = %u bytes total\n", + count, message_size, count * message_size); + + run(vconn_open_block(argv[1], &vconn), "connecting to %s", argv[1]); + gettimeofday(&start, NULL); + for (i = 0; i < count; i++) { + struct buffer *request, *reply; + struct ofp_header *rq_hdr; + + rq_hdr = make_openflow(message_size, OFPT_ECHO_REQUEST, &request); + memset(rq_hdr + 1, 0, payload_size); + run(vconn_transact(vconn, request, &reply), "transact"); + buffer_delete(reply); + } + gettimeofday(&end, NULL); + vconn_close(vconn); + + duration = ((1000*(double)(end.tv_sec - start.tv_sec)) + + (.001*(end.tv_usec - start.tv_usec))); + printf("Finished in %.1f ms (%.0f packets/s) (%.0f bytes/s)\n", + duration, count / (duration / 1000.0), + count * message_size / (duration / 1000.0)); +} + +static void do_help(int argc UNUSED, char *argv[] UNUSED) { usage(); } @@ -879,7 +1016,6 @@ static struct command all_commands[] = { { "deldp", 1, 1, do_del_dp }, { "addif", 2, 2, do_add_port }, { "delif", 2, 2, do_del_port }, - { "benchmark-nl", 3, 3, do_benchmark_nl }, #endif { "show", 1, 1, do_show }, @@ -889,11 +1025,15 @@ static struct command all_commands[] = { { "dump-tables", 1, 1, do_dump_tables }, { "dump-flows", 1, 2, do_dump_flows }, { "dump-aggregate", 1, 2, do_dump_aggregate }, + { "add-flow", 2, 2, do_add_flow }, { "add-flows", 2, 2, do_add_flows }, { "del-flows", 1, 2, do_del_flows }, { "dump-ports", 1, 1, do_dump_ports }, #ifdef HAVE_CURSES { "table-mtr", 1, 2, do_table_monitoring }, #endif + { "probe", 1, 1, do_probe }, + { "ping", 1, 2, do_ping }, + { "benchmark", 3, 3, do_benchmark }, { NULL, 0, 0, NULL }, }; diff --git a/utilities/ofp-discover.8.in b/utilities/ofp-discover.8.in new file mode 100644 index 0000000..19923a3 --- /dev/null +++ b/utilities/ofp-discover.8.in @@ -0,0 +1,131 @@ +.TH ofp\-discover 8 "May 2008" "OpenFlow" "OpenFlow Manual" + +.SH NAME +ofp\-discover \- controller discovery utility + +.SH SYNOPSIS +.B ofp\-discover +[\fIoptions\fR] \fInetdev\fR [\fInetdev\fR...] + +.SH DESCRIPTION +The \fBofp\-discover\fR program attempts to discover the location of +an OpenFlow controller on one of the network devices listed on the +command line. It repeatedly broadcasts a DHCP request with vendor +class identifier \fBOpenFlow\fR on each network device until it +receives an acceptable DHCP response. It will accept any valid DHCP +reply that has the same vendor class identifier and includes a +vendor-specific option with code 1 whose contents are a string +specifying the location of the controller in the same format used on +the \fBsecchan\fR command line (e.g. \fBssl:192.168.0.1\fR). + +When \fBofp\-discover\fR receives an acceptable response, it prints +the details of the response on \fBstdout\fR. Then, by default, it +configures the network device on which the response was received with +the received IP address, netmask, and default gateway, and detaches +itself to the background. + +.SH OPTIONS +.TP +\fB--accept-vconn=\fIregex\fR +By default, \fBofp\-discover\fR accepts any controller location +advertised over DHCP. With this option, only controllers whose names +match POSIX extended regular expression \fIregex\fR will be accepted. +Specifying \fBssl:.*\fR for \fIregex\fR, for example, would cause only +SSL controller connections to be accepted. + +The \fIregex\fR is implicitly anchored at the beginning of the +controller location string, as if it begins with \fB^\fR. + +.TP +\fB--exit-without-bind\fR +By default, \fBofp\-discover\fR binds the network device that receives +the first acceptable response to the IP address received over DHCP. +With this option, the configuration of the network device is not +changed at all, except to bring it up if it is initially down, and +\fBofp\-discover\fR will exit immediately after it receives an +acceptable DHCP response. + +This option is mutually exclusive with \fB--exit-after-bind\fR and +\fB--no-detach\fR. + +.TP +\fB--exit-after-bind\fR +By default, after it receives an acceptable DHCP response, +\fBofp\-discover\fR detaches itself from the foreground session and +runs in the background maintaining the DHCP lease as necessary. With +this option, \fBofp\-discover\fR will exit immediately after it +receives an acceptable DHCP response and configures the network device +with the received IP address. The address obtained via DHCP could +therefore be used past the expiration of its lease. + +This option is mutually exclusive with \fB--exit-without-bind\fR and +\fB--no-detach\fR. + +.TP +\fB--no-detach\fR +By default, \fBofp\-discover\fR runs in the foreground until it obtains +an acceptable DHCP response, then it detaches itself from the +foreground session and run as a background process. This option +prevents \fBofp\-discover\fR from detaching, causing it to run in the +foreground even after it obtains a DHCP response. + +This option is mutually exclusive with \fB--exit-without-bind\fR and +\fB--exit-after-bind\fR. + +.TP +\fB-P\fR[\fIpidfile\fR], \fB--pidfile\fR[\fB=\fIpidfile\fR] +Causes a file (by default, \fBofp\-discover.pid\fR) to be created indicating +the PID of the running process. If \fIpidfile\fR is not specified, or +if it does not begin with \fB/\fR, then it is created in +\fB@rundir@\fR. + +The \fIpidfile\fR is created when \fBofp\-discover\fR detaches, so +this this option has no effect when one of \fB--exit-without-bind\fR, +\fB--exit-after-bind\fR, or \fB--no-detach\fR is also given. + +.TP +.BR \-h ", " \-\^\-help +Prints a brief help message to the console. + +.TP +\fB-v\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--verbose=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] +Sets the logging level for \fImodule\fR in \fIfacility\fR to +\fIlevel\fR. The \fImodule\fR may be any valid module name (as +displayed by the \fB--list\fR action on \fBvlogconf\fR(8)), or the +special name \fBANY\fR to set the logging levels for all modules. The +\fIfacility\fR may be \fBsyslog\fR or \fBconsole\fR to set the levels +for logging to the system log or to the console, respectively, or +\fBANY\fR to set the logging levels for both facilities. If it is +omitted, \fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must +be one of \fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating +the minimum severity of a message for it to be logged. If it is +omitted, \fIlevel\fR defaults to \fBdbg\fR. + +.TP +\fB-v\fR, \fB--verbose\fR +Sets the maximum logging verbosity level, equivalent to +\fB--verbose=ANY:ANY:dbg\fR. + +.TP +.BR \-V ", " \-\^\-version +Prints version information to the console. + +.SH BUGS + +If the network devices specified on the command line have been added +to an OpenFlow switch with \fBdpctl addif\fR, then controller +discovery will fail because \fBofp\-discover\fR will not be able to +see DHCP responses, even though tools such as \fBtcpdump\fR(8) and +\fBwireshark\fR(1) can see them on the wire. This is because of the +structure of the Linux kernel networking stack, which hands packets +first to programs that listen for all arriving packets, then to +OpenFlow, then to programs that listen for a specific kind of packet. +OpenFlow consumes all the packets handed to it, so tools like +\fBtcpdump\fR that look at all packets will see packets arriving on +OpenFlow interfaces, but \fRofp\-discover\fR, which listens only for +arriving IP packets, will not. + +.SH "SEE ALSO" + +.BR secchan (8), +.BR ofp-pki (8) diff --git a/utilities/ofp-discover.c b/utilities/ofp-discover.c new file mode 100644 index 0000000..7dfc56a --- /dev/null +++ b/utilities/ofp-discover.c @@ -0,0 +1,411 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include "command-line.h" +#include "daemon.h" +#include "dhcp-client.h" +#include "dhcp.h" +#include "dynamic-string.h" +#include "fatal-signal.h" +#include "netdev.h" +#include "poll-loop.h" +#include "util.h" +#include "vlog-socket.h" + +#include "vlog.h" +#define THIS_MODULE VLM_ofp_discover + +struct iface { + const char *name; + struct dhclient *dhcp; +}; + +/* The interfaces that we serve. */ +static struct iface *ifaces; +static int n_ifaces; + +/* --accept-vconn: Regular expression specifying the class of controller vconns + * that we will accept during autodiscovery. */ +static const char *accept_controller_re = ".*"; +static regex_t accept_controller_regex; + +/* --exit-without-bind: Exit after discovering the controller, without binding + * the network device to an IP address? */ +static bool exit_without_bind; + +/* --exit-after-bind: Exit after discovering the controller, after binding the + * network device to an IP address? */ +static bool exit_after_bind; + +static bool iface_init(struct iface *, const char *netdev_name); +static void release_ifaces(void *aux UNUSED); + +static void parse_options(int argc, char *argv[]); +static void usage(void) NO_RETURN; + +static void modify_dhcp_request(struct dhcp_msg *, void *aux); +static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux); + +int +main(int argc, char *argv[]) +{ + int retval; + int i; + + set_program_name(argv[0]); + vlog_init(); + parse_options(argc, argv); + + argc -= optind; + argv += optind; + if (argc < 1) { + fatal(0, "need at least one non-option argument; " + "use --help for usage"); + } + + ifaces = xmalloc(argc * sizeof *ifaces); + n_ifaces = 0; + for (i = 0; i < argc; i++) { + if (iface_init(&ifaces[n_ifaces], argv[i])) { + n_ifaces++; + } + } + if (!n_ifaces) { + fatal(0, "failed to initialize any DHCP clients"); + } + + for (i = 0; i < n_ifaces; i++) { + struct iface *iface = &ifaces[i]; + dhclient_init(iface->dhcp, 0); + } + fatal_signal_add_hook(release_ifaces, NULL); + + retval = regcomp(&accept_controller_regex, accept_controller_re, + REG_NOSUB | REG_EXTENDED); + if (retval) { + size_t length = regerror(retval, &accept_controller_regex, NULL, 0); + char *buffer = xmalloc(length); + regerror(retval, &accept_controller_regex, buffer, length); + fatal(0, "%s: %s", accept_controller_re, buffer); + } + + retval = vlog_server_listen(NULL, NULL); + if (retval) { + fatal(retval, "Could not listen for vlog connections"); + } + + signal(SIGPIPE, SIG_IGN); + for (;;) { + fatal_signal_block(); + for (i = 0; i < n_ifaces; i++) { + struct iface *iface = &ifaces[i]; + dhclient_run(iface->dhcp); + if (dhclient_changed(iface->dhcp)) { + bool is_bound = dhclient_is_bound(iface->dhcp); + int j; + + /* Configure network device. */ + if (!exit_without_bind) { + dhclient_configure_netdev(iface->dhcp); + dhclient_update_resolv_conf(iface->dhcp); + } + + if (is_bound) { + static bool detached = false; + struct ds ds; + + /* Disable timeout, since discovery was successful. */ + alarm(0); + + /* Print discovered parameters. */ + ds_init(&ds); + dhcp_msg_to_string(dhclient_get_config(iface->dhcp), + true, &ds); + fputs(ds_cstr(&ds), stdout); + putchar('\n'); + fflush(stdout); + ds_destroy(&ds); + + /* Exit if the user requested it. */ + if (exit_without_bind) { + VLOG_DBG("exiting because of successful binding on %s " + "and --exit-without-bind specified", + iface->name); + exit(0); + } + if (exit_after_bind) { + VLOG_DBG("exiting because of successful binding on %s " + "and --exit-after-bind specified", + iface->name); + exit(0); + } + + /* Detach into background, if we haven't already. */ + if (!detached) { + detached = true; + daemonize(); + } + } + + /* We only want an address on a single one of our interfaces. + * So: if we have an address on this interface, stop looking + * for one on the others; if we don't have an address on this + * interface, start looking everywhere. */ + for (j = 0; j < n_ifaces; j++) { + struct iface *if2 = &ifaces[j]; + if (iface != if2) { + if (is_bound) { + dhclient_release(if2->dhcp); + } else { + dhclient_init(if2->dhcp, 0); + } + } + } + } + } + for (i = 0; i < n_ifaces; i++) { + struct iface *iface = &ifaces[i]; + dhclient_wait(iface->dhcp); + } + fatal_signal_unblock(); + poll_block(); + } + + return 0; +} + +static bool +iface_init(struct iface *iface, const char *netdev_name) +{ + int retval; + + iface->name = netdev_name; + iface->dhcp = NULL; + + if (exit_after_bind) { + /* Bring this interface up permanently, so that the bound address + * persists past program termination. */ + struct netdev *netdev; + + retval = netdev_open(iface->name, NETDEV_ETH_TYPE_NONE, &netdev); + if (retval) { + error(retval, "Could not open %s device", iface->name); + return false; + } + retval = netdev_turn_flags_on(netdev, NETDEV_UP, true); + if (retval) { + error(retval, "Could not bring %s device up", iface->name); + return false; + } + netdev_close(netdev); + } + + retval = dhclient_create(iface->name, modify_dhcp_request, + validate_dhcp_offer, NULL, &iface->dhcp); + if (retval) { + error(retval, "%s: failed to initialize DHCP client", iface->name); + return false; + } + + return true; +} + +static void +release_ifaces(void *aux UNUSED) +{ + int i; + + for (i = 0; i < n_ifaces; i++) { + struct dhclient *dhcp = ifaces[i].dhcp; + dhclient_release(dhcp); + if (dhclient_changed(dhcp)) { + dhclient_configure_netdev(dhcp); + } + } +} + +static void +modify_dhcp_request(struct dhcp_msg *msg, void *aux) +{ + dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow"); +} + +static bool +validate_dhcp_offer(const struct dhcp_msg *msg, void *aux) +{ + char *vconn_name; + bool accept; + + vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN); + if (!vconn_name) { + VLOG_WARN("rejecting DHCP offer missing controller vconn"); + return false; + } + accept = !regexec(&accept_controller_regex, vconn_name, 0, NULL, 0); + free(vconn_name); + return accept; +} + +static void +parse_options(int argc, char *argv[]) +{ + enum { + OPT_ACCEPT_VCONN = UCHAR_MAX + 1, + OPT_EXIT_WITHOUT_BIND, + OPT_EXIT_AFTER_BIND, + OPT_NO_DETACH, + }; + static struct option long_options[] = { + {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN}, + {"exit-without-bind", no_argument, 0, OPT_EXIT_WITHOUT_BIND}, + {"exit-after-bind", no_argument, 0, OPT_EXIT_AFTER_BIND}, + {"no-detach", no_argument, 0, OPT_NO_DETACH}, + {"timeout", required_argument, 0, 't'}, + {"pidfile", optional_argument, 0, 'P'}, + {"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); + bool detach_after_bind = true; + + for (;;) { + unsigned long int timeout; + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case OPT_ACCEPT_VCONN: + accept_controller_re = (optarg[0] == '^' + ? optarg + : xasprintf("^%s", optarg)); + break; + + case OPT_EXIT_WITHOUT_BIND: + exit_without_bind = true; + break; + + case OPT_EXIT_AFTER_BIND: + exit_after_bind = true; + break; + + case OPT_NO_DETACH: + detach_after_bind = false; + break; + + case 'P': + set_pidfile(optarg); + break; + + case 't': + timeout = strtoul(optarg, NULL, 10); + if (timeout <= 0) { + fatal(0, "value %s on -t or --timeout is not at least 1", + optarg); + } else if (timeout < UINT_MAX) { + /* Add 1 because historical implementations allow an alarm to + * occur up to a second early. */ + alarm(timeout + 1); + } else { + alarm(UINT_MAX); + } + signal(SIGALRM, SIG_DFL); + 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); + + if ((exit_without_bind + exit_after_bind + !detach_after_bind) > 1) { + fatal(0, "--exit-without-bind, --exit-after-bind, and --no-detach " + "are mutually exclusive"); + } + if (detach_after_bind) { + set_detach(); + } +} + +static void +usage(void) +{ + printf("%s: a tool for discovering OpenFlow controllers.\n" + "usage: %s [OPTIONS] NETDEV [NETDEV...]\n" + "where each NETDEV is a network device on which to perform\n" + "controller discovery.\n" + "\nOrdinarily, ofp-discover runs in the foreground until it\n" + "obtains an IP address and discovers an OpenFlow controller via\n" + "DHCP, then it prints information about the controller to stdout\n" + "and detaches to the background to maintain the IP address lease.\n" + "\nNetworking options:\n" + " --accept-vconn=REGEX accept matching discovered controllers\n" + " --exit-without-bind exit after discovery, without binding\n" + " --exit-after-bind exit after discovery, after binding\n" + " --no-detach do not detach after discovery\n" + "\nOther options:\n" + " -t, --timeout=SECS give up discovery after SECS seconds\n" + " -P, --pidfile[=FILE] create pidfile (default: %s/%s.pid)\n" + " -v, --verbose=MODULE[:FACILITY[:LEVEL]] set 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, RUNDIR, program_name); + exit(EXIT_SUCCESS); +} diff --git a/utilities/ofp-kill.8.in b/utilities/ofp-kill.8.in new file mode 100644 index 0000000..122ff54 --- /dev/null +++ b/utilities/ofp-kill.8.in @@ -0,0 +1,65 @@ +.TH ofp\-kill 8 "May 2008" "OpenFlow" "OpenFlow Manual" + +.SH NAME +ofp\-kill \- kills processes given their pidfiles + +.SH SYNOPSIS +.B ofp\-kill +[\fIoptions\fR] \fIpidfile\fR [\fIpidfile\fR...] + +.SH DESCRIPTION +The \fBofp\-kill\fR program reads each \fIpidfile\fR specified on the +command line and sends a signal to the program associated with it, if +any. It reads one line of text from \fIpidfile\fR, which must contain +the PID of the process to kill as a text string. It then uses +\fBfcntl\fR(2) to verify that a process with the PID from the file +owns a lock on \fIpidfile\fR before it sends the signal. + +A \fIpidfile\fR whose name begins with \fB/\fR is used literally. +Otherwise, \fB@rundir@/\fR is prefixed. + +This program exists for use by \fBofp\-switch\-setup\fR, which cannot +easily implement its functionality since Perl has no portable +interface to \fBfcntl\fR-based file locking. + +.SH OPTIONS +.TP +\fB-s \fInumber\fR|\fIname\fR, \fB\-\^\-signal=\fInumber\fR|\fIname\fR +Sets the signal to be sent to each process. Signals may be given by +number (e.g. \fB1\fR) or by name (e.g. \fBHUP\fR or \fBSIGHUP\fR). +By default, \fBSIGTERM\fR is sent. + +.TP +\fB-f\fR, \fB\-\^\-force\fR +Causes \fBofp\-kill\fR to ignore all errors without printing a message +to \fBstderr\fR, and to exit with return code 0. + +.TP +.BR \-h ", " \-\^\-help +Prints a brief help message to the console. + +.TP +.BR \-V ", " \-\^\-version +Prints version information to the console. + +.SH "EXIT CODE" + +Without \fB-f\fR or \fB\-\^\-force\fR, \fBofp\-kill\fR exits with +status 0 if at least one \fIpidfile\fR was given and the process +represented by every \fIpidfile\fR was signaled successfully, +otherwise with status 1. + +With \fB-f\fR or \fB\-\^\-force\fR, \fBofp\-kill\fR always exits with +status 0. + +.SH BUGS + +There is a race between verifying the lock on \fIpidfile\fR and +actually killing the process. + +\fBofp\-kill\fR does not wait for the signaled processes to die before +exiting. + +.SH "SEE ALSO" + +.BR ofp\-switch\-setup (8) diff --git a/utilities/ofp-kill.c b/utilities/ofp-kill.c new file mode 100644 index 0000000..9647f46 --- /dev/null +++ b/utilities/ofp-kill.c @@ -0,0 +1,258 @@ +/* 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 +#include +#include +#include +#include +#include +#include +#include +#include "command-line.h" +#include "daemon.h" +#include "util.h" + +/* -s, --signal: signal to send. */ +static int sig_nr = SIGTERM; + +/* -f, --force: ignore errors. */ +static bool force; + +static void cond_error(int err_no, const char *, ...) PRINTF_FORMAT(2, 3); +static bool kill_pidfile(const char *pidfile, FILE *); + +static void parse_options(int argc, char *argv[]); +static void usage(void); + +int +main(int argc, char *argv[]) +{ + bool ok = true; + int i; + + set_program_name(argv[0]); + parse_options(argc, argv); + + argc -= optind; + argv += optind; + if (argc < 1) { + if (!force) { + fatal(0, "need at least one non-option argument; " + "use --help for usage"); + } + } + + for (i = 0; i < argc; i++) { + char *pidfile; + FILE *file; + + pidfile = make_pidfile_name(argv[i]); + file = fopen(pidfile, "r"); + if (!file) { + ok = false; + cond_error(errno, "%s: open", pidfile); + } else { + ok = kill_pidfile(argv[i], file) && ok; + fclose(file); + } + free(pidfile); + } + + return ok || force ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static bool +kill_pidfile(const char *pidfile, FILE *file) +{ + char line[128]; + struct flock lck; + + lck.l_type = F_WRLCK; + lck.l_whence = SEEK_SET; + lck.l_start = 0; + lck.l_len = 0; + if (fcntl(fileno(file), F_GETLK, &lck)) { + cond_error(errno, "%s: fcntl", pidfile); + return false; + } + + if (!fgets(line, sizeof line, file)) { + cond_error(errno, "%s: read", pidfile); + return false; + } + + if (lck.l_pid != strtoul(line, NULL, 10)) { + cond_error(errno, "l_pid (%ld) != %s pid (%s)", + (long int) lck.l_pid, pidfile, line); + return false; + } + + if (kill(lck.l_pid, sig_nr) < 0) { + cond_error(errno, "%s: kill(%ld)", pidfile, (long int) lck.l_pid); + return false; + } + + return true; +} + +static void +parse_options(int argc, char *argv[]) +{ + static struct option long_options[] = { + {"signal", required_argument, 0, 's'}, + {"force", no_argument, 0, 'f'}, + {"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 's': + if (atoi(optarg) || !strcmp(optarg, "0")) { + sig_nr = atoi(optarg); + } else { + struct signal_name { + const char *name; + int number; + }; + + static const struct signal_name signals[] = { +#define SIGNAL(NAME) { #NAME, NAME } + SIGNAL(SIGABRT), + SIGNAL(SIGALRM), + SIGNAL(SIGBUS), + SIGNAL(SIGCHLD), + SIGNAL(SIGCONT), + SIGNAL(SIGFPE), + SIGNAL(SIGHUP), + SIGNAL(SIGILL), + SIGNAL(SIGINT), + SIGNAL(SIGKILL), + SIGNAL(SIGPIPE), + SIGNAL(SIGQUIT), + SIGNAL(SIGSEGV), + SIGNAL(SIGSTOP), + SIGNAL(SIGTERM), + SIGNAL(SIGTSTP), + SIGNAL(SIGTTIN), + SIGNAL(SIGTTOU), + SIGNAL(SIGUSR1), + SIGNAL(SIGUSR2), +#ifdef SIGPOLL + SIGNAL(SIGPOLL), +#endif + SIGNAL(SIGPROF), + SIGNAL(SIGSYS), + SIGNAL(SIGTRAP), + SIGNAL(SIGURG), + SIGNAL(SIGVTALRM), + SIGNAL(SIGXCPU), + SIGNAL(SIGXFSZ), +#undef SIGNAL + }; + int i; + + for (i = 0; i < ARRAY_SIZE(signals); i++) { + const struct signal_name *s = &signals[i]; + if (!strcmp(optarg, s->name) + || !strcmp(optarg, s->name + 3)) { + sig_nr = s->number; + goto got_name; + } + } + fatal(0, "unknown signal \"%s\"", optarg); + got_name: ; + } + break; + + case 'f': + force = true; + break; + + case 'h': + usage(); + + case 'V': + printf("%s "VERSION" compiled "__DATE__" "__TIME__"\n", argv[0]); + exit(EXIT_SUCCESS); + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); +} + +static void +usage(void) +{ + printf("%s: kills a program using a pidfile\n" + "usage: %s [OPTIONS] PIDFILE [PIDFILE...]\n" + "where each PIDFILE is a pidfile created by an OpenFlow daemon.\n" + "\nOptions:\n" + " -s, --signal=NUMBER|NAME signal to send (default: TERM)\n" + " -f, --force ignore errors\n" + " -h, --help display this help message\n" + " -V, --version display version information\n", + program_name, program_name); + exit(EXIT_SUCCESS); +} + +static void +cond_error(int err_no, const char *format, ...) +{ + if (!force) { + va_list args; + + fprintf(stderr, "%s: ", program_name); + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + if (err_no != 0) + fprintf(stderr, " (%s)", strerror(err_no)); + putc('\n', stderr); + } +} diff --git a/utilities/vlogconf.8 b/utilities/vlogconf.8 index 8555fd5..74e8161 100644 --- a/utilities/vlogconf.8 +++ b/utilities/vlogconf.8 @@ -13,8 +13,8 @@ The available \fItarget\fR options are: The available \fIaction\fR options are: .br [\fB-l\fR | \fB--list\fR] [\fB-s\fR -\fImodule\fB:\fIfacility\fB:\fIlevel\fR | -\fB--set=\fImodule\fB:\fIfacility\fB:\fIlevel\fR] +\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] | +\fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]]] .SH DESCRIPTION The \fBvlogconf\fR program configures the logging system used by @@ -43,17 +43,19 @@ Print the list of known modules and their current logging levels to stdout. .TP -\fB-s\fR \fImodule\fB:\fIfacility\fB:\fIlevel\fR, \fB--set=\fImodule\fB:\fIfacility\fB:\fIlevel\fR +\fB-s\fR \fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]], \fB--set=\fImodule\fR[\fB:\fIfacility\fR[\fB:\fIlevel\fR]] Sets the logging level for \fImodule\fR in \fIfacility\fR to -\fIlevel\fR. The \fImodule\fR may be any valid module name (as displayed -by the \fB--list\fR option) or the special name \fBANY\fR to set the -logging levels for all modules. The \fIfacility\fR may be \fBsyslog\fR or -\fBconsole\fR to set the levels for logging to the system log or to -the console, respectively, or \fBANY\fR to set the logging levels for -both facilities. The \fIlevel\fR must be one of \fBemer\fR, \fBerr\fR, -\fBwarn\fR, or \fBdbg\fR, designating the minimum severity of a -message for it to be logged. +\fIlevel\fR. The \fImodule\fR may be any valid module name (as +displayed by the \fB--list\fR option) or the special name \fBANY\fR to +set the logging levels for all modules. The \fIfacility\fR may be +\fBsyslog\fR or \fBconsole\fR to set the levels for logging to the +system log or to the console, respectively, or \fBANY\fR to set the +logging levels for both facilities. If it is omitted, +\fIfacility\fR defaults to \fBANY\fR. The \fIlevel\fR must be one of +\fBemer\fR, \fBerr\fR, \fBwarn\fR, or \fBdbg\fR, designating the +minimum severity of a message for it to be logged. If it is omitted, +\fIlevel\fR defaults to \fBdbg\fR. .SH OPTIONS .TP diff --git a/utilities/vlogconf.c b/utilities/vlogconf.c index 9fbcc84..838597d 100644 --- a/utilities/vlogconf.c +++ b/utilities/vlogconf.c @@ -30,6 +30,7 @@ * advertising or publicity pertaining to the Software or any * derivatives without specific, written prior permission. */ +#include #include "vlog.h" #include @@ -55,11 +56,11 @@ usage(char *prog_name, int exit_code) " absolute path to a Unix domain socket\n" "Actions:\n" " -l, --list List current settings\n" - " -s, --set=MODULE:FACILITY:LEVEL\n" + " -s, --set=MODULE[:FACILITY[:LEVEL]]\n" " Set MODULE and FACILITY log level to LEVEL\n" " MODULE may be any valid module name or 'ANY'\n" - " FACILITY may be 'syslog' or 'console' or 'ANY'\n" - " LEVEL may be 'emer', 'err', 'warn', or 'dbg'\n" + " FACILITY may be 'syslog' or 'console' or 'ANY' (default)\n" + " LEVEL may be 'emer', 'err', 'warn', or 'dbg (default)'\n" " -h, --help Print this helpful information\n", prog_name); exit(exit_code);