From: Ben Pfaff Date: Wed, 8 Jul 2009 17:30:42 +0000 (-0700) Subject: Rename "secchan" to "ofproto" (library) and "ovs-openflowd" (program). X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=8cd4882fd5c3080816a070ad582ef06842f7c482;p=openvswitch Rename "secchan" to "ofproto" (library) and "ovs-openflowd" (program). These names are more meaningful, so we prefer them. --- diff --git a/Makefile.am b/Makefile.am index 59ffc193..18dbb53d 100644 --- a/Makefile.am +++ b/Makefile.am @@ -57,7 +57,7 @@ SUFFIXES = .in -e 's,[@]PERL[@],$(PERL),g' > $@ include lib/automake.mk -include secchan/automake.mk +include ofproto/automake.mk include utilities/automake.mk include tests/automake.mk include include/automake.mk diff --git a/README b/README index 3e986a48..4f5882b2 100644 --- a/README +++ b/README @@ -40,15 +40,15 @@ The main components of this distribution are: to be installed on a Citrix XenServer host as a drop-in replacement for its switch, with additional functionality. - * vlog-appctl, a utility that can control Open vSwitch daemons, + * ovs-appctl, a utility that can control Open vSwitch daemons, adjusting their logging levels among other uses. Open vSwitch also provides an OpenFlow implementation and tools for those interested in OpenFlow but not additional Open vSwitch features: - * secchan, a program that implements a simple OpenFlow switch - (without the special features provided by ovs-vswitchd) using - the same kernel module as ovs-vswitchd. + * ovs-openflowd, a program that implements a simple OpenFlow + switch (without the special features provided by ovs-vswitchd) + using the same kernel module as ovs-vswitchd. * ovs-controller, a simple OpenFlow controller. diff --git a/debian/control b/debian/control index a59dcd3a..13da9d7b 100644 --- a/debian/control +++ b/debian/control @@ -110,9 +110,9 @@ Architecture: any Recommends: openvswitch-switch Depends: ${shlibs:Depends}, ${misc:Depends} Description: Monitor utility for Open vSwitch switches - The ovs-monitor utility included in this package monitors the secure - channel and datapath. If either become unresponsive, the switch is - rebooted. + The ovs-monitor utility included in this package monitors the + ovs-openflowd process and the kernel datapath. If either become + unresponsive, it reboots the machine. Package: openvswitch-wdt Architecture: any diff --git a/debian/openvswitch-controller.README.Debian b/debian/openvswitch-controller.README.Debian index 18819a79..94b95c4a 100644 --- a/debian/openvswitch-controller.README.Debian +++ b/debian/openvswitch-controller.README.Debian @@ -6,7 +6,8 @@ README.Debian for openvswitch-controller * 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 openvswitch-switch package) gives - a working example configuration file for the ISC DHCP server. + The ovs-openflowd(8) manpage (found in the openvswitch-switch + package) gives a working example configuration file for the ISC DHCP + server. - -- Ben Pfaff , Mon, 11 May 2009 13:26:38 -0700 + -- Ben Pfaff , Wed, 8 Jul 2009 09:39:53 -0700 diff --git a/debian/openvswitch-monitor.default b/debian/openvswitch-monitor.default index f0c356e8..3b6ccdf0 100644 --- a/debian/openvswitch-monitor.default +++ b/debian/openvswitch-monitor.default @@ -13,15 +13,16 @@ # it reboots the system. A value of zero disables the monitor. THRESHOLD=3 -# INTERVAL: The number of seconds to wait between probing secchan and -# the datapath. +# INTERVAL: The number of seconds to wait between probing +# ovs-openflowd and the datapath. INTERVAL=1 # LOG_FILE: File to log messages related to monitoring. LOG_FILE="/var/log/openvswitch/monitor" -# SWITCH_VCONN: The vconn used to connect to the switch (secchan). -# The secchan must be configured to listen to this vconn. The default -# here set is also listened to by default by the openvswitch-switch -# package, so ordinarily there is no need to modify this. -SWITCH_VCONN="/var/run/secchan.mgmt" +# SWITCH_VCONN: The vconn used to connect to the switch +# (ovs-openflowd). The ovs-openflowd must be configured to listen to +# this vconn. The default here set is also listened to by default by +# the openvswitch-switch package, so ordinarily there is no need to +# modify this. +SWITCH_VCONN="/var/run/ovs-openflowd.mgmt" diff --git a/debian/openvswitch-switch-config.templates b/debian/openvswitch-switch-config.templates index 24bf0352..16646824 100644 --- a/debian/openvswitch-switch-config.templates +++ b/debian/openvswitch-switch-config.templates @@ -64,7 +64,7 @@ _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 + See ovs-openflowd(8) for instructions on how to configure a DHCP server for controller discovery. Template: openvswitch-switch/discovery-failure @@ -73,7 +73,7 @@ _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 + ovs-openflowd(8) for instructions on how to configure a DHCP server for controller discovery. Template: openvswitch-switch/discovery-success diff --git a/debian/openvswitch-switch.init b/debian/openvswitch-switch.init index b238f72e..ece07a83 100755 --- a/debian/openvswitch-switch.init +++ b/debian/openvswitch-switch.init @@ -19,9 +19,9 @@ ### END INIT INFO PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin -DAEMON=/usr/sbin/secchan -NAME=secchan -DESC=secchan +DAEMON=/usr/sbin/ovs-openflowd +NAME=ovs-openflowd +DESC=ovs-openflowd test -x $DAEMON || exit 0 @@ -35,7 +35,7 @@ DODTIME=1 # Time to wait for the server to die, in seconds # let some servers to die gracefully and # 'restart' will not work -# Include secchan defaults if available +# Include ovs-openflowd defaults if available unset NETDEVS unset MODE unset SWITCH_IP @@ -319,7 +319,7 @@ case "$1" in check_op "Setting core limit to $CORE_LIMIT" ulimit -c "$CORE_LIMIT" fi - # Compose secchan options. + # Compose ovs-openflowd options. set -- set -- "$@" --verbose=ANY:console:emer --verbose=ANY:syslog:err set -- "$@" --log-file diff --git a/debian/openvswitch-switch.install b/debian/openvswitch-switch.install index 9fddacf0..a0cf9bf2 100644 --- a/debian/openvswitch-switch.install +++ b/debian/openvswitch-switch.install @@ -1,4 +1,4 @@ -_debian/secchan/secchan usr/sbin +_debian/utilities/ovs-openflowd usr/sbin _debian/utilities/ovs-dpctl usr/sbin _debian/utilities/ovs-discover usr/sbin _debian/utilities/ovs-kill usr/sbin diff --git a/debian/openvswitch-switch.logrotate b/debian/openvswitch-switch.logrotate index 41394e86..a45cc2a7 100644 --- a/debian/openvswitch-switch.logrotate +++ b/debian/openvswitch-switch.logrotate @@ -1,4 +1,4 @@ -/var/log/openvswitch/secchan.log { +/var/log/openvswitch/ovs-openflowd.log { daily compress create 640 root adm @@ -6,6 +6,6 @@ missingok rotate 30 postrotate - ovs-appctl --target /var/run/secchan.pid --reopen + ovs-appctl --target /var/run/ovs-openflowd.pid --reopen endscript } diff --git a/debian/openvswitch-switch.manpages b/debian/openvswitch-switch.manpages index f789eba9..821503a7 100644 --- a/debian/openvswitch-switch.manpages +++ b/debian/openvswitch-switch.manpages @@ -1,4 +1,4 @@ -_debian/secchan/secchan.8 +_debian/utilities/ovs-openflowd.8 _debian/utilities/ovs-discover.8 _debian/utilities/ovs-dpctl.8 _debian/utilities/ovs-kill.8 diff --git a/debian/openvswitch-switch.template b/debian/openvswitch-switch.template index 7fe0e15c..0a72198f 100644 --- a/debian/openvswitch-switch.template +++ b/debian/openvswitch-switch.template @@ -1,7 +1,7 @@ # This is a POSIX shell fragment -*- sh -*- -# To configure the secure channel, fill in the following properly and -# uncomment them. Afterward, the secure channel will come up +# To configure the OpenFlow switch, fill in the following properly and +# uncomment them. Afterward, the switch will come up # automatically at boot time. It can be started immediately with # /etc/init.d/openvswitch-switch start # Alternatively, use the ovs-switch-setup program (from the @@ -101,12 +101,12 @@ SWITCH_IP=dhcp # Set CACERT_MODE to 'secure' or 'bootstrap' for these respective cases. #CACERT_MODE=secure -# MGMT_VCONNS: List of vconns (space-separated) on which secchan +# MGMT_VCONNS: List of vconns (space-separated) on which ovs-openflowd # should listen for management connections from ovs-ofctl, etc. # openvswitch-switchui by default connects to -# unix:/var/run/secchan.mgmt, so do not disable this if you want to +# unix:/var/run/ovs-openflowd.mgmt, so do not disable this if you want to # use openvswitch-switchui. -MGMT_VCONNS="punix:/var/run/secchan.mgmt" +MGMT_VCONNS="punix:/var/run/ovs-openflowd.mgmt" # COMMANDS: Access control list for the commands that can be executed # remotely over the OpenFlow protocol, as a comma-separated list of @@ -122,7 +122,7 @@ MGMT_VCONNS="punix:/var/run/secchan.mgmt" #DISCONNECTED_MODE=switch # STP: Enable or disabled 802.1D-1998 Spanning Tree Protocol. Set to -# 'yes' to enable STP, 'no' to disable it. If unset, secchan's +# 'yes' to enable STP, 'no' to disable it. If unset, ovs-openflowd's # current default is 'no' (but this may change in the future). #STP=no @@ -133,17 +133,17 @@ MGMT_VCONNS="punix:/var/run/secchan.mgmt" #RATE_LIMIT=1000 # INACTIVITY_PROBE: The maximum number of seconds of inactivity on the -# controller connection before secchan sends an inactivity probe +# controller connection before ovs-openflowd sends an inactivity probe # message to the controller. The valid range is 5 and up. If unset, -# secchan defaults to 15 seconds. +# ovs-openflowd defaults to 15 seconds. #INACTIVITY_PROBE=5 -# MAX_BACKOFF: The maximum time that secchan will wait between +# MAX_BACKOFF: The maximum time that ovs-openflowd will wait between # attempts to connect to the controller. The valid range is 1 and up. -# If unset, secchan defaults to 15 seconds. +# If unset, ovs-openflowd defaults to 15 seconds. #MAX_BACKOFF=15 -# DAEMON_OPTS: Additional options to pass to secchan, e.g. "--fail=open" +# DAEMON_OPTS: Additional options to pass to ovs-openflowd, e.g. "--fail=open" DAEMON_OPTS="" # CORE_LIMIT: Maximum size for core dumps. diff --git a/debian/openvswitch-switchui.default b/debian/openvswitch-switchui.default index 6cdbf7a5..a759af83 100644 --- a/debian/openvswitch-switchui.default +++ b/debian/openvswitch-switchui.default @@ -1,7 +1,7 @@ # This is a POSIX shell fragment -*- sh -*- -# To configure the switch monitor, modify the following. Afterward, -# the secure channel will come up automatically at boot time. It can +# To configure the switch UI, modify the following. Afterward, +# the switch UI will come up automatically at boot time. It can # be restarted immediately with # /etc/init.d/openvswitch-switchui start @@ -9,11 +9,11 @@ # sourced by /etc/init.d/openvswitch-switchui # installed at /etc/default/openvswitch-switchui by the maintainer scripts -# SWITCH_VCONN: The vconn used to connect to the switch (secchan). -# The secchan must be configured to listen to this vconn. The default +# SWITCH_VCONN: The vconn used to connect to the switch (ovs-openflowd). +# The ovs-openflowd must be configured to listen to this vconn. The default # here set is also listened to by default by the openvswitch-switch # package, so ordinarily there is no need to modify this. -SWITCH_VCONN="unix:/var/run/secchan.mgmt" +SWITCH_VCONN="unix:/var/run/ovs-openflowd.mgmt" # EZIO3_DEVICE: To display the switch monitor on an EZIO3 (aka # MTB-134) 16x2 LCD displays found on server appliances made by diff --git a/debian/ovs-switch-setup.8 b/debian/ovs-switch-setup.8 index 696ad365..9c0da4ce 100644 --- a/debian/ovs-switch-setup.8 +++ b/debian/ovs-switch-setup.8 @@ -38,4 +38,4 @@ obtained from the OpenFlow PKI server. .BR ovs\-dpctl (8), .BR ovs-pki (8), -.BR secchan (8) +.BR ovs-openflowd (8) diff --git a/debian/po/templates.pot b/debian/po/templates.pot index ec14b8c0..d425b8a4 100644 --- a/debian/po/templates.pot +++ b/debian/po/templates.pot @@ -168,7 +168,7 @@ msgstr "" #. Description #: ../openvswitch-switch-config.templates:5001 msgid "" -"See secchan(8) for instructions on how to configure a DHCP server for " +"See ovs-openflowd(8) for instructions on how to configure a DHCP server for " "controller discovery." msgstr "" @@ -188,7 +188,7 @@ msgstr "" #. Description #: ../openvswitch-switch-config.templates:6001 msgid "" -"Ensure that the OpenFlow DHCP server is properly configured. See secchan(8) " +"Ensure that the OpenFlow DHCP server is properly configured. See ovs-openflowd(8) " "for instructions on how to configure a DHCP server for controller discovery." msgstr "" diff --git a/extras/ezio/ovs-switchui.c b/extras/ezio/ovs-switchui.c index b4391079..e25d4e1a 100644 --- a/extras/ezio/ovs-switchui.c +++ b/extras/ezio/ovs-switchui.c @@ -114,7 +114,7 @@ static void compose_messages(const struct dict *, struct rconn *rconn); static void show_flows(struct rconn *); static void show_dpid_ip(struct rconn *, const struct dict *); -static void show_secchan_state(const struct dict *); +static void show_ofproto_state(const struct dict *); static void show_fail_open_state(const struct dict *); static void show_discovery_state(const struct dict *); static void show_remote_state(const struct dict *); @@ -257,7 +257,7 @@ compose_messages(const struct dict *dict, struct rconn *rconn) if (!show_reboot_state()) { show_flows(rconn); show_dpid_ip(rconn, dict); - show_secchan_state(dict); + show_ofproto_state(dict); show_fail_open_state(dict); show_discovery_state(dict); show_remote_state(dict); @@ -648,7 +648,7 @@ addf(const char *format, ...) } static void -show_secchan_state(const struct dict *dict) +show_ofproto_state(const struct dict *dict) { static struct message *msg; const char *is_connected; diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h index 44875881..ae363827 100644 --- a/include/openvswitch/datapath-protocol.h +++ b/include/openvswitch/datapath-protocol.h @@ -37,7 +37,7 @@ * ---------------------------------------------------------------------- */ -/* Protocol between secchan and datapath. */ +/* Protocol between userspace and kernel datapath. */ #ifndef OPENVSWITCH_DATAPATH_PROTOCOL_H #define OPENVSWITCH_DATAPATH_PROTOCOL_H 1 diff --git a/lib/automake.mk b/lib/automake.mk index 80d85c33..d129491d 100644 --- a/lib/automake.mk +++ b/lib/automake.mk @@ -187,8 +187,8 @@ COVERAGE_FILES = \ lib/unixctl.c \ lib/util.c \ lib/vconn.c \ - secchan/ofproto.c \ - secchan/pktbuf.c \ + ofproto/ofproto.c \ + ofproto/pktbuf.c \ vswitchd/bridge.c \ vswitchd/mgmt.c \ vswitchd/ovs-brcompatd.c diff --git a/lib/rconn.c b/lib/rconn.c index b18a2e59..b4da257d 100644 --- a/lib/rconn.c +++ b/lib/rconn.c @@ -89,7 +89,7 @@ struct rconn { time_t last_admitted; /* These values are simply for statistics reporting, not used directly by - * anything internal to the rconn (or the secchan for that matter). */ + * anything internal to the rconn (or ofproto for that matter). */ unsigned int packets_received; unsigned int n_attempted_connections, n_successful_connections; time_t creation_time; diff --git a/lib/vlog-modules.def b/lib/vlog-modules.def index a9b46a47..63b25cc2 100644 --- a/lib/vlog-modules.def +++ b/lib/vlog-modules.def @@ -50,16 +50,15 @@ VLOG_MODULE(netlink) VLOG_MODULE(ofctl) VLOG_MODULE(ovs_discover) VLOG_MODULE(ofproto) +VLOG_MODULE(openflowd) VLOG_MODULE(pktbuf) VLOG_MODULE(pcap) VLOG_MODULE(poll_loop) VLOG_MODULE(port_watcher) VLOG_MODULE(proc_net_compat) VLOG_MODULE(process) -VLOG_MODULE(secchan) VLOG_MODULE(rconn) VLOG_MODULE(stp) -VLOG_MODULE(stp_secchan) VLOG_MODULE(stats) VLOG_MODULE(status) VLOG_MODULE(svec) diff --git a/ofproto/.gitignore b/ofproto/.gitignore new file mode 100644 index 00000000..b336cc7c --- /dev/null +++ b/ofproto/.gitignore @@ -0,0 +1,2 @@ +/Makefile +/Makefile.in diff --git a/ofproto/automake.mk b/ofproto/automake.mk new file mode 100644 index 00000000..232d45f6 --- /dev/null +++ b/ofproto/automake.mk @@ -0,0 +1,29 @@ +# Copyright (C) 2009 Nicira Networks, Inc. +# +# Copying and distribution of this file, with or without modification, +# are permitted in any medium without royalty provided the copyright +# notice and this notice are preserved. This file is offered as-is, +# without warranty of any kind. + +noinst_LIBRARIES += ofproto/libofproto.a +ofproto_libofproto_a_SOURCES = \ + ofproto/discovery.c \ + ofproto/discovery.h \ + ofproto/executer.c \ + ofproto/executer.h \ + ofproto/fail-open.c \ + ofproto/fail-open.h \ + ofproto/in-band.c \ + ofproto/in-band.h \ + ofproto/netflow.c \ + ofproto/netflow.h \ + ofproto/ofproto.c \ + ofproto/ofproto.h \ + ofproto/pktbuf.c \ + ofproto/pktbuf.h \ + ofproto/pinsched.c \ + ofproto/pinsched.h \ + ofproto/status.c \ + ofproto/status.h + +include ofproto/commands/automake.mk diff --git a/ofproto/commands/automake.mk b/ofproto/commands/automake.mk new file mode 100644 index 00000000..96d165f5 --- /dev/null +++ b/ofproto/commands/automake.mk @@ -0,0 +1,3 @@ +commandsdir = ${pkgdatadir}/commands +dist_commands_SCRIPTS = \ + ofproto/commands/reboot diff --git a/ofproto/commands/reboot b/ofproto/commands/reboot new file mode 100755 index 00000000..42fd10c1 --- /dev/null +++ b/ofproto/commands/reboot @@ -0,0 +1,3 @@ +#! /bin/sh +ovs-kill --force --signal=USR1 ovs-switchui.pid +reboot diff --git a/ofproto/discovery.c b/ofproto/discovery.c new file mode 100644 index 00000000..2868db5a --- /dev/null +++ b/ofproto/discovery.c @@ -0,0 +1,271 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "discovery.h" +#include +#include +#include +#include +#include +#include +#include "dhcp-client.h" +#include "dhcp.h" +#include "dpif.h" +#include "netdev.h" +#include "openflow/openflow.h" +#include "packets.h" +#include "status.h" +#include "vconn-ssl.h" + +#define THIS_MODULE VLM_discovery +#include "vlog.h" + +struct discovery { + char *re; + bool update_resolv_conf; + regex_t *regex; + struct dhclient *dhcp; + int n_changes; + struct status_category *ss_cat; +}; + +static void modify_dhcp_request(struct dhcp_msg *, void *aux); +static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux); + +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); + +static void +discovery_status_cb(struct status_reply *sr, void *d_) +{ + struct discovery *d = d_; + + status_reply_put(sr, "accept-remote=%s", d->re); + status_reply_put(sr, "n-changes=%d", d->n_changes); + if (d->dhcp) { + status_reply_put(sr, "state=%s", dhclient_get_state(d->dhcp)); + status_reply_put(sr, "state-elapsed=%u", + dhclient_get_state_elapsed(d->dhcp)); + if (dhclient_is_bound(d->dhcp)) { + uint32_t ip = dhclient_get_ip(d->dhcp); + uint32_t netmask = dhclient_get_netmask(d->dhcp); + uint32_t router = dhclient_get_router(d->dhcp); + + const struct dhcp_msg *cfg = dhclient_get_config(d->dhcp); + uint32_t dns_server; + char *domain_name; + int i; + + status_reply_put(sr, "ip="IP_FMT, IP_ARGS(&ip)); + status_reply_put(sr, "netmask="IP_FMT, IP_ARGS(&netmask)); + if (router) { + status_reply_put(sr, "router="IP_FMT, IP_ARGS(&router)); + } + + for (i = 0; dhcp_msg_get_ip(cfg, DHCP_CODE_DNS_SERVER, i, + &dns_server); + i++) { + status_reply_put(sr, "dns%d="IP_FMT, i, IP_ARGS(&dns_server)); + } + + domain_name = dhcp_msg_get_string(cfg, DHCP_CODE_DOMAIN_NAME); + if (domain_name) { + status_reply_put(sr, "domain=%s", domain_name); + free(domain_name); + } + + status_reply_put(sr, "lease-remaining=%u", + dhclient_get_lease_remaining(d->dhcp)); + } + } +} + +int +discovery_create(const char *re, bool update_resolv_conf, + struct dpif *dpif, struct switch_status *ss, + struct discovery **discoveryp) +{ + struct discovery *d; + char local_name[IF_NAMESIZE]; + int error; + + d = xcalloc(1, sizeof *d); + + /* Controller regular expression. */ + error = discovery_set_accept_controller_re(d, re); + if (error) { + goto error_free; + } + d->update_resolv_conf = update_resolv_conf; + + /* Initialize DHCP client. */ + error = dpif_port_get_name(dpif, ODPP_LOCAL, + local_name, sizeof local_name); + if (error) { + VLOG_ERR("failed to query datapath local port: %s", strerror(error)); + goto error_regfree; + } + error = dhclient_create(local_name, modify_dhcp_request, + validate_dhcp_offer, d, &d->dhcp); + if (error) { + VLOG_ERR("failed to initialize DHCP client: %s", strerror(error)); + goto error_regfree; + } + dhclient_set_max_timeout(d->dhcp, 3); + dhclient_init(d->dhcp, 0); + + d->ss_cat = switch_status_register(ss, "discovery", + discovery_status_cb, d); + + *discoveryp = d; + return 0; + +error_regfree: + regfree(d->regex); + free(d->regex); +error_free: + free(d); + *discoveryp = 0; + return error; +} + +void +discovery_destroy(struct discovery *d) +{ + if (d) { + free(d->re); + regfree(d->regex); + free(d->regex); + dhclient_destroy(d->dhcp); + switch_status_unregister(d->ss_cat); + free(d); + } +} + +void +discovery_set_update_resolv_conf(struct discovery *d, + bool update_resolv_conf) +{ + d->update_resolv_conf = update_resolv_conf; +} + +int +discovery_set_accept_controller_re(struct discovery *d, const char *re_) +{ + regex_t *regex; + int error; + char *re; + + re = (!re_ ? xstrdup(vconn_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*") + : re_[0] == '^' ? xstrdup(re_) : xasprintf("^%s", re_)); + regex = xmalloc(sizeof *regex); + error = regcomp(regex, re, REG_NOSUB | REG_EXTENDED); + if (error) { + size_t length = regerror(error, regex, NULL, 0); + char *buffer = xmalloc(length); + regerror(error, regex, buffer, length); + VLOG_WARN("%s: %s", re, buffer); + free(regex); + free(re); + return EINVAL; + } else { + if (d->regex) { + regfree(d->regex); + free(d->regex); + } + free(d->re); + + d->regex = regex; + d->re = re; + return 0; + } +} + +void +discovery_question_connectivity(struct discovery *d) +{ + if (d->dhcp) { + dhclient_force_renew(d->dhcp, 15); + } +} + +bool +discovery_run(struct discovery *d, char **controller_name) +{ + if (!d->dhcp) { + *controller_name = NULL; + return true; + } + + dhclient_run(d->dhcp); + if (!dhclient_changed(d->dhcp)) { + return false; + } + + dhclient_configure_netdev(d->dhcp); + if (d->update_resolv_conf) { + dhclient_update_resolv_conf(d->dhcp); + } + + if (dhclient_is_bound(d->dhcp)) { + *controller_name = dhcp_msg_get_string(dhclient_get_config(d->dhcp), + DHCP_CODE_OFP_CONTROLLER_VCONN); + VLOG_INFO("%s: discovered controller", *controller_name); + d->n_changes++; + } else { + *controller_name = NULL; + if (d->n_changes) { + VLOG_INFO("discovered controller no longer available"); + d->n_changes++; + } + } + return true; +} + +void +discovery_wait(struct discovery *d) +{ + if (d->dhcp) { + dhclient_wait(d->dhcp); + } +} + +static void +modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED) +{ + dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow"); +} + +static bool +validate_dhcp_offer(const struct dhcp_msg *msg, void *d_) +{ + const struct discovery *d = d_; + char *vconn_name; + bool accept; + + vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN); + if (!vconn_name) { + VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn"); + return false; + } + accept = !regexec(d->regex, vconn_name, 0, NULL, 0); + if (!accept) { + VLOG_WARN_RL(&rl, "rejecting controller vconn that fails to match %s", + d->re); + } + free(vconn_name); + return accept; +} diff --git a/ofproto/discovery.h b/ofproto/discovery.h new file mode 100644 index 00000000..98ff5b8b --- /dev/null +++ b/ofproto/discovery.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef DISCOVERY_H +#define DISCOVERY_H 1 + +#include + +struct dpif; +struct discovery; +struct settings; +struct switch_status; + +int discovery_create(const char *accept_controller_re, bool update_resolv_conf, + struct dpif *, struct switch_status *, + struct discovery **); +void discovery_destroy(struct discovery *); +void discovery_set_update_resolv_conf(struct discovery *, + bool update_resolv_conf); +int discovery_set_accept_controller_re(struct discovery *, const char *re); +void discovery_question_connectivity(struct discovery *); +bool discovery_run(struct discovery *, char **controller_name); +void discovery_wait(struct discovery *); + +#endif /* discovery.h */ diff --git a/ofproto/executer.c b/ofproto/executer.c new file mode 100644 index 00000000..87b76526 --- /dev/null +++ b/ofproto/executer.c @@ -0,0 +1,519 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "executer.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "dirs.h" +#include "dynamic-string.h" +#include "fatal-signal.h" +#include "openflow/nicira-ext.h" +#include "ofpbuf.h" +#include "openflow/openflow.h" +#include "poll-loop.h" +#include "rconn.h" +#include "socket-util.h" +#include "util.h" +#include "vconn.h" + +#define THIS_MODULE VLM_executer +#include "vlog.h" + +#define MAX_CHILDREN 8 + +struct child { + /* Information about child process. */ + char *name; /* argv[0] passed to child. */ + pid_t pid; /* Child's process ID. */ + + /* For sending a reply to the controller when the child dies. */ + struct rconn *rconn; + uint32_t xid; /* Transaction ID used by controller. */ + + /* We read up to MAX_OUTPUT bytes of output and send them back to the + * controller when the child dies. */ +#define MAX_OUTPUT 4096 + int output_fd; /* FD from which to read child's output. */ + uint8_t *output; /* Output data. */ + size_t output_size; /* Number of bytes of output data so far. */ +}; + +struct executer { + /* Settings. */ + char *command_acl; /* Command white/blacklist, as shell globs. */ + char *command_dir; /* Directory that contains commands. */ + + /* Children. */ + struct child children[MAX_CHILDREN]; + size_t n_children; +}; + +/* File descriptors for waking up when a child dies. */ +static int signal_fds[2]; + +/* File descriptor for /dev/null. */ +static int null_fd = -1; + +static void send_child_status(struct rconn *, uint32_t xid, uint32_t status, + const void *data, size_t size); +static void send_child_message(struct rconn *, uint32_t xid, uint32_t status, + const char *message); + +/* Returns true if 'cmd' is allowed by 'acl', which is a command-separated + * access control list in the format described for --command-acl in + * ovs-openflowd(8). */ +static bool +executer_is_permitted(const char *acl_, const char *cmd) +{ + char *acl, *save_ptr, *pattern; + bool allowed, denied; + + /* Verify that 'cmd' consists only of alphanumerics plus _ or -. */ + if (cmd[strspn(cmd, "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")] != '\0') { + VLOG_WARN("rejecting command name \"%s\" that contain forbidden " + "characters", cmd); + return false; + } + + /* Check 'cmd' against 'acl'. */ + acl = xstrdup(acl_); + save_ptr = acl; + allowed = denied = false; + while ((pattern = strsep(&save_ptr, ",")) != NULL && !denied) { + if (pattern[0] != '!' && !fnmatch(pattern, cmd, 0)) { + allowed = true; + } else if (pattern[0] == '!' && !fnmatch(pattern + 1, cmd, 0)) { + denied = true; + } + } + free(acl); + + /* Check the command white/blacklisted state. */ + if (allowed && !denied) { + VLOG_INFO("permitting command execution: \"%s\" is whitelisted", cmd); + } else if (allowed && denied) { + VLOG_WARN("denying command execution: \"%s\" is both blacklisted " + "and whitelisted", cmd); + } else if (!allowed) { + VLOG_WARN("denying command execution: \"%s\" is not whitelisted", cmd); + } else if (denied) { + VLOG_WARN("denying command execution: \"%s\" is blacklisted", cmd); + } + return allowed && !denied; +} + +int +executer_handle_request(struct executer *e, struct rconn *rconn, + struct nicira_header *request) +{ + char **argv; + char *args; + char *exec_file = NULL; + int max_fds; + struct stat s; + size_t args_size; + size_t argc; + size_t i; + pid_t pid; + int output_fds[2]; + + /* Verify limit on children not exceeded. + * XXX should probably kill children when the connection drops? */ + if (e->n_children >= MAX_CHILDREN) { + send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, + "too many child processes"); + return 0; + } + + /* Copy argument buffer, adding a null terminator at the end. Now every + * argument is null-terminated, instead of being merely null-delimited. */ + args_size = ntohs(request->header.length) - sizeof *request; + args = xmemdup0((const void *) (request + 1), args_size); + + /* Count arguments. */ + argc = 0; + for (i = 0; i <= args_size; i++) { + argc += args[i] == '\0'; + } + + /* Set argv[*] to point to each argument. */ + argv = xmalloc((argc + 1) * sizeof *argv); + argv[0] = args; + for (i = 1; i < argc; i++) { + argv[i] = strchr(argv[i - 1], '\0') + 1; + } + argv[argc] = NULL; + + /* Check permissions. */ + if (!executer_is_permitted(e->command_acl, argv[0])) { + send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, + "command not allowed"); + goto done; + } + + /* Find the executable. */ + exec_file = xasprintf("%s/%s", e->command_dir, argv[0]); + if (stat(exec_file, &s)) { + VLOG_WARN("failed to stat \"%s\": %s", exec_file, strerror(errno)); + send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, + "command not allowed"); + goto done; + } + if (!S_ISREG(s.st_mode)) { + VLOG_WARN("\"%s\" is not a regular file", exec_file); + send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, + "command not allowed"); + goto done; + } + argv[0] = exec_file; + + /* Arrange to capture output. */ + if (pipe(output_fds)) { + VLOG_WARN("pipe failed: %s", strerror(errno)); + send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, + "internal error (pipe)"); + goto done; + } + + pid = fork(); + if (!pid) { + /* Running in child. + * XXX should run in new process group so that we can signal all + * subprocesses at once? Would also want to catch fatal signals and + * kill them at the same time though. */ + fatal_signal_fork(); + dup2(null_fd, 0); + dup2(output_fds[1], 1); + dup2(null_fd, 2); + max_fds = get_max_fds(); + for (i = 3; i < max_fds; i++) { + close(i); + } + if (chdir(e->command_dir)) { + printf("could not change directory to \"%s\": %s", + e->command_dir, strerror(errno)); + exit(EXIT_FAILURE); + } + execv(argv[0], argv); + printf("failed to start \"%s\": %s\n", argv[0], strerror(errno)); + exit(EXIT_FAILURE); + } else if (pid > 0) { + /* Running in parent. */ + struct child *child; + + VLOG_INFO("started \"%s\" subprocess", argv[0]); + send_child_status(rconn, request->header.xid, NXT_STATUS_STARTED, + NULL, 0); + child = &e->children[e->n_children++]; + child->name = xstrdup(argv[0]); + child->pid = pid; + child->rconn = rconn; + child->xid = request->header.xid; + child->output_fd = output_fds[0]; + child->output = xmalloc(MAX_OUTPUT); + child->output_size = 0; + set_nonblocking(output_fds[0]); + close(output_fds[1]); + } else { + VLOG_WARN("fork failed: %s", strerror(errno)); + send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, + "internal error (fork)"); + close(output_fds[0]); + close(output_fds[1]); + } + +done: + free(exec_file); + free(args); + free(argv); + return 0; +} + +static void +send_child_status(struct rconn *rconn, uint32_t xid, uint32_t status, + const void *data, size_t size) +{ + if (rconn) { + struct nx_command_reply *r; + struct ofpbuf *buffer; + + r = make_openflow_xid(sizeof *r, OFPT_VENDOR, xid, &buffer); + r->nxh.vendor = htonl(NX_VENDOR_ID); + r->nxh.subtype = htonl(NXT_COMMAND_REPLY); + r->status = htonl(status); + ofpbuf_put(buffer, data, size); + update_openflow_length(buffer); + if (rconn_send(rconn, buffer, NULL)) { + ofpbuf_delete(buffer); + } + } +} + +static void +send_child_message(struct rconn *rconn, uint32_t xid, uint32_t status, + const char *message) +{ + send_child_status(rconn, xid, status, message, strlen(message)); +} + +/* 'child' died with 'status' as its return code. Deal with it. */ +static void +child_terminated(struct child *child, int status) +{ + struct ds ds; + uint32_t ofp_status; + + /* Log how it terminated. */ + ds_init(&ds); + if (WIFEXITED(status)) { + ds_put_format(&ds, "normally with status %d", WEXITSTATUS(status)); + } else if (WIFSIGNALED(status)) { + const char *name = NULL; +#ifdef HAVE_STRSIGNAL + name = strsignal(WTERMSIG(status)); +#endif + ds_put_format(&ds, "by signal %d", WTERMSIG(status)); + if (name) { + ds_put_format(&ds, " (%s)", name); + } + } + if (WCOREDUMP(status)) { + ds_put_cstr(&ds, " (core dumped)"); + } + VLOG_INFO("child process \"%s\" with pid %ld terminated %s", + child->name, (long int) child->pid, ds_cstr(&ds)); + ds_destroy(&ds); + + /* Send a status message back to the controller that requested the + * command. */ + if (WIFEXITED(status)) { + ofp_status = WEXITSTATUS(status) | NXT_STATUS_EXITED; + } else if (WIFSIGNALED(status)) { + ofp_status = WTERMSIG(status) | NXT_STATUS_SIGNALED; + } else { + ofp_status = NXT_STATUS_UNKNOWN; + } + if (WCOREDUMP(status)) { + ofp_status |= NXT_STATUS_COREDUMP; + } + send_child_status(child->rconn, child->xid, ofp_status, + child->output, child->output_size); +} + +/* Read output from 'child' and append it to its output buffer. */ +static void +poll_child(struct child *child) +{ + ssize_t n; + + if (child->output_fd < 0) { + return; + } + + do { + n = read(child->output_fd, child->output + child->output_size, + MAX_OUTPUT - child->output_size); + } while (n < 0 && errno == EINTR); + if (n > 0) { + child->output_size += n; + if (child->output_size < MAX_OUTPUT) { + return; + } + } else if (n < 0 && errno == EAGAIN) { + return; + } + close(child->output_fd); + child->output_fd = -1; +} + +void +executer_run(struct executer *e) +{ + char buffer[MAX_CHILDREN]; + size_t i; + + if (!e->n_children) { + return; + } + + /* Read output from children. */ + for (i = 0; i < e->n_children; i++) { + struct child *child = &e->children[i]; + poll_child(child); + } + + /* If SIGCHLD was received, reap dead children. */ + if (read(signal_fds[0], buffer, sizeof buffer) <= 0) { + return; + } + for (;;) { + int status; + pid_t pid; + + /* Get dead child in 'pid' and its return code in 'status'. */ + pid = waitpid(WAIT_ANY, &status, WNOHANG); + if (pid < 0 && errno == EINTR) { + continue; + } else if (pid <= 0) { + return; + } + + /* Find child with given 'pid' and drop it from the list. */ + for (i = 0; i < e->n_children; i++) { + struct child *child = &e->children[i]; + if (child->pid == pid) { + poll_child(child); + child_terminated(child, status); + free(child->name); + free(child->output); + *child = e->children[--e->n_children]; + goto found; + } + } + VLOG_WARN("child with unknown pid %ld terminated", (long int) pid); + found:; + } + +} + +void +executer_wait(struct executer *e) +{ + if (e->n_children) { + size_t i; + + /* Wake up on SIGCHLD. */ + poll_fd_wait(signal_fds[0], POLLIN); + + /* Wake up when we get output from a child. */ + for (i = 0; i < e->n_children; i++) { + struct child *child = &e->children[i]; + if (child->output_fd >= 0) { + poll_fd_wait(child->output_fd, POLLIN); + } + } + } +} + +void +executer_rconn_closing(struct executer *e, struct rconn *rconn) +{ + size_t i; + + /* If any of our children was connected to 'r', then disconnect it so we + * don't try to reference a dead connection when the process terminates + * later. + * XXX kill the children started by 'r'? */ + for (i = 0; i < e->n_children; i++) { + if (e->children[i].rconn == rconn) { + e->children[i].rconn = NULL; + } + } +} + +static void +sigchld_handler(int signr UNUSED) +{ + write(signal_fds[1], "", 1); +} + +int +executer_create(const char *command_acl, const char *command_dir, + struct executer **executerp) +{ + struct executer *e; + struct sigaction sa; + + *executerp = NULL; + if (null_fd == -1) { + /* Create pipe for notifying us that SIGCHLD was invoked. */ + if (pipe(signal_fds)) { + VLOG_ERR("pipe failed: %s", strerror(errno)); + return errno; + } + set_nonblocking(signal_fds[0]); + set_nonblocking(signal_fds[1]); + + /* Open /dev/null. */ + null_fd = open("/dev/null", O_RDWR); + if (null_fd < 0) { + int error = errno; + VLOG_ERR("could not open /dev/null: %s", strerror(error)); + close(signal_fds[0]); + close(signal_fds[1]); + return error; + } + } + + /* Set up signal handler. */ + memset(&sa, 0, sizeof sa); + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; + if (sigaction(SIGCHLD, &sa, NULL)) { + VLOG_ERR("sigaction(SIGCHLD) failed: %s", strerror(errno)); + return errno; + } + + e = xcalloc(1, sizeof *e); + e->command_acl = xstrdup(command_acl); + e->command_dir = (command_dir + ? xstrdup(command_dir) + : xasprintf("%s/commands", ovs_pkgdatadir)); + e->n_children = 0; + *executerp = e; + return 0; +} + +void +executer_destroy(struct executer *e) +{ + if (e) { + size_t i; + + free(e->command_acl); + free(e->command_dir); + for (i = 0; i < e->n_children; i++) { + struct child *child = &e->children[i]; + + free(child->name); + kill(child->pid, SIGHUP); + /* We don't own child->rconn. */ + free(child->output); + free(child); + } + free(e); + } +} + +void +executer_set_acl(struct executer *e, const char *acl, const char *dir) +{ + free(e->command_acl); + e->command_acl = xstrdup(acl); + free(e->command_dir); + e->command_dir = xstrdup(dir); +} diff --git a/ofproto/executer.h b/ofproto/executer.h new file mode 100644 index 00000000..fbed85b8 --- /dev/null +++ b/ofproto/executer.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef EXECUTER_H +#define EXECUTER_H 1 + +struct executer; +struct nicira_header; +struct rconn; + +int executer_create(const char *acl, const char *dir, struct executer **); +void executer_set_acl(struct executer *, const char *acl, const char *dir); +void executer_destroy(struct executer *); +void executer_run(struct executer *); +void executer_wait(struct executer *); +void executer_rconn_closing(struct executer *, struct rconn *); +int executer_handle_request(struct executer *, struct rconn *, + struct nicira_header *); + +#endif /* executer.h */ diff --git a/ofproto/fail-open.c b/ofproto/fail-open.c new file mode 100644 index 00000000..0e887293 --- /dev/null +++ b/ofproto/fail-open.c @@ -0,0 +1,140 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "fail-open.h" +#include +#include +#include "flow.h" +#include "mac-learning.h" +#include "odp-util.h" +#include "ofproto.h" +#include "rconn.h" +#include "status.h" +#include "timeval.h" + +#define THIS_MODULE VLM_fail_open +#include "vlog.h" + +struct fail_open { + struct ofproto *ofproto; + struct rconn *controller; + int trigger_duration; + int last_disconn_secs; + struct status_category *ss_cat; +}; + +/* Causes the switch to enter or leave fail-open mode, if appropriate. */ +void +fail_open_run(struct fail_open *fo) +{ + int disconn_secs = rconn_failure_duration(fo->controller); + bool open = disconn_secs >= fo->trigger_duration; + if (open != (fo->last_disconn_secs != 0)) { + if (!open) { + flow_t flow; + + VLOG_WARN("No longer in fail-open mode"); + fo->last_disconn_secs = 0; + + memset(&flow, 0, sizeof flow); + ofproto_delete_flow(fo->ofproto, &flow, OFPFW_ALL, 70000); + } else { + VLOG_WARN("Could not connect to controller for %d seconds, " + "failing open", disconn_secs); + fo->last_disconn_secs = disconn_secs; + + /* Flush all OpenFlow and datapath flows. We will set up our + * fail-open rule from fail_open_flushed() when + * ofproto_flush_flows() calls back to us. */ + ofproto_flush_flows(fo->ofproto); + } + } else if (open && disconn_secs > fo->last_disconn_secs + 60) { + VLOG_INFO("Still in fail-open mode after %d seconds disconnected " + "from controller", disconn_secs); + fo->last_disconn_secs = disconn_secs; + } +} + +void +fail_open_wait(struct fail_open *fo UNUSED) +{ + /* Nothing to do. */ +} + +void +fail_open_flushed(struct fail_open *fo) +{ + int disconn_secs = rconn_failure_duration(fo->controller); + bool open = disconn_secs >= fo->trigger_duration; + if (open) { + union ofp_action action; + flow_t flow; + + /* Set up a flow that matches every packet and directs them to + * OFPP_NORMAL. */ + memset(&action, 0, sizeof action); + action.type = htons(OFPAT_OUTPUT); + action.output.len = htons(sizeof action); + action.output.port = htons(OFPP_NORMAL); + memset(&flow, 0, sizeof flow); + ofproto_add_flow(fo->ofproto, &flow, OFPFW_ALL, 70000, + &action, 1, 0); + } +} + +static void +fail_open_status_cb(struct status_reply *sr, void *fo_) +{ + struct fail_open *fo = fo_; + int cur_duration = rconn_failure_duration(fo->controller); + + status_reply_put(sr, "trigger-duration=%d", fo->trigger_duration); + status_reply_put(sr, "current-duration=%d", cur_duration); + status_reply_put(sr, "triggered=%s", + cur_duration >= fo->trigger_duration ? "true" : "false"); +} + +struct fail_open * +fail_open_create(struct ofproto *ofproto, + int trigger_duration, struct switch_status *switch_status, + struct rconn *controller) +{ + struct fail_open *fo = xmalloc(sizeof *fo); + fo->ofproto = ofproto; + fo->controller = controller; + fo->trigger_duration = trigger_duration; + fo->last_disconn_secs = 0; + fo->ss_cat = switch_status_register(switch_status, "fail-open", + fail_open_status_cb, fo); + return fo; +} + +void +fail_open_set_trigger_duration(struct fail_open *fo, int trigger_duration) +{ + fo->trigger_duration = trigger_duration; +} + +void +fail_open_destroy(struct fail_open *fo) +{ + if (fo) { + /* We don't own fo->controller. */ + switch_status_unregister(fo->ss_cat); + free(fo); + } +} diff --git a/ofproto/fail-open.h b/ofproto/fail-open.h new file mode 100644 index 00000000..c0ada2eb --- /dev/null +++ b/ofproto/fail-open.h @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef FAIL_OPEN_H +#define FAIL_OPEN_H 1 + +#include +#include +#include "flow.h" + +struct fail_open; +struct ofproto; +struct rconn; +struct switch_status; + +struct fail_open *fail_open_create(struct ofproto *, int trigger_duration, + struct switch_status *, + struct rconn *controller); +void fail_open_set_trigger_duration(struct fail_open *, int trigger_duration); +void fail_open_destroy(struct fail_open *); +void fail_open_wait(struct fail_open *); +void fail_open_run(struct fail_open *); +void fail_open_flushed(struct fail_open *); + +#endif /* fail-open.h */ diff --git a/ofproto/in-band.c b/ofproto/in-band.c new file mode 100644 index 00000000..8f5f977e --- /dev/null +++ b/ofproto/in-band.c @@ -0,0 +1,359 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "in-band.h" +#include +#include +#include +#include +#include +#include "dpif.h" +#include "flow.h" +#include "mac-learning.h" +#include "netdev.h" +#include "odp-util.h" +#include "ofp-print.h" +#include "ofproto.h" +#include "ofpbuf.h" +#include "openflow/openflow.h" +#include "packets.h" +#include "poll-loop.h" +#include "rconn.h" +#include "status.h" +#include "timeval.h" +#include "vconn.h" + +#define THIS_MODULE VLM_in_band +#include "vlog.h" + +#define IB_BASE_PRIORITY 18181800 + +enum { + IBR_FROM_LOCAL_PORT, /* Sent by ofproto local port. */ + IBR_TO_LOCAL_PORT, /* Sent to ofproto local port. */ + IBR_ARP_FROM_CTL, /* ARP from the controller. */ + IBR_TO_CTL_OFP_SRC, /* To controller, OpenFlow source port. */ + IBR_TO_CTL_OFP_DST, /* To controller, OpenFlow dest port. */ + IBR_FROM_CTL_OFP_SRC, /* From controller, OpenFlow source port. */ + IBR_FROM_CTL_OFP_DST, /* From controller, OpenFlow dest port. */ +#if OFP_TCP_PORT != OFP_SSL_PORT +#error Need to support separate TCP and SSL flows. +#endif + N_IB_RULES +}; + +struct ib_rule { + bool installed; + flow_t flow; + uint32_t wildcards; + unsigned int priority; +}; + +struct in_band { + struct ofproto *ofproto; + struct netdev *netdev; + struct rconn *controller; + struct status_category *ss_cat; + + /* Keeping track of controller's MAC address. */ + uint32_t ip; /* Current IP, 0 if unknown. */ + uint32_t last_ip; /* Last known IP, 0 if never known. */ + uint8_t mac[ETH_ADDR_LEN]; /* Current MAC, 0 if unknown. */ + uint8_t last_mac[ETH_ADDR_LEN]; /* Last known MAC, 0 if never known */ + time_t next_refresh; /* Next time to refresh MAC address. */ + + /* Keeping track of the local port's MAC address. */ + uint8_t local_mac[ETH_ADDR_LEN]; /* Current MAC. */ + time_t next_local_refresh; /* Next time to refresh MAC address. */ + + /* Rules that we set up. */ + struct ib_rule rules[N_IB_RULES]; +}; + +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); + +static const uint8_t * +get_controller_mac(struct in_band *ib) +{ + time_t now = time_now(); + uint32_t ip; + + ip = rconn_get_ip(ib->controller); + if (ip != ib->ip || now >= ib->next_refresh) { + bool have_mac; + + ib->ip = ip; + + /* Look up MAC address. */ + memset(ib->mac, 0, sizeof ib->mac); + if (ib->ip) { + int retval = netdev_arp_lookup(ib->netdev, ib->ip, ib->mac); + if (retval) { + VLOG_DBG_RL(&rl, "cannot look up controller hw address " + "("IP_FMT"): %s", + IP_ARGS(&ib->ip), strerror(retval)); + } + } + have_mac = !eth_addr_is_zero(ib->mac); + + /* Log changes in IP, MAC addresses. */ + if (ib->ip && ib->ip != ib->last_ip) { + VLOG_DBG("controller IP address changed from "IP_FMT + " to "IP_FMT, IP_ARGS(&ib->last_ip), IP_ARGS(&ib->ip)); + ib->last_ip = ib->ip; + } + if (have_mac && memcmp(ib->last_mac, ib->mac, ETH_ADDR_LEN)) { + VLOG_DBG("controller MAC address changed from "ETH_ADDR_FMT" to " + ETH_ADDR_FMT, + ETH_ADDR_ARGS(ib->last_mac), ETH_ADDR_ARGS(ib->mac)); + memcpy(ib->last_mac, ib->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. */ + ib->next_refresh = now + (!ib->ip || have_mac ? 10 : 1); + } + return !eth_addr_is_zero(ib->mac) ? ib->mac : NULL; +} + +static const uint8_t * +get_local_mac(struct in_band *ib) +{ + time_t now = time_now(); + if (now >= ib->next_local_refresh) { + uint8_t ea[ETH_ADDR_LEN]; + if (!netdev_nodev_get_etheraddr(netdev_get_name(ib->netdev), ea)) { + memcpy(ib->local_mac, ea, ETH_ADDR_LEN); + } + ib->next_local_refresh = now + 1; + } + return !eth_addr_is_zero(ib->local_mac) ? ib->local_mac : NULL; +} + +static void +in_band_status_cb(struct status_reply *sr, void *in_band_) +{ + struct in_band *in_band = in_band_; + struct in_addr local_ip; + const uint8_t *local_mac; + uint32_t controller_ip; + const uint8_t *controller_mac; + + if (netdev_get_in4(in_band->netdev, &local_ip)) { + status_reply_put(sr, "local-ip="IP_FMT, IP_ARGS(&local_ip.s_addr)); + } + local_mac = get_local_mac(in_band); + if (local_mac) { + status_reply_put(sr, "local-mac="ETH_ADDR_FMT, + ETH_ADDR_ARGS(local_mac)); + } + + controller_ip = rconn_get_ip(in_band->controller); + if (controller_ip) { + status_reply_put(sr, "controller-ip="IP_FMT, + IP_ARGS(&controller_ip)); + } + controller_mac = get_controller_mac(in_band); + if (controller_mac) { + status_reply_put(sr, "controller-mac="ETH_ADDR_FMT, + ETH_ADDR_ARGS(controller_mac)); + } +} + +static void +drop_flow(struct in_band *in_band, int rule_idx) +{ + struct ib_rule *rule = &in_band->rules[rule_idx]; + + if (rule->installed) { + rule->installed = false; + ofproto_delete_flow(in_band->ofproto, &rule->flow, rule->wildcards, + rule->priority); + } +} + +/* out_port and fixed_fields are assumed never to change. */ +static void +setup_flow(struct in_band *in_band, int rule_idx, const flow_t *flow, + uint32_t fixed_fields, uint16_t out_port) +{ + struct ib_rule *rule = &in_band->rules[rule_idx]; + + if (!rule->installed || memcmp(flow, &rule->flow, sizeof *flow)) { + union ofp_action action; + + drop_flow(in_band, rule_idx); + + rule->installed = true; + rule->flow = *flow; + rule->wildcards = OFPFW_ALL & ~fixed_fields; + rule->priority = IB_BASE_PRIORITY + (N_IB_RULES - rule_idx); + + action.type = htons(OFPAT_OUTPUT); + action.output.len = htons(sizeof action); + action.output.port = htons(out_port); + action.output.max_len = htons(0); + ofproto_add_flow(in_band->ofproto, &rule->flow, rule->wildcards, + rule->priority, &action, 1, 0); + } +} + +void +in_band_run(struct in_band *in_band) +{ + const uint8_t *controller_mac; + const uint8_t *local_mac; + flow_t flow; + + if (time_now() < MIN(in_band->next_refresh, in_band->next_local_refresh)) { + return; + } + controller_mac = get_controller_mac(in_band); + local_mac = get_local_mac(in_band); + + /* Switch traffic sent from the local port. */ + memset(&flow, 0, sizeof flow); + flow.in_port = ODPP_LOCAL; + setup_flow(in_band, IBR_FROM_LOCAL_PORT, &flow, OFPFW_IN_PORT, + OFPP_NORMAL); + + /* Deliver traffic sent to the local port. */ + if (local_mac) { + memset(&flow, 0, sizeof flow); + memcpy(flow.dl_dst, local_mac, ETH_ADDR_LEN); + setup_flow(in_band, IBR_TO_LOCAL_PORT, &flow, OFPFW_DL_DST, + OFPP_NORMAL); + } else { + drop_flow(in_band, IBR_TO_LOCAL_PORT); + } + + if (controller_mac) { + /* Switch ARP requests sent by the controller. (OFPP_NORMAL will "do + * the right thing" regarding VLANs here.) */ + memset(&flow, 0, sizeof flow); + flow.dl_type = htons(ETH_TYPE_ARP); + memcpy(flow.dl_dst, eth_addr_broadcast, ETH_ADDR_LEN); + memcpy(flow.dl_src, controller_mac, ETH_ADDR_LEN); + setup_flow(in_band, IBR_ARP_FROM_CTL, &flow, + OFPFW_DL_TYPE | OFPFW_DL_DST | OFPFW_DL_SRC, + OFPP_NORMAL); + + /* OpenFlow traffic to or from the controller. + * + * (A given field's value is completely ignored if it is wildcarded, + * which is why we can get away with using a single 'flow' in each + * case here.) */ + memset(&flow, 0, sizeof flow); + flow.dl_type = htons(ETH_TYPE_IP); + memcpy(flow.dl_src, controller_mac, ETH_ADDR_LEN); + memcpy(flow.dl_dst, controller_mac, ETH_ADDR_LEN); + flow.nw_proto = IP_TYPE_TCP; + flow.tp_src = htons(OFP_TCP_PORT); + flow.tp_dst = htons(OFP_TCP_PORT); + setup_flow(in_band, IBR_TO_CTL_OFP_SRC, &flow, + (OFPFW_DL_TYPE | OFPFW_DL_DST | OFPFW_NW_PROTO + | OFPFW_TP_SRC), OFPP_NORMAL); + setup_flow(in_band, IBR_TO_CTL_OFP_DST, &flow, + (OFPFW_DL_TYPE | OFPFW_DL_DST | OFPFW_NW_PROTO + | OFPFW_TP_DST), OFPP_NORMAL); + setup_flow(in_band, IBR_FROM_CTL_OFP_SRC, &flow, + (OFPFW_DL_TYPE | OFPFW_DL_SRC | OFPFW_NW_PROTO + | OFPFW_TP_SRC), OFPP_NORMAL); + setup_flow(in_band, IBR_FROM_CTL_OFP_DST, &flow, + (OFPFW_DL_TYPE | OFPFW_DL_SRC | OFPFW_NW_PROTO + | OFPFW_TP_DST), OFPP_NORMAL); + } else { + drop_flow(in_band, IBR_ARP_FROM_CTL); + drop_flow(in_band, IBR_TO_CTL_OFP_DST); + drop_flow(in_band, IBR_TO_CTL_OFP_SRC); + drop_flow(in_band, IBR_FROM_CTL_OFP_DST); + drop_flow(in_band, IBR_FROM_CTL_OFP_SRC); + } +} + +void +in_band_wait(struct in_band *in_band) +{ + time_t now = time_now(); + time_t wakeup = MIN(in_band->next_refresh, in_band->next_local_refresh); + if (wakeup > now) { + poll_timer_wait((wakeup - now) * 1000); + } else { + poll_immediate_wake(); + } +} + +void +in_band_flushed(struct in_band *in_band) +{ + int i; + + for (i = 0; i < N_IB_RULES; i++) { + in_band->rules[i].installed = false; + } +} + +int +in_band_create(struct ofproto *ofproto, + struct dpif *dpif, struct switch_status *ss, + struct rconn *controller, struct in_band **in_bandp) +{ + struct in_band *in_band; + struct netdev *netdev; + char local_name[IF_NAMESIZE]; + int error; + + *in_bandp = NULL; + error = dpif_port_get_name(dpif, ODPP_LOCAL, + local_name, sizeof local_name); + if (error) { + return error; + } + + error = netdev_open(local_name, NETDEV_ETH_TYPE_NONE, &netdev); + if (error) { + VLOG_ERR("failed to open %s network device: %s", + local_name, strerror(error)); + return error; + } + + in_band = xcalloc(1, sizeof *in_band); + in_band->ofproto = ofproto; + in_band->netdev = netdev; + in_band->controller = controller; + in_band->ss_cat = switch_status_register(ss, "in-band", + in_band_status_cb, in_band); + in_band->next_refresh = TIME_MIN; + in_band->next_local_refresh = TIME_MIN; + + *in_bandp = in_band; + return 0; +} + +void +in_band_destroy(struct in_band *in_band) +{ + if (in_band) { + netdev_close(in_band->netdev); + switch_status_unregister(in_band->ss_cat); + /* We don't own the rconn. */ + } +} + diff --git a/ofproto/in-band.h b/ofproto/in-band.h new file mode 100644 index 00000000..8d8d3535 --- /dev/null +++ b/ofproto/in-band.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef IN_BAND_H +#define IN_BAND_H 1 + +#include "flow.h" + +struct dpif; +struct in_band; +struct ofproto; +struct rconn; +struct settings; +struct switch_status; + +int in_band_create(struct ofproto *, struct dpif *, struct switch_status *, + struct rconn *controller, struct in_band **); +void in_band_destroy(struct in_band *); +void in_band_run(struct in_band *); +void in_band_wait(struct in_band *); +void in_band_flushed(struct in_band *); + +#endif /* in-band.h */ diff --git a/ofproto/netflow.c b/ofproto/netflow.c new file mode 100644 index 00000000..e867c0eb --- /dev/null +++ b/ofproto/netflow.c @@ -0,0 +1,328 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "netflow.h" +#include +#include +#include +#include +#include "cfg.h" +#include "flow.h" +#include "netflow.h" +#include "ofpbuf.h" +#include "ofproto.h" +#include "packets.h" +#include "socket-util.h" +#include "svec.h" +#include "timeval.h" +#include "util.h" +#include "xtoxll.h" + +#define THIS_MODULE VLM_netflow +#include "vlog.h" + +#define NETFLOW_V5_VERSION 5 + +/* Every NetFlow v5 message contains the header that follows. This is + * followed by up to thirty records that describe a terminating flow. + * We only send a single record per NetFlow message. + */ +struct netflow_v5_header { + uint16_t version; /* NetFlow version is 5. */ + uint16_t count; /* Number of records in this message. */ + uint32_t sysuptime; /* System uptime in milliseconds. */ + uint32_t unix_secs; /* Number of seconds since Unix epoch. */ + uint32_t unix_nsecs; /* Number of residual nanoseconds + after epoch seconds. */ + uint32_t flow_seq; /* Number of flows since sending + messages began. */ + uint8_t engine_type; /* Engine type. */ + uint8_t engine_id; /* Engine id. */ + uint16_t sampling_interval; /* Set to zero. */ +}; +BUILD_ASSERT_DECL(sizeof(struct netflow_v5_header) == 24); + +/* A NetFlow v5 description of a terminating flow. It is preceded by a + * NetFlow v5 header. + */ +struct netflow_v5_record { + uint32_t src_addr; /* Source IP address. */ + uint32_t dst_addr; /* Destination IP address. */ + uint32_t nexthop; /* IP address of next hop. Set to 0. */ + uint16_t input; /* Input interface index. */ + uint16_t output; /* Output interface index. */ + uint32_t packet_count; /* Number of packets. */ + uint32_t byte_count; /* Number of bytes. */ + uint32_t init_time; /* Value of sysuptime on first packet. */ + uint32_t used_time; /* Value of sysuptime on last packet. */ + + /* The 'src_port' and 'dst_port' identify the source and destination + * port, respectively, for TCP and UDP. For ICMP, the high-order + * byte identifies the type and low-order byte identifies the code + * in the 'dst_port' field. */ + uint16_t src_port; + uint16_t dst_port; + + uint8_t pad1; + uint8_t tcp_flags; /* Union of seen TCP flags. */ + uint8_t ip_proto; /* IP protocol. */ + uint8_t ip_tos; /* IP TOS value. */ + uint16_t src_as; /* Source AS ID. Set to 0. */ + uint16_t dst_as; /* Destination AS ID. Set to 0. */ + uint8_t src_mask; /* Source mask bits. Set to 0. */ + uint8_t dst_mask; /* Destination mask bits. Set to 0. */ + uint8_t pad[2]; +}; +BUILD_ASSERT_DECL(sizeof(struct netflow_v5_record) == 48); + +struct netflow { + uint8_t engine_type; /* Value of engine_type to use. */ + uint8_t engine_id; /* Value of engine_id to use. */ + long long int boot_time; /* Time when netflow_create() was called. */ + int *fds; /* Sockets for NetFlow collectors. */ + size_t n_fds; /* Number of Netflow collectors. */ + bool add_id_to_iface; /* Put the 7 least signficiant bits of + * 'engine_id' into the most signficant + * bits of the interface fields. */ + uint32_t netflow_cnt; /* Flow sequence number for NetFlow. */ + struct ofpbuf packet; /* NetFlow packet being accumulated. */ +}; + +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + +static int +open_collector(char *dst) +{ + char *save_ptr; + const char *host_name; + const char *port_string; + struct sockaddr_in sin; + int retval; + int fd; + + /* Glibc 2.7 has a bug in strtok_r when compiling with optimization that + * can cause segfaults here: + * http://sources.redhat.com/bugzilla/show_bug.cgi?id=5614. + * Using "::" instead of the obvious ":" works around it. */ + host_name = strtok_r(dst, ":", &save_ptr); + port_string = strtok_r(NULL, ":", &save_ptr); + if (!host_name) { + ovs_error(0, "%s: bad peer name format", dst); + return -EAFNOSUPPORT; + } + if (!port_string) { + ovs_error(0, "%s: bad port format", dst); + return -EAFNOSUPPORT; + } + + memset(&sin, 0, sizeof sin); + sin.sin_family = AF_INET; + if (lookup_ip(host_name, &sin.sin_addr)) { + return -ENOENT; + } + sin.sin_port = htons(atoi(port_string)); + + fd = socket(AF_INET, SOCK_DGRAM, 0); + if (fd < 0) { + VLOG_ERR("%s: socket: %s", dst, strerror(errno)); + return -errno; + } + + retval = set_nonblocking(fd); + if (retval) { + close(fd); + return -retval; + } + + retval = connect(fd, (struct sockaddr *) &sin, sizeof sin); + if (retval < 0) { + int error = errno; + VLOG_ERR("%s: connect: %s", dst, strerror(error)); + close(fd); + return -error; + } + + return fd; +} + +void +netflow_expire(struct netflow *nf, const struct ofexpired *expired) +{ + struct netflow_v5_header *nf_hdr; + struct netflow_v5_record *nf_rec; + struct timeval now; + + /* NetFlow only reports on IP packets. */ + if (expired->flow.dl_type != htons(ETH_TYPE_IP)) { + return; + } + + time_timeval(&now); + + if (!nf->packet.size) { + nf_hdr = ofpbuf_put_zeros(&nf->packet, sizeof *nf_hdr); + nf_hdr->version = htons(NETFLOW_V5_VERSION); + nf_hdr->count = htons(0); + nf_hdr->sysuptime = htonl(time_msec() - nf->boot_time); + nf_hdr->unix_secs = htonl(now.tv_sec); + nf_hdr->unix_nsecs = htonl(now.tv_usec * 1000); + nf_hdr->flow_seq = htonl(nf->netflow_cnt++); + nf_hdr->engine_type = nf->engine_type; + nf_hdr->engine_id = nf->engine_id; + nf_hdr->sampling_interval = htons(0); + } + + nf_hdr = nf->packet.data; + nf_hdr->count = htons(ntohs(nf_hdr->count) + 1); + + nf_rec = ofpbuf_put_zeros(&nf->packet, sizeof *nf_rec); + nf_rec->src_addr = expired->flow.nw_src; + nf_rec->dst_addr = expired->flow.nw_dst; + nf_rec->nexthop = htons(0); + if (nf->add_id_to_iface) { + uint16_t iface = (nf->engine_id & 0x7f) << 9; + nf_rec->input = htons(iface | (expired->flow.in_port & 0x1ff)); + nf_rec->output = htons(iface); + printf("input: %x\n", ntohs(nf_rec->input)); + } else { + nf_rec->input = htons(expired->flow.in_port); + nf_rec->output = htons(0); + } + nf_rec->packet_count = htonl(MIN(expired->packet_count, UINT32_MAX)); + nf_rec->byte_count = htonl(MIN(expired->byte_count, UINT32_MAX)); + nf_rec->init_time = htonl(expired->created - nf->boot_time); + nf_rec->used_time = htonl(MAX(expired->created, expired->used) + - nf->boot_time); + if (expired->flow.nw_proto == IP_TYPE_ICMP) { + /* In NetFlow, the ICMP type and code are concatenated and + * placed in the 'dst_port' field. */ + uint8_t type = ntohs(expired->flow.tp_src); + uint8_t code = ntohs(expired->flow.tp_dst); + nf_rec->src_port = htons(0); + nf_rec->dst_port = htons((type << 8) | code); + } else { + nf_rec->src_port = expired->flow.tp_src; + nf_rec->dst_port = expired->flow.tp_dst; + } + nf_rec->tcp_flags = expired->tcp_flags; + nf_rec->ip_proto = expired->flow.nw_proto; + nf_rec->ip_tos = expired->ip_tos; + + /* NetFlow messages are limited to 30 records. A length of 1400 + * bytes guarantees that the limit is not exceeded. */ + if (nf->packet.size >= 1400) { + netflow_run(nf); + } +} + +void +netflow_run(struct netflow *nf) +{ + size_t i; + + if (!nf->packet.size) { + return; + } + + for (i = 0; i < nf->n_fds; i++) { + if (send(nf->fds[i], nf->packet.data, nf->packet.size, 0) == -1) { + VLOG_WARN_RL(&rl, "netflow message send failed: %s", + strerror(errno)); + } + } + nf->packet.size = 0; +} + +static void +clear_collectors(struct netflow *nf) +{ + size_t i; + + for (i = 0; i < nf->n_fds; i++) { + close(nf->fds[i]); + } + free(nf->fds); + nf->fds = NULL; + nf->n_fds = 0; +} + +int +netflow_set_collectors(struct netflow *nf, const struct svec *collectors_) +{ + struct svec collectors; + int error = 0; + size_t i; + + clear_collectors(nf); + + svec_clone(&collectors, collectors_); + svec_sort_unique(&collectors); + + nf->fds = xmalloc(sizeof *nf->fds * collectors.n); + for (i = 0; i < collectors.n; i++) { + const char *name = collectors.names[i]; + char *tmpname = xstrdup(name); + int fd = open_collector(tmpname); + free(tmpname); + if (fd >= 0) { + nf->fds[nf->n_fds++] = fd; + } else { + VLOG_WARN("couldn't open connection to collector (%s), " + "ignoring %s\n", strerror(-fd), name); + if (!error) { + error = -fd; + } + } + } + + svec_destroy(&collectors); + return error; +} + +void +netflow_set_engine(struct netflow *nf, uint8_t engine_type, + uint8_t engine_id, bool add_id_to_iface) +{ + nf->engine_type = engine_type; + nf->engine_id = engine_id; + nf->add_id_to_iface = add_id_to_iface; +} + +struct netflow * +netflow_create(void) +{ + struct netflow *nf = xmalloc(sizeof *nf); + nf->engine_type = 0; + nf->engine_id = 0; + nf->boot_time = time_msec(); + nf->fds = NULL; + nf->n_fds = 0; + nf->add_id_to_iface = false; + nf->netflow_cnt = 0; + ofpbuf_init(&nf->packet, 1500); + return nf; +} + +void +netflow_destroy(struct netflow *nf) +{ + if (nf) { + ofpbuf_uninit(&nf->packet); + clear_collectors(nf); + free(nf); + } +} diff --git a/ofproto/netflow.h b/ofproto/netflow.h new file mode 100644 index 00000000..13be90b4 --- /dev/null +++ b/ofproto/netflow.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef NETFLOW_H +#define NETFLOW_H 1 + +#include "flow.h" + +struct ofexpired; +struct svec; + +struct netflow *netflow_create(void); +void netflow_destroy(struct netflow *); +int netflow_set_collectors(struct netflow *, const struct svec *collectors); +void netflow_set_engine(struct netflow *nf, uint8_t engine_type, + uint8_t engine_id, bool add_id_to_iface); +void netflow_expire(struct netflow *, const struct ofexpired *); +void netflow_run(struct netflow *); + +#endif /* netflow.h */ diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c new file mode 100644 index 00000000..79fe14c4 --- /dev/null +++ b/ofproto/ofproto.c @@ -0,0 +1,3319 @@ +/* + * Copyright (c) 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "ofproto.h" +#include +#include +#include +#include +#include +#include +#include "classifier.h" +#include "coverage.h" +#include "discovery.h" +#include "dpif.h" +#include "executer.h" +#include "fail-open.h" +#include "in-band.h" +#include "mac-learning.h" +#include "netdev.h" +#include "netflow.h" +#include "odp-util.h" +#include "ofp-print.h" +#include "ofpbuf.h" +#include "openflow/nicira-ext.h" +#include "openflow/openflow.h" +#include "openflow/openflow-mgmt.h" +#include "openvswitch/datapath-protocol.h" +#include "packets.h" +#include "pinsched.h" +#include "pktbuf.h" +#include "poll-loop.h" +#include "port-array.h" +#include "rconn.h" +#include "shash.h" +#include "status.h" +#include "stp.h" +#include "svec.h" +#include "tag.h" +#include "timeval.h" +#include "vconn.h" +#include "vconn-ssl.h" +#include "xtoxll.h" + +#define THIS_MODULE VLM_ofproto +#include "vlog.h" + +enum { + DP_GROUP_FLOOD = 0, + DP_GROUP_ALL = 1 +}; + +enum { + TABLEID_HASH = 0, + TABLEID_CLASSIFIER = 1 +}; + +struct ofport { + struct netdev *netdev; + struct ofp_phy_port opp; /* In host byte order. */ +}; + +static void ofport_free(struct ofport *); +static void hton_ofp_phy_port(struct ofp_phy_port *); + +static int xlate_actions(const union ofp_action *in, size_t n_in, + const flow_t *flow, struct ofproto *ofproto, + const struct ofpbuf *packet, + struct odp_actions *out, tag_type *tags, + bool *may_setup_flow); + +struct rule { + struct cls_rule cr; + + uint16_t idle_timeout; /* In seconds from time of last use. */ + uint16_t hard_timeout; /* In seconds from time of creation. */ + long long int used; /* Last-used time (0 if never used). */ + long long int created; /* Creation time. */ + uint64_t packet_count; /* Number of packets received. */ + uint64_t byte_count; /* Number of bytes received. */ + uint64_t accounted_bytes; /* Number of bytes passed to account_cb. */ + uint8_t tcp_flags; /* Bitwise-OR of all TCP flags seen. */ + uint8_t ip_tos; /* Last-seen IP type-of-service. */ + tag_type tags; /* Tags (set only by hooks). */ + + /* If 'super' is non-NULL, this rule is a subrule, that is, it is an + * exact-match rule (having cr.wc.wildcards of 0) generated from the + * wildcard rule 'super'. In this case, 'list' is an element of the + * super-rule's list. + * + * If 'super' is NULL, this rule is a super-rule, and 'list' is the head of + * a list of subrules. A super-rule with no wildcards (where + * cr.wc.wildcards is 0) will never have any subrules. */ + struct rule *super; + struct list list; + + /* OpenFlow actions. + * + * A subrule has no actions (it uses the super-rule's actions). */ + int n_actions; + union ofp_action *actions; + + /* Datapath actions. + * + * A super-rule with wildcard fields never has ODP actions (since the + * datapath only supports exact-match flows). */ + bool installed; /* Installed in datapath? */ + bool may_install; /* True ordinarily; false if actions must + * be reassessed for every packet. */ + int n_odp_actions; + union odp_action *odp_actions; +}; + +static inline bool +rule_is_hidden(const struct rule *rule) +{ + /* Subrules are merely an implementation detail, so hide them from the + * controller. */ + if (rule->super != NULL) { + return true; + } + + /* Rules with priority higher than UINT16_MAX are set up by ofproto itself + * (e.g. by in-band control) and are intentionally hidden from the + * controller. */ + if (rule->cr.priority > UINT16_MAX) { + return true; + } + + return false; +} + +static struct rule *rule_create(struct rule *super, const union ofp_action *, + size_t n_actions, uint16_t idle_timeout, + uint16_t hard_timeout); +static void rule_free(struct rule *); +static void rule_destroy(struct ofproto *, struct rule *); +static struct rule *rule_from_cls_rule(const struct cls_rule *); +static void rule_insert(struct ofproto *, struct rule *, + struct ofpbuf *packet, uint16_t in_port); +static void rule_remove(struct ofproto *, struct rule *); +static bool rule_make_actions(struct ofproto *, struct rule *, + const struct ofpbuf *packet); +static void rule_install(struct ofproto *, struct rule *, + struct rule *displaced_rule); +static void rule_uninstall(struct ofproto *, struct rule *); +static void rule_post_uninstall(struct ofproto *, struct rule *); + +struct ofconn { + struct list node; + struct rconn *rconn; + struct pktbuf *pktbuf; + bool send_flow_exp; + int miss_send_len; + + struct rconn_packet_counter *packet_in_counter; + + /* Number of OpenFlow messages queued as replies to OpenFlow requests, and + * the maximum number before we stop reading OpenFlow requests. */ +#define OFCONN_REPLY_MAX 100 + struct rconn_packet_counter *reply_counter; +}; + +static struct ofconn *ofconn_create(struct ofproto *, struct rconn *); +static void ofconn_destroy(struct ofconn *, struct ofproto *); +static void ofconn_run(struct ofconn *, struct ofproto *); +static void ofconn_wait(struct ofconn *); +static void queue_tx(struct ofpbuf *msg, const struct ofconn *ofconn, + struct rconn_packet_counter *counter); + +struct ofproto { + /* Settings. */ + uint64_t datapath_id; /* Datapath ID. */ + uint64_t fallback_dpid; /* Datapath ID if no better choice found. */ + uint64_t mgmt_id; /* Management channel identifier. */ + char *manufacturer; /* Manufacturer. */ + char *hardware; /* Hardware. */ + char *software; /* Software version. */ + char *serial; /* Serial number. */ + + /* Datapath. */ + struct dpif *dpif; + struct netdev_monitor *netdev_monitor; + struct port_array ports; /* Index is ODP port nr; ofport->opp.port_no is + * OFP port nr. */ + struct shash port_by_name; + uint32_t max_ports; + + /* Configuration. */ + struct switch_status *switch_status; + struct status_category *ss_cat; + struct in_band *in_band; + struct discovery *discovery; + struct fail_open *fail_open; + struct pinsched *miss_sched, *action_sched; + struct executer *executer; + struct netflow *netflow; + + /* Flow table. */ + struct classifier cls; + bool need_revalidate; + long long int next_expiration; + struct tag_set revalidate_set; + + /* OpenFlow connections. */ + struct list all_conns; + struct ofconn *controller; + struct pvconn **listeners; + size_t n_listeners; + struct pvconn **snoops; + size_t n_snoops; + + /* Hooks for ovs-vswitchd. */ + const struct ofhooks *ofhooks; + void *aux; + + /* Used by default ofhooks. */ + struct mac_learning *ml; +}; + +static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + +static const struct ofhooks default_ofhooks; + +static uint64_t pick_datapath_id(struct dpif *, uint64_t fallback_dpid); +static uint64_t pick_fallback_dpid(void); +static void send_packet_in_miss(struct ofpbuf *, void *ofproto); +static void send_packet_in_action(struct ofpbuf *, void *ofproto); +static void update_used(struct ofproto *); +static void update_stats(struct rule *, const struct odp_flow_stats *); +static void expire_rule(struct cls_rule *, void *ofproto); +static bool revalidate_rule(struct ofproto *p, struct rule *rule); +static void revalidate_cb(struct cls_rule *rule_, void *p_); + +static void handle_odp_msg(struct ofproto *, struct ofpbuf *); + +static void handle_openflow(struct ofconn *, struct ofproto *, + struct ofpbuf *); + +static void refresh_port_group(struct ofproto *, unsigned int group); +static void update_port(struct ofproto *, const char *devname); +static int init_ports(struct ofproto *); +static void reinit_ports(struct ofproto *); + +int +ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux, + struct ofproto **ofprotop) +{ + struct netdev_monitor *netdev_monitor; + struct odp_stats stats; + struct ofproto *p; + struct dpif *dpif; + int error; + + *ofprotop = NULL; + + /* Connect to datapath and start listening for messages. */ + error = dpif_open(datapath, &dpif); + if (error) { + VLOG_ERR("failed to open datapath %s: %s", datapath, strerror(error)); + return error; + } + error = dpif_get_dp_stats(dpif, &stats); + if (error) { + VLOG_ERR("failed to obtain stats for datapath %s: %s", + datapath, strerror(error)); + dpif_close(dpif); + return error; + } + error = dpif_recv_set_mask(dpif, ODPL_MISS | ODPL_ACTION); + if (error) { + VLOG_ERR("failed to listen on datapath %s: %s", + datapath, strerror(error)); + dpif_close(dpif); + return error; + } + dpif_flow_flush(dpif); + dpif_recv_purge(dpif); + + /* Arrange to monitor datapath ports for status changes. */ + error = netdev_monitor_create(&netdev_monitor); + if (error) { + VLOG_ERR("failed to starting monitoring datapath %s: %s", + datapath, strerror(error)); + dpif_close(dpif); + return error; + } + + /* Initialize settings. */ + p = xcalloc(1, sizeof *p); + p->fallback_dpid = pick_fallback_dpid(); + p->datapath_id = pick_datapath_id(dpif, p->fallback_dpid); + VLOG_INFO("using datapath ID %012"PRIx64, p->datapath_id); + p->manufacturer = xstrdup("Nicira Networks, Inc."); + p->hardware = xstrdup("Reference Implementation"); + p->software = xstrdup(VERSION BUILDNR); + p->serial = xstrdup("None"); + + /* Initialize datapath. */ + p->dpif = dpif; + p->netdev_monitor = netdev_monitor; + port_array_init(&p->ports); + shash_init(&p->port_by_name); + p->max_ports = stats.max_ports; + + /* Initialize submodules. */ + p->switch_status = switch_status_create(p); + p->in_band = NULL; + p->discovery = NULL; + p->fail_open = NULL; + p->miss_sched = p->action_sched = NULL; + p->executer = NULL; + p->netflow = NULL; + + /* Initialize flow table. */ + classifier_init(&p->cls); + p->need_revalidate = false; + p->next_expiration = time_msec() + 1000; + tag_set_init(&p->revalidate_set); + + /* Initialize OpenFlow connections. */ + list_init(&p->all_conns); + p->controller = ofconn_create(p, rconn_create(15, 15)); + p->controller->pktbuf = pktbuf_create(); + p->controller->miss_send_len = OFP_DEFAULT_MISS_SEND_LEN; + p->listeners = NULL; + p->n_listeners = 0; + p->snoops = NULL; + p->n_snoops = 0; + + /* Initialize hooks. */ + if (ofhooks) { + p->ofhooks = ofhooks; + p->aux = aux; + p->ml = NULL; + } else { + p->ofhooks = &default_ofhooks; + p->aux = p; + p->ml = mac_learning_create(); + } + + /* Register switch status category. */ + p->ss_cat = switch_status_register(p->switch_status, "remote", + rconn_status_cb, p->controller->rconn); + + /* Almost done... */ + error = init_ports(p); + if (error) { + ofproto_destroy(p); + return error; + } + + *ofprotop = p; + return 0; +} + +void +ofproto_set_datapath_id(struct ofproto *p, uint64_t datapath_id) +{ + uint64_t old_dpid = p->datapath_id; + p->datapath_id = (datapath_id + ? datapath_id + : pick_datapath_id(p->dpif, p->fallback_dpid)); + if (p->datapath_id != old_dpid) { + VLOG_INFO("datapath ID changed to %012"PRIx64, p->datapath_id); + rconn_reconnect(p->controller->rconn); + } +} + +void +ofproto_set_mgmt_id(struct ofproto *p, uint64_t mgmt_id) +{ + p->mgmt_id = mgmt_id; +} + +void +ofproto_set_probe_interval(struct ofproto *p, int probe_interval) +{ + probe_interval = probe_interval ? MAX(probe_interval, 5) : 0; + rconn_set_probe_interval(p->controller->rconn, probe_interval); + if (p->fail_open) { + int trigger_duration = probe_interval ? probe_interval * 3 : 15; + fail_open_set_trigger_duration(p->fail_open, trigger_duration); + } +} + +void +ofproto_set_max_backoff(struct ofproto *p, int max_backoff) +{ + rconn_set_max_backoff(p->controller->rconn, max_backoff); +} + +void +ofproto_set_desc(struct ofproto *p, + const char *manufacturer, const char *hardware, + const char *software, const char *serial) +{ + if (manufacturer) { + free(p->manufacturer); + p->manufacturer = xstrdup(manufacturer); + } + if (hardware) { + free(p->hardware); + p->hardware = xstrdup(hardware); + } + if (software) { + free(p->software); + p->software = xstrdup(software); + } + if (serial) { + free(p->serial); + p->serial = xstrdup(serial); + } +} + +int +ofproto_set_in_band(struct ofproto *p, bool in_band) +{ + if (in_band != (p->in_band != NULL)) { + if (in_band) { + return in_band_create(p, p->dpif, p->switch_status, + p->controller->rconn, &p->in_band); + } else { + ofproto_set_discovery(p, false, NULL, true); + in_band_destroy(p->in_band); + p->in_band = NULL; + } + rconn_reconnect(p->controller->rconn); + } + return 0; +} + +int +ofproto_set_discovery(struct ofproto *p, bool discovery, + const char *re, bool update_resolv_conf) +{ + if (discovery != (p->discovery != NULL)) { + if (discovery) { + int error = ofproto_set_in_band(p, true); + if (error) { + return error; + } + error = discovery_create(re, update_resolv_conf, + p->dpif, p->switch_status, + &p->discovery); + if (error) { + return error; + } + } else { + discovery_destroy(p->discovery); + p->discovery = NULL; + } + rconn_disconnect(p->controller->rconn); + } else if (discovery) { + discovery_set_update_resolv_conf(p->discovery, update_resolv_conf); + return discovery_set_accept_controller_re(p->discovery, re); + } + return 0; +} + +int +ofproto_set_controller(struct ofproto *ofproto, const char *controller) +{ + if (ofproto->discovery) { + return EINVAL; + } else if (controller) { + if (strcmp(rconn_get_name(ofproto->controller->rconn), controller)) { + return rconn_connect(ofproto->controller->rconn, controller); + } else { + return 0; + } + } else { + rconn_disconnect(ofproto->controller->rconn); + return 0; + } +} + +static int +set_pvconns(struct pvconn ***pvconnsp, size_t *n_pvconnsp, + const struct svec *svec) +{ + struct pvconn **pvconns = *pvconnsp; + size_t n_pvconns = *n_pvconnsp; + int retval = 0; + size_t i; + + for (i = 0; i < n_pvconns; i++) { + pvconn_close(pvconns[i]); + } + free(pvconns); + + pvconns = xmalloc(svec->n * sizeof *pvconns); + n_pvconns = 0; + for (i = 0; i < svec->n; i++) { + const char *name = svec->names[i]; + struct pvconn *pvconn; + int error; + + error = pvconn_open(name, &pvconn); + if (!error) { + pvconns[n_pvconns++] = pvconn; + } else { + VLOG_ERR("failed to listen on %s: %s", name, strerror(error)); + if (!retval) { + retval = error; + } + } + } + + *pvconnsp = pvconns; + *n_pvconnsp = n_pvconns; + + return retval; +} + +int +ofproto_set_listeners(struct ofproto *ofproto, const struct svec *listeners) +{ + return set_pvconns(&ofproto->listeners, &ofproto->n_listeners, listeners); +} + +int +ofproto_set_snoops(struct ofproto *ofproto, const struct svec *snoops) +{ + return set_pvconns(&ofproto->snoops, &ofproto->n_snoops, snoops); +} + +int +ofproto_set_netflow(struct ofproto *ofproto, const struct svec *collectors, + uint8_t engine_type, uint8_t engine_id, bool add_id_to_iface) +{ + if (collectors && collectors->n) { + if (!ofproto->netflow) { + ofproto->netflow = netflow_create(); + } + netflow_set_engine(ofproto->netflow, engine_type, engine_id, + add_id_to_iface); + return netflow_set_collectors(ofproto->netflow, collectors); + } else { + netflow_destroy(ofproto->netflow); + ofproto->netflow = NULL; + return 0; + } +} + +void +ofproto_set_failure(struct ofproto *ofproto, bool fail_open) +{ + if (fail_open) { + struct rconn *rconn = ofproto->controller->rconn; + int trigger_duration = rconn_get_probe_interval(rconn) * 3; + if (!ofproto->fail_open) { + ofproto->fail_open = fail_open_create(ofproto, trigger_duration, + ofproto->switch_status, + rconn); + } else { + fail_open_set_trigger_duration(ofproto->fail_open, + trigger_duration); + } + } else { + fail_open_destroy(ofproto->fail_open); + ofproto->fail_open = NULL; + } +} + +void +ofproto_set_rate_limit(struct ofproto *ofproto, + int rate_limit, int burst_limit) +{ + if (rate_limit > 0) { + if (!ofproto->miss_sched) { + ofproto->miss_sched = pinsched_create(rate_limit, burst_limit, + ofproto->switch_status); + ofproto->action_sched = pinsched_create(rate_limit, burst_limit, + NULL); + } else { + pinsched_set_limits(ofproto->miss_sched, rate_limit, burst_limit); + pinsched_set_limits(ofproto->action_sched, + rate_limit, burst_limit); + } + } else { + pinsched_destroy(ofproto->miss_sched); + ofproto->miss_sched = NULL; + pinsched_destroy(ofproto->action_sched); + ofproto->action_sched = NULL; + } +} + +int +ofproto_set_stp(struct ofproto *ofproto UNUSED, bool enable_stp) +{ + /* XXX */ + if (enable_stp) { + VLOG_WARN("STP is not yet implemented"); + return EINVAL; + } else { + return 0; + } +} + +int +ofproto_set_remote_execution(struct ofproto *ofproto, const char *command_acl, + const char *command_dir) +{ + if (command_acl) { + if (!ofproto->executer) { + return executer_create(command_acl, command_dir, + &ofproto->executer); + } else { + executer_set_acl(ofproto->executer, command_acl, command_dir); + } + } else { + executer_destroy(ofproto->executer); + ofproto->executer = NULL; + } + return 0; +} + +uint64_t +ofproto_get_datapath_id(const struct ofproto *ofproto) +{ + return ofproto->datapath_id; +} + +int +ofproto_get_probe_interval(const struct ofproto *ofproto) +{ + return rconn_get_probe_interval(ofproto->controller->rconn); +} + +int +ofproto_get_max_backoff(const struct ofproto *ofproto) +{ + return rconn_get_max_backoff(ofproto->controller->rconn); +} + +bool +ofproto_get_in_band(const struct ofproto *ofproto) +{ + return ofproto->in_band != NULL; +} + +bool +ofproto_get_discovery(const struct ofproto *ofproto) +{ + return ofproto->discovery != NULL; +} + +const char * +ofproto_get_controller(const struct ofproto *ofproto) +{ + return rconn_get_name(ofproto->controller->rconn); +} + +void +ofproto_get_listeners(const struct ofproto *ofproto, struct svec *listeners) +{ + size_t i; + + for (i = 0; i < ofproto->n_listeners; i++) { + svec_add(listeners, pvconn_get_name(ofproto->listeners[i])); + } +} + +void +ofproto_get_snoops(const struct ofproto *ofproto, struct svec *snoops) +{ + size_t i; + + for (i = 0; i < ofproto->n_snoops; i++) { + svec_add(snoops, pvconn_get_name(ofproto->snoops[i])); + } +} + +void +ofproto_destroy(struct ofproto *p) +{ + struct ofconn *ofconn, *next_ofconn; + struct ofport *ofport; + unsigned int port_no; + size_t i; + + if (!p) { + return; + } + + ofproto_flush_flows(p); + classifier_destroy(&p->cls); + + LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node, + &p->all_conns) { + ofconn_destroy(ofconn, p); + } + + dpif_close(p->dpif); + netdev_monitor_destroy(p->netdev_monitor); + PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) { + ofport_free(ofport); + } + shash_destroy(&p->port_by_name); + + switch_status_destroy(p->switch_status); + in_band_destroy(p->in_band); + discovery_destroy(p->discovery); + fail_open_destroy(p->fail_open); + pinsched_destroy(p->miss_sched); + pinsched_destroy(p->action_sched); + executer_destroy(p->executer); + netflow_destroy(p->netflow); + + switch_status_unregister(p->ss_cat); + + for (i = 0; i < p->n_listeners; i++) { + pvconn_close(p->listeners[i]); + } + free(p->listeners); + + for (i = 0; i < p->n_snoops; i++) { + pvconn_close(p->snoops[i]); + } + free(p->snoops); + + mac_learning_destroy(p->ml); + + free(p); +} + +int +ofproto_run(struct ofproto *p) +{ + int error = ofproto_run1(p); + if (!error) { + error = ofproto_run2(p, false); + } + return error; +} + +static void +process_port_change(struct ofproto *ofproto, int error, char *devname) +{ + if (error == ENOBUFS) { + reinit_ports(ofproto); + } else if (!error) { + update_port(ofproto, devname); + free(devname); + } +} + +int +ofproto_run1(struct ofproto *p) +{ + struct ofconn *ofconn, *next_ofconn; + char *devname; + int error; + int i; + + for (i = 0; i < 50; i++) { + struct ofpbuf *buf; + int error; + + error = dpif_recv(p->dpif, &buf); + if (error) { + if (error == ENODEV) { + /* Someone destroyed the datapath behind our back. The caller + * better destroy us and give up, because we're just going to + * spin from here on out. */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); + VLOG_ERR_RL(&rl, "%s: datapath was destroyed externally", + dpif_name(p->dpif)); + return ENODEV; + } + break; + } + + handle_odp_msg(p, buf); + } + + while ((error = dpif_port_poll(p->dpif, &devname)) != EAGAIN) { + process_port_change(p, error, devname); + } + while ((error = netdev_monitor_poll(p->netdev_monitor, + &devname)) != EAGAIN) { + process_port_change(p, error, devname); + } + + if (p->in_band) { + in_band_run(p->in_band); + } + if (p->discovery) { + char *controller_name; + if (rconn_is_connectivity_questionable(p->controller->rconn)) { + discovery_question_connectivity(p->discovery); + } + if (discovery_run(p->discovery, &controller_name)) { + if (controller_name) { + rconn_connect(p->controller->rconn, controller_name); + } else { + rconn_disconnect(p->controller->rconn); + } + } + } + if (p->fail_open) { + fail_open_run(p->fail_open); + } + pinsched_run(p->miss_sched, send_packet_in_miss, p); + pinsched_run(p->action_sched, send_packet_in_action, p); + if (p->executer) { + executer_run(p->executer); + } + + LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node, + &p->all_conns) { + ofconn_run(ofconn, p); + } + + for (i = 0; i < p->n_listeners; i++) { + struct vconn *vconn; + int retval; + + retval = pvconn_accept(p->listeners[i], OFP_VERSION, &vconn); + if (!retval) { + ofconn_create(p, rconn_new_from_vconn("passive", vconn)); + } else if (retval != EAGAIN) { + VLOG_WARN_RL(&rl, "accept failed (%s)", strerror(retval)); + } + } + + for (i = 0; i < p->n_snoops; i++) { + struct vconn *vconn; + int retval; + + retval = pvconn_accept(p->snoops[i], OFP_VERSION, &vconn); + if (!retval) { + rconn_add_monitor(p->controller->rconn, vconn); + } else if (retval != EAGAIN) { + VLOG_WARN_RL(&rl, "accept failed (%s)", strerror(retval)); + } + } + + if (time_msec() >= p->next_expiration) { + COVERAGE_INC(ofproto_expiration); + p->next_expiration = time_msec() + 1000; + update_used(p); + + classifier_for_each(&p->cls, CLS_INC_ALL, expire_rule, p); + + /* Let the hook know that we're at a stable point: all outstanding data + * in existing flows has been accounted to the account_cb. Thus, the + * hook can now reasonably do operations that depend on having accurate + * flow volume accounting (currently, that's just bond rebalancing). */ + if (p->ofhooks->account_checkpoint_cb) { + p->ofhooks->account_checkpoint_cb(p->aux); + } + } + + if (p->netflow) { + netflow_run(p->netflow); + } + + return 0; +} + +struct revalidate_cbdata { + struct ofproto *ofproto; + bool revalidate_all; /* Revalidate all exact-match rules? */ + bool revalidate_subrules; /* Revalidate all exact-match subrules? */ + struct tag_set revalidate_set; /* Set of tags to revalidate. */ +}; + +int +ofproto_run2(struct ofproto *p, bool revalidate_all) +{ + if (p->need_revalidate || revalidate_all + || !tag_set_is_empty(&p->revalidate_set)) { + struct revalidate_cbdata cbdata; + cbdata.ofproto = p; + cbdata.revalidate_all = revalidate_all; + cbdata.revalidate_subrules = p->need_revalidate; + cbdata.revalidate_set = p->revalidate_set; + tag_set_init(&p->revalidate_set); + COVERAGE_INC(ofproto_revalidate); + classifier_for_each(&p->cls, CLS_INC_EXACT, revalidate_cb, &cbdata); + p->need_revalidate = false; + } + + return 0; +} + +void +ofproto_wait(struct ofproto *p) +{ + struct ofconn *ofconn; + size_t i; + + dpif_recv_wait(p->dpif); + dpif_port_poll_wait(p->dpif); + netdev_monitor_poll_wait(p->netdev_monitor); + LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { + ofconn_wait(ofconn); + } + if (p->in_band) { + in_band_wait(p->in_band); + } + if (p->discovery) { + discovery_wait(p->discovery); + } + if (p->fail_open) { + fail_open_wait(p->fail_open); + } + pinsched_wait(p->miss_sched); + pinsched_wait(p->action_sched); + if (p->executer) { + executer_wait(p->executer); + } + if (!tag_set_is_empty(&p->revalidate_set)) { + poll_immediate_wake(); + } + if (p->need_revalidate) { + /* Shouldn't happen, but if it does just go around again. */ + VLOG_DBG_RL(&rl, "need revalidate in ofproto_wait_cb()"); + poll_immediate_wake(); + } else if (p->next_expiration != LLONG_MAX) { + poll_timer_wait(p->next_expiration - time_msec()); + } + for (i = 0; i < p->n_listeners; i++) { + pvconn_wait(p->listeners[i]); + } + for (i = 0; i < p->n_snoops; i++) { + pvconn_wait(p->snoops[i]); + } +} + +void +ofproto_revalidate(struct ofproto *ofproto, tag_type tag) +{ + tag_set_add(&ofproto->revalidate_set, tag); +} + +struct tag_set * +ofproto_get_revalidate_set(struct ofproto *ofproto) +{ + return &ofproto->revalidate_set; +} + +bool +ofproto_is_alive(const struct ofproto *p) +{ + return p->discovery || rconn_is_alive(p->controller->rconn); +} + +int +ofproto_send_packet(struct ofproto *p, const flow_t *flow, + const union ofp_action *actions, size_t n_actions, + const struct ofpbuf *packet) +{ + struct odp_actions odp_actions; + int error; + + error = xlate_actions(actions, n_actions, flow, p, packet, &odp_actions, + NULL, NULL); + if (error) { + return error; + } + + /* XXX Should we translate the dpif_execute() errno value into an OpenFlow + * error code? */ + dpif_execute(p->dpif, flow->in_port, odp_actions.actions, + odp_actions.n_actions, packet); + return 0; +} + +void +ofproto_add_flow(struct ofproto *p, + const flow_t *flow, uint32_t wildcards, unsigned int priority, + const union ofp_action *actions, size_t n_actions, + int idle_timeout) +{ + struct rule *rule; + rule = rule_create(NULL, actions, n_actions, + idle_timeout >= 0 ? idle_timeout : 5 /* XXX */, 0); + cls_rule_from_flow(&rule->cr, flow, wildcards, priority); + rule_insert(p, rule, NULL, 0); +} + +void +ofproto_delete_flow(struct ofproto *ofproto, const flow_t *flow, + uint32_t wildcards, unsigned int priority) +{ + struct rule *rule; + + rule = rule_from_cls_rule(classifier_find_rule_exactly(&ofproto->cls, + flow, wildcards, + priority)); + if (rule) { + rule_remove(ofproto, rule); + } +} + +static void +destroy_rule(struct cls_rule *rule_, void *ofproto_) +{ + struct rule *rule = rule_from_cls_rule(rule_); + struct ofproto *ofproto = ofproto_; + + /* Mark the flow as not installed, even though it might really be + * installed, so that rule_remove() doesn't bother trying to uninstall it. + * There is no point in uninstalling it individually since we are about to + * blow away all the flows with dpif_flow_flush(). */ + rule->installed = false; + + rule_remove(ofproto, rule); +} + +void +ofproto_flush_flows(struct ofproto *ofproto) +{ + COVERAGE_INC(ofproto_flush); + classifier_for_each(&ofproto->cls, CLS_INC_ALL, destroy_rule, ofproto); + dpif_flow_flush(ofproto->dpif); + if (ofproto->in_band) { + in_band_flushed(ofproto->in_band); + } + if (ofproto->fail_open) { + fail_open_flushed(ofproto->fail_open); + } +} + +static void +reinit_ports(struct ofproto *p) +{ + struct svec devnames; + struct ofport *ofport; + unsigned int port_no; + struct odp_port *odp_ports; + size_t n_odp_ports; + size_t i; + + svec_init(&devnames); + PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) { + svec_add (&devnames, (char *) ofport->opp.name); + } + dpif_port_list(p->dpif, &odp_ports, &n_odp_ports); + for (i = 0; i < n_odp_ports; i++) { + svec_add (&devnames, odp_ports[i].devname); + } + free(odp_ports); + + svec_sort_unique(&devnames); + for (i = 0; i < devnames.n; i++) { + update_port(p, devnames.names[i]); + } + svec_destroy(&devnames); +} + +static void +refresh_port_group(struct ofproto *p, unsigned int group) +{ + uint16_t *ports; + size_t n_ports; + struct ofport *port; + unsigned int port_no; + + assert(group == DP_GROUP_ALL || group == DP_GROUP_FLOOD); + + ports = xmalloc(port_array_count(&p->ports) * sizeof *ports); + n_ports = 0; + PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) { + if (group == DP_GROUP_ALL || !(port->opp.config & OFPPC_NO_FLOOD)) { + ports[n_ports++] = port_no; + } + } + dpif_port_group_set(p->dpif, group, ports, n_ports); + free(ports); +} + +static void +refresh_port_groups(struct ofproto *p) +{ + refresh_port_group(p, DP_GROUP_FLOOD); + refresh_port_group(p, DP_GROUP_ALL); +} + +static struct ofport * +make_ofport(const struct odp_port *odp_port) +{ + enum netdev_flags flags; + struct ofport *ofport; + struct netdev *netdev; + bool carrier; + int error; + + error = netdev_open(odp_port->devname, NETDEV_ETH_TYPE_NONE, &netdev); + if (error) { + VLOG_WARN_RL(&rl, "ignoring port %s (%"PRIu16") because netdev %s " + "cannot be opened (%s)", + odp_port->devname, odp_port->port, + odp_port->devname, strerror(error)); + return NULL; + } + + ofport = xmalloc(sizeof *ofport); + ofport->netdev = netdev; + ofport->opp.port_no = odp_port_to_ofp_port(odp_port->port); + memcpy(ofport->opp.hw_addr, netdev_get_etheraddr(netdev), ETH_ALEN); + memcpy(ofport->opp.name, odp_port->devname, + MIN(sizeof ofport->opp.name, sizeof odp_port->devname)); + ofport->opp.name[sizeof ofport->opp.name - 1] = '\0'; + + netdev_get_flags(netdev, &flags); + ofport->opp.config = flags & NETDEV_UP ? 0 : OFPPC_PORT_DOWN; + + netdev_get_carrier(netdev, &carrier); + ofport->opp.state = carrier ? 0 : OFPPS_LINK_DOWN; + + netdev_get_features(netdev, + &ofport->opp.curr, &ofport->opp.advertised, + &ofport->opp.supported, &ofport->opp.peer); + return ofport; +} + +static bool +ofport_conflicts(const struct ofproto *p, const struct odp_port *odp_port) +{ + if (port_array_get(&p->ports, odp_port->port)) { + VLOG_WARN_RL(&rl, "ignoring duplicate port %"PRIu16" in datapath", + odp_port->port); + return true; + } else if (shash_find(&p->port_by_name, odp_port->devname)) { + VLOG_WARN_RL(&rl, "ignoring duplicate device %s in datapath", + odp_port->devname); + return true; + } else { + return false; + } +} + +static int +ofport_equal(const struct ofport *a_, const struct ofport *b_) +{ + const struct ofp_phy_port *a = &a_->opp; + const struct ofp_phy_port *b = &b_->opp; + + BUILD_ASSERT_DECL(sizeof *a == 48); /* Detect ofp_phy_port changes. */ + return (a->port_no == b->port_no + && !memcmp(a->hw_addr, b->hw_addr, sizeof a->hw_addr) + && !strcmp((char *) a->name, (char *) b->name) + && a->state == b->state + && a->config == b->config + && a->curr == b->curr + && a->advertised == b->advertised + && a->supported == b->supported + && a->peer == b->peer); +} + +static void +send_port_status(struct ofproto *p, const struct ofport *ofport, + uint8_t reason) +{ + /* XXX Should limit the number of queued port status change messages. */ + struct ofconn *ofconn; + LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { + struct ofp_port_status *ops; + struct ofpbuf *b; + + ops = make_openflow_xid(sizeof *ops, OFPT_PORT_STATUS, 0, &b); + ops->reason = reason; + ops->desc = ofport->opp; + hton_ofp_phy_port(&ops->desc); + queue_tx(b, ofconn, NULL); + } + if (p->ofhooks->port_changed_cb) { + p->ofhooks->port_changed_cb(reason, &ofport->opp, p->aux); + } +} + +static void +ofport_install(struct ofproto *p, struct ofport *ofport) +{ + netdev_monitor_add(p->netdev_monitor, ofport->netdev); + port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no), + ofport); + shash_add(&p->port_by_name, (char *) ofport->opp.name, ofport); +} + +static void +ofport_remove(struct ofproto *p, struct ofport *ofport) +{ + netdev_monitor_remove(p->netdev_monitor, ofport->netdev); + port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no), NULL); + shash_delete(&p->port_by_name, + shash_find(&p->port_by_name, (char *) ofport->opp.name)); +} + +static void +ofport_free(struct ofport *ofport) +{ + if (ofport) { + netdev_close(ofport->netdev); + free(ofport); + } +} + +static void +update_port(struct ofproto *p, const char *devname) +{ + struct odp_port odp_port; + struct ofport *ofport; + int error; + + COVERAGE_INC(ofproto_update_port); + ofport = shash_find_data(&p->port_by_name, devname); + error = dpif_port_query_by_name(p->dpif, devname, &odp_port); + if (!error) { + if (!ofport) { + /* New port. */ + if (!ofport_conflicts(p, &odp_port)) { + ofport = make_ofport(&odp_port); + if (ofport) { + ofport_install(p, ofport); + send_port_status(p, ofport, OFPPR_ADD); + } + } + } else { + /* Modified port. */ + struct ofport *new_ofport = make_ofport(&odp_port); + if (!new_ofport) { + return; + } + + new_ofport->opp.config &= OFPPC_PORT_DOWN; + new_ofport->opp.config |= ofport->opp.config & ~OFPPC_PORT_DOWN; + if (ofport_equal(ofport, new_ofport)) { + /* False alarm--no change. */ + ofport_free(new_ofport); + } else { + ofport_remove(p, ofport); + ofport_install(p, new_ofport); + ofport_free(ofport); + send_port_status(p, new_ofport, OFPPR_MODIFY); + } + } + } else if (error == ENOENT || error == ENODEV) { + /* Deleted port. */ + if (ofport) { + send_port_status(p, ofport, OFPPR_DELETE); + ofport_remove(p, ofport); + ofport_free(ofport); + } + } else { + VLOG_WARN_RL(&rl, "dpif_port_query_by_name returned unexpected error " + "%s", strerror(error)); + return; + } + refresh_port_groups(p); +} + +static int +init_ports(struct ofproto *p) +{ + struct odp_port *ports; + size_t n_ports; + size_t i; + int error; + + error = dpif_port_list(p->dpif, &ports, &n_ports); + if (error) { + return error; + } + + for (i = 0; i < n_ports; i++) { + const struct odp_port *odp_port = &ports[i]; + if (!ofport_conflicts(p, odp_port)) { + struct ofport *ofport = make_ofport(odp_port); + if (ofport) { + ofport_install(p, ofport); + } + } + } + free(ports); + refresh_port_groups(p); + return 0; +} + +static struct ofconn * +ofconn_create(struct ofproto *p, struct rconn *rconn) +{ + struct ofconn *ofconn = xmalloc(sizeof *ofconn); + list_push_back(&p->all_conns, &ofconn->node); + ofconn->rconn = rconn; + ofconn->pktbuf = NULL; + ofconn->send_flow_exp = false; + ofconn->miss_send_len = 0; + ofconn->packet_in_counter = rconn_packet_counter_create (); + ofconn->reply_counter = rconn_packet_counter_create (); + return ofconn; +} + +static void +ofconn_destroy(struct ofconn *ofconn, struct ofproto *p) +{ + if (p->executer) { + executer_rconn_closing(p->executer, ofconn->rconn); + } + + list_remove(&ofconn->node); + rconn_destroy(ofconn->rconn); + rconn_packet_counter_destroy(ofconn->packet_in_counter); + rconn_packet_counter_destroy(ofconn->reply_counter); + pktbuf_destroy(ofconn->pktbuf); + free(ofconn); +} + +static void +ofconn_run(struct ofconn *ofconn, struct ofproto *p) +{ + int iteration; + + rconn_run(ofconn->rconn); + + if (rconn_packet_counter_read (ofconn->reply_counter) < OFCONN_REPLY_MAX) { + /* Limit the number of iterations to prevent other tasks from + * starving. */ + for (iteration = 0; iteration < 50; iteration++) { + struct ofpbuf *of_msg = rconn_recv(ofconn->rconn); + if (!of_msg) { + break; + } + handle_openflow(ofconn, p, of_msg); + ofpbuf_delete(of_msg); + } + } + + if (ofconn != p->controller && !rconn_is_alive(ofconn->rconn)) { + ofconn_destroy(ofconn, p); + } +} + +static void +ofconn_wait(struct ofconn *ofconn) +{ + rconn_run_wait(ofconn->rconn); + if (rconn_packet_counter_read (ofconn->reply_counter) < OFCONN_REPLY_MAX) { + rconn_recv_wait(ofconn->rconn); + } else { + COVERAGE_INC(ofproto_ofconn_stuck); + } +} + +/* Caller is responsible for initializing the 'cr' member of the returned + * rule. */ +static struct rule * +rule_create(struct rule *super, + const union ofp_action *actions, size_t n_actions, + uint16_t idle_timeout, uint16_t hard_timeout) +{ + struct rule *rule = xcalloc(1, sizeof *rule); + rule->idle_timeout = idle_timeout; + rule->hard_timeout = hard_timeout; + rule->used = rule->created = time_msec(); + rule->super = super; + if (super) { + list_push_back(&super->list, &rule->list); + } else { + list_init(&rule->list); + } + rule->n_actions = n_actions; + rule->actions = xmemdup(actions, n_actions * sizeof *actions); + return rule; +} + +static struct rule * +rule_from_cls_rule(const struct cls_rule *cls_rule) +{ + return cls_rule ? CONTAINER_OF(cls_rule, struct rule, cr) : NULL; +} + +static void +rule_free(struct rule *rule) +{ + free(rule->actions); + free(rule->odp_actions); + free(rule); +} + +/* Destroys 'rule'. If 'rule' is a subrule, also removes it from its + * super-rule's list of subrules. If 'rule' is a super-rule, also iterates + * through all of its subrules and revalidates them, destroying any that no + * longer has a super-rule (which is probably all of them). + * + * Before calling this function, the caller must make have removed 'rule' from + * the classifier. If 'rule' is an exact-match rule, the caller is also + * responsible for ensuring that it has been uninstalled from the datapath. */ +static void +rule_destroy(struct ofproto *ofproto, struct rule *rule) +{ + if (!rule->super) { + struct rule *subrule, *next; + LIST_FOR_EACH_SAFE (subrule, next, struct rule, list, &rule->list) { + revalidate_rule(ofproto, subrule); + } + } else { + list_remove(&rule->list); + } + rule_free(rule); +} + +static bool +rule_has_out_port(const struct rule *rule, uint16_t out_port) +{ + const union ofp_action *oa; + struct actions_iterator i; + + if (out_port == htons(OFPP_NONE)) { + return true; + } + for (oa = actions_first(&i, rule->actions, rule->n_actions); oa; + oa = actions_next(&i)) { + if (oa->type == htons(OFPAT_OUTPUT) && oa->output.port == out_port) { + return true; + } + } + return false; +} + +/* Executes the actions indicated by 'rule' on 'packet', which is in flow + * 'flow' and is considered to have arrived on ODP port 'in_port'. + * + * The flow that 'packet' actually contains does not need to actually match + * 'rule'; the actions in 'rule' will be applied to it either way. Likewise, + * the packet and byte counters for 'rule' will be credited for the packet sent + * out whether or not the packet actually matches 'rule'. + * + * If 'rule' is an exact-match rule and 'flow' actually equals the rule's flow, + * the caller must already have accurately composed ODP actions for it given + * 'packet' using rule_make_actions(). If 'rule' is a wildcard rule, or if + * 'rule' is an exact-match rule but 'flow' is not the rule's flow, then this + * function will compose a set of ODP actions based on 'rule''s OpenFlow + * actions and apply them to 'packet'. */ +static void +rule_execute(struct ofproto *ofproto, struct rule *rule, + struct ofpbuf *packet, const flow_t *flow) +{ + const union odp_action *actions; + size_t n_actions; + struct odp_actions a; + + /* Grab or compose the ODP actions. + * + * The special case for an exact-match 'rule' where 'flow' is not the + * rule's flow is important to avoid, e.g., sending a packet out its input + * port simply because the ODP actions were composed for the wrong + * scenario. */ + if (rule->cr.wc.wildcards || !flow_equal(flow, &rule->cr.flow)) { + struct rule *super = rule->super ? rule->super : rule; + if (xlate_actions(super->actions, super->n_actions, flow, ofproto, + packet, &a, NULL, 0)) { + return; + } + actions = a.actions; + n_actions = a.n_actions; + } else { + actions = rule->odp_actions; + n_actions = rule->n_odp_actions; + } + + /* Execute the ODP actions. */ + if (!dpif_execute(ofproto->dpif, flow->in_port, + actions, n_actions, packet)) { + struct odp_flow_stats stats; + flow_extract_stats(flow, packet, &stats); + update_stats(rule, &stats); + rule->used = time_msec(); + } +} + +static void +rule_insert(struct ofproto *p, struct rule *rule, struct ofpbuf *packet, + uint16_t in_port) +{ + struct rule *displaced_rule; + + /* Insert the rule in the classifier. */ + displaced_rule = rule_from_cls_rule(classifier_insert(&p->cls, &rule->cr)); + if (!rule->cr.wc.wildcards) { + rule_make_actions(p, rule, packet); + } + + /* Send the packet and credit it to the rule. */ + if (packet) { + flow_t flow; + flow_extract(packet, in_port, &flow); + rule_execute(p, rule, packet, &flow); + } + + /* Install the rule in the datapath only after sending the packet, to + * avoid packet reordering. */ + if (rule->cr.wc.wildcards) { + COVERAGE_INC(ofproto_add_wc_flow); + p->need_revalidate = true; + } else { + rule_install(p, rule, displaced_rule); + } + + /* Free the rule that was displaced, if any. */ + if (displaced_rule) { + rule_destroy(p, displaced_rule); + } +} + +static struct rule * +rule_create_subrule(struct ofproto *ofproto, struct rule *rule, + const flow_t *flow) +{ + struct rule *subrule = rule_create(rule, NULL, 0, + rule->idle_timeout, rule->hard_timeout); + COVERAGE_INC(ofproto_subrule_create); + cls_rule_from_flow(&subrule->cr, flow, 0, + (rule->cr.priority <= UINT16_MAX ? UINT16_MAX + : rule->cr.priority)); + classifier_insert_exact(&ofproto->cls, &subrule->cr); + + return subrule; +} + +static void +rule_remove(struct ofproto *ofproto, struct rule *rule) +{ + if (rule->cr.wc.wildcards) { + COVERAGE_INC(ofproto_del_wc_flow); + ofproto->need_revalidate = true; + } else { + rule_uninstall(ofproto, rule); + } + classifier_remove(&ofproto->cls, &rule->cr); + rule_destroy(ofproto, rule); +} + +/* Returns true if the actions changed, false otherwise. */ +static bool +rule_make_actions(struct ofproto *p, struct rule *rule, + const struct ofpbuf *packet) +{ + const struct rule *super; + struct odp_actions a; + size_t actions_len; + + assert(!rule->cr.wc.wildcards); + + super = rule->super ? rule->super : rule; + rule->tags = 0; + xlate_actions(super->actions, super->n_actions, &rule->cr.flow, p, + packet, &a, &rule->tags, &rule->may_install); + + actions_len = a.n_actions * sizeof *a.actions; + if (rule->n_odp_actions != a.n_actions + || memcmp(rule->odp_actions, a.actions, actions_len)) { + COVERAGE_INC(ofproto_odp_unchanged); + free(rule->odp_actions); + rule->n_odp_actions = a.n_actions; + rule->odp_actions = xmemdup(a.actions, actions_len); + return true; + } else { + return false; + } +} + +static int +do_put_flow(struct ofproto *ofproto, struct rule *rule, int flags, + struct odp_flow_put *put) +{ + memset(&put->flow.stats, 0, sizeof put->flow.stats); + put->flow.key = rule->cr.flow; + put->flow.actions = rule->odp_actions; + put->flow.n_actions = rule->n_odp_actions; + put->flags = flags; + return dpif_flow_put(ofproto->dpif, put); +} + +static void +rule_install(struct ofproto *p, struct rule *rule, struct rule *displaced_rule) +{ + assert(!rule->cr.wc.wildcards); + + if (rule->may_install) { + struct odp_flow_put put; + if (!do_put_flow(p, rule, + ODPPF_CREATE | ODPPF_MODIFY | ODPPF_ZERO_STATS, + &put)) { + rule->installed = true; + if (displaced_rule) { + update_stats(rule, &put.flow.stats); + rule_post_uninstall(p, displaced_rule); + } + } + } else if (displaced_rule) { + rule_uninstall(p, displaced_rule); + } +} + +static void +rule_reinstall(struct ofproto *ofproto, struct rule *rule) +{ + if (rule->installed) { + struct odp_flow_put put; + COVERAGE_INC(ofproto_dp_missed); + do_put_flow(ofproto, rule, ODPPF_CREATE | ODPPF_MODIFY, &put); + } else { + rule_install(ofproto, rule, NULL); + } +} + +static void +rule_update_actions(struct ofproto *ofproto, struct rule *rule) +{ + bool actions_changed = rule_make_actions(ofproto, rule, NULL); + if (rule->may_install) { + if (rule->installed) { + if (actions_changed) { + /* XXX should really do rule_post_uninstall() for the *old* set + * of actions, and distinguish the old stats from the new. */ + struct odp_flow_put put; + do_put_flow(ofproto, rule, ODPPF_CREATE | ODPPF_MODIFY, &put); + } + } else { + rule_install(ofproto, rule, NULL); + } + } else { + rule_uninstall(ofproto, rule); + } +} + +static void +rule_account(struct ofproto *ofproto, struct rule *rule, uint64_t extra_bytes) +{ + uint64_t total_bytes = rule->byte_count + extra_bytes; + + if (ofproto->ofhooks->account_flow_cb + && total_bytes > rule->accounted_bytes) + { + ofproto->ofhooks->account_flow_cb( + &rule->cr.flow, rule->odp_actions, rule->n_odp_actions, + total_bytes - rule->accounted_bytes, ofproto->aux); + rule->accounted_bytes = total_bytes; + } +} + +static void +rule_uninstall(struct ofproto *p, struct rule *rule) +{ + assert(!rule->cr.wc.wildcards); + if (rule->installed) { + struct odp_flow odp_flow; + + odp_flow.key = rule->cr.flow; + odp_flow.actions = NULL; + odp_flow.n_actions = 0; + if (!dpif_flow_del(p->dpif, &odp_flow)) { + update_stats(rule, &odp_flow.stats); + } + rule->installed = false; + + rule_post_uninstall(p, rule); + } +} + +static void +rule_post_uninstall(struct ofproto *ofproto, struct rule *rule) +{ + struct rule *super = rule->super; + + rule_account(ofproto, rule, 0); + if (ofproto->netflow) { + struct ofexpired expired; + expired.flow = rule->cr.flow; + expired.packet_count = rule->packet_count; + expired.byte_count = rule->byte_count; + expired.used = rule->used; + expired.created = rule->created; + expired.tcp_flags = rule->tcp_flags; + expired.ip_tos = rule->ip_tos; + netflow_expire(ofproto->netflow, &expired); + } + if (super) { + super->packet_count += rule->packet_count; + super->byte_count += rule->byte_count; + super->tcp_flags |= rule->tcp_flags; + if (rule->packet_count) { + super->ip_tos = rule->ip_tos; + } + } + + /* Reset counters to prevent double counting if the rule ever gets + * reinstalled. */ + rule->packet_count = 0; + rule->byte_count = 0; + rule->accounted_bytes = 0; + rule->tcp_flags = 0; + rule->ip_tos = 0; +} + +static void +queue_tx(struct ofpbuf *msg, const struct ofconn *ofconn, + struct rconn_packet_counter *counter) +{ + update_openflow_length(msg); + if (rconn_send(ofconn->rconn, msg, counter)) { + ofpbuf_delete(msg); + } +} + +static void +send_error(const struct ofconn *ofconn, const struct ofp_header *oh, + int error, const void *data, size_t len) +{ + struct ofpbuf *buf; + struct ofp_error_msg *oem; + + if (!(error >> 16)) { + VLOG_WARN_RL(&rl, "not sending bad error code %d to controller", + error); + return; + } + + COVERAGE_INC(ofproto_error); + oem = make_openflow_xid(len + sizeof *oem, OFPT_ERROR, + oh ? oh->xid : 0, &buf); + oem->type = htons((unsigned int) error >> 16); + oem->code = htons(error & 0xffff); + memcpy(oem->data, data, len); + queue_tx(buf, ofconn, ofconn->reply_counter); +} + +static void +send_error_oh(const struct ofconn *ofconn, const struct ofp_header *oh, + int error) +{ + size_t oh_length = ntohs(oh->length); + send_error(ofconn, oh, error, oh, MIN(oh_length, 64)); +} + +static void +hton_ofp_phy_port(struct ofp_phy_port *opp) +{ + opp->port_no = htons(opp->port_no); + opp->config = htonl(opp->config); + opp->state = htonl(opp->state); + opp->curr = htonl(opp->curr); + opp->advertised = htonl(opp->advertised); + opp->supported = htonl(opp->supported); + opp->peer = htonl(opp->peer); +} + +static int +handle_echo_request(struct ofconn *ofconn, struct ofp_header *oh) +{ + struct ofp_header *rq = oh; + queue_tx(make_echo_reply(rq), ofconn, ofconn->reply_counter); + return 0; +} + +static int +handle_features_request(struct ofproto *p, struct ofconn *ofconn, + struct ofp_header *oh) +{ + struct ofp_switch_features *osf; + struct ofpbuf *buf; + unsigned int port_no; + struct ofport *port; + + osf = make_openflow_xid(sizeof *osf, OFPT_FEATURES_REPLY, oh->xid, &buf); + osf->datapath_id = htonll(p->datapath_id); + osf->n_buffers = htonl(pktbuf_capacity()); + osf->n_tables = 2; + osf->capabilities = htonl(OFPC_FLOW_STATS | OFPC_TABLE_STATS | + OFPC_PORT_STATS | OFPC_MULTI_PHY_TX); + osf->actions = htonl((1u << OFPAT_OUTPUT) | + (1u << OFPAT_SET_VLAN_VID) | + (1u << OFPAT_SET_VLAN_PCP) | + (1u << OFPAT_STRIP_VLAN) | + (1u << OFPAT_SET_DL_SRC) | + (1u << OFPAT_SET_DL_DST) | + (1u << OFPAT_SET_NW_SRC) | + (1u << OFPAT_SET_NW_DST) | + (1u << OFPAT_SET_TP_SRC) | + (1u << OFPAT_SET_TP_DST)); + + PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) { + hton_ofp_phy_port(ofpbuf_put(buf, &port->opp, sizeof port->opp)); + } + + queue_tx(buf, ofconn, ofconn->reply_counter); + return 0; +} + +static int +handle_get_config_request(struct ofproto *p, struct ofconn *ofconn, + struct ofp_header *oh) +{ + struct ofpbuf *buf; + struct ofp_switch_config *osc; + uint16_t flags; + bool drop_frags; + + /* Figure out flags. */ + dpif_get_drop_frags(p->dpif, &drop_frags); + flags = drop_frags ? OFPC_FRAG_DROP : OFPC_FRAG_NORMAL; + if (ofconn->send_flow_exp) { + flags |= OFPC_SEND_FLOW_EXP; + } + + /* Send reply. */ + osc = make_openflow_xid(sizeof *osc, OFPT_GET_CONFIG_REPLY, oh->xid, &buf); + osc->flags = htons(flags); + osc->miss_send_len = htons(ofconn->miss_send_len); + queue_tx(buf, ofconn, ofconn->reply_counter); + + return 0; +} + +static int +handle_set_config(struct ofproto *p, struct ofconn *ofconn, + struct ofp_switch_config *osc) +{ + uint16_t flags; + int error; + + error = check_ofp_message(&osc->header, OFPT_SET_CONFIG, sizeof *osc); + if (error) { + return error; + } + flags = ntohs(osc->flags); + + ofconn->send_flow_exp = (flags & OFPC_SEND_FLOW_EXP) != 0; + + if (ofconn == p->controller) { + switch (flags & OFPC_FRAG_MASK) { + case OFPC_FRAG_NORMAL: + dpif_set_drop_frags(p->dpif, false); + break; + case OFPC_FRAG_DROP: + dpif_set_drop_frags(p->dpif, true); + break; + default: + VLOG_WARN_RL(&rl, "requested bad fragment mode (flags=%"PRIx16")", + osc->flags); + break; + } + } + + if ((ntohs(osc->miss_send_len) != 0) != (ofconn->miss_send_len != 0)) { + if (ntohs(osc->miss_send_len) != 0) { + ofconn->pktbuf = pktbuf_create(); + } else { + pktbuf_destroy(ofconn->pktbuf); + } + } + + ofconn->miss_send_len = ntohs(osc->miss_send_len); + + return 0; +} + +static void +add_output_group_action(struct odp_actions *actions, uint16_t group) +{ + odp_actions_add(actions, ODPAT_OUTPUT_GROUP)->output_group.group = group; +} + +static void +add_controller_action(struct odp_actions *actions, + const struct ofp_action_output *oao) +{ + union odp_action *a = odp_actions_add(actions, ODPAT_CONTROLLER); + a->controller.arg = oao->max_len ? ntohs(oao->max_len) : UINT32_MAX; +} + +struct action_xlate_ctx { + /* Input. */ + const flow_t *flow; /* Flow to which these actions correspond. */ + int recurse; /* Recursion level, via xlate_table_action. */ + struct ofproto *ofproto; + const struct ofpbuf *packet; /* The packet corresponding to 'flow', or a + * null pointer if we are revalidating + * without a packet to refer to. */ + + /* Output. */ + struct odp_actions *out; /* Datapath actions. */ + tag_type *tags; /* Tags associated with OFPP_NORMAL actions. */ + bool may_setup_flow; /* True ordinarily; false if the actions must + * be reassessed for every packet. */ +}; + +static void do_xlate_actions(const union ofp_action *in, size_t n_in, + struct action_xlate_ctx *ctx); + +static void +add_output_action(struct action_xlate_ctx *ctx, uint16_t port) +{ + const struct ofport *ofport = port_array_get(&ctx->ofproto->ports, port); + if (!ofport || !(ofport->opp.config & OFPPC_NO_FWD)) { + odp_actions_add(ctx->out, ODPAT_OUTPUT)->output.port = port; + } +} + +static struct rule * +lookup_valid_rule(struct ofproto *ofproto, const flow_t *flow) +{ + struct rule *rule; + rule = rule_from_cls_rule(classifier_lookup(&ofproto->cls, flow)); + + /* The rule we found might not be valid, since we could be in need of + * revalidation. If it is not valid, don't return it. */ + if (rule + && rule->super + && ofproto->need_revalidate + && !revalidate_rule(ofproto, rule)) { + COVERAGE_INC(ofproto_invalidated); + return NULL; + } + + return rule; +} + +static void +xlate_table_action(struct action_xlate_ctx *ctx, uint16_t in_port) +{ + if (!ctx->recurse) { + struct rule *rule; + flow_t flow; + + flow = *ctx->flow; + flow.in_port = in_port; + + rule = lookup_valid_rule(ctx->ofproto, &flow); + if (rule) { + if (rule->super) { + rule = rule->super; + } + + ctx->recurse++; + do_xlate_actions(rule->actions, rule->n_actions, ctx); + ctx->recurse--; + } + } +} + +static void +xlate_output_action(struct action_xlate_ctx *ctx, + const struct ofp_action_output *oao) +{ + uint16_t odp_port; + + switch (ntohs(oao->port)) { + case OFPP_IN_PORT: + add_output_action(ctx, ctx->flow->in_port); + break; + case OFPP_TABLE: + xlate_table_action(ctx, ctx->flow->in_port); + break; + case OFPP_NORMAL: + if (!ctx->ofproto->ofhooks->normal_cb(ctx->flow, ctx->packet, + ctx->out, ctx->tags, + ctx->ofproto->aux)) { + COVERAGE_INC(ofproto_uninstallable); + ctx->may_setup_flow = false; + } + break; + case OFPP_FLOOD: + add_output_group_action(ctx->out, DP_GROUP_FLOOD); + break; + case OFPP_ALL: + add_output_group_action(ctx->out, DP_GROUP_ALL); + break; + case OFPP_CONTROLLER: + add_controller_action(ctx->out, oao); + break; + case OFPP_LOCAL: + add_output_action(ctx, ODPP_LOCAL); + break; + default: + odp_port = ofp_port_to_odp_port(ntohs(oao->port)); + if (odp_port != ctx->flow->in_port) { + add_output_action(ctx, odp_port); + } + break; + } +} + +static void +xlate_nicira_action(struct action_xlate_ctx *ctx, + const struct nx_action_header *nah) +{ + const struct nx_action_resubmit *nar; + int subtype = ntohs(nah->subtype); + + assert(nah->vendor == htonl(NX_VENDOR_ID)); + switch (subtype) { + case NXAST_RESUBMIT: + nar = (const struct nx_action_resubmit *) nah; + xlate_table_action(ctx, ofp_port_to_odp_port(ntohs(nar->in_port))); + break; + + default: + VLOG_DBG_RL(&rl, "unknown Nicira action type %"PRIu16, subtype); + break; + } +} + +static void +do_xlate_actions(const union ofp_action *in, size_t n_in, + struct action_xlate_ctx *ctx) +{ + struct actions_iterator iter; + const union ofp_action *ia; + const struct ofport *port; + + port = port_array_get(&ctx->ofproto->ports, ctx->flow->in_port); + if (port && port->opp.config & (OFPPC_NO_RECV | OFPPC_NO_RECV_STP) && + port->opp.config & (eth_addr_equals(ctx->flow->dl_dst, stp_eth_addr) + ? OFPPC_NO_RECV_STP : OFPPC_NO_RECV)) { + /* Drop this flow. */ + return; + } + + for (ia = actions_first(&iter, in, n_in); ia; ia = actions_next(&iter)) { + uint16_t type = ntohs(ia->type); + union odp_action *oa; + + switch (type) { + case OFPAT_OUTPUT: + xlate_output_action(ctx, &ia->output); + break; + + case OFPAT_SET_VLAN_VID: + oa = odp_actions_add(ctx->out, ODPAT_SET_VLAN_VID); + oa->vlan_vid.vlan_vid = ia->vlan_vid.vlan_vid; + break; + + case OFPAT_SET_VLAN_PCP: + oa = odp_actions_add(ctx->out, ODPAT_SET_VLAN_PCP); + oa->vlan_pcp.vlan_pcp = ia->vlan_pcp.vlan_pcp; + break; + + case OFPAT_STRIP_VLAN: + odp_actions_add(ctx->out, ODPAT_STRIP_VLAN); + break; + + case OFPAT_SET_DL_SRC: + oa = odp_actions_add(ctx->out, ODPAT_SET_DL_SRC); + memcpy(oa->dl_addr.dl_addr, + ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN); + break; + + case OFPAT_SET_DL_DST: + oa = odp_actions_add(ctx->out, ODPAT_SET_DL_DST); + memcpy(oa->dl_addr.dl_addr, + ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN); + break; + + case OFPAT_SET_NW_SRC: + oa = odp_actions_add(ctx->out, ODPAT_SET_NW_SRC); + oa->nw_addr.nw_addr = ia->nw_addr.nw_addr; + break; + + case OFPAT_SET_TP_SRC: + oa = odp_actions_add(ctx->out, ODPAT_SET_TP_SRC); + oa->tp_port.tp_port = ia->tp_port.tp_port; + break; + + case OFPAT_VENDOR: + xlate_nicira_action(ctx, (const struct nx_action_header *) ia); + break; + + default: + VLOG_DBG_RL(&rl, "unknown action type %"PRIu16, type); + break; + } + } +} + +static int +xlate_actions(const union ofp_action *in, size_t n_in, + const flow_t *flow, struct ofproto *ofproto, + const struct ofpbuf *packet, + struct odp_actions *out, tag_type *tags, bool *may_setup_flow) +{ + tag_type no_tags = 0; + struct action_xlate_ctx ctx; + COVERAGE_INC(ofproto_ofp2odp); + odp_actions_init(out); + ctx.flow = flow; + ctx.recurse = 0; + ctx.ofproto = ofproto; + ctx.packet = packet; + ctx.out = out; + ctx.tags = tags ? tags : &no_tags; + ctx.may_setup_flow = true; + do_xlate_actions(in, n_in, &ctx); + if (may_setup_flow) { + *may_setup_flow = ctx.may_setup_flow; + } + if (odp_actions_overflow(out)) { + odp_actions_init(out); + return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_TOO_MANY); + } + return 0; +} + +static int +handle_packet_out(struct ofproto *p, struct ofconn *ofconn, + struct ofp_header *oh) +{ + struct ofp_packet_out *opo; + struct ofpbuf payload, *buffer; + struct odp_actions actions; + int n_actions; + uint16_t in_port; + flow_t flow; + int error; + + error = check_ofp_packet_out(oh, &payload, &n_actions, p->max_ports); + if (error) { + return error; + } + opo = (struct ofp_packet_out *) oh; + + COVERAGE_INC(ofproto_packet_out); + if (opo->buffer_id != htonl(UINT32_MAX)) { + error = pktbuf_retrieve(ofconn->pktbuf, ntohl(opo->buffer_id), + &buffer, &in_port); + if (error) { + return error; + } + payload = *buffer; + } else { + buffer = NULL; + } + + flow_extract(&payload, ofp_port_to_odp_port(ntohs(opo->in_port)), &flow); + error = xlate_actions((const union ofp_action *) opo->actions, n_actions, + &flow, p, &payload, &actions, NULL, NULL); + if (error) { + return error; + } + + dpif_execute(p->dpif, flow.in_port, actions.actions, actions.n_actions, + &payload); + ofpbuf_delete(buffer); + + return 0; +} + +static void +update_port_config(struct ofproto *p, struct ofport *port, + uint32_t config, uint32_t mask) +{ + mask &= config ^ port->opp.config; + if (mask & OFPPC_PORT_DOWN) { + if (config & OFPPC_PORT_DOWN) { + netdev_turn_flags_off(port->netdev, NETDEV_UP, true); + } else { + netdev_turn_flags_on(port->netdev, NETDEV_UP, true); + } + } +#define REVALIDATE_BITS (OFPPC_NO_RECV | OFPPC_NO_RECV_STP | OFPPC_NO_FWD) + if (mask & REVALIDATE_BITS) { + COVERAGE_INC(ofproto_costly_flags); + port->opp.config ^= mask & REVALIDATE_BITS; + p->need_revalidate = true; + } +#undef REVALIDATE_BITS + if (mask & OFPPC_NO_FLOOD) { + port->opp.config ^= OFPPC_NO_FLOOD; + refresh_port_group(p, DP_GROUP_FLOOD); + } + if (mask & OFPPC_NO_PACKET_IN) { + port->opp.config ^= OFPPC_NO_PACKET_IN; + } +} + +static int +handle_port_mod(struct ofproto *p, struct ofp_header *oh) +{ + const struct ofp_port_mod *opm; + struct ofport *port; + int error; + + error = check_ofp_message(oh, OFPT_PORT_MOD, sizeof *opm); + if (error) { + return error; + } + opm = (struct ofp_port_mod *) oh; + + port = port_array_get(&p->ports, + ofp_port_to_odp_port(ntohs(opm->port_no))); + if (!port) { + return ofp_mkerr(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_PORT); + } else if (memcmp(port->opp.hw_addr, opm->hw_addr, OFP_ETH_ALEN)) { + return ofp_mkerr(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_HW_ADDR); + } else { + update_port_config(p, port, ntohl(opm->config), ntohl(opm->mask)); + if (opm->advertise) { + netdev_set_advertisements(port->netdev, ntohl(opm->advertise)); + } + } + return 0; +} + +static struct ofpbuf * +make_stats_reply(uint32_t xid, uint16_t type, size_t body_len) +{ + struct ofp_stats_reply *osr; + struct ofpbuf *msg; + + msg = ofpbuf_new(MIN(sizeof *osr + body_len, UINT16_MAX)); + osr = put_openflow_xid(sizeof *osr, OFPT_STATS_REPLY, xid, msg); + osr->type = type; + osr->flags = htons(0); + return msg; +} + +static struct ofpbuf * +start_stats_reply(const struct ofp_stats_request *request, size_t body_len) +{ + return make_stats_reply(request->header.xid, request->type, body_len); +} + +static void * +append_stats_reply(size_t nbytes, struct ofconn *ofconn, struct ofpbuf **msgp) +{ + struct ofpbuf *msg = *msgp; + assert(nbytes <= UINT16_MAX - sizeof(struct ofp_stats_reply)); + if (nbytes + msg->size > UINT16_MAX) { + struct ofp_stats_reply *reply = msg->data; + reply->flags = htons(OFPSF_REPLY_MORE); + *msgp = make_stats_reply(reply->header.xid, reply->type, nbytes); + queue_tx(msg, ofconn, ofconn->reply_counter); + } + return ofpbuf_put_uninit(*msgp, nbytes); +} + +static int +handle_desc_stats_request(struct ofproto *p, struct ofconn *ofconn, + struct ofp_stats_request *request) +{ + struct ofp_desc_stats *ods; + struct ofpbuf *msg; + + msg = start_stats_reply(request, sizeof *ods); + ods = append_stats_reply(sizeof *ods, ofconn, &msg); + strncpy(ods->mfr_desc, p->manufacturer, sizeof ods->mfr_desc); + strncpy(ods->hw_desc, p->hardware, sizeof ods->hw_desc); + strncpy(ods->sw_desc, p->software, sizeof ods->sw_desc); + strncpy(ods->serial_num, p->serial, sizeof ods->serial_num); + queue_tx(msg, ofconn, ofconn->reply_counter); + + return 0; +} + +static void +count_subrules(struct cls_rule *cls_rule, void *n_subrules_) +{ + struct rule *rule = rule_from_cls_rule(cls_rule); + int *n_subrules = n_subrules_; + + if (rule->super) { + (*n_subrules)++; + } +} + +static int +handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn, + struct ofp_stats_request *request) +{ + struct ofp_table_stats *ots; + struct ofpbuf *msg; + struct odp_stats dpstats; + int n_exact, n_subrules, n_wild; + + msg = start_stats_reply(request, sizeof *ots * 2); + + /* Count rules of various kinds. */ + n_subrules = 0; + classifier_for_each(&p->cls, CLS_INC_EXACT, count_subrules, &n_subrules); + n_exact = classifier_count_exact(&p->cls) - n_subrules; + n_wild = classifier_count(&p->cls) - classifier_count_exact(&p->cls); + + /* Hash table. */ + dpif_get_dp_stats(p->dpif, &dpstats); + ots = append_stats_reply(sizeof *ots, ofconn, &msg); + memset(ots, 0, sizeof *ots); + ots->table_id = TABLEID_HASH; + strcpy(ots->name, "hash"); + ots->wildcards = htonl(0); + ots->max_entries = htonl(dpstats.max_capacity); + ots->active_count = htonl(n_exact); + ots->lookup_count = htonll(dpstats.n_frags + dpstats.n_hit + + dpstats.n_missed); + ots->matched_count = htonll(dpstats.n_hit); /* XXX */ + + /* Classifier table. */ + ots = append_stats_reply(sizeof *ots, ofconn, &msg); + memset(ots, 0, sizeof *ots); + ots->table_id = TABLEID_CLASSIFIER; + strcpy(ots->name, "classifier"); + ots->wildcards = htonl(OFPFW_ALL); + ots->max_entries = htonl(65536); + ots->active_count = htonl(n_wild); + ots->lookup_count = htonll(0); /* XXX */ + ots->matched_count = htonll(0); /* XXX */ + + queue_tx(msg, ofconn, ofconn->reply_counter); + return 0; +} + +static int +handle_port_stats_request(struct ofproto *p, struct ofconn *ofconn, + struct ofp_stats_request *request) +{ + struct ofp_port_stats *ops; + struct ofpbuf *msg; + struct ofport *port; + unsigned int port_no; + + msg = start_stats_reply(request, sizeof *ops * 16); + PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) { + struct netdev_stats stats; + + /* Intentionally ignore return value, since errors will set 'stats' to + * all-1s, which is correct for OpenFlow, and netdev_get_stats() will + * log errors. */ + netdev_get_stats(port->netdev, &stats); + + ops = append_stats_reply(sizeof *ops, ofconn, &msg); + ops->port_no = htons(odp_port_to_ofp_port(port_no)); + memset(ops->pad, 0, sizeof ops->pad); + ops->rx_packets = htonll(stats.rx_packets); + ops->tx_packets = htonll(stats.tx_packets); + ops->rx_bytes = htonll(stats.rx_bytes); + ops->tx_bytes = htonll(stats.tx_bytes); + ops->rx_dropped = htonll(stats.rx_dropped); + ops->tx_dropped = htonll(stats.tx_dropped); + ops->rx_errors = htonll(stats.rx_errors); + ops->tx_errors = htonll(stats.tx_errors); + ops->rx_frame_err = htonll(stats.rx_frame_errors); + ops->rx_over_err = htonll(stats.rx_over_errors); + ops->rx_crc_err = htonll(stats.rx_crc_errors); + ops->collisions = htonll(stats.collisions); + } + + queue_tx(msg, ofconn, ofconn->reply_counter); + return 0; +} + +struct flow_stats_cbdata { + struct ofproto *ofproto; + struct ofconn *ofconn; + uint16_t out_port; + struct ofpbuf *msg; +}; + +static void +query_stats(struct ofproto *p, struct rule *rule, + uint64_t *packet_countp, uint64_t *byte_countp) +{ + uint64_t packet_count, byte_count; + struct rule *subrule; + struct odp_flow *odp_flows; + size_t n_odp_flows; + + n_odp_flows = rule->cr.wc.wildcards ? list_size(&rule->list) : 1; + odp_flows = xcalloc(1, n_odp_flows * sizeof *odp_flows); + if (rule->cr.wc.wildcards) { + size_t i = 0; + LIST_FOR_EACH (subrule, struct rule, list, &rule->list) { + odp_flows[i++].key = subrule->cr.flow; + } + } else { + odp_flows[0].key = rule->cr.flow; + } + + packet_count = rule->packet_count; + byte_count = rule->byte_count; + if (!dpif_flow_get_multiple(p->dpif, odp_flows, n_odp_flows)) { + size_t i; + for (i = 0; i < n_odp_flows; i++) { + struct odp_flow *odp_flow = &odp_flows[i]; + packet_count += odp_flow->stats.n_packets; + byte_count += odp_flow->stats.n_bytes; + } + } + free(odp_flows); + + *packet_countp = packet_count; + *byte_countp = byte_count; +} + +static void +flow_stats_cb(struct cls_rule *rule_, void *cbdata_) +{ + struct rule *rule = rule_from_cls_rule(rule_); + struct flow_stats_cbdata *cbdata = cbdata_; + struct ofp_flow_stats *ofs; + uint64_t packet_count, byte_count; + size_t act_len, len; + + if (rule_is_hidden(rule) || !rule_has_out_port(rule, cbdata->out_port)) { + return; + } + + act_len = sizeof *rule->actions * rule->n_actions; + len = offsetof(struct ofp_flow_stats, actions) + act_len; + + query_stats(cbdata->ofproto, rule, &packet_count, &byte_count); + + ofs = append_stats_reply(len, cbdata->ofconn, &cbdata->msg); + ofs->length = htons(len); + ofs->table_id = rule->cr.wc.wildcards ? TABLEID_CLASSIFIER : TABLEID_HASH; + ofs->pad = 0; + flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofs->match); + ofs->duration = htonl((time_msec() - rule->created) / 1000); + ofs->priority = htons(rule->cr.priority); + ofs->idle_timeout = htons(rule->idle_timeout); + ofs->hard_timeout = htons(rule->hard_timeout); + memset(ofs->pad2, 0, sizeof ofs->pad2); + ofs->packet_count = htonll(packet_count); + ofs->byte_count = htonll(byte_count); + memcpy(ofs->actions, rule->actions, act_len); +} + +static int +table_id_to_include(uint8_t table_id) +{ + return (table_id == TABLEID_HASH ? CLS_INC_EXACT + : table_id == TABLEID_CLASSIFIER ? CLS_INC_WILD + : table_id == 0xff ? CLS_INC_ALL + : 0); +} + +static int +handle_flow_stats_request(struct ofproto *p, struct ofconn *ofconn, + const struct ofp_stats_request *osr, + size_t arg_size) +{ + struct ofp_flow_stats_request *fsr; + struct flow_stats_cbdata cbdata; + struct cls_rule target; + + if (arg_size != sizeof *fsr) { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + fsr = (struct ofp_flow_stats_request *) osr->body; + + COVERAGE_INC(ofproto_flows_req); + cbdata.ofproto = p; + cbdata.ofconn = ofconn; + cbdata.out_port = fsr->out_port; + cbdata.msg = start_stats_reply(osr, 1024); + cls_rule_from_match(&target, &fsr->match, 0); + classifier_for_each_match(&p->cls, &target, + table_id_to_include(fsr->table_id), + flow_stats_cb, &cbdata); + queue_tx(cbdata.msg, ofconn, ofconn->reply_counter); + return 0; +} + +struct aggregate_stats_cbdata { + struct ofproto *ofproto; + uint16_t out_port; + uint64_t packet_count; + uint64_t byte_count; + uint32_t n_flows; +}; + +static void +aggregate_stats_cb(struct cls_rule *rule_, void *cbdata_) +{ + struct rule *rule = rule_from_cls_rule(rule_); + struct aggregate_stats_cbdata *cbdata = cbdata_; + uint64_t packet_count, byte_count; + + if (rule_is_hidden(rule) || !rule_has_out_port(rule, cbdata->out_port)) { + return; + } + + query_stats(cbdata->ofproto, rule, &packet_count, &byte_count); + + cbdata->packet_count += packet_count; + cbdata->byte_count += byte_count; + cbdata->n_flows++; +} + +static int +handle_aggregate_stats_request(struct ofproto *p, struct ofconn *ofconn, + const struct ofp_stats_request *osr, + size_t arg_size) +{ + struct ofp_aggregate_stats_request *asr; + struct ofp_aggregate_stats_reply *reply; + struct aggregate_stats_cbdata cbdata; + struct cls_rule target; + struct ofpbuf *msg; + + if (arg_size != sizeof *asr) { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + asr = (struct ofp_aggregate_stats_request *) osr->body; + + COVERAGE_INC(ofproto_agg_request); + cbdata.ofproto = p; + cbdata.out_port = asr->out_port; + cbdata.packet_count = 0; + cbdata.byte_count = 0; + cbdata.n_flows = 0; + cls_rule_from_match(&target, &asr->match, 0); + classifier_for_each_match(&p->cls, &target, + table_id_to_include(asr->table_id), + aggregate_stats_cb, &cbdata); + + msg = start_stats_reply(osr, sizeof *reply); + reply = append_stats_reply(sizeof *reply, ofconn, &msg); + reply->flow_count = htonl(cbdata.n_flows); + reply->packet_count = htonll(cbdata.packet_count); + reply->byte_count = htonll(cbdata.byte_count); + queue_tx(msg, ofconn, ofconn->reply_counter); + return 0; +} + +static int +handle_stats_request(struct ofproto *p, struct ofconn *ofconn, + struct ofp_header *oh) +{ + struct ofp_stats_request *osr; + size_t arg_size; + int error; + + error = check_ofp_message_array(oh, OFPT_STATS_REQUEST, sizeof *osr, + 1, &arg_size); + if (error) { + return error; + } + osr = (struct ofp_stats_request *) oh; + + switch (ntohs(osr->type)) { + case OFPST_DESC: + return handle_desc_stats_request(p, ofconn, osr); + + case OFPST_FLOW: + return handle_flow_stats_request(p, ofconn, osr, arg_size); + + case OFPST_AGGREGATE: + return handle_aggregate_stats_request(p, ofconn, osr, arg_size); + + case OFPST_TABLE: + return handle_table_stats_request(p, ofconn, osr); + + case OFPST_PORT: + return handle_port_stats_request(p, ofconn, osr); + + case OFPST_VENDOR: + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR); + + default: + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_STAT); + } +} + +static long long int +msec_from_nsec(uint64_t sec, uint32_t nsec) +{ + return !sec ? 0 : sec * 1000 + nsec / 1000000; +} + +static void +update_time(struct rule *rule, const struct odp_flow_stats *stats) +{ + long long int used = msec_from_nsec(stats->used_sec, stats->used_nsec); + if (used > rule->used) { + rule->used = used; + } +} + +static void +update_stats(struct rule *rule, const struct odp_flow_stats *stats) +{ + update_time(rule, stats); + rule->packet_count += stats->n_packets; + rule->byte_count += stats->n_bytes; + rule->tcp_flags |= stats->tcp_flags; + if (stats->n_packets) { + rule->ip_tos = stats->ip_tos; + } +} + +static int +add_flow(struct ofproto *p, struct ofconn *ofconn, + struct ofp_flow_mod *ofm, size_t n_actions) +{ + struct ofpbuf *packet; + struct rule *rule; + uint16_t in_port; + int error; + + rule = rule_create(NULL, (const union ofp_action *) ofm->actions, + n_actions, ntohs(ofm->idle_timeout), + ntohs(ofm->hard_timeout)); + cls_rule_from_match(&rule->cr, &ofm->match, ntohs(ofm->priority)); + + packet = NULL; + error = 0; + if (ofm->buffer_id != htonl(UINT32_MAX)) { + error = pktbuf_retrieve(ofconn->pktbuf, ntohl(ofm->buffer_id), + &packet, &in_port); + } + + rule_insert(p, rule, packet, in_port); + ofpbuf_delete(packet); + return error; +} + +static int +modify_flow(struct ofproto *p, const struct ofp_flow_mod *ofm, + size_t n_actions, uint16_t command, struct rule *rule) +{ + if (rule_is_hidden(rule)) { + return 0; + } + + if (command == OFPFC_DELETE) { + rule_remove(p, rule); + } else { + size_t actions_len = n_actions * sizeof *rule->actions; + + if (n_actions == rule->n_actions + && !memcmp(ofm->actions, rule->actions, actions_len)) + { + return 0; + } + + free(rule->actions); + rule->actions = xmemdup(ofm->actions, actions_len); + rule->n_actions = n_actions; + + if (rule->cr.wc.wildcards) { + COVERAGE_INC(ofproto_mod_wc_flow); + p->need_revalidate = true; + } else { + rule_update_actions(p, rule); + } + } + + return 0; +} + +static int +modify_flows_strict(struct ofproto *p, const struct ofp_flow_mod *ofm, + size_t n_actions, uint16_t command) +{ + struct rule *rule; + uint32_t wildcards; + flow_t flow; + + flow_from_match(&flow, &wildcards, &ofm->match); + rule = rule_from_cls_rule(classifier_find_rule_exactly( + &p->cls, &flow, wildcards, + ntohs(ofm->priority))); + + if (rule) { + if (command == OFPFC_DELETE + && ofm->out_port != htons(OFPP_NONE) + && !rule_has_out_port(rule, ofm->out_port)) { + return 0; + } + + modify_flow(p, ofm, n_actions, command, rule); + } + return 0; +} + +struct modify_flows_cbdata { + struct ofproto *ofproto; + const struct ofp_flow_mod *ofm; + uint16_t out_port; + size_t n_actions; + uint16_t command; +}; + +static void +modify_flows_cb(struct cls_rule *rule_, void *cbdata_) +{ + struct rule *rule = rule_from_cls_rule(rule_); + struct modify_flows_cbdata *cbdata = cbdata_; + + if (cbdata->out_port != htons(OFPP_NONE) + && !rule_has_out_port(rule, cbdata->out_port)) { + return; + } + + modify_flow(cbdata->ofproto, cbdata->ofm, cbdata->n_actions, + cbdata->command, rule); +} + +static int +modify_flows_loose(struct ofproto *p, const struct ofp_flow_mod *ofm, + size_t n_actions, uint16_t command) +{ + struct modify_flows_cbdata cbdata; + struct cls_rule target; + + cbdata.ofproto = p; + cbdata.ofm = ofm; + cbdata.out_port = (command == OFPFC_DELETE ? ofm->out_port + : htons(OFPP_NONE)); + cbdata.n_actions = n_actions; + cbdata.command = command; + + cls_rule_from_match(&target, &ofm->match, 0); + + classifier_for_each_match(&p->cls, &target, CLS_INC_ALL, + modify_flows_cb, &cbdata); + return 0; +} + +static int +handle_flow_mod(struct ofproto *p, struct ofconn *ofconn, + struct ofp_flow_mod *ofm) +{ + size_t n_actions; + int error; + + error = check_ofp_message_array(&ofm->header, OFPT_FLOW_MOD, sizeof *ofm, + sizeof *ofm->actions, &n_actions); + if (error) { + return error; + } + + normalize_match(&ofm->match); + if (!ofm->match.wildcards) { + ofm->priority = htons(UINT16_MAX); + } + + error = validate_actions((const union ofp_action *) ofm->actions, + n_actions, p->max_ports); + if (error) { + return error; + } + + switch (ntohs(ofm->command)) { + case OFPFC_ADD: + return add_flow(p, ofconn, ofm, n_actions); + + case OFPFC_MODIFY: + return modify_flows_loose(p, ofm, n_actions, OFPFC_MODIFY); + + case OFPFC_MODIFY_STRICT: + return modify_flows_strict(p, ofm, n_actions, OFPFC_MODIFY); + + case OFPFC_DELETE: + return modify_flows_loose(p, ofm, n_actions, OFPFC_DELETE); + + case OFPFC_DELETE_STRICT: + return modify_flows_strict(p, ofm, n_actions, OFPFC_DELETE); + + default: + return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_BAD_COMMAND); + } +} + +static void +send_capability_reply(struct ofproto *p, struct ofconn *ofconn, uint32_t xid) +{ + struct ofmp_capability_reply *ocr; + struct ofpbuf *b; + char capabilities[] = "com.nicira.mgmt.manager=false\n"; + + ocr = make_openflow_xid(sizeof(*ocr), OFPT_VENDOR, xid, &b); + ocr->header.header.vendor = htonl(NX_VENDOR_ID); + ocr->header.header.subtype = htonl(NXT_MGMT); + ocr->header.type = htons(OFMPT_CAPABILITY_REPLY); + + ocr->format = htonl(OFMPCOF_SIMPLE); + ocr->mgmt_id = htonll(p->mgmt_id); + + ofpbuf_put(b, capabilities, strlen(capabilities)); + + queue_tx(b, ofconn, ofconn->reply_counter); +} + +static int +handle_ofmp(struct ofproto *p, struct ofconn *ofconn, + struct ofmp_header *ofmph) +{ + size_t msg_len = ntohs(ofmph->header.header.length); + if (msg_len < sizeof(*ofmph)) { + VLOG_WARN_RL(&rl, "dropping short managment message: %d\n", msg_len); + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + + if (ofmph->type == htons(OFMPT_CAPABILITY_REQUEST)) { + struct ofmp_capability_request *ofmpcr; + + if (msg_len < sizeof(struct ofmp_capability_request)) { + VLOG_WARN_RL(&rl, "dropping short capability request: %d\n", + msg_len); + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + + ofmpcr = (struct ofmp_capability_request *)ofmph; + if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) { + /* xxx Find a better type than bad subtype */ + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); + } + + send_capability_reply(p, ofconn, ofmph->header.header.xid); + return 0; + } else { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); + } +} + +static int +handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg) +{ + struct ofp_vendor_header *ovh = msg; + struct nicira_header *nh; + + if (ntohs(ovh->header.length) < sizeof(struct ofp_vendor_header)) { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + if (ovh->vendor != htonl(NX_VENDOR_ID)) { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR); + } + if (ntohs(ovh->header.length) < sizeof(struct nicira_header)) { + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); + } + + nh = msg; + switch (ntohl(nh->subtype)) { + case NXT_STATUS_REQUEST: + return switch_status_handle_request(p->switch_status, ofconn->rconn, + msg); + + case NXT_ACT_SET_CONFIG: + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */ + + case NXT_ACT_GET_CONFIG: + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */ + + case NXT_COMMAND_REQUEST: + if (p->executer) { + return executer_handle_request(p->executer, ofconn->rconn, msg); + } + break; + + case NXT_MGMT: + return handle_ofmp(p, ofconn, msg); + } + + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); +} + +static void +handle_openflow(struct ofconn *ofconn, struct ofproto *p, + struct ofpbuf *ofp_msg) +{ + struct ofp_header *oh = ofp_msg->data; + int error; + + COVERAGE_INC(ofproto_recv_openflow); + switch (oh->type) { + case OFPT_ECHO_REQUEST: + error = handle_echo_request(ofconn, oh); + break; + + case OFPT_ECHO_REPLY: + error = 0; + break; + + case OFPT_FEATURES_REQUEST: + error = handle_features_request(p, ofconn, oh); + break; + + case OFPT_GET_CONFIG_REQUEST: + error = handle_get_config_request(p, ofconn, oh); + break; + + case OFPT_SET_CONFIG: + error = handle_set_config(p, ofconn, ofp_msg->data); + break; + + case OFPT_PACKET_OUT: + error = handle_packet_out(p, ofconn, ofp_msg->data); + break; + + case OFPT_PORT_MOD: + error = handle_port_mod(p, oh); + break; + + case OFPT_FLOW_MOD: + error = handle_flow_mod(p, ofconn, ofp_msg->data); + break; + + case OFPT_STATS_REQUEST: + error = handle_stats_request(p, ofconn, oh); + break; + + case OFPT_VENDOR: + error = handle_vendor(p, ofconn, ofp_msg->data); + break; + + default: + if (VLOG_IS_WARN_ENABLED()) { + char *s = ofp_to_string(oh, ntohs(oh->length), 2); + VLOG_DBG_RL(&rl, "OpenFlow message ignored: %s", s); + free(s); + } + error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE); + break; + } + + if (error) { + send_error_oh(ofconn, ofp_msg->data, error); + } +} + +static void +handle_odp_msg(struct ofproto *p, struct ofpbuf *packet) +{ + struct odp_msg *msg = packet->data; + uint16_t in_port = odp_port_to_ofp_port(msg->port); + struct rule *rule; + struct ofpbuf payload; + flow_t flow; + + /* Handle controller actions. */ + if (msg->type == _ODPL_ACTION_NR) { + COVERAGE_INC(ofproto_ctlr_action); + pinsched_send(p->action_sched, in_port, packet, + send_packet_in_action, p); + return; + } + + payload.data = msg + 1; + payload.size = msg->length - sizeof *msg; + flow_extract(&payload, msg->port, &flow); + + rule = lookup_valid_rule(p, &flow); + if (!rule) { + /* Don't send a packet-in if OFPPC_NO_PACKET_IN asserted. */ + struct ofport *port = port_array_get(&p->ports, msg->port); + if (port) { + if (port->opp.config & OFPPC_NO_PACKET_IN) { + COVERAGE_INC(ofproto_no_packet_in); + /* XXX install 'drop' flow entry */ + ofpbuf_delete(packet); + return; + } + } else { + VLOG_WARN_RL(&rl, "packet-in on unknown port %"PRIu16, msg->port); + } + + COVERAGE_INC(ofproto_packet_in); + pinsched_send(p->miss_sched, in_port, packet, send_packet_in_miss, p); + return; + } + + if (rule->cr.wc.wildcards) { + rule = rule_create_subrule(p, rule, &flow); + rule_make_actions(p, rule, packet); + } else { + if (!rule->may_install) { + /* The rule is not installable, that is, we need to process every + * packet, so process the current packet and set its actions into + * 'subrule'. */ + rule_make_actions(p, rule, packet); + } else { + /* XXX revalidate rule if it needs it */ + } + } + + rule_execute(p, rule, &payload, &flow); + rule_reinstall(p, rule); + ofpbuf_delete(packet); +} + +static void +revalidate_cb(struct cls_rule *sub_, void *cbdata_) +{ + struct rule *sub = rule_from_cls_rule(sub_); + struct revalidate_cbdata *cbdata = cbdata_; + + if (cbdata->revalidate_all + || (cbdata->revalidate_subrules && sub->super) + || (tag_set_intersects(&cbdata->revalidate_set, sub->tags))) { + revalidate_rule(cbdata->ofproto, sub); + } +} + +static bool +revalidate_rule(struct ofproto *p, struct rule *rule) +{ + const flow_t *flow = &rule->cr.flow; + + COVERAGE_INC(ofproto_revalidate_rule); + if (rule->super) { + struct rule *super; + super = rule_from_cls_rule(classifier_lookup_wild(&p->cls, flow)); + if (!super) { + rule_remove(p, rule); + return false; + } else if (super != rule->super) { + COVERAGE_INC(ofproto_revalidate_moved); + list_remove(&rule->list); + list_push_back(&super->list, &rule->list); + rule->super = super; + rule->hard_timeout = super->hard_timeout; + rule->idle_timeout = super->idle_timeout; + rule->created = super->created; + rule->used = 0; + } + } + + rule_update_actions(p, rule); + return true; +} + +static struct ofpbuf * +compose_flow_exp(const struct rule *rule, long long int now, uint8_t reason) +{ + struct ofp_flow_expired *ofe; + struct ofpbuf *buf; + + ofe = make_openflow(sizeof *ofe, OFPT_FLOW_EXPIRED, &buf); + flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofe->match); + ofe->priority = htons(rule->cr.priority); + ofe->reason = reason; + ofe->duration = (now - rule->created) / 1000; + ofe->packet_count = rule->packet_count; + ofe->byte_count = rule->byte_count; + + return buf; +} + +static void +send_flow_exp(struct ofproto *p, struct rule *rule, + long long int now, uint8_t reason) +{ + struct ofconn *ofconn; + struct ofconn *prev; + struct ofpbuf *buf; + + /* We limit the maximum number of queued flow expirations it by accounting + * them under the counter for replies. That works because preventing + * OpenFlow requests from being processed also prevents new flows from + * being added (and expiring). (It also prevents processing OpenFlow + * requests that would not add new flows, so it is imperfect.) */ + + prev = NULL; + LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { + if (ofconn->send_flow_exp && rconn_is_connected(ofconn->rconn)) { + if (prev) { + queue_tx(ofpbuf_clone(buf), prev, ofconn->reply_counter); + } else { + buf = compose_flow_exp(rule, now, reason); + } + prev = ofconn; + } + } + if (prev) { + queue_tx(buf, prev, ofconn->reply_counter); + } +} + +static void +uninstall_idle_flow(struct ofproto *ofproto, struct rule *rule) +{ + assert(rule->installed); + assert(!rule->cr.wc.wildcards); + + if (rule->super) { + rule_remove(ofproto, rule); + } else { + rule_uninstall(ofproto, rule); + } +} + +static void +expire_rule(struct cls_rule *cls_rule, void *p_) +{ + struct ofproto *p = p_; + struct rule *rule = rule_from_cls_rule(cls_rule); + long long int hard_expire, idle_expire, expire, now; + + hard_expire = (rule->hard_timeout + ? rule->created + rule->hard_timeout * 1000 + : LLONG_MAX); + idle_expire = (rule->idle_timeout + && (rule->super || list_is_empty(&rule->list)) + ? rule->used + rule->idle_timeout * 1000 + : LLONG_MAX); + expire = MIN(hard_expire, idle_expire); + if (expire == LLONG_MAX) { + if (rule->installed && time_msec() >= rule->used + 5000) { + uninstall_idle_flow(p, rule); + } + return; + } + + now = time_msec(); + if (now < expire) { + if (rule->installed && now >= rule->used + 5000) { + uninstall_idle_flow(p, rule); + } + return; + } + + COVERAGE_INC(ofproto_expired); + if (rule->cr.wc.wildcards) { + /* Update stats. (This code will be a no-op if the rule expired + * due to an idle timeout, because in that case the rule has no + * subrules left.) */ + struct rule *subrule, *next; + LIST_FOR_EACH_SAFE (subrule, next, struct rule, list, &rule->list) { + rule_remove(p, subrule); + } + } + + send_flow_exp(p, rule, now, + (now >= hard_expire + ? OFPER_HARD_TIMEOUT : OFPER_IDLE_TIMEOUT)); + rule_remove(p, rule); +} + +static void +update_used(struct ofproto *p) +{ + struct odp_flow *flows; + size_t n_flows; + size_t i; + int error; + + error = dpif_flow_list_all(p->dpif, &flows, &n_flows); + if (error) { + return; + } + + for (i = 0; i < n_flows; i++) { + struct odp_flow *f = &flows[i]; + struct rule *rule; + + rule = rule_from_cls_rule( + classifier_find_rule_exactly(&p->cls, &f->key, 0, UINT16_MAX)); + if (!rule || !rule->installed) { + COVERAGE_INC(ofproto_unexpected_rule); + dpif_flow_del(p->dpif, f); + continue; + } + + update_time(rule, &f->stats); + rule_account(p, rule, f->stats.n_bytes); + } + free(flows); +} + +static void +do_send_packet_in(struct ofconn *ofconn, uint32_t buffer_id, + const struct ofpbuf *packet, int send_len) +{ + struct ofp_packet_in *opi; + struct ofpbuf payload, *buf; + struct odp_msg *msg; + + msg = packet->data; + payload.data = msg + 1; + payload.size = msg->length - sizeof *msg; + + send_len = MIN(send_len, payload.size); + buf = ofpbuf_new(sizeof *opi + send_len); + opi = put_openflow_xid(offsetof(struct ofp_packet_in, data), + OFPT_PACKET_IN, 0, buf); + opi->buffer_id = htonl(buffer_id); + opi->total_len = htons(payload.size); + opi->in_port = htons(odp_port_to_ofp_port(msg->port)); + opi->reason = msg->type == _ODPL_ACTION_NR ? OFPR_ACTION : OFPR_NO_MATCH; + ofpbuf_put(buf, payload.data, MIN(send_len, payload.size)); + update_openflow_length(buf); + rconn_send_with_limit(ofconn->rconn, buf, ofconn->packet_in_counter, 100); +} + +static void +send_packet_in_action(struct ofpbuf *packet, void *p_) +{ + struct ofproto *p = p_; + struct ofconn *ofconn; + struct odp_msg *msg; + + msg = packet->data; + LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { + if (ofconn == p->controller || ofconn->miss_send_len) { + do_send_packet_in(ofconn, UINT32_MAX, packet, msg->arg); + } + } + ofpbuf_delete(packet); +} + +static void +send_packet_in_miss(struct ofpbuf *packet, void *p_) +{ + struct ofproto *p = p_; + struct ofconn *ofconn; + struct ofpbuf payload; + struct odp_msg *msg; + + msg = packet->data; + payload.data = msg + 1; + payload.size = msg->length - sizeof *msg; + LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { + if (ofconn->miss_send_len) { + uint32_t buffer_id = pktbuf_save(ofconn->pktbuf, &payload, + msg->port); + int send_len = (buffer_id != UINT32_MAX ? ofconn->miss_send_len + : UINT32_MAX); + do_send_packet_in(ofconn, buffer_id, packet, send_len); + } + } + ofpbuf_delete(packet); +} + +static uint64_t +pick_datapath_id(struct dpif *dpif, uint64_t fallback_dpid) +{ + char local_name[IF_NAMESIZE]; + uint8_t ea[ETH_ADDR_LEN]; + int error; + + error = dpif_port_get_name(dpif, ODPP_LOCAL, + local_name, sizeof local_name); + if (!error) { + error = netdev_nodev_get_etheraddr(local_name, ea); + if (!error) { + return eth_addr_to_uint64(ea); + } + VLOG_WARN("could not get MAC address for %s (%s)", + local_name, strerror(error)); + } + + return fallback_dpid; +} + +static uint64_t +pick_fallback_dpid(void) +{ + uint8_t ea[ETH_ADDR_LEN]; + eth_addr_random(ea); + ea[0] = 0x00; /* Set Nicira OUI. */ + ea[1] = 0x23; + ea[2] = 0x20; + return eth_addr_to_uint64(ea); +} + +static bool +default_normal_ofhook_cb(const flow_t *flow, const struct ofpbuf *packet, + struct odp_actions *actions, tag_type *tags, + void *ofproto_) +{ + struct ofproto *ofproto = ofproto_; + int out_port; + + /* Drop frames for reserved multicast addresses. */ + if (eth_addr_is_reserved(flow->dl_dst)) { + return true; + } + + /* Learn source MAC (but don't try to learn from revalidation). */ + if (packet != NULL) { + tag_type rev_tag = mac_learning_learn(ofproto->ml, flow->dl_src, + 0, flow->in_port); + if (rev_tag) { + /* The log messages here could actually be useful in debugging, + * so keep the rate limit relatively high. */ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); + VLOG_DBG_RL(&rl, "learned that "ETH_ADDR_FMT" is on port %"PRIu16, + ETH_ADDR_ARGS(flow->dl_src), flow->in_port); + ofproto_revalidate(ofproto, rev_tag); + } + } + + /* Determine output port. */ + out_port = mac_learning_lookup_tag(ofproto->ml, flow->dl_dst, 0, tags); + if (out_port < 0) { + add_output_group_action(actions, DP_GROUP_FLOOD); + } else if (out_port != flow->in_port) { + odp_actions_add(actions, ODPAT_OUTPUT)->output.port = out_port; + } else { + /* Drop. */ + } + + return true; +} + +static const struct ofhooks default_ofhooks = { + NULL, + default_normal_ofhook_cb, + NULL, + NULL +}; diff --git a/ofproto/ofproto.h b/ofproto/ofproto.h new file mode 100644 index 00000000..44fc023f --- /dev/null +++ b/ofproto/ofproto.h @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef OFPROTO_H +#define OFPROTO_H 1 + +#include +#include +#include +#include "flow.h" +#include "tag.h" + +struct odp_actions; +struct ofhooks; +struct ofproto; +struct svec; + +struct ofexpired { + flow_t flow; + uint64_t packet_count; /* Packets from *expired* subrules. */ + uint64_t byte_count; /* Bytes from *expired* subrules. */ + long long int used; /* Last-used time (0 if never used). */ + long long int created; /* Creation time. */ + uint8_t tcp_flags; /* Bitwise-OR of all TCP flags seen. */ + uint8_t ip_tos; /* Last-seen IP type-of-service. */ +}; + +int ofproto_create(const char *datapath, const struct ofhooks *, void *aux, + struct ofproto **ofprotop); +void ofproto_destroy(struct ofproto *); +int ofproto_run(struct ofproto *); +int ofproto_run1(struct ofproto *); +int ofproto_run2(struct ofproto *, bool revalidate_all); +void ofproto_wait(struct ofproto *); +bool ofproto_is_alive(const struct ofproto *); + +/* Configuration. */ +void ofproto_set_datapath_id(struct ofproto *, uint64_t datapath_id); +void ofproto_set_mgmt_id(struct ofproto *, uint64_t mgmt_id); +void ofproto_set_probe_interval(struct ofproto *, int probe_interval); +void ofproto_set_max_backoff(struct ofproto *, int max_backoff); +void ofproto_set_desc(struct ofproto *, + const char *manufacturer, const char *hardware, + const char *software, const char *serial); +int ofproto_set_in_band(struct ofproto *, bool in_band); +int ofproto_set_discovery(struct ofproto *, bool discovery, + const char *accept_controller_re, + bool update_resolv_conf); +int ofproto_set_controller(struct ofproto *, const char *controller); +int ofproto_set_listeners(struct ofproto *, const struct svec *listeners); +int ofproto_set_snoops(struct ofproto *, const struct svec *snoops); +int ofproto_set_netflow(struct ofproto *, const struct svec *collectors, + uint8_t engine_type, uint8_t engine_id, bool add_id_to_iface); +void ofproto_set_failure(struct ofproto *, bool fail_open); +void ofproto_set_rate_limit(struct ofproto *, int rate_limit, int burst_limit); +int ofproto_set_stp(struct ofproto *, bool enable_stp); +int ofproto_set_remote_execution(struct ofproto *, const char *command_acl, + const char *command_dir); + +/* Configuration querying. */ +uint64_t ofproto_get_datapath_id(const struct ofproto *); +int ofproto_get_probe_interval(const struct ofproto *); +int ofproto_get_max_backoff(const struct ofproto *); +bool ofproto_get_in_band(const struct ofproto *); +bool ofproto_get_discovery(const struct ofproto *); +const char *ofproto_get_controller(const struct ofproto *); +void ofproto_get_listeners(const struct ofproto *, struct svec *); +void ofproto_get_snoops(const struct ofproto *, struct svec *); + +/* Functions for use by ofproto implementation modules, not by clients. */ +int ofproto_send_packet(struct ofproto *, const flow_t *, + const union ofp_action *, size_t n_actions, + const struct ofpbuf *); +void ofproto_add_flow(struct ofproto *, const flow_t *, uint32_t wildcards, + unsigned int priority, + const union ofp_action *, size_t n_actions, + int idle_timeout); +void ofproto_delete_flow(struct ofproto *, const flow_t *, uint32_t wildcards, + unsigned int priority); +void ofproto_flush_flows(struct ofproto *); + +/* Hooks for ovs-vswitchd. */ +struct ofhooks { + void (*port_changed_cb)(enum ofp_port_reason, const struct ofp_phy_port *, + void *aux); + bool (*normal_cb)(const flow_t *, const struct ofpbuf *packet, + struct odp_actions *, tag_type *, void *aux); + void (*account_flow_cb)(const flow_t *, const union odp_action *, + size_t n_actions, unsigned long long int n_bytes, + void *aux); + void (*account_checkpoint_cb)(void *aux); +}; +void ofproto_revalidate(struct ofproto *, tag_type); +struct tag_set *ofproto_get_revalidate_set(struct ofproto *); + +#endif /* ofproto.h */ diff --git a/ofproto/pinsched.c b/ofproto/pinsched.c new file mode 100644 index 00000000..0afd22ff --- /dev/null +++ b/ofproto/pinsched.c @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "pinsched.h" +#include +#include +#include "ofpbuf.h" +#include "openflow/openflow.h" +#include "poll-loop.h" +#include "port-array.h" +#include "queue.h" +#include "random.h" +#include "rconn.h" +#include "status.h" +#include "timeval.h" +#include "vconn.h" + +struct pinsched { + /* Client-supplied parameters. */ + int rate_limit; /* Packets added to bucket per second. */ + int burst_limit; /* Maximum token bucket size, in packets. */ + + /* One queue per physical port. */ + struct port_array queues; /* Array of "struct ovs_queue *". */ + int n_queued; /* Sum over queues[*].n. */ + unsigned int last_tx_port; /* Last port checked in round-robin. */ + + /* Token bucket. + * + * It costs 1000 tokens to send a single packet_in message. A single token + * per message would be more straightforward, but this choice lets us avoid + * round-off error in refill_bucket()'s calculation of how many tokens to + * add to the bucket, since no division step is needed. */ + long long int last_fill; /* Time at which we last added tokens. */ + int tokens; /* Current number of tokens. */ + + /* Transmission queue. */ + int n_txq; /* No. of packets waiting in rconn for tx. */ + + /* Statistics reporting. */ + unsigned long long n_normal; /* # txed w/o rate limit queuing. */ + unsigned long long n_limited; /* # queued for rate limiting. */ + unsigned long long n_queue_dropped; /* # dropped due to queue overflow. */ + + /* Switch status. */ + struct status_category *ss_cat; +}; + +static struct ofpbuf * +dequeue_packet(struct pinsched *ps, struct ovs_queue *q, + unsigned int port_no) +{ + struct ofpbuf *packet = queue_pop_head(q); + if (!q->n) { + free(q); + port_array_set(&ps->queues, port_no, NULL); + } + ps->n_queued--; + return packet; +} + +/* Drop a packet from the longest queue in 'ps'. */ +static void +drop_packet(struct pinsched *ps) +{ + struct ovs_queue *longest; /* Queue currently selected as longest. */ + int n_longest; /* # of queues of same length as 'longest'. */ + unsigned int longest_port_no; + unsigned int port_no; + struct ovs_queue *q; + + ps->n_queue_dropped++; + + longest = port_array_first(&ps->queues, &port_no); + longest_port_no = port_no; + n_longest = 1; + while ((q = port_array_next(&ps->queues, &port_no)) != NULL) { + if (longest->n < q->n) { + longest = q; + n_longest = 1; + } else if (longest->n == q->n) { + n_longest++; + + /* Randomly select one of the longest queues, with a uniform + * distribution (Knuth algorithm 3.4.2R). */ + if (!random_range(n_longest)) { + longest = q; + longest_port_no = port_no; + } + } + } + + /* FIXME: do we want to pop the tail instead? */ + ofpbuf_delete(dequeue_packet(ps, longest, longest_port_no)); +} + +/* Remove and return the next packet to transmit (in round-robin order). */ +static struct ofpbuf * +get_tx_packet(struct pinsched *ps) +{ + struct ovs_queue *q = port_array_next(&ps->queues, &ps->last_tx_port); + if (!q) { + q = port_array_first(&ps->queues, &ps->last_tx_port); + } + return dequeue_packet(ps, q, ps->last_tx_port); +} + +/* Add tokens to the bucket based on elapsed time. */ +static void +refill_bucket(struct pinsched *ps) +{ + long long int now = time_msec(); + long long int tokens = (now - ps->last_fill) * ps->rate_limit + ps->tokens; + if (tokens >= 1000) { + ps->last_fill = now; + ps->tokens = MIN(tokens, ps->burst_limit * 1000); + } +} + +/* Attempts to remove enough tokens from 'ps' to transmit a packet. Returns + * true if successful, false otherwise. (In the latter case no tokens are + * removed.) */ +static bool +get_token(struct pinsched *ps) +{ + if (ps->tokens >= 1000) { + ps->tokens -= 1000; + return true; + } else { + return false; + } +} + +void +pinsched_send(struct pinsched *ps, uint16_t port_no, + struct ofpbuf *packet, pinsched_tx_cb *cb, void *aux) +{ + if (!ps) { + cb(packet, aux); + } else if (!ps->n_queued && get_token(ps)) { + /* In the common case where we are not constrained by the rate limit, + * let the packet take the normal path. */ + ps->n_normal++; + cb(packet, aux); + } else { + /* Otherwise queue it up for the periodic callback to drain out. */ + struct ovs_queue *q; + + /* We are called with a buffer obtained from dpif_recv() that has much + * more allocated space than actual content most of the time. Since + * we're going to store the packet for some time, free up that + * otherwise wasted space. */ + ofpbuf_trim(packet); + + if (ps->n_queued >= ps->burst_limit) { + drop_packet(ps); + } + q = port_array_get(&ps->queues, port_no); + if (!q) { + q = xmalloc(sizeof *q); + queue_init(q); + port_array_set(&ps->queues, port_no, q); + } + queue_push_tail(q, packet); + ps->n_queued++; + ps->n_limited++; + } +} + +static void +pinsched_status_cb(struct status_reply *sr, void *ps_) +{ + struct pinsched *ps = ps_; + + status_reply_put(sr, "normal=%llu", ps->n_normal); + status_reply_put(sr, "limited=%llu", ps->n_limited); + status_reply_put(sr, "queue-dropped=%llu", ps->n_queue_dropped); +} + +void +pinsched_run(struct pinsched *ps, pinsched_tx_cb *cb, void *aux) +{ + if (ps) { + int i; + + /* Drain some packets out of the bucket if possible, but limit the + * number of iterations to allow other code to get work done too. */ + refill_bucket(ps); + for (i = 0; ps->n_queued && get_token(ps) && i < 50; i++) { + cb(get_tx_packet(ps), aux); + } + } +} + +void +pinsched_wait(struct pinsched *ps) +{ + if (ps && ps->n_queued) { + if (ps->tokens >= 1000) { + /* We can transmit more packets as soon as we're called again. */ + poll_immediate_wake(); + } else { + /* We have to wait for the bucket to re-fill. We could calculate + * the exact amount of time here for increased smoothness. */ + poll_timer_wait(TIME_UPDATE_INTERVAL / 2); + } + } +} + +/* Creates and returns a scheduler for sending packet-in messages. */ +struct pinsched * +pinsched_create(int rate_limit, int burst_limit, struct switch_status *ss) +{ + struct pinsched *ps; + + ps = xcalloc(1, sizeof *ps); + port_array_init(&ps->queues); + ps->n_queued = 0; + ps->last_tx_port = PORT_ARRAY_SIZE; + ps->last_fill = time_msec(); + ps->tokens = rate_limit * 100; + ps->n_txq = 0; + ps->n_normal = 0; + ps->n_limited = 0; + ps->n_queue_dropped = 0; + pinsched_set_limits(ps, rate_limit, burst_limit); + + if (ss) { + ps->ss_cat = switch_status_register(ss, "rate-limit", + pinsched_status_cb, ps); + } + + return ps; +} + +void +pinsched_destroy(struct pinsched *ps) +{ + if (ps) { + struct ovs_queue *queue; + unsigned int port_no; + + PORT_ARRAY_FOR_EACH (queue, &ps->queues, port_no) { + queue_destroy(queue); + free(queue); + } + port_array_destroy(&ps->queues); + switch_status_unregister(ps->ss_cat); + free(ps); + } +} + +void +pinsched_set_limits(struct pinsched *ps, int rate_limit, int burst_limit) +{ + if (rate_limit <= 0) { + rate_limit = 1000; + } + if (burst_limit <= 0) { + burst_limit = rate_limit / 4; + } + burst_limit = MAX(burst_limit, 1); + burst_limit = MIN(burst_limit, INT_MAX / 1000); + + ps->rate_limit = rate_limit; + ps->burst_limit = burst_limit; + while (ps->n_queued > burst_limit) { + drop_packet(ps); + } +} diff --git a/ofproto/pinsched.h b/ofproto/pinsched.h new file mode 100644 index 00000000..aead7a45 --- /dev/null +++ b/ofproto/pinsched.h @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PINSCHED_H +#define PINSCHED_H_H 1 + +#include + +struct ofpbuf; +struct switch_status; + +typedef void pinsched_tx_cb(struct ofpbuf *, void *aux); +struct pinsched *pinsched_create(int rate_limit, int burst_limit, + struct switch_status *); +void pinsched_set_limits(struct pinsched *, int rate_limit, int burst_limit); +void pinsched_destroy(struct pinsched *); +void pinsched_send(struct pinsched *, uint16_t port_no, struct ofpbuf *, + pinsched_tx_cb *, void *aux); +void pinsched_run(struct pinsched *, pinsched_tx_cb *, void *aux); +void pinsched_wait(struct pinsched *); + +#endif /* pinsched.h */ diff --git a/ofproto/pktbuf.c b/ofproto/pktbuf.c new file mode 100644 index 00000000..b4198a8f --- /dev/null +++ b/ofproto/pktbuf.c @@ -0,0 +1,150 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "pktbuf.h" +#include +#include +#include "coverage.h" +#include "ofpbuf.h" +#include "timeval.h" +#include "util.h" +#include "vconn.h" + +#define THIS_MODULE VLM_pktbuf +#include "vlog.h" + +/* Buffers are identified by a 32-bit opaque ID. We divide the ID + * into a buffer number (low bits) and a cookie (high bits). The buffer number + * is an index into an array of buffers. The cookie distinguishes between + * different packets that have occupied a single buffer. Thus, the more + * buffers we have, the lower-quality the cookie... */ +#define PKTBUF_BITS 8 +#define PKTBUF_MASK (PKTBUF_CNT - 1) +#define PKTBUF_CNT (1u << PKTBUF_BITS) + +#define COOKIE_BITS (32 - PKTBUF_BITS) +#define COOKIE_MAX ((1u << COOKIE_BITS) - 1) + +#define OVERWRITE_MSECS 5000 + +struct packet { + struct ofpbuf *buffer; + uint32_t cookie; + long long int timeout; + uint16_t in_port; +}; + +struct pktbuf { + struct packet packets[PKTBUF_CNT]; + unsigned int buffer_idx; +}; + +int +pktbuf_capacity(void) +{ + return PKTBUF_CNT; +} + +struct pktbuf * +pktbuf_create(void) +{ + return xcalloc(1, sizeof *pktbuf_create()); +} + +void +pktbuf_destroy(struct pktbuf *pb) +{ + if (pb) { + size_t i; + + for (i = 0; i < PKTBUF_CNT; i++) { + ofpbuf_delete(pb->packets[i].buffer); + } + free(pb); + } +} + +uint32_t +pktbuf_save(struct pktbuf *pb, struct ofpbuf *buffer, uint16_t in_port) +{ + struct packet *p = &pb->packets[pb->buffer_idx]; + pb->buffer_idx = (pb->buffer_idx + 1) & PKTBUF_MASK; + if (p->buffer) { + if (time_msec() < p->timeout) { + return UINT32_MAX; + } + ofpbuf_delete(p->buffer); + } + + /* Don't use maximum cookie value since all-1-bits ID is special. */ + if (++p->cookie >= COOKIE_MAX) { + p->cookie = 0; + } + p->buffer = ofpbuf_clone(buffer); + p->timeout = time_msec() + OVERWRITE_MSECS; + p->in_port = in_port; + return (p - pb->packets) | (p->cookie << PKTBUF_BITS); +} + +int +pktbuf_retrieve(struct pktbuf *pb, uint32_t id, struct ofpbuf **bufferp, + uint16_t *in_port) +{ + static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 20); + struct packet *p; + int error; + + if (!pb) { + VLOG_WARN_RL(&rl, "attempt to send buffered packet via connection " + "without buffers"); + return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_COOKIE); + } + + p = &pb->packets[id & PKTBUF_MASK]; + if (p->cookie == id >> PKTBUF_BITS) { + struct ofpbuf *buffer = p->buffer; + if (buffer) { + *bufferp = buffer; + *in_port = p->in_port; + p->buffer = NULL; + COVERAGE_INC(pktbuf_retrieved); + return 0; + } else { + COVERAGE_INC(pktbuf_reuse_error); + VLOG_WARN_RL(&rl, "attempt to reuse buffer %08"PRIx32, id); + error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BUFFER_EMPTY); + } + } else { + COVERAGE_INC(pktbuf_bad_cookie); + VLOG_WARN_RL(&rl, "cookie mismatch: %08"PRIx32" != %08"PRIx32, + id, (id & PKTBUF_MASK) | (p->cookie << PKTBUF_BITS)); + error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_COOKIE); + } + *bufferp = NULL; + *in_port = -1; + return error; +} + +void +pktbuf_discard(struct pktbuf *pb, uint32_t id) +{ + struct packet *p = &pb->packets[id & PKTBUF_MASK]; + if (p->cookie == id >> PKTBUF_BITS) { + ofpbuf_delete(p->buffer); + p->buffer = NULL; + } +} diff --git a/ofproto/pktbuf.h b/ofproto/pktbuf.h new file mode 100644 index 00000000..b27b7490 --- /dev/null +++ b/ofproto/pktbuf.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PKTBUF_H +#define PKTBUF_H 1 + +#include + +struct pktbuf; +struct ofpbuf; + +int pktbuf_capacity(void); + +struct pktbuf *pktbuf_create(void); +void pktbuf_destroy(struct pktbuf *); +uint32_t pktbuf_save(struct pktbuf *, struct ofpbuf *buffer, uint16_t in_port); +int pktbuf_retrieve(struct pktbuf *, uint32_t id, struct ofpbuf **bufferp, + uint16_t *in_port); +void pktbuf_discard(struct pktbuf *, uint32_t id); + +#endif /* pktbuf.h */ diff --git a/ofproto/status.c b/ofproto/status.c new file mode 100644 index 00000000..c7598f37 --- /dev/null +++ b/ofproto/status.c @@ -0,0 +1,232 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include "status.h" +#include +#include +#include +#include +#include +#include "dynamic-string.h" +#include "list.h" +#include "ofpbuf.h" +#include "ofproto.h" +#include "openflow/nicira-ext.h" +#include "rconn.h" +#include "svec.h" +#include "timeval.h" +#include "vconn.h" + +#define THIS_MODULE VLM_status +#include "vlog.h" + +struct status_category { + struct list node; + char *name; + void (*cb)(struct status_reply *, void *aux); + void *aux; +}; + +struct switch_status { + time_t booted; + struct status_category *config_cat; + struct status_category *switch_cat; + struct list categories; +}; + +struct status_reply { + struct status_category *category; + struct ds request; + struct ds output; +}; + +int +switch_status_handle_request(struct switch_status *ss, struct rconn *rconn, + struct nicira_header *request) +{ + struct status_category *c; + struct nicira_header *reply; + struct status_reply sr; + struct ofpbuf *b; + int retval; + + sr.request.string = (void *) (request + 1); + sr.request.length = ntohs(request->header.length) - sizeof *request; + ds_init(&sr.output); + LIST_FOR_EACH (c, struct status_category, node, &ss->categories) { + if (!memcmp(c->name, sr.request.string, + MIN(strlen(c->name), sr.request.length))) { + sr.category = c; + c->cb(&sr, c->aux); + } + } + reply = make_openflow_xid(sizeof *reply + sr.output.length, + OFPT_VENDOR, request->header.xid, &b); + reply->vendor = htonl(NX_VENDOR_ID); + reply->subtype = htonl(NXT_STATUS_REPLY); + memcpy(reply + 1, sr.output.string, sr.output.length); + retval = rconn_send(rconn, b, NULL); + if (retval && retval != EAGAIN) { + VLOG_WARN("send failed (%s)", strerror(retval)); + } + ds_destroy(&sr.output); + return 0; +} + +void +rconn_status_cb(struct status_reply *sr, void *rconn_) +{ + struct rconn *rconn = rconn_; + time_t now = time_now(); + + status_reply_put(sr, "name=%s", rconn_get_name(rconn)); + status_reply_put(sr, "state=%s", rconn_get_state(rconn)); + status_reply_put(sr, "backoff=%d", rconn_get_backoff(rconn)); + status_reply_put(sr, "probe-interval=%d", rconn_get_probe_interval(rconn)); + status_reply_put(sr, "is-connected=%s", + rconn_is_connected(rconn) ? "true" : "false"); + status_reply_put(sr, "sent-msgs=%u", rconn_packets_sent(rconn)); + status_reply_put(sr, "received-msgs=%u", rconn_packets_received(rconn)); + status_reply_put(sr, "attempted-connections=%u", + rconn_get_attempted_connections(rconn)); + status_reply_put(sr, "successful-connections=%u", + rconn_get_successful_connections(rconn)); + status_reply_put(sr, "last-connection=%ld", + (long int) (now - rconn_get_last_connection(rconn))); + status_reply_put(sr, "last-received=%ld", + (long int) (now - rconn_get_last_received(rconn))); + status_reply_put(sr, "time-connected=%lu", + rconn_get_total_time_connected(rconn)); + status_reply_put(sr, "state-elapsed=%u", rconn_get_state_elapsed(rconn)); +} + +static void +config_status_cb(struct status_reply *sr, void *ofproto_) +{ + const struct ofproto *ofproto = ofproto_; + struct svec listeners; + int probe_interval, max_backoff; + size_t i; + + svec_init(&listeners); + ofproto_get_listeners(ofproto, &listeners); + for (i = 0; i < listeners.n; i++) { + status_reply_put(sr, "management%zu=%s", i, listeners.names[i]); + } + svec_destroy(&listeners); + + probe_interval = ofproto_get_probe_interval(ofproto); + if (probe_interval) { + status_reply_put(sr, "probe-interval=%d", probe_interval); + } + + max_backoff = ofproto_get_max_backoff(ofproto); + if (max_backoff) { + status_reply_put(sr, "max-backoff=%d", max_backoff); + } +} + +static void +switch_status_cb(struct status_reply *sr, void *ss_) +{ + struct switch_status *ss = ss_; + time_t now = time_now(); + + status_reply_put(sr, "now=%ld", (long int) now); + status_reply_put(sr, "uptime=%ld", (long int) (now - ss->booted)); + status_reply_put(sr, "pid=%ld", (long int) getpid()); +} + +struct switch_status * +switch_status_create(const struct ofproto *ofproto) +{ + struct switch_status *ss = xcalloc(1, sizeof *ss); + ss->booted = time_now(); + list_init(&ss->categories); + ss->config_cat = switch_status_register(ss, "config", config_status_cb, + (void *) ofproto); + ss->switch_cat = switch_status_register(ss, "switch", switch_status_cb, + ss); + return ss; +} + +void +switch_status_destroy(struct switch_status *ss) +{ + if (ss) { + /* Orphan any remaining categories, so that unregistering them later + * won't write to bad memory. */ + struct status_category *c, *next; + LIST_FOR_EACH_SAFE (c, next, + struct status_category, node, &ss->categories) { + list_init(&c->node); + } + switch_status_unregister(ss->config_cat); + switch_status_unregister(ss->switch_cat); + free(ss); + } +} + +struct status_category * +switch_status_register(struct switch_status *ss, + const char *category, + status_cb_func *cb, void *aux) +{ + struct status_category *c = xmalloc(sizeof *c); + c->cb = cb; + c->aux = aux; + c->name = xstrdup(category); + list_push_back(&ss->categories, &c->node); + return c; +} + +void +switch_status_unregister(struct status_category *c) +{ + if (c) { + if (!list_is_empty(&c->node)) { + list_remove(&c->node); + } + free(c->name); + free(c); + } +} + +void +status_reply_put(struct status_reply *sr, const char *content, ...) +{ + size_t old_length = sr->output.length; + size_t added; + va_list args; + + /* Append the status reply to the output. */ + ds_put_format(&sr->output, "%s.", sr->category->name); + va_start(args, content); + ds_put_format_valist(&sr->output, content, args); + va_end(args); + if (ds_last(&sr->output) != '\n') { + ds_put_char(&sr->output, '\n'); + } + + /* Drop what we just added if it doesn't match the request. */ + added = sr->output.length - old_length; + if (added < sr->request.length + || memcmp(&sr->output.string[old_length], + sr->request.string, sr->request.length)) { + ds_truncate(&sr->output, old_length); + } +} diff --git a/ofproto/status.h b/ofproto/status.h new file mode 100644 index 00000000..1186fa52 --- /dev/null +++ b/ofproto/status.h @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef STATUS_H +#define STATUS_H 1 + +#include "compiler.h" + +struct nicira_header; +struct rconn; +struct ofproto; +struct status_reply; + +struct switch_status *switch_status_create(const struct ofproto *); +void switch_status_destroy(struct switch_status *); + +int switch_status_handle_request(struct switch_status *, struct rconn *, + struct nicira_header *); + +typedef void status_cb_func(struct status_reply *, void *aux); +struct status_category *switch_status_register(struct switch_status *, + const char *category, + status_cb_func *, void *aux); +void switch_status_unregister(struct status_category *); + +void status_reply_put(struct status_reply *, const char *, ...) + PRINTF_FORMAT(2, 3); + +void rconn_status_cb(struct status_reply *, void *rconn_); + +#endif /* status.h */ diff --git a/secchan/.gitignore b/secchan/.gitignore deleted file mode 100644 index ada65665..00000000 --- a/secchan/.gitignore +++ /dev/null @@ -1,4 +0,0 @@ -/Makefile -/Makefile.in -/secchan -/secchan.8 diff --git a/secchan/automake.mk b/secchan/automake.mk deleted file mode 100644 index d6bf1b0c..00000000 --- a/secchan/automake.mk +++ /dev/null @@ -1,42 +0,0 @@ -# Copyright (C) 2009 Nicira Networks, Inc. -# -# Copying and distribution of this file, with or without modification, -# are permitted in any medium without royalty provided the copyright -# notice and this notice are preserved. This file is offered as-is, -# without warranty of any kind. - -bin_PROGRAMS += secchan/secchan -man_MANS += secchan/secchan.8 - -secchan_secchan_SOURCES = secchan/main.c -secchan_secchan_LDADD = \ - secchan/libsecchan.a \ - lib/libopenvswitch.a \ - $(FAULT_LIBS) \ - $(SSL_LIBS) - -noinst_LIBRARIES += secchan/libsecchan.a -secchan_libsecchan_a_SOURCES = \ - secchan/discovery.c \ - secchan/discovery.h \ - secchan/executer.c \ - secchan/executer.h \ - secchan/fail-open.c \ - secchan/fail-open.h \ - secchan/in-band.c \ - secchan/in-band.h \ - secchan/netflow.c \ - secchan/netflow.h \ - secchan/ofproto.c \ - secchan/ofproto.h \ - secchan/pktbuf.c \ - secchan/pktbuf.h \ - secchan/pinsched.c \ - secchan/pinsched.h \ - secchan/status.c \ - secchan/status.h - -EXTRA_DIST += secchan/secchan.8.in -DISTCLEANFILES += secchan/secchan.8 - -include secchan/commands/automake.mk diff --git a/secchan/commands/automake.mk b/secchan/commands/automake.mk deleted file mode 100644 index cbe44d8c..00000000 --- a/secchan/commands/automake.mk +++ /dev/null @@ -1,3 +0,0 @@ -commandsdir = ${pkgdatadir}/commands -dist_commands_SCRIPTS = \ - secchan/commands/reboot diff --git a/secchan/commands/reboot b/secchan/commands/reboot deleted file mode 100755 index 42fd10c1..00000000 --- a/secchan/commands/reboot +++ /dev/null @@ -1,3 +0,0 @@ -#! /bin/sh -ovs-kill --force --signal=USR1 ovs-switchui.pid -reboot diff --git a/secchan/discovery.c b/secchan/discovery.c deleted file mode 100644 index 2868db5a..00000000 --- a/secchan/discovery.c +++ /dev/null @@ -1,271 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "discovery.h" -#include -#include -#include -#include -#include -#include -#include "dhcp-client.h" -#include "dhcp.h" -#include "dpif.h" -#include "netdev.h" -#include "openflow/openflow.h" -#include "packets.h" -#include "status.h" -#include "vconn-ssl.h" - -#define THIS_MODULE VLM_discovery -#include "vlog.h" - -struct discovery { - char *re; - bool update_resolv_conf; - regex_t *regex; - struct dhclient *dhcp; - int n_changes; - struct status_category *ss_cat; -}; - -static void modify_dhcp_request(struct dhcp_msg *, void *aux); -static bool validate_dhcp_offer(const struct dhcp_msg *, void *aux); - -static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); - -static void -discovery_status_cb(struct status_reply *sr, void *d_) -{ - struct discovery *d = d_; - - status_reply_put(sr, "accept-remote=%s", d->re); - status_reply_put(sr, "n-changes=%d", d->n_changes); - if (d->dhcp) { - status_reply_put(sr, "state=%s", dhclient_get_state(d->dhcp)); - status_reply_put(sr, "state-elapsed=%u", - dhclient_get_state_elapsed(d->dhcp)); - if (dhclient_is_bound(d->dhcp)) { - uint32_t ip = dhclient_get_ip(d->dhcp); - uint32_t netmask = dhclient_get_netmask(d->dhcp); - uint32_t router = dhclient_get_router(d->dhcp); - - const struct dhcp_msg *cfg = dhclient_get_config(d->dhcp); - uint32_t dns_server; - char *domain_name; - int i; - - status_reply_put(sr, "ip="IP_FMT, IP_ARGS(&ip)); - status_reply_put(sr, "netmask="IP_FMT, IP_ARGS(&netmask)); - if (router) { - status_reply_put(sr, "router="IP_FMT, IP_ARGS(&router)); - } - - for (i = 0; dhcp_msg_get_ip(cfg, DHCP_CODE_DNS_SERVER, i, - &dns_server); - i++) { - status_reply_put(sr, "dns%d="IP_FMT, i, IP_ARGS(&dns_server)); - } - - domain_name = dhcp_msg_get_string(cfg, DHCP_CODE_DOMAIN_NAME); - if (domain_name) { - status_reply_put(sr, "domain=%s", domain_name); - free(domain_name); - } - - status_reply_put(sr, "lease-remaining=%u", - dhclient_get_lease_remaining(d->dhcp)); - } - } -} - -int -discovery_create(const char *re, bool update_resolv_conf, - struct dpif *dpif, struct switch_status *ss, - struct discovery **discoveryp) -{ - struct discovery *d; - char local_name[IF_NAMESIZE]; - int error; - - d = xcalloc(1, sizeof *d); - - /* Controller regular expression. */ - error = discovery_set_accept_controller_re(d, re); - if (error) { - goto error_free; - } - d->update_resolv_conf = update_resolv_conf; - - /* Initialize DHCP client. */ - error = dpif_port_get_name(dpif, ODPP_LOCAL, - local_name, sizeof local_name); - if (error) { - VLOG_ERR("failed to query datapath local port: %s", strerror(error)); - goto error_regfree; - } - error = dhclient_create(local_name, modify_dhcp_request, - validate_dhcp_offer, d, &d->dhcp); - if (error) { - VLOG_ERR("failed to initialize DHCP client: %s", strerror(error)); - goto error_regfree; - } - dhclient_set_max_timeout(d->dhcp, 3); - dhclient_init(d->dhcp, 0); - - d->ss_cat = switch_status_register(ss, "discovery", - discovery_status_cb, d); - - *discoveryp = d; - return 0; - -error_regfree: - regfree(d->regex); - free(d->regex); -error_free: - free(d); - *discoveryp = 0; - return error; -} - -void -discovery_destroy(struct discovery *d) -{ - if (d) { - free(d->re); - regfree(d->regex); - free(d->regex); - dhclient_destroy(d->dhcp); - switch_status_unregister(d->ss_cat); - free(d); - } -} - -void -discovery_set_update_resolv_conf(struct discovery *d, - bool update_resolv_conf) -{ - d->update_resolv_conf = update_resolv_conf; -} - -int -discovery_set_accept_controller_re(struct discovery *d, const char *re_) -{ - regex_t *regex; - int error; - char *re; - - re = (!re_ ? xstrdup(vconn_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*") - : re_[0] == '^' ? xstrdup(re_) : xasprintf("^%s", re_)); - regex = xmalloc(sizeof *regex); - error = regcomp(regex, re, REG_NOSUB | REG_EXTENDED); - if (error) { - size_t length = regerror(error, regex, NULL, 0); - char *buffer = xmalloc(length); - regerror(error, regex, buffer, length); - VLOG_WARN("%s: %s", re, buffer); - free(regex); - free(re); - return EINVAL; - } else { - if (d->regex) { - regfree(d->regex); - free(d->regex); - } - free(d->re); - - d->regex = regex; - d->re = re; - return 0; - } -} - -void -discovery_question_connectivity(struct discovery *d) -{ - if (d->dhcp) { - dhclient_force_renew(d->dhcp, 15); - } -} - -bool -discovery_run(struct discovery *d, char **controller_name) -{ - if (!d->dhcp) { - *controller_name = NULL; - return true; - } - - dhclient_run(d->dhcp); - if (!dhclient_changed(d->dhcp)) { - return false; - } - - dhclient_configure_netdev(d->dhcp); - if (d->update_resolv_conf) { - dhclient_update_resolv_conf(d->dhcp); - } - - if (dhclient_is_bound(d->dhcp)) { - *controller_name = dhcp_msg_get_string(dhclient_get_config(d->dhcp), - DHCP_CODE_OFP_CONTROLLER_VCONN); - VLOG_INFO("%s: discovered controller", *controller_name); - d->n_changes++; - } else { - *controller_name = NULL; - if (d->n_changes) { - VLOG_INFO("discovered controller no longer available"); - d->n_changes++; - } - } - return true; -} - -void -discovery_wait(struct discovery *d) -{ - if (d->dhcp) { - dhclient_wait(d->dhcp); - } -} - -static void -modify_dhcp_request(struct dhcp_msg *msg, void *aux UNUSED) -{ - dhcp_msg_put_string(msg, DHCP_CODE_VENDOR_CLASS, "OpenFlow"); -} - -static bool -validate_dhcp_offer(const struct dhcp_msg *msg, void *d_) -{ - const struct discovery *d = d_; - char *vconn_name; - bool accept; - - vconn_name = dhcp_msg_get_string(msg, DHCP_CODE_OFP_CONTROLLER_VCONN); - if (!vconn_name) { - VLOG_WARN_RL(&rl, "rejecting DHCP offer missing controller vconn"); - return false; - } - accept = !regexec(d->regex, vconn_name, 0, NULL, 0); - if (!accept) { - VLOG_WARN_RL(&rl, "rejecting controller vconn that fails to match %s", - d->re); - } - free(vconn_name); - return accept; -} diff --git a/secchan/discovery.h b/secchan/discovery.h deleted file mode 100644 index 98ff5b8b..00000000 --- a/secchan/discovery.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef DISCOVERY_H -#define DISCOVERY_H 1 - -#include - -struct dpif; -struct discovery; -struct settings; -struct switch_status; - -int discovery_create(const char *accept_controller_re, bool update_resolv_conf, - struct dpif *, struct switch_status *, - struct discovery **); -void discovery_destroy(struct discovery *); -void discovery_set_update_resolv_conf(struct discovery *, - bool update_resolv_conf); -int discovery_set_accept_controller_re(struct discovery *, const char *re); -void discovery_question_connectivity(struct discovery *); -bool discovery_run(struct discovery *, char **controller_name); -void discovery_wait(struct discovery *); - -#endif /* discovery.h */ diff --git a/secchan/executer.c b/secchan/executer.c deleted file mode 100644 index 210d7cbc..00000000 --- a/secchan/executer.c +++ /dev/null @@ -1,519 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "executer.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include "dirs.h" -#include "dynamic-string.h" -#include "fatal-signal.h" -#include "openflow/nicira-ext.h" -#include "ofpbuf.h" -#include "openflow/openflow.h" -#include "poll-loop.h" -#include "rconn.h" -#include "socket-util.h" -#include "util.h" -#include "vconn.h" - -#define THIS_MODULE VLM_executer -#include "vlog.h" - -#define MAX_CHILDREN 8 - -struct child { - /* Information about child process. */ - char *name; /* argv[0] passed to child. */ - pid_t pid; /* Child's process ID. */ - - /* For sending a reply to the controller when the child dies. */ - struct rconn *rconn; - uint32_t xid; /* Transaction ID used by controller. */ - - /* We read up to MAX_OUTPUT bytes of output and send them back to the - * controller when the child dies. */ -#define MAX_OUTPUT 4096 - int output_fd; /* FD from which to read child's output. */ - uint8_t *output; /* Output data. */ - size_t output_size; /* Number of bytes of output data so far. */ -}; - -struct executer { - /* Settings. */ - char *command_acl; /* Command white/blacklist, as shell globs. */ - char *command_dir; /* Directory that contains commands. */ - - /* Children. */ - struct child children[MAX_CHILDREN]; - size_t n_children; -}; - -/* File descriptors for waking up when a child dies. */ -static int signal_fds[2]; - -/* File descriptor for /dev/null. */ -static int null_fd = -1; - -static void send_child_status(struct rconn *, uint32_t xid, uint32_t status, - const void *data, size_t size); -static void send_child_message(struct rconn *, uint32_t xid, uint32_t status, - const char *message); - -/* Returns true if 'cmd' is allowed by 'acl', which is a command-separated - * access control list in the format described for --command-acl in - * secchan(8). */ -static bool -executer_is_permitted(const char *acl_, const char *cmd) -{ - char *acl, *save_ptr, *pattern; - bool allowed, denied; - - /* Verify that 'cmd' consists only of alphanumerics plus _ or -. */ - if (cmd[strspn(cmd, "abcdefghijklmnopqrstuvwxyz" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-")] != '\0') { - VLOG_WARN("rejecting command name \"%s\" that contain forbidden " - "characters", cmd); - return false; - } - - /* Check 'cmd' against 'acl'. */ - acl = xstrdup(acl_); - save_ptr = acl; - allowed = denied = false; - while ((pattern = strsep(&save_ptr, ",")) != NULL && !denied) { - if (pattern[0] != '!' && !fnmatch(pattern, cmd, 0)) { - allowed = true; - } else if (pattern[0] == '!' && !fnmatch(pattern + 1, cmd, 0)) { - denied = true; - } - } - free(acl); - - /* Check the command white/blacklisted state. */ - if (allowed && !denied) { - VLOG_INFO("permitting command execution: \"%s\" is whitelisted", cmd); - } else if (allowed && denied) { - VLOG_WARN("denying command execution: \"%s\" is both blacklisted " - "and whitelisted", cmd); - } else if (!allowed) { - VLOG_WARN("denying command execution: \"%s\" is not whitelisted", cmd); - } else if (denied) { - VLOG_WARN("denying command execution: \"%s\" is blacklisted", cmd); - } - return allowed && !denied; -} - -int -executer_handle_request(struct executer *e, struct rconn *rconn, - struct nicira_header *request) -{ - char **argv; - char *args; - char *exec_file = NULL; - int max_fds; - struct stat s; - size_t args_size; - size_t argc; - size_t i; - pid_t pid; - int output_fds[2]; - - /* Verify limit on children not exceeded. - * XXX should probably kill children when the connection drops? */ - if (e->n_children >= MAX_CHILDREN) { - send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, - "too many child processes"); - return 0; - } - - /* Copy argument buffer, adding a null terminator at the end. Now every - * argument is null-terminated, instead of being merely null-delimited. */ - args_size = ntohs(request->header.length) - sizeof *request; - args = xmemdup0((const void *) (request + 1), args_size); - - /* Count arguments. */ - argc = 0; - for (i = 0; i <= args_size; i++) { - argc += args[i] == '\0'; - } - - /* Set argv[*] to point to each argument. */ - argv = xmalloc((argc + 1) * sizeof *argv); - argv[0] = args; - for (i = 1; i < argc; i++) { - argv[i] = strchr(argv[i - 1], '\0') + 1; - } - argv[argc] = NULL; - - /* Check permissions. */ - if (!executer_is_permitted(e->command_acl, argv[0])) { - send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, - "command not allowed"); - goto done; - } - - /* Find the executable. */ - exec_file = xasprintf("%s/%s", e->command_dir, argv[0]); - if (stat(exec_file, &s)) { - VLOG_WARN("failed to stat \"%s\": %s", exec_file, strerror(errno)); - send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, - "command not allowed"); - goto done; - } - if (!S_ISREG(s.st_mode)) { - VLOG_WARN("\"%s\" is not a regular file", exec_file); - send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, - "command not allowed"); - goto done; - } - argv[0] = exec_file; - - /* Arrange to capture output. */ - if (pipe(output_fds)) { - VLOG_WARN("pipe failed: %s", strerror(errno)); - send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, - "internal error (pipe)"); - goto done; - } - - pid = fork(); - if (!pid) { - /* Running in child. - * XXX should run in new process group so that we can signal all - * subprocesses at once? Would also want to catch fatal signals and - * kill them at the same time though. */ - fatal_signal_fork(); - dup2(null_fd, 0); - dup2(output_fds[1], 1); - dup2(null_fd, 2); - max_fds = get_max_fds(); - for (i = 3; i < max_fds; i++) { - close(i); - } - if (chdir(e->command_dir)) { - printf("could not change directory to \"%s\": %s", - e->command_dir, strerror(errno)); - exit(EXIT_FAILURE); - } - execv(argv[0], argv); - printf("failed to start \"%s\": %s\n", argv[0], strerror(errno)); - exit(EXIT_FAILURE); - } else if (pid > 0) { - /* Running in parent. */ - struct child *child; - - VLOG_INFO("started \"%s\" subprocess", argv[0]); - send_child_status(rconn, request->header.xid, NXT_STATUS_STARTED, - NULL, 0); - child = &e->children[e->n_children++]; - child->name = xstrdup(argv[0]); - child->pid = pid; - child->rconn = rconn; - child->xid = request->header.xid; - child->output_fd = output_fds[0]; - child->output = xmalloc(MAX_OUTPUT); - child->output_size = 0; - set_nonblocking(output_fds[0]); - close(output_fds[1]); - } else { - VLOG_WARN("fork failed: %s", strerror(errno)); - send_child_message(rconn, request->header.xid, NXT_STATUS_ERROR, - "internal error (fork)"); - close(output_fds[0]); - close(output_fds[1]); - } - -done: - free(exec_file); - free(args); - free(argv); - return 0; -} - -static void -send_child_status(struct rconn *rconn, uint32_t xid, uint32_t status, - const void *data, size_t size) -{ - if (rconn) { - struct nx_command_reply *r; - struct ofpbuf *buffer; - - r = make_openflow_xid(sizeof *r, OFPT_VENDOR, xid, &buffer); - r->nxh.vendor = htonl(NX_VENDOR_ID); - r->nxh.subtype = htonl(NXT_COMMAND_REPLY); - r->status = htonl(status); - ofpbuf_put(buffer, data, size); - update_openflow_length(buffer); - if (rconn_send(rconn, buffer, NULL)) { - ofpbuf_delete(buffer); - } - } -} - -static void -send_child_message(struct rconn *rconn, uint32_t xid, uint32_t status, - const char *message) -{ - send_child_status(rconn, xid, status, message, strlen(message)); -} - -/* 'child' died with 'status' as its return code. Deal with it. */ -static void -child_terminated(struct child *child, int status) -{ - struct ds ds; - uint32_t ofp_status; - - /* Log how it terminated. */ - ds_init(&ds); - if (WIFEXITED(status)) { - ds_put_format(&ds, "normally with status %d", WEXITSTATUS(status)); - } else if (WIFSIGNALED(status)) { - const char *name = NULL; -#ifdef HAVE_STRSIGNAL - name = strsignal(WTERMSIG(status)); -#endif - ds_put_format(&ds, "by signal %d", WTERMSIG(status)); - if (name) { - ds_put_format(&ds, " (%s)", name); - } - } - if (WCOREDUMP(status)) { - ds_put_cstr(&ds, " (core dumped)"); - } - VLOG_INFO("child process \"%s\" with pid %ld terminated %s", - child->name, (long int) child->pid, ds_cstr(&ds)); - ds_destroy(&ds); - - /* Send a status message back to the controller that requested the - * command. */ - if (WIFEXITED(status)) { - ofp_status = WEXITSTATUS(status) | NXT_STATUS_EXITED; - } else if (WIFSIGNALED(status)) { - ofp_status = WTERMSIG(status) | NXT_STATUS_SIGNALED; - } else { - ofp_status = NXT_STATUS_UNKNOWN; - } - if (WCOREDUMP(status)) { - ofp_status |= NXT_STATUS_COREDUMP; - } - send_child_status(child->rconn, child->xid, ofp_status, - child->output, child->output_size); -} - -/* Read output from 'child' and append it to its output buffer. */ -static void -poll_child(struct child *child) -{ - ssize_t n; - - if (child->output_fd < 0) { - return; - } - - do { - n = read(child->output_fd, child->output + child->output_size, - MAX_OUTPUT - child->output_size); - } while (n < 0 && errno == EINTR); - if (n > 0) { - child->output_size += n; - if (child->output_size < MAX_OUTPUT) { - return; - } - } else if (n < 0 && errno == EAGAIN) { - return; - } - close(child->output_fd); - child->output_fd = -1; -} - -void -executer_run(struct executer *e) -{ - char buffer[MAX_CHILDREN]; - size_t i; - - if (!e->n_children) { - return; - } - - /* Read output from children. */ - for (i = 0; i < e->n_children; i++) { - struct child *child = &e->children[i]; - poll_child(child); - } - - /* If SIGCHLD was received, reap dead children. */ - if (read(signal_fds[0], buffer, sizeof buffer) <= 0) { - return; - } - for (;;) { - int status; - pid_t pid; - - /* Get dead child in 'pid' and its return code in 'status'. */ - pid = waitpid(WAIT_ANY, &status, WNOHANG); - if (pid < 0 && errno == EINTR) { - continue; - } else if (pid <= 0) { - return; - } - - /* Find child with given 'pid' and drop it from the list. */ - for (i = 0; i < e->n_children; i++) { - struct child *child = &e->children[i]; - if (child->pid == pid) { - poll_child(child); - child_terminated(child, status); - free(child->name); - free(child->output); - *child = e->children[--e->n_children]; - goto found; - } - } - VLOG_WARN("child with unknown pid %ld terminated", (long int) pid); - found:; - } - -} - -void -executer_wait(struct executer *e) -{ - if (e->n_children) { - size_t i; - - /* Wake up on SIGCHLD. */ - poll_fd_wait(signal_fds[0], POLLIN); - - /* Wake up when we get output from a child. */ - for (i = 0; i < e->n_children; i++) { - struct child *child = &e->children[i]; - if (child->output_fd >= 0) { - poll_fd_wait(child->output_fd, POLLIN); - } - } - } -} - -void -executer_rconn_closing(struct executer *e, struct rconn *rconn) -{ - size_t i; - - /* If any of our children was connected to 'r', then disconnect it so we - * don't try to reference a dead connection when the process terminates - * later. - * XXX kill the children started by 'r'? */ - for (i = 0; i < e->n_children; i++) { - if (e->children[i].rconn == rconn) { - e->children[i].rconn = NULL; - } - } -} - -static void -sigchld_handler(int signr UNUSED) -{ - write(signal_fds[1], "", 1); -} - -int -executer_create(const char *command_acl, const char *command_dir, - struct executer **executerp) -{ - struct executer *e; - struct sigaction sa; - - *executerp = NULL; - if (null_fd == -1) { - /* Create pipe for notifying us that SIGCHLD was invoked. */ - if (pipe(signal_fds)) { - VLOG_ERR("pipe failed: %s", strerror(errno)); - return errno; - } - set_nonblocking(signal_fds[0]); - set_nonblocking(signal_fds[1]); - - /* Open /dev/null. */ - null_fd = open("/dev/null", O_RDWR); - if (null_fd < 0) { - int error = errno; - VLOG_ERR("could not open /dev/null: %s", strerror(error)); - close(signal_fds[0]); - close(signal_fds[1]); - return error; - } - } - - /* Set up signal handler. */ - memset(&sa, 0, sizeof sa); - sa.sa_handler = sigchld_handler; - sigemptyset(&sa.sa_mask); - sa.sa_flags = SA_NOCLDSTOP | SA_RESTART; - if (sigaction(SIGCHLD, &sa, NULL)) { - VLOG_ERR("sigaction(SIGCHLD) failed: %s", strerror(errno)); - return errno; - } - - e = xcalloc(1, sizeof *e); - e->command_acl = xstrdup(command_acl); - e->command_dir = (command_dir - ? xstrdup(command_dir) - : xasprintf("%s/commands", ovs_pkgdatadir)); - e->n_children = 0; - *executerp = e; - return 0; -} - -void -executer_destroy(struct executer *e) -{ - if (e) { - size_t i; - - free(e->command_acl); - free(e->command_dir); - for (i = 0; i < e->n_children; i++) { - struct child *child = &e->children[i]; - - free(child->name); - kill(child->pid, SIGHUP); - /* We don't own child->rconn. */ - free(child->output); - free(child); - } - free(e); - } -} - -void -executer_set_acl(struct executer *e, const char *acl, const char *dir) -{ - free(e->command_acl); - e->command_acl = xstrdup(acl); - free(e->command_dir); - e->command_dir = xstrdup(dir); -} diff --git a/secchan/executer.h b/secchan/executer.h deleted file mode 100644 index fbed85b8..00000000 --- a/secchan/executer.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef EXECUTER_H -#define EXECUTER_H 1 - -struct executer; -struct nicira_header; -struct rconn; - -int executer_create(const char *acl, const char *dir, struct executer **); -void executer_set_acl(struct executer *, const char *acl, const char *dir); -void executer_destroy(struct executer *); -void executer_run(struct executer *); -void executer_wait(struct executer *); -void executer_rconn_closing(struct executer *, struct rconn *); -int executer_handle_request(struct executer *, struct rconn *, - struct nicira_header *); - -#endif /* executer.h */ diff --git a/secchan/fail-open.c b/secchan/fail-open.c deleted file mode 100644 index 0e887293..00000000 --- a/secchan/fail-open.c +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "fail-open.h" -#include -#include -#include "flow.h" -#include "mac-learning.h" -#include "odp-util.h" -#include "ofproto.h" -#include "rconn.h" -#include "status.h" -#include "timeval.h" - -#define THIS_MODULE VLM_fail_open -#include "vlog.h" - -struct fail_open { - struct ofproto *ofproto; - struct rconn *controller; - int trigger_duration; - int last_disconn_secs; - struct status_category *ss_cat; -}; - -/* Causes the switch to enter or leave fail-open mode, if appropriate. */ -void -fail_open_run(struct fail_open *fo) -{ - int disconn_secs = rconn_failure_duration(fo->controller); - bool open = disconn_secs >= fo->trigger_duration; - if (open != (fo->last_disconn_secs != 0)) { - if (!open) { - flow_t flow; - - VLOG_WARN("No longer in fail-open mode"); - fo->last_disconn_secs = 0; - - memset(&flow, 0, sizeof flow); - ofproto_delete_flow(fo->ofproto, &flow, OFPFW_ALL, 70000); - } else { - VLOG_WARN("Could not connect to controller for %d seconds, " - "failing open", disconn_secs); - fo->last_disconn_secs = disconn_secs; - - /* Flush all OpenFlow and datapath flows. We will set up our - * fail-open rule from fail_open_flushed() when - * ofproto_flush_flows() calls back to us. */ - ofproto_flush_flows(fo->ofproto); - } - } else if (open && disconn_secs > fo->last_disconn_secs + 60) { - VLOG_INFO("Still in fail-open mode after %d seconds disconnected " - "from controller", disconn_secs); - fo->last_disconn_secs = disconn_secs; - } -} - -void -fail_open_wait(struct fail_open *fo UNUSED) -{ - /* Nothing to do. */ -} - -void -fail_open_flushed(struct fail_open *fo) -{ - int disconn_secs = rconn_failure_duration(fo->controller); - bool open = disconn_secs >= fo->trigger_duration; - if (open) { - union ofp_action action; - flow_t flow; - - /* Set up a flow that matches every packet and directs them to - * OFPP_NORMAL. */ - memset(&action, 0, sizeof action); - action.type = htons(OFPAT_OUTPUT); - action.output.len = htons(sizeof action); - action.output.port = htons(OFPP_NORMAL); - memset(&flow, 0, sizeof flow); - ofproto_add_flow(fo->ofproto, &flow, OFPFW_ALL, 70000, - &action, 1, 0); - } -} - -static void -fail_open_status_cb(struct status_reply *sr, void *fo_) -{ - struct fail_open *fo = fo_; - int cur_duration = rconn_failure_duration(fo->controller); - - status_reply_put(sr, "trigger-duration=%d", fo->trigger_duration); - status_reply_put(sr, "current-duration=%d", cur_duration); - status_reply_put(sr, "triggered=%s", - cur_duration >= fo->trigger_duration ? "true" : "false"); -} - -struct fail_open * -fail_open_create(struct ofproto *ofproto, - int trigger_duration, struct switch_status *switch_status, - struct rconn *controller) -{ - struct fail_open *fo = xmalloc(sizeof *fo); - fo->ofproto = ofproto; - fo->controller = controller; - fo->trigger_duration = trigger_duration; - fo->last_disconn_secs = 0; - fo->ss_cat = switch_status_register(switch_status, "fail-open", - fail_open_status_cb, fo); - return fo; -} - -void -fail_open_set_trigger_duration(struct fail_open *fo, int trigger_duration) -{ - fo->trigger_duration = trigger_duration; -} - -void -fail_open_destroy(struct fail_open *fo) -{ - if (fo) { - /* We don't own fo->controller. */ - switch_status_unregister(fo->ss_cat); - free(fo); - } -} diff --git a/secchan/fail-open.h b/secchan/fail-open.h deleted file mode 100644 index c0ada2eb..00000000 --- a/secchan/fail-open.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef FAIL_OPEN_H -#define FAIL_OPEN_H 1 - -#include -#include -#include "flow.h" - -struct fail_open; -struct ofproto; -struct rconn; -struct switch_status; - -struct fail_open *fail_open_create(struct ofproto *, int trigger_duration, - struct switch_status *, - struct rconn *controller); -void fail_open_set_trigger_duration(struct fail_open *, int trigger_duration); -void fail_open_destroy(struct fail_open *); -void fail_open_wait(struct fail_open *); -void fail_open_run(struct fail_open *); -void fail_open_flushed(struct fail_open *); - -#endif /* fail-open.h */ diff --git a/secchan/in-band.c b/secchan/in-band.c deleted file mode 100644 index 1e242385..00000000 --- a/secchan/in-band.c +++ /dev/null @@ -1,359 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "in-band.h" -#include -#include -#include -#include -#include -#include "dpif.h" -#include "flow.h" -#include "mac-learning.h" -#include "netdev.h" -#include "odp-util.h" -#include "ofp-print.h" -#include "ofproto.h" -#include "ofpbuf.h" -#include "openflow/openflow.h" -#include "packets.h" -#include "poll-loop.h" -#include "rconn.h" -#include "status.h" -#include "timeval.h" -#include "vconn.h" - -#define THIS_MODULE VLM_in_band -#include "vlog.h" - -#define IB_BASE_PRIORITY 18181800 - -enum { - IBR_FROM_LOCAL_PORT, /* Sent by secure channel. */ - IBR_TO_LOCAL_PORT, /* Sent to secure channel. */ - IBR_ARP_FROM_CTL, /* ARP from the controller. */ - IBR_TO_CTL_OFP_SRC, /* To controller, OpenFlow source port. */ - IBR_TO_CTL_OFP_DST, /* To controller, OpenFlow dest port. */ - IBR_FROM_CTL_OFP_SRC, /* From controller, OpenFlow source port. */ - IBR_FROM_CTL_OFP_DST, /* From controller, OpenFlow dest port. */ -#if OFP_TCP_PORT != OFP_SSL_PORT -#error Need to support separate TCP and SSL flows. -#endif - N_IB_RULES -}; - -struct ib_rule { - bool installed; - flow_t flow; - uint32_t wildcards; - unsigned int priority; -}; - -struct in_band { - struct ofproto *ofproto; - struct netdev *netdev; - struct rconn *controller; - struct status_category *ss_cat; - - /* Keeping track of controller's MAC address. */ - uint32_t ip; /* Current IP, 0 if unknown. */ - uint32_t last_ip; /* Last known IP, 0 if never known. */ - uint8_t mac[ETH_ADDR_LEN]; /* Current MAC, 0 if unknown. */ - uint8_t last_mac[ETH_ADDR_LEN]; /* Last known MAC, 0 if never known */ - time_t next_refresh; /* Next time to refresh MAC address. */ - - /* Keeping track of the local port's MAC address. */ - uint8_t local_mac[ETH_ADDR_LEN]; /* Current MAC. */ - time_t next_local_refresh; /* Next time to refresh MAC address. */ - - /* Rules that we set up. */ - struct ib_rule rules[N_IB_RULES]; -}; - -static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(60, 60); - -static const uint8_t * -get_controller_mac(struct in_band *ib) -{ - time_t now = time_now(); - uint32_t ip; - - ip = rconn_get_ip(ib->controller); - if (ip != ib->ip || now >= ib->next_refresh) { - bool have_mac; - - ib->ip = ip; - - /* Look up MAC address. */ - memset(ib->mac, 0, sizeof ib->mac); - if (ib->ip) { - int retval = netdev_arp_lookup(ib->netdev, ib->ip, ib->mac); - if (retval) { - VLOG_DBG_RL(&rl, "cannot look up controller hw address " - "("IP_FMT"): %s", - IP_ARGS(&ib->ip), strerror(retval)); - } - } - have_mac = !eth_addr_is_zero(ib->mac); - - /* Log changes in IP, MAC addresses. */ - if (ib->ip && ib->ip != ib->last_ip) { - VLOG_DBG("controller IP address changed from "IP_FMT - " to "IP_FMT, IP_ARGS(&ib->last_ip), IP_ARGS(&ib->ip)); - ib->last_ip = ib->ip; - } - if (have_mac && memcmp(ib->last_mac, ib->mac, ETH_ADDR_LEN)) { - VLOG_DBG("controller MAC address changed from "ETH_ADDR_FMT" to " - ETH_ADDR_FMT, - ETH_ADDR_ARGS(ib->last_mac), ETH_ADDR_ARGS(ib->mac)); - memcpy(ib->last_mac, ib->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. */ - ib->next_refresh = now + (!ib->ip || have_mac ? 10 : 1); - } - return !eth_addr_is_zero(ib->mac) ? ib->mac : NULL; -} - -static const uint8_t * -get_local_mac(struct in_band *ib) -{ - time_t now = time_now(); - if (now >= ib->next_local_refresh) { - uint8_t ea[ETH_ADDR_LEN]; - if (!netdev_nodev_get_etheraddr(netdev_get_name(ib->netdev), ea)) { - memcpy(ib->local_mac, ea, ETH_ADDR_LEN); - } - ib->next_local_refresh = now + 1; - } - return !eth_addr_is_zero(ib->local_mac) ? ib->local_mac : NULL; -} - -static void -in_band_status_cb(struct status_reply *sr, void *in_band_) -{ - struct in_band *in_band = in_band_; - struct in_addr local_ip; - const uint8_t *local_mac; - uint32_t controller_ip; - const uint8_t *controller_mac; - - if (netdev_get_in4(in_band->netdev, &local_ip)) { - status_reply_put(sr, "local-ip="IP_FMT, IP_ARGS(&local_ip.s_addr)); - } - local_mac = get_local_mac(in_band); - if (local_mac) { - status_reply_put(sr, "local-mac="ETH_ADDR_FMT, - ETH_ADDR_ARGS(local_mac)); - } - - controller_ip = rconn_get_ip(in_band->controller); - if (controller_ip) { - status_reply_put(sr, "controller-ip="IP_FMT, - IP_ARGS(&controller_ip)); - } - controller_mac = get_controller_mac(in_band); - if (controller_mac) { - status_reply_put(sr, "controller-mac="ETH_ADDR_FMT, - ETH_ADDR_ARGS(controller_mac)); - } -} - -static void -drop_flow(struct in_band *in_band, int rule_idx) -{ - struct ib_rule *rule = &in_band->rules[rule_idx]; - - if (rule->installed) { - rule->installed = false; - ofproto_delete_flow(in_band->ofproto, &rule->flow, rule->wildcards, - rule->priority); - } -} - -/* out_port and fixed_fields are assumed never to change. */ -static void -setup_flow(struct in_band *in_band, int rule_idx, const flow_t *flow, - uint32_t fixed_fields, uint16_t out_port) -{ - struct ib_rule *rule = &in_band->rules[rule_idx]; - - if (!rule->installed || memcmp(flow, &rule->flow, sizeof *flow)) { - union ofp_action action; - - drop_flow(in_band, rule_idx); - - rule->installed = true; - rule->flow = *flow; - rule->wildcards = OFPFW_ALL & ~fixed_fields; - rule->priority = IB_BASE_PRIORITY + (N_IB_RULES - rule_idx); - - action.type = htons(OFPAT_OUTPUT); - action.output.len = htons(sizeof action); - action.output.port = htons(out_port); - action.output.max_len = htons(0); - ofproto_add_flow(in_band->ofproto, &rule->flow, rule->wildcards, - rule->priority, &action, 1, 0); - } -} - -void -in_band_run(struct in_band *in_band) -{ - const uint8_t *controller_mac; - const uint8_t *local_mac; - flow_t flow; - - if (time_now() < MIN(in_band->next_refresh, in_band->next_local_refresh)) { - return; - } - controller_mac = get_controller_mac(in_band); - local_mac = get_local_mac(in_band); - - /* Switch traffic sent by the secure channel. */ - memset(&flow, 0, sizeof flow); - flow.in_port = ODPP_LOCAL; - setup_flow(in_band, IBR_FROM_LOCAL_PORT, &flow, OFPFW_IN_PORT, - OFPP_NORMAL); - - /* Deliver traffic sent to the secure channel to the local port. */ - if (local_mac) { - memset(&flow, 0, sizeof flow); - memcpy(flow.dl_dst, local_mac, ETH_ADDR_LEN); - setup_flow(in_band, IBR_TO_LOCAL_PORT, &flow, OFPFW_DL_DST, - OFPP_NORMAL); - } else { - drop_flow(in_band, IBR_TO_LOCAL_PORT); - } - - if (controller_mac) { - /* Switch ARP requests sent by the controller. (OFPP_NORMAL will "do - * the right thing" regarding VLANs here.) */ - memset(&flow, 0, sizeof flow); - flow.dl_type = htons(ETH_TYPE_ARP); - memcpy(flow.dl_dst, eth_addr_broadcast, ETH_ADDR_LEN); - memcpy(flow.dl_src, controller_mac, ETH_ADDR_LEN); - setup_flow(in_band, IBR_ARP_FROM_CTL, &flow, - OFPFW_DL_TYPE | OFPFW_DL_DST | OFPFW_DL_SRC, - OFPP_NORMAL); - - /* OpenFlow traffic to or from the controller. - * - * (A given field's value is completely ignored if it is wildcarded, - * which is why we can get away with using a single 'flow' in each - * case here.) */ - memset(&flow, 0, sizeof flow); - flow.dl_type = htons(ETH_TYPE_IP); - memcpy(flow.dl_src, controller_mac, ETH_ADDR_LEN); - memcpy(flow.dl_dst, controller_mac, ETH_ADDR_LEN); - flow.nw_proto = IP_TYPE_TCP; - flow.tp_src = htons(OFP_TCP_PORT); - flow.tp_dst = htons(OFP_TCP_PORT); - setup_flow(in_band, IBR_TO_CTL_OFP_SRC, &flow, - (OFPFW_DL_TYPE | OFPFW_DL_DST | OFPFW_NW_PROTO - | OFPFW_TP_SRC), OFPP_NORMAL); - setup_flow(in_band, IBR_TO_CTL_OFP_DST, &flow, - (OFPFW_DL_TYPE | OFPFW_DL_DST | OFPFW_NW_PROTO - | OFPFW_TP_DST), OFPP_NORMAL); - setup_flow(in_band, IBR_FROM_CTL_OFP_SRC, &flow, - (OFPFW_DL_TYPE | OFPFW_DL_SRC | OFPFW_NW_PROTO - | OFPFW_TP_SRC), OFPP_NORMAL); - setup_flow(in_band, IBR_FROM_CTL_OFP_DST, &flow, - (OFPFW_DL_TYPE | OFPFW_DL_SRC | OFPFW_NW_PROTO - | OFPFW_TP_DST), OFPP_NORMAL); - } else { - drop_flow(in_band, IBR_ARP_FROM_CTL); - drop_flow(in_band, IBR_TO_CTL_OFP_DST); - drop_flow(in_band, IBR_TO_CTL_OFP_SRC); - drop_flow(in_band, IBR_FROM_CTL_OFP_DST); - drop_flow(in_band, IBR_FROM_CTL_OFP_SRC); - } -} - -void -in_band_wait(struct in_band *in_band) -{ - time_t now = time_now(); - time_t wakeup = MIN(in_band->next_refresh, in_band->next_local_refresh); - if (wakeup > now) { - poll_timer_wait((wakeup - now) * 1000); - } else { - poll_immediate_wake(); - } -} - -void -in_band_flushed(struct in_band *in_band) -{ - int i; - - for (i = 0; i < N_IB_RULES; i++) { - in_band->rules[i].installed = false; - } -} - -int -in_band_create(struct ofproto *ofproto, - struct dpif *dpif, struct switch_status *ss, - struct rconn *controller, struct in_band **in_bandp) -{ - struct in_band *in_band; - struct netdev *netdev; - char local_name[IF_NAMESIZE]; - int error; - - *in_bandp = NULL; - error = dpif_port_get_name(dpif, ODPP_LOCAL, - local_name, sizeof local_name); - if (error) { - return error; - } - - error = netdev_open(local_name, NETDEV_ETH_TYPE_NONE, &netdev); - if (error) { - VLOG_ERR("failed to open %s network device: %s", - local_name, strerror(error)); - return error; - } - - in_band = xcalloc(1, sizeof *in_band); - in_band->ofproto = ofproto; - in_band->netdev = netdev; - in_band->controller = controller; - in_band->ss_cat = switch_status_register(ss, "in-band", - in_band_status_cb, in_band); - in_band->next_refresh = TIME_MIN; - in_band->next_local_refresh = TIME_MIN; - - *in_bandp = in_band; - return 0; -} - -void -in_band_destroy(struct in_band *in_band) -{ - if (in_band) { - netdev_close(in_band->netdev); - switch_status_unregister(in_band->ss_cat); - /* We don't own the rconn. */ - } -} - diff --git a/secchan/in-band.h b/secchan/in-band.h deleted file mode 100644 index 972611d6..00000000 --- a/secchan/in-band.h +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef IN_BAND_H -#define IN_BAND_H 1 - -#include "flow.h" - -struct dpif; -struct in_band; -struct ofproto; -struct rconn; -struct secchan; -struct settings; -struct switch_status; - -int in_band_create(struct ofproto *, struct dpif *, struct switch_status *, - struct rconn *controller, struct in_band **); -void in_band_destroy(struct in_band *); -void in_band_run(struct in_band *); -void in_band_wait(struct in_band *); -void in_band_flushed(struct in_band *); - -#endif /* in-band.h */ diff --git a/secchan/main.c b/secchan/main.c deleted file mode 100644 index c886abe0..00000000 --- a/secchan/main.c +++ /dev/null @@ -1,568 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "command-line.h" -#include "compiler.h" -#include "daemon.h" -#include "dirs.h" -#include "discovery.h" -#include "dpif.h" -#include "fail-open.h" -#include "fault.h" -#include "in-band.h" -#include "leak-checker.h" -#include "list.h" -#include "netdev.h" -#include "ofpbuf.h" -#include "ofproto.h" -#include "openflow/openflow.h" -#include "packets.h" -#include "poll-loop.h" -#include "rconn.h" -#include "status.h" -#include "svec.h" -#include "timeval.h" -#include "unixctl.h" -#include "util.h" -#include "vconn-ssl.h" -#include "vconn.h" - -#include "vlog.h" -#define THIS_MODULE VLM_secchan - -/* 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 ofsettings { - /* Overall mode of operation. */ - bool discovery; /* Discover the controller automatically? */ - bool in_band; /* Connect to controller in-band? */ - - /* Datapath. */ - uint64_t datapath_id; /* Datapath ID. */ - const char *dp_name; /* Name of local datapath. */ - - /* Description strings. */ - const char *mfr_desc; /* Manufacturer. */ - const char *hw_desc; /* Hardware. */ - const char *sw_desc; /* Software version. */ - const char *serial_desc; /* Serial number. */ - - /* Related vconns and network devices. */ - const char *controller_name; /* Controller (if not discovery mode). */ - struct svec listeners; /* Listen for management connections. */ - struct svec snoops; /* Listen for controller snooping conns. */ - - /* 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. */ - - /* Packet-in rate-limiting. */ - int rate_limit; /* Tokens added to bucket per second. */ - int burst_limit; /* Maximum number token bucket size. */ - - /* Discovery behavior. */ - const char *accept_controller_re; /* Controller vconns to accept. */ - bool update_resolv_conf; /* Update /etc/resolv.conf? */ - - /* Spanning tree protocol. */ - bool enable_stp; - - /* Remote command execution. */ - char *command_acl; /* Command white/blacklist, as shell globs. */ - char *command_dir; /* Directory that contains commands. */ - - /* Management. */ - uint64_t mgmt_id; /* Management ID. */ - - /* NetFlow. */ - struct svec netflow; /* NetFlow targets. */ -}; - -static void parse_options(int argc, char *argv[], struct ofsettings *); -static void usage(void) NO_RETURN; - -int -main(int argc, char *argv[]) -{ - struct unixctl_server *unixctl; - struct ofproto *ofproto; - struct ofsettings s; - int error; - - set_program_name(argv[0]); - register_fault_handlers(); - time_init(); - vlog_init(); - parse_options(argc, argv, &s); - signal(SIGPIPE, SIG_IGN); - - die_if_already_running(); - daemonize(); - - /* Start listening for ovs-appctl requests. */ - error = unixctl_server_create(NULL, &unixctl); - if (error) { - ovs_fatal(error, "Could not listen for unixctl connections"); - } - - VLOG_INFO("Open vSwitch version %s", VERSION BUILDNR); - VLOG_INFO("OpenFlow protocol version 0x%02x", OFP_VERSION); - - /* Start OpenFlow processing. */ - error = ofproto_create(s.dp_name, NULL, NULL, &ofproto); - if (error) { - ovs_fatal(error, "could not initialize openflow switch"); - } - error = ofproto_set_in_band(ofproto, s.in_band); - if (error) { - ovs_fatal(error, "failed to configure in-band control"); - } - error = ofproto_set_discovery(ofproto, s.discovery, s.accept_controller_re, - s.update_resolv_conf); - if (error) { - ovs_fatal(error, "failed to configure controller discovery"); - } - if (s.datapath_id) { - ofproto_set_datapath_id(ofproto, s.datapath_id); - } - if (s.mgmt_id) { - ofproto_set_mgmt_id(ofproto, s.mgmt_id); - } - ofproto_set_desc(ofproto, s.mfr_desc, s.hw_desc, s.sw_desc, s.serial_desc); - error = ofproto_set_listeners(ofproto, &s.listeners); - if (error) { - ovs_fatal(error, "failed to configure management connections"); - } - error = ofproto_set_snoops(ofproto, &s.snoops); - if (error) { - ovs_fatal(error, - "failed to configure controller snooping connections"); - } - error = ofproto_set_netflow(ofproto, &s.netflow, 0, 0, false); - if (error) { - ovs_fatal(error, "failed to configure NetFlow collectors"); - } - ofproto_set_failure(ofproto, s.fail_mode == FAIL_OPEN); - ofproto_set_probe_interval(ofproto, s.probe_interval); - ofproto_set_max_backoff(ofproto, s.max_backoff); - ofproto_set_rate_limit(ofproto, s.rate_limit, s.burst_limit); - error = ofproto_set_stp(ofproto, s.enable_stp); - if (error) { - ovs_fatal(error, "failed to configure STP"); - } - error = ofproto_set_remote_execution(ofproto, s.command_acl, - s.command_dir); - if (error) { - ovs_fatal(error, "failed to configure remote command execution"); - } - if (!s.discovery) { - error = ofproto_set_controller(ofproto, s.controller_name); - if (error) { - ovs_fatal(error, "failed to configure controller"); - } - } - - while (ofproto_is_alive(ofproto)) { - error = ofproto_run(ofproto); - if (error) { - ovs_fatal(error, "unrecoverable datapath error"); - } - unixctl_server_run(unixctl); - dp_run(); - - ofproto_wait(ofproto); - unixctl_server_wait(unixctl); - dp_wait(); - poll_block(); - } - - return 0; -} - -/* User interface. */ - -static void -parse_options(int argc, char *argv[], struct ofsettings *s) -{ - enum { - OPT_DATAPATH_ID = UCHAR_MAX + 1, - OPT_MANUFACTURER, - OPT_HARDWARE, - OPT_SOFTWARE, - OPT_SERIAL, - OPT_ACCEPT_VCONN, - OPT_NO_RESOLV_CONF, - OPT_BR_NAME, - OPT_FAIL_MODE, - OPT_INACTIVITY_PROBE, - OPT_MAX_IDLE, - OPT_MAX_BACKOFF, - OPT_SNOOP, - OPT_RATE_LIMIT, - OPT_BURST_LIMIT, - OPT_BOOTSTRAP_CA_CERT, - OPT_STP, - OPT_NO_STP, - OPT_OUT_OF_BAND, - OPT_IN_BAND, - OPT_COMMAND_ACL, - OPT_COMMAND_DIR, - OPT_NETFLOW, - OPT_MGMT_ID, - VLOG_OPTION_ENUMS, - LEAK_CHECKER_OPTION_ENUMS - }; - static struct option long_options[] = { - {"datapath-id", required_argument, 0, OPT_DATAPATH_ID}, - {"manufacturer", required_argument, 0, OPT_MANUFACTURER}, - {"hardware", required_argument, 0, OPT_HARDWARE}, - {"software", required_argument, 0, OPT_SOFTWARE}, - {"serial", required_argument, 0, OPT_SERIAL}, - {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN}, - {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF}, - {"config", required_argument, 0, 'F'}, - {"br-name", required_argument, 0, OPT_BR_NAME}, - {"fail", required_argument, 0, OPT_FAIL_MODE}, - {"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'}, - {"snoop", required_argument, 0, OPT_SNOOP}, - {"rate-limit", optional_argument, 0, OPT_RATE_LIMIT}, - {"burst-limit", required_argument, 0, OPT_BURST_LIMIT}, - {"stp", no_argument, 0, OPT_STP}, - {"no-stp", no_argument, 0, OPT_NO_STP}, - {"out-of-band", no_argument, 0, OPT_OUT_OF_BAND}, - {"in-band", no_argument, 0, OPT_IN_BAND}, - {"command-acl", required_argument, 0, OPT_COMMAND_ACL}, - {"command-dir", required_argument, 0, OPT_COMMAND_DIR}, - {"netflow", required_argument, 0, OPT_NETFLOW}, - {"mgmt-id", required_argument, 0, OPT_MGMT_ID}, - {"verbose", optional_argument, 0, 'v'}, - {"help", no_argument, 0, 'h'}, - {"version", no_argument, 0, 'V'}, - DAEMON_LONG_OPTIONS, - VLOG_LONG_OPTIONS, - LEAK_CHECKER_LONG_OPTIONS, -#ifdef HAVE_OPENSSL - VCONN_SSL_LONG_OPTIONS - {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, -#endif - {0, 0, 0, 0}, - }; - char *short_options = long_options_to_short_options(long_options); - - /* Set defaults that we can figure out before parsing options. */ - s->datapath_id = 0; - s->mfr_desc = NULL; - s->hw_desc = NULL; - s->sw_desc = NULL; - s->serial_desc = NULL; - svec_init(&s->listeners); - svec_init(&s->snoops); - s->fail_mode = FAIL_OPEN; - s->max_idle = 0; - s->probe_interval = 0; - s->max_backoff = 15; - s->update_resolv_conf = true; - s->rate_limit = 0; - s->burst_limit = 0; - s->accept_controller_re = NULL; - s->enable_stp = false; - s->in_band = true; - s->command_acl = ""; - s->command_dir = NULL; - svec_init(&s->netflow); - s->mgmt_id = 0; - for (;;) { - int c; - - c = getopt_long(argc, argv, short_options, long_options, NULL); - if (c == -1) { - break; - } - - switch (c) { - case OPT_DATAPATH_ID: - if (strlen(optarg) != 12 - || strspn(optarg, "0123456789abcdefABCDEF") != 12) { - ovs_fatal(0, "argument to --datapath-id must be " - "exactly 12 hex digits"); - } - s->datapath_id = strtoll(optarg, NULL, 16); - if (!s->datapath_id) { - ovs_fatal(0, "argument to --datapath-id must be nonzero"); - } - break; - - case OPT_MANUFACTURER: - s->mfr_desc = optarg; - break; - - case OPT_HARDWARE: - s->hw_desc = optarg; - break; - - case OPT_SOFTWARE: - s->sw_desc = optarg; - break; - - case OPT_SERIAL: - s->serial_desc = optarg; - break; - - case OPT_ACCEPT_VCONN: - s->accept_controller_re = optarg; - break; - - case OPT_NO_RESOLV_CONF: - s->update_resolv_conf = false; - break; - - case OPT_FAIL_MODE: - if (!strcmp(optarg, "open")) { - s->fail_mode = FAIL_OPEN; - } else if (!strcmp(optarg, "closed")) { - s->fail_mode = FAIL_CLOSED; - } else { - ovs_fatal(0, "-f or --fail argument must be \"open\" " - "or \"closed\""); - } - break; - - case OPT_INACTIVITY_PROBE: - s->probe_interval = atoi(optarg); - if (s->probe_interval < 5) { - ovs_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) { - ovs_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) { - ovs_fatal(0, "--max-backoff argument must be at least 1"); - } else if (s->max_backoff > 3600) { - s->max_backoff = 3600; - } - break; - - case OPT_RATE_LIMIT: - if (optarg) { - s->rate_limit = atoi(optarg); - if (s->rate_limit < 1) { - ovs_fatal(0, "--rate-limit argument must be at least 1"); - } - } else { - s->rate_limit = 1000; - } - break; - - case OPT_BURST_LIMIT: - s->burst_limit = atoi(optarg); - if (s->burst_limit < 1) { - ovs_fatal(0, "--burst-limit argument must be at least 1"); - } - break; - - case OPT_STP: - s->enable_stp = true; - break; - - case OPT_NO_STP: - s->enable_stp = false; - break; - - case OPT_OUT_OF_BAND: - s->in_band = false; - break; - - case OPT_IN_BAND: - s->in_band = true; - break; - - case OPT_COMMAND_ACL: - s->command_acl = (s->command_acl[0] - ? xasprintf("%s,%s", s->command_acl, optarg) - : optarg); - break; - - case OPT_COMMAND_DIR: - s->command_dir = optarg; - break; - - case OPT_NETFLOW: - svec_add(&s->netflow, optarg); - break; - - case OPT_MGMT_ID: - if (strlen(optarg) != 12 - || strspn(optarg, "0123456789abcdefABCDEF") != 12) { - ovs_fatal(0, "argument to --mgmt-id must be " - "exactly 12 hex digits"); - } - s->mgmt_id = strtoll(optarg, NULL, 16); - if (!s->mgmt_id) { - ovs_fatal(0, "argument to --mgmt-id must be nonzero"); - } - break; - - case 'l': - svec_add(&s->listeners, optarg); - break; - - case OPT_SNOOP: - svec_add(&s->snoops, optarg); - break; - - case 'h': - usage(); - - case 'V': - OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION); - exit(EXIT_SUCCESS); - - DAEMON_OPTION_HANDLERS - - VLOG_OPTION_HANDLERS - - LEAK_CHECKER_OPTION_HANDLERS - -#ifdef HAVE_OPENSSL - VCONN_SSL_OPTION_HANDLERS - - case OPT_BOOTSTRAP_CA_CERT: - vconn_ssl_set_ca_cert_file(optarg, true); - break; -#endif - - case '?': - exit(EXIT_FAILURE); - - default: - abort(); - } - } - free(short_options); - - argc -= optind; - argv += optind; - if (argc < 1 || argc > 2) { - ovs_fatal(0, "need one or two non-option arguments; " - "use --help for usage"); - } - - /* Local and remote vconns. */ - s->dp_name = argv[0]; - s->controller_name = argc > 1 ? xstrdup(argv[1]) : NULL; - - /* Set accept_controller_regex. */ - if (!s->accept_controller_re) { - s->accept_controller_re - = vconn_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*"; - } - - /* Mode of operation. */ - s->discovery = s->controller_name == NULL; - if (s->discovery && !s->in_band) { - ovs_fatal(0, "Cannot perform discovery with out-of-band control"); - } - - /* Rate limiting. */ - if (s->rate_limit && s->rate_limit < 100) { - VLOG_WARN("Rate limit set to unusually low value %d", s->rate_limit); - } -} - -static void -usage(void) -{ - printf("%s: an OpenFlow switch implementation.\n" - "usage: %s [OPTIONS] DATAPATH [CONTROLLER]\n" - "DATAPATH is a local datapath (e.g. \"dp0\").\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, true); - printf("\nOpenFlow options:\n" - " -d, --datapath-id=ID Use ID as the OpenFlow switch ID\n" - " (ID must consist of 12 hex digits)\n" - " --mgmt-id=ID Use ID as the management ID\n" - " (ID must consist of 12 hex digits)\n" - " --manufacturer=MFR Identify manufacturer as MFR\n" - " --hardware=HW Identify hardware as HW\n" - " --software=SW Identify software as SW\n" - " --serial=SERIAL Identify serial number as SERIAL\n" - "\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" - " --fail=open|closed when controller connection fails:\n" - " closed: drop all packets\n" - " open (default): act as learning switch\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" - " --snoop=METHOD allow controller snooping on METHOD\n" - " (a passive OpenFlow connection method)\n" - " --out-of-band controller connection is out-of-band\n" - " --netflow=HOST:PORT configure NetFlow output target\n" - "\nRate-limiting of \"packet-in\" messages to the controller:\n" - " --rate-limit[=PACKETS] max rate, in packets/s (default: 1000)\n" - " --burst-limit=BURST limit on packet credit for idle time\n" - "\nRemote command execution options:\n" - " --command-acl=[!]GLOB[,[!]GLOB...] set allowed/denied commands\n" - " --command-dir=DIR set command dir (default: %s/commands)\n", - ovs_pkgdatadir); - daemon_usage(); - vlog_usage(); - printf("\nOther options:\n" - " -h, --help display this help message\n" - " -V, --version display version information\n"); - leak_checker_usage(); - exit(EXIT_SUCCESS); -} diff --git a/secchan/netflow.c b/secchan/netflow.c deleted file mode 100644 index e867c0eb..00000000 --- a/secchan/netflow.c +++ /dev/null @@ -1,328 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "netflow.h" -#include -#include -#include -#include -#include "cfg.h" -#include "flow.h" -#include "netflow.h" -#include "ofpbuf.h" -#include "ofproto.h" -#include "packets.h" -#include "socket-util.h" -#include "svec.h" -#include "timeval.h" -#include "util.h" -#include "xtoxll.h" - -#define THIS_MODULE VLM_netflow -#include "vlog.h" - -#define NETFLOW_V5_VERSION 5 - -/* Every NetFlow v5 message contains the header that follows. This is - * followed by up to thirty records that describe a terminating flow. - * We only send a single record per NetFlow message. - */ -struct netflow_v5_header { - uint16_t version; /* NetFlow version is 5. */ - uint16_t count; /* Number of records in this message. */ - uint32_t sysuptime; /* System uptime in milliseconds. */ - uint32_t unix_secs; /* Number of seconds since Unix epoch. */ - uint32_t unix_nsecs; /* Number of residual nanoseconds - after epoch seconds. */ - uint32_t flow_seq; /* Number of flows since sending - messages began. */ - uint8_t engine_type; /* Engine type. */ - uint8_t engine_id; /* Engine id. */ - uint16_t sampling_interval; /* Set to zero. */ -}; -BUILD_ASSERT_DECL(sizeof(struct netflow_v5_header) == 24); - -/* A NetFlow v5 description of a terminating flow. It is preceded by a - * NetFlow v5 header. - */ -struct netflow_v5_record { - uint32_t src_addr; /* Source IP address. */ - uint32_t dst_addr; /* Destination IP address. */ - uint32_t nexthop; /* IP address of next hop. Set to 0. */ - uint16_t input; /* Input interface index. */ - uint16_t output; /* Output interface index. */ - uint32_t packet_count; /* Number of packets. */ - uint32_t byte_count; /* Number of bytes. */ - uint32_t init_time; /* Value of sysuptime on first packet. */ - uint32_t used_time; /* Value of sysuptime on last packet. */ - - /* The 'src_port' and 'dst_port' identify the source and destination - * port, respectively, for TCP and UDP. For ICMP, the high-order - * byte identifies the type and low-order byte identifies the code - * in the 'dst_port' field. */ - uint16_t src_port; - uint16_t dst_port; - - uint8_t pad1; - uint8_t tcp_flags; /* Union of seen TCP flags. */ - uint8_t ip_proto; /* IP protocol. */ - uint8_t ip_tos; /* IP TOS value. */ - uint16_t src_as; /* Source AS ID. Set to 0. */ - uint16_t dst_as; /* Destination AS ID. Set to 0. */ - uint8_t src_mask; /* Source mask bits. Set to 0. */ - uint8_t dst_mask; /* Destination mask bits. Set to 0. */ - uint8_t pad[2]; -}; -BUILD_ASSERT_DECL(sizeof(struct netflow_v5_record) == 48); - -struct netflow { - uint8_t engine_type; /* Value of engine_type to use. */ - uint8_t engine_id; /* Value of engine_id to use. */ - long long int boot_time; /* Time when netflow_create() was called. */ - int *fds; /* Sockets for NetFlow collectors. */ - size_t n_fds; /* Number of Netflow collectors. */ - bool add_id_to_iface; /* Put the 7 least signficiant bits of - * 'engine_id' into the most signficant - * bits of the interface fields. */ - uint32_t netflow_cnt; /* Flow sequence number for NetFlow. */ - struct ofpbuf packet; /* NetFlow packet being accumulated. */ -}; - -static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - -static int -open_collector(char *dst) -{ - char *save_ptr; - const char *host_name; - const char *port_string; - struct sockaddr_in sin; - int retval; - int fd; - - /* Glibc 2.7 has a bug in strtok_r when compiling with optimization that - * can cause segfaults here: - * http://sources.redhat.com/bugzilla/show_bug.cgi?id=5614. - * Using "::" instead of the obvious ":" works around it. */ - host_name = strtok_r(dst, ":", &save_ptr); - port_string = strtok_r(NULL, ":", &save_ptr); - if (!host_name) { - ovs_error(0, "%s: bad peer name format", dst); - return -EAFNOSUPPORT; - } - if (!port_string) { - ovs_error(0, "%s: bad port format", dst); - return -EAFNOSUPPORT; - } - - memset(&sin, 0, sizeof sin); - sin.sin_family = AF_INET; - if (lookup_ip(host_name, &sin.sin_addr)) { - return -ENOENT; - } - sin.sin_port = htons(atoi(port_string)); - - fd = socket(AF_INET, SOCK_DGRAM, 0); - if (fd < 0) { - VLOG_ERR("%s: socket: %s", dst, strerror(errno)); - return -errno; - } - - retval = set_nonblocking(fd); - if (retval) { - close(fd); - return -retval; - } - - retval = connect(fd, (struct sockaddr *) &sin, sizeof sin); - if (retval < 0) { - int error = errno; - VLOG_ERR("%s: connect: %s", dst, strerror(error)); - close(fd); - return -error; - } - - return fd; -} - -void -netflow_expire(struct netflow *nf, const struct ofexpired *expired) -{ - struct netflow_v5_header *nf_hdr; - struct netflow_v5_record *nf_rec; - struct timeval now; - - /* NetFlow only reports on IP packets. */ - if (expired->flow.dl_type != htons(ETH_TYPE_IP)) { - return; - } - - time_timeval(&now); - - if (!nf->packet.size) { - nf_hdr = ofpbuf_put_zeros(&nf->packet, sizeof *nf_hdr); - nf_hdr->version = htons(NETFLOW_V5_VERSION); - nf_hdr->count = htons(0); - nf_hdr->sysuptime = htonl(time_msec() - nf->boot_time); - nf_hdr->unix_secs = htonl(now.tv_sec); - nf_hdr->unix_nsecs = htonl(now.tv_usec * 1000); - nf_hdr->flow_seq = htonl(nf->netflow_cnt++); - nf_hdr->engine_type = nf->engine_type; - nf_hdr->engine_id = nf->engine_id; - nf_hdr->sampling_interval = htons(0); - } - - nf_hdr = nf->packet.data; - nf_hdr->count = htons(ntohs(nf_hdr->count) + 1); - - nf_rec = ofpbuf_put_zeros(&nf->packet, sizeof *nf_rec); - nf_rec->src_addr = expired->flow.nw_src; - nf_rec->dst_addr = expired->flow.nw_dst; - nf_rec->nexthop = htons(0); - if (nf->add_id_to_iface) { - uint16_t iface = (nf->engine_id & 0x7f) << 9; - nf_rec->input = htons(iface | (expired->flow.in_port & 0x1ff)); - nf_rec->output = htons(iface); - printf("input: %x\n", ntohs(nf_rec->input)); - } else { - nf_rec->input = htons(expired->flow.in_port); - nf_rec->output = htons(0); - } - nf_rec->packet_count = htonl(MIN(expired->packet_count, UINT32_MAX)); - nf_rec->byte_count = htonl(MIN(expired->byte_count, UINT32_MAX)); - nf_rec->init_time = htonl(expired->created - nf->boot_time); - nf_rec->used_time = htonl(MAX(expired->created, expired->used) - - nf->boot_time); - if (expired->flow.nw_proto == IP_TYPE_ICMP) { - /* In NetFlow, the ICMP type and code are concatenated and - * placed in the 'dst_port' field. */ - uint8_t type = ntohs(expired->flow.tp_src); - uint8_t code = ntohs(expired->flow.tp_dst); - nf_rec->src_port = htons(0); - nf_rec->dst_port = htons((type << 8) | code); - } else { - nf_rec->src_port = expired->flow.tp_src; - nf_rec->dst_port = expired->flow.tp_dst; - } - nf_rec->tcp_flags = expired->tcp_flags; - nf_rec->ip_proto = expired->flow.nw_proto; - nf_rec->ip_tos = expired->ip_tos; - - /* NetFlow messages are limited to 30 records. A length of 1400 - * bytes guarantees that the limit is not exceeded. */ - if (nf->packet.size >= 1400) { - netflow_run(nf); - } -} - -void -netflow_run(struct netflow *nf) -{ - size_t i; - - if (!nf->packet.size) { - return; - } - - for (i = 0; i < nf->n_fds; i++) { - if (send(nf->fds[i], nf->packet.data, nf->packet.size, 0) == -1) { - VLOG_WARN_RL(&rl, "netflow message send failed: %s", - strerror(errno)); - } - } - nf->packet.size = 0; -} - -static void -clear_collectors(struct netflow *nf) -{ - size_t i; - - for (i = 0; i < nf->n_fds; i++) { - close(nf->fds[i]); - } - free(nf->fds); - nf->fds = NULL; - nf->n_fds = 0; -} - -int -netflow_set_collectors(struct netflow *nf, const struct svec *collectors_) -{ - struct svec collectors; - int error = 0; - size_t i; - - clear_collectors(nf); - - svec_clone(&collectors, collectors_); - svec_sort_unique(&collectors); - - nf->fds = xmalloc(sizeof *nf->fds * collectors.n); - for (i = 0; i < collectors.n; i++) { - const char *name = collectors.names[i]; - char *tmpname = xstrdup(name); - int fd = open_collector(tmpname); - free(tmpname); - if (fd >= 0) { - nf->fds[nf->n_fds++] = fd; - } else { - VLOG_WARN("couldn't open connection to collector (%s), " - "ignoring %s\n", strerror(-fd), name); - if (!error) { - error = -fd; - } - } - } - - svec_destroy(&collectors); - return error; -} - -void -netflow_set_engine(struct netflow *nf, uint8_t engine_type, - uint8_t engine_id, bool add_id_to_iface) -{ - nf->engine_type = engine_type; - nf->engine_id = engine_id; - nf->add_id_to_iface = add_id_to_iface; -} - -struct netflow * -netflow_create(void) -{ - struct netflow *nf = xmalloc(sizeof *nf); - nf->engine_type = 0; - nf->engine_id = 0; - nf->boot_time = time_msec(); - nf->fds = NULL; - nf->n_fds = 0; - nf->add_id_to_iface = false; - nf->netflow_cnt = 0; - ofpbuf_init(&nf->packet, 1500); - return nf; -} - -void -netflow_destroy(struct netflow *nf) -{ - if (nf) { - ofpbuf_uninit(&nf->packet); - clear_collectors(nf); - free(nf); - } -} diff --git a/secchan/netflow.h b/secchan/netflow.h deleted file mode 100644 index 13be90b4..00000000 --- a/secchan/netflow.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef NETFLOW_H -#define NETFLOW_H 1 - -#include "flow.h" - -struct ofexpired; -struct svec; - -struct netflow *netflow_create(void); -void netflow_destroy(struct netflow *); -int netflow_set_collectors(struct netflow *, const struct svec *collectors); -void netflow_set_engine(struct netflow *nf, uint8_t engine_type, - uint8_t engine_id, bool add_id_to_iface); -void netflow_expire(struct netflow *, const struct ofexpired *); -void netflow_run(struct netflow *); - -#endif /* netflow.h */ diff --git a/secchan/ofproto.c b/secchan/ofproto.c deleted file mode 100644 index 44d1a850..00000000 --- a/secchan/ofproto.c +++ /dev/null @@ -1,3319 +0,0 @@ -/* - * Copyright (c) 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "ofproto.h" -#include -#include -#include -#include -#include -#include -#include "classifier.h" -#include "coverage.h" -#include "discovery.h" -#include "dpif.h" -#include "executer.h" -#include "fail-open.h" -#include "in-band.h" -#include "mac-learning.h" -#include "netdev.h" -#include "netflow.h" -#include "odp-util.h" -#include "ofp-print.h" -#include "ofpbuf.h" -#include "openflow/nicira-ext.h" -#include "openflow/openflow.h" -#include "openflow/openflow-mgmt.h" -#include "openvswitch/datapath-protocol.h" -#include "packets.h" -#include "pinsched.h" -#include "pktbuf.h" -#include "poll-loop.h" -#include "port-array.h" -#include "rconn.h" -#include "shash.h" -#include "status.h" -#include "stp.h" -#include "svec.h" -#include "tag.h" -#include "timeval.h" -#include "vconn.h" -#include "vconn-ssl.h" -#include "xtoxll.h" - -#define THIS_MODULE VLM_ofproto -#include "vlog.h" - -enum { - DP_GROUP_FLOOD = 0, - DP_GROUP_ALL = 1 -}; - -enum { - TABLEID_HASH = 0, - TABLEID_CLASSIFIER = 1 -}; - -struct ofport { - struct netdev *netdev; - struct ofp_phy_port opp; /* In host byte order. */ -}; - -static void ofport_free(struct ofport *); -static void hton_ofp_phy_port(struct ofp_phy_port *); - -static int xlate_actions(const union ofp_action *in, size_t n_in, - const flow_t *flow, struct ofproto *ofproto, - const struct ofpbuf *packet, - struct odp_actions *out, tag_type *tags, - bool *may_setup_flow); - -struct rule { - struct cls_rule cr; - - uint16_t idle_timeout; /* In seconds from time of last use. */ - uint16_t hard_timeout; /* In seconds from time of creation. */ - long long int used; /* Last-used time (0 if never used). */ - long long int created; /* Creation time. */ - uint64_t packet_count; /* Number of packets received. */ - uint64_t byte_count; /* Number of bytes received. */ - uint64_t accounted_bytes; /* Number of bytes passed to account_cb. */ - uint8_t tcp_flags; /* Bitwise-OR of all TCP flags seen. */ - uint8_t ip_tos; /* Last-seen IP type-of-service. */ - tag_type tags; /* Tags (set only by hooks). */ - - /* If 'super' is non-NULL, this rule is a subrule, that is, it is an - * exact-match rule (having cr.wc.wildcards of 0) generated from the - * wildcard rule 'super'. In this case, 'list' is an element of the - * super-rule's list. - * - * If 'super' is NULL, this rule is a super-rule, and 'list' is the head of - * a list of subrules. A super-rule with no wildcards (where - * cr.wc.wildcards is 0) will never have any subrules. */ - struct rule *super; - struct list list; - - /* OpenFlow actions. - * - * A subrule has no actions (it uses the super-rule's actions). */ - int n_actions; - union ofp_action *actions; - - /* Datapath actions. - * - * A super-rule with wildcard fields never has ODP actions (since the - * datapath only supports exact-match flows). */ - bool installed; /* Installed in datapath? */ - bool may_install; /* True ordinarily; false if actions must - * be reassessed for every packet. */ - int n_odp_actions; - union odp_action *odp_actions; -}; - -static inline bool -rule_is_hidden(const struct rule *rule) -{ - /* Subrules are merely an implementation detail, so hide them from the - * controller. */ - if (rule->super != NULL) { - return true; - } - - /* Rules with priority higher than UINT16_MAX are set up by secchan itself - * (e.g. by in-band control) and are intentionally hidden from the - * controller. */ - if (rule->cr.priority > UINT16_MAX) { - return true; - } - - return false; -} - -static struct rule *rule_create(struct rule *super, const union ofp_action *, - size_t n_actions, uint16_t idle_timeout, - uint16_t hard_timeout); -static void rule_free(struct rule *); -static void rule_destroy(struct ofproto *, struct rule *); -static struct rule *rule_from_cls_rule(const struct cls_rule *); -static void rule_insert(struct ofproto *, struct rule *, - struct ofpbuf *packet, uint16_t in_port); -static void rule_remove(struct ofproto *, struct rule *); -static bool rule_make_actions(struct ofproto *, struct rule *, - const struct ofpbuf *packet); -static void rule_install(struct ofproto *, struct rule *, - struct rule *displaced_rule); -static void rule_uninstall(struct ofproto *, struct rule *); -static void rule_post_uninstall(struct ofproto *, struct rule *); - -struct ofconn { - struct list node; - struct rconn *rconn; - struct pktbuf *pktbuf; - bool send_flow_exp; - int miss_send_len; - - struct rconn_packet_counter *packet_in_counter; - - /* Number of OpenFlow messages queued as replies to OpenFlow requests, and - * the maximum number before we stop reading OpenFlow requests. */ -#define OFCONN_REPLY_MAX 100 - struct rconn_packet_counter *reply_counter; -}; - -static struct ofconn *ofconn_create(struct ofproto *, struct rconn *); -static void ofconn_destroy(struct ofconn *, struct ofproto *); -static void ofconn_run(struct ofconn *, struct ofproto *); -static void ofconn_wait(struct ofconn *); -static void queue_tx(struct ofpbuf *msg, const struct ofconn *ofconn, - struct rconn_packet_counter *counter); - -struct ofproto { - /* Settings. */ - uint64_t datapath_id; /* Datapath ID. */ - uint64_t fallback_dpid; /* Datapath ID if no better choice found. */ - uint64_t mgmt_id; /* Management channel identifier. */ - char *manufacturer; /* Manufacturer. */ - char *hardware; /* Hardware. */ - char *software; /* Software version. */ - char *serial; /* Serial number. */ - - /* Datapath. */ - struct dpif *dpif; - struct netdev_monitor *netdev_monitor; - struct port_array ports; /* Index is ODP port nr; ofport->opp.port_no is - * OFP port nr. */ - struct shash port_by_name; - uint32_t max_ports; - - /* Configuration. */ - struct switch_status *switch_status; - struct status_category *ss_cat; - struct in_band *in_band; - struct discovery *discovery; - struct fail_open *fail_open; - struct pinsched *miss_sched, *action_sched; - struct executer *executer; - struct netflow *netflow; - - /* Flow table. */ - struct classifier cls; - bool need_revalidate; - long long int next_expiration; - struct tag_set revalidate_set; - - /* OpenFlow connections. */ - struct list all_conns; - struct ofconn *controller; - struct pvconn **listeners; - size_t n_listeners; - struct pvconn **snoops; - size_t n_snoops; - - /* Hooks for ovs-vswitchd. */ - const struct ofhooks *ofhooks; - void *aux; - - /* Used by default ofhooks. */ - struct mac_learning *ml; -}; - -static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - -static const struct ofhooks default_ofhooks; - -static uint64_t pick_datapath_id(struct dpif *, uint64_t fallback_dpid); -static uint64_t pick_fallback_dpid(void); -static void send_packet_in_miss(struct ofpbuf *, void *ofproto); -static void send_packet_in_action(struct ofpbuf *, void *ofproto); -static void update_used(struct ofproto *); -static void update_stats(struct rule *, const struct odp_flow_stats *); -static void expire_rule(struct cls_rule *, void *ofproto); -static bool revalidate_rule(struct ofproto *p, struct rule *rule); -static void revalidate_cb(struct cls_rule *rule_, void *p_); - -static void handle_odp_msg(struct ofproto *, struct ofpbuf *); - -static void handle_openflow(struct ofconn *, struct ofproto *, - struct ofpbuf *); - -static void refresh_port_group(struct ofproto *, unsigned int group); -static void update_port(struct ofproto *, const char *devname); -static int init_ports(struct ofproto *); -static void reinit_ports(struct ofproto *); - -int -ofproto_create(const char *datapath, const struct ofhooks *ofhooks, void *aux, - struct ofproto **ofprotop) -{ - struct netdev_monitor *netdev_monitor; - struct odp_stats stats; - struct ofproto *p; - struct dpif *dpif; - int error; - - *ofprotop = NULL; - - /* Connect to datapath and start listening for messages. */ - error = dpif_open(datapath, &dpif); - if (error) { - VLOG_ERR("failed to open datapath %s: %s", datapath, strerror(error)); - return error; - } - error = dpif_get_dp_stats(dpif, &stats); - if (error) { - VLOG_ERR("failed to obtain stats for datapath %s: %s", - datapath, strerror(error)); - dpif_close(dpif); - return error; - } - error = dpif_recv_set_mask(dpif, ODPL_MISS | ODPL_ACTION); - if (error) { - VLOG_ERR("failed to listen on datapath %s: %s", - datapath, strerror(error)); - dpif_close(dpif); - return error; - } - dpif_flow_flush(dpif); - dpif_recv_purge(dpif); - - /* Arrange to monitor datapath ports for status changes. */ - error = netdev_monitor_create(&netdev_monitor); - if (error) { - VLOG_ERR("failed to starting monitoring datapath %s: %s", - datapath, strerror(error)); - dpif_close(dpif); - return error; - } - - /* Initialize settings. */ - p = xcalloc(1, sizeof *p); - p->fallback_dpid = pick_fallback_dpid(); - p->datapath_id = pick_datapath_id(dpif, p->fallback_dpid); - VLOG_INFO("using datapath ID %012"PRIx64, p->datapath_id); - p->manufacturer = xstrdup("Nicira Networks, Inc."); - p->hardware = xstrdup("Reference Implementation"); - p->software = xstrdup(VERSION BUILDNR); - p->serial = xstrdup("None"); - - /* Initialize datapath. */ - p->dpif = dpif; - p->netdev_monitor = netdev_monitor; - port_array_init(&p->ports); - shash_init(&p->port_by_name); - p->max_ports = stats.max_ports; - - /* Initialize submodules. */ - p->switch_status = switch_status_create(p); - p->in_band = NULL; - p->discovery = NULL; - p->fail_open = NULL; - p->miss_sched = p->action_sched = NULL; - p->executer = NULL; - p->netflow = NULL; - - /* Initialize flow table. */ - classifier_init(&p->cls); - p->need_revalidate = false; - p->next_expiration = time_msec() + 1000; - tag_set_init(&p->revalidate_set); - - /* Initialize OpenFlow connections. */ - list_init(&p->all_conns); - p->controller = ofconn_create(p, rconn_create(15, 15)); - p->controller->pktbuf = pktbuf_create(); - p->controller->miss_send_len = OFP_DEFAULT_MISS_SEND_LEN; - p->listeners = NULL; - p->n_listeners = 0; - p->snoops = NULL; - p->n_snoops = 0; - - /* Initialize hooks. */ - if (ofhooks) { - p->ofhooks = ofhooks; - p->aux = aux; - p->ml = NULL; - } else { - p->ofhooks = &default_ofhooks; - p->aux = p; - p->ml = mac_learning_create(); - } - - /* Register switch status category. */ - p->ss_cat = switch_status_register(p->switch_status, "remote", - rconn_status_cb, p->controller->rconn); - - /* Almost done... */ - error = init_ports(p); - if (error) { - ofproto_destroy(p); - return error; - } - - *ofprotop = p; - return 0; -} - -void -ofproto_set_datapath_id(struct ofproto *p, uint64_t datapath_id) -{ - uint64_t old_dpid = p->datapath_id; - p->datapath_id = (datapath_id - ? datapath_id - : pick_datapath_id(p->dpif, p->fallback_dpid)); - if (p->datapath_id != old_dpid) { - VLOG_INFO("datapath ID changed to %012"PRIx64, p->datapath_id); - rconn_reconnect(p->controller->rconn); - } -} - -void -ofproto_set_mgmt_id(struct ofproto *p, uint64_t mgmt_id) -{ - p->mgmt_id = mgmt_id; -} - -void -ofproto_set_probe_interval(struct ofproto *p, int probe_interval) -{ - probe_interval = probe_interval ? MAX(probe_interval, 5) : 0; - rconn_set_probe_interval(p->controller->rconn, probe_interval); - if (p->fail_open) { - int trigger_duration = probe_interval ? probe_interval * 3 : 15; - fail_open_set_trigger_duration(p->fail_open, trigger_duration); - } -} - -void -ofproto_set_max_backoff(struct ofproto *p, int max_backoff) -{ - rconn_set_max_backoff(p->controller->rconn, max_backoff); -} - -void -ofproto_set_desc(struct ofproto *p, - const char *manufacturer, const char *hardware, - const char *software, const char *serial) -{ - if (manufacturer) { - free(p->manufacturer); - p->manufacturer = xstrdup(manufacturer); - } - if (hardware) { - free(p->hardware); - p->hardware = xstrdup(hardware); - } - if (software) { - free(p->software); - p->software = xstrdup(software); - } - if (serial) { - free(p->serial); - p->serial = xstrdup(serial); - } -} - -int -ofproto_set_in_band(struct ofproto *p, bool in_band) -{ - if (in_band != (p->in_band != NULL)) { - if (in_band) { - return in_band_create(p, p->dpif, p->switch_status, - p->controller->rconn, &p->in_band); - } else { - ofproto_set_discovery(p, false, NULL, true); - in_band_destroy(p->in_band); - p->in_band = NULL; - } - rconn_reconnect(p->controller->rconn); - } - return 0; -} - -int -ofproto_set_discovery(struct ofproto *p, bool discovery, - const char *re, bool update_resolv_conf) -{ - if (discovery != (p->discovery != NULL)) { - if (discovery) { - int error = ofproto_set_in_band(p, true); - if (error) { - return error; - } - error = discovery_create(re, update_resolv_conf, - p->dpif, p->switch_status, - &p->discovery); - if (error) { - return error; - } - } else { - discovery_destroy(p->discovery); - p->discovery = NULL; - } - rconn_disconnect(p->controller->rconn); - } else if (discovery) { - discovery_set_update_resolv_conf(p->discovery, update_resolv_conf); - return discovery_set_accept_controller_re(p->discovery, re); - } - return 0; -} - -int -ofproto_set_controller(struct ofproto *ofproto, const char *controller) -{ - if (ofproto->discovery) { - return EINVAL; - } else if (controller) { - if (strcmp(rconn_get_name(ofproto->controller->rconn), controller)) { - return rconn_connect(ofproto->controller->rconn, controller); - } else { - return 0; - } - } else { - rconn_disconnect(ofproto->controller->rconn); - return 0; - } -} - -static int -set_pvconns(struct pvconn ***pvconnsp, size_t *n_pvconnsp, - const struct svec *svec) -{ - struct pvconn **pvconns = *pvconnsp; - size_t n_pvconns = *n_pvconnsp; - int retval = 0; - size_t i; - - for (i = 0; i < n_pvconns; i++) { - pvconn_close(pvconns[i]); - } - free(pvconns); - - pvconns = xmalloc(svec->n * sizeof *pvconns); - n_pvconns = 0; - for (i = 0; i < svec->n; i++) { - const char *name = svec->names[i]; - struct pvconn *pvconn; - int error; - - error = pvconn_open(name, &pvconn); - if (!error) { - pvconns[n_pvconns++] = pvconn; - } else { - VLOG_ERR("failed to listen on %s: %s", name, strerror(error)); - if (!retval) { - retval = error; - } - } - } - - *pvconnsp = pvconns; - *n_pvconnsp = n_pvconns; - - return retval; -} - -int -ofproto_set_listeners(struct ofproto *ofproto, const struct svec *listeners) -{ - return set_pvconns(&ofproto->listeners, &ofproto->n_listeners, listeners); -} - -int -ofproto_set_snoops(struct ofproto *ofproto, const struct svec *snoops) -{ - return set_pvconns(&ofproto->snoops, &ofproto->n_snoops, snoops); -} - -int -ofproto_set_netflow(struct ofproto *ofproto, const struct svec *collectors, - uint8_t engine_type, uint8_t engine_id, bool add_id_to_iface) -{ - if (collectors && collectors->n) { - if (!ofproto->netflow) { - ofproto->netflow = netflow_create(); - } - netflow_set_engine(ofproto->netflow, engine_type, engine_id, - add_id_to_iface); - return netflow_set_collectors(ofproto->netflow, collectors); - } else { - netflow_destroy(ofproto->netflow); - ofproto->netflow = NULL; - return 0; - } -} - -void -ofproto_set_failure(struct ofproto *ofproto, bool fail_open) -{ - if (fail_open) { - struct rconn *rconn = ofproto->controller->rconn; - int trigger_duration = rconn_get_probe_interval(rconn) * 3; - if (!ofproto->fail_open) { - ofproto->fail_open = fail_open_create(ofproto, trigger_duration, - ofproto->switch_status, - rconn); - } else { - fail_open_set_trigger_duration(ofproto->fail_open, - trigger_duration); - } - } else { - fail_open_destroy(ofproto->fail_open); - ofproto->fail_open = NULL; - } -} - -void -ofproto_set_rate_limit(struct ofproto *ofproto, - int rate_limit, int burst_limit) -{ - if (rate_limit > 0) { - if (!ofproto->miss_sched) { - ofproto->miss_sched = pinsched_create(rate_limit, burst_limit, - ofproto->switch_status); - ofproto->action_sched = pinsched_create(rate_limit, burst_limit, - NULL); - } else { - pinsched_set_limits(ofproto->miss_sched, rate_limit, burst_limit); - pinsched_set_limits(ofproto->action_sched, - rate_limit, burst_limit); - } - } else { - pinsched_destroy(ofproto->miss_sched); - ofproto->miss_sched = NULL; - pinsched_destroy(ofproto->action_sched); - ofproto->action_sched = NULL; - } -} - -int -ofproto_set_stp(struct ofproto *ofproto UNUSED, bool enable_stp) -{ - /* XXX */ - if (enable_stp) { - VLOG_WARN("STP is not yet implemented"); - return EINVAL; - } else { - return 0; - } -} - -int -ofproto_set_remote_execution(struct ofproto *ofproto, const char *command_acl, - const char *command_dir) -{ - if (command_acl) { - if (!ofproto->executer) { - return executer_create(command_acl, command_dir, - &ofproto->executer); - } else { - executer_set_acl(ofproto->executer, command_acl, command_dir); - } - } else { - executer_destroy(ofproto->executer); - ofproto->executer = NULL; - } - return 0; -} - -uint64_t -ofproto_get_datapath_id(const struct ofproto *ofproto) -{ - return ofproto->datapath_id; -} - -int -ofproto_get_probe_interval(const struct ofproto *ofproto) -{ - return rconn_get_probe_interval(ofproto->controller->rconn); -} - -int -ofproto_get_max_backoff(const struct ofproto *ofproto) -{ - return rconn_get_max_backoff(ofproto->controller->rconn); -} - -bool -ofproto_get_in_band(const struct ofproto *ofproto) -{ - return ofproto->in_band != NULL; -} - -bool -ofproto_get_discovery(const struct ofproto *ofproto) -{ - return ofproto->discovery != NULL; -} - -const char * -ofproto_get_controller(const struct ofproto *ofproto) -{ - return rconn_get_name(ofproto->controller->rconn); -} - -void -ofproto_get_listeners(const struct ofproto *ofproto, struct svec *listeners) -{ - size_t i; - - for (i = 0; i < ofproto->n_listeners; i++) { - svec_add(listeners, pvconn_get_name(ofproto->listeners[i])); - } -} - -void -ofproto_get_snoops(const struct ofproto *ofproto, struct svec *snoops) -{ - size_t i; - - for (i = 0; i < ofproto->n_snoops; i++) { - svec_add(snoops, pvconn_get_name(ofproto->snoops[i])); - } -} - -void -ofproto_destroy(struct ofproto *p) -{ - struct ofconn *ofconn, *next_ofconn; - struct ofport *ofport; - unsigned int port_no; - size_t i; - - if (!p) { - return; - } - - ofproto_flush_flows(p); - classifier_destroy(&p->cls); - - LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node, - &p->all_conns) { - ofconn_destroy(ofconn, p); - } - - dpif_close(p->dpif); - netdev_monitor_destroy(p->netdev_monitor); - PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) { - ofport_free(ofport); - } - shash_destroy(&p->port_by_name); - - switch_status_destroy(p->switch_status); - in_band_destroy(p->in_band); - discovery_destroy(p->discovery); - fail_open_destroy(p->fail_open); - pinsched_destroy(p->miss_sched); - pinsched_destroy(p->action_sched); - executer_destroy(p->executer); - netflow_destroy(p->netflow); - - switch_status_unregister(p->ss_cat); - - for (i = 0; i < p->n_listeners; i++) { - pvconn_close(p->listeners[i]); - } - free(p->listeners); - - for (i = 0; i < p->n_snoops; i++) { - pvconn_close(p->snoops[i]); - } - free(p->snoops); - - mac_learning_destroy(p->ml); - - free(p); -} - -int -ofproto_run(struct ofproto *p) -{ - int error = ofproto_run1(p); - if (!error) { - error = ofproto_run2(p, false); - } - return error; -} - -static void -process_port_change(struct ofproto *ofproto, int error, char *devname) -{ - if (error == ENOBUFS) { - reinit_ports(ofproto); - } else if (!error) { - update_port(ofproto, devname); - free(devname); - } -} - -int -ofproto_run1(struct ofproto *p) -{ - struct ofconn *ofconn, *next_ofconn; - char *devname; - int error; - int i; - - for (i = 0; i < 50; i++) { - struct ofpbuf *buf; - int error; - - error = dpif_recv(p->dpif, &buf); - if (error) { - if (error == ENODEV) { - /* Someone destroyed the datapath behind our back. The caller - * better destroy us and give up, because we're just going to - * spin from here on out. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); - VLOG_ERR_RL(&rl, "%s: datapath was destroyed externally", - dpif_name(p->dpif)); - return ENODEV; - } - break; - } - - handle_odp_msg(p, buf); - } - - while ((error = dpif_port_poll(p->dpif, &devname)) != EAGAIN) { - process_port_change(p, error, devname); - } - while ((error = netdev_monitor_poll(p->netdev_monitor, - &devname)) != EAGAIN) { - process_port_change(p, error, devname); - } - - if (p->in_band) { - in_band_run(p->in_band); - } - if (p->discovery) { - char *controller_name; - if (rconn_is_connectivity_questionable(p->controller->rconn)) { - discovery_question_connectivity(p->discovery); - } - if (discovery_run(p->discovery, &controller_name)) { - if (controller_name) { - rconn_connect(p->controller->rconn, controller_name); - } else { - rconn_disconnect(p->controller->rconn); - } - } - } - if (p->fail_open) { - fail_open_run(p->fail_open); - } - pinsched_run(p->miss_sched, send_packet_in_miss, p); - pinsched_run(p->action_sched, send_packet_in_action, p); - if (p->executer) { - executer_run(p->executer); - } - - LIST_FOR_EACH_SAFE (ofconn, next_ofconn, struct ofconn, node, - &p->all_conns) { - ofconn_run(ofconn, p); - } - - for (i = 0; i < p->n_listeners; i++) { - struct vconn *vconn; - int retval; - - retval = pvconn_accept(p->listeners[i], OFP_VERSION, &vconn); - if (!retval) { - ofconn_create(p, rconn_new_from_vconn("passive", vconn)); - } else if (retval != EAGAIN) { - VLOG_WARN_RL(&rl, "accept failed (%s)", strerror(retval)); - } - } - - for (i = 0; i < p->n_snoops; i++) { - struct vconn *vconn; - int retval; - - retval = pvconn_accept(p->snoops[i], OFP_VERSION, &vconn); - if (!retval) { - rconn_add_monitor(p->controller->rconn, vconn); - } else if (retval != EAGAIN) { - VLOG_WARN_RL(&rl, "accept failed (%s)", strerror(retval)); - } - } - - if (time_msec() >= p->next_expiration) { - COVERAGE_INC(ofproto_expiration); - p->next_expiration = time_msec() + 1000; - update_used(p); - - classifier_for_each(&p->cls, CLS_INC_ALL, expire_rule, p); - - /* Let the hook know that we're at a stable point: all outstanding data - * in existing flows has been accounted to the account_cb. Thus, the - * hook can now reasonably do operations that depend on having accurate - * flow volume accounting (currently, that's just bond rebalancing). */ - if (p->ofhooks->account_checkpoint_cb) { - p->ofhooks->account_checkpoint_cb(p->aux); - } - } - - if (p->netflow) { - netflow_run(p->netflow); - } - - return 0; -} - -struct revalidate_cbdata { - struct ofproto *ofproto; - bool revalidate_all; /* Revalidate all exact-match rules? */ - bool revalidate_subrules; /* Revalidate all exact-match subrules? */ - struct tag_set revalidate_set; /* Set of tags to revalidate. */ -}; - -int -ofproto_run2(struct ofproto *p, bool revalidate_all) -{ - if (p->need_revalidate || revalidate_all - || !tag_set_is_empty(&p->revalidate_set)) { - struct revalidate_cbdata cbdata; - cbdata.ofproto = p; - cbdata.revalidate_all = revalidate_all; - cbdata.revalidate_subrules = p->need_revalidate; - cbdata.revalidate_set = p->revalidate_set; - tag_set_init(&p->revalidate_set); - COVERAGE_INC(ofproto_revalidate); - classifier_for_each(&p->cls, CLS_INC_EXACT, revalidate_cb, &cbdata); - p->need_revalidate = false; - } - - return 0; -} - -void -ofproto_wait(struct ofproto *p) -{ - struct ofconn *ofconn; - size_t i; - - dpif_recv_wait(p->dpif); - dpif_port_poll_wait(p->dpif); - netdev_monitor_poll_wait(p->netdev_monitor); - LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { - ofconn_wait(ofconn); - } - if (p->in_band) { - in_band_wait(p->in_band); - } - if (p->discovery) { - discovery_wait(p->discovery); - } - if (p->fail_open) { - fail_open_wait(p->fail_open); - } - pinsched_wait(p->miss_sched); - pinsched_wait(p->action_sched); - if (p->executer) { - executer_wait(p->executer); - } - if (!tag_set_is_empty(&p->revalidate_set)) { - poll_immediate_wake(); - } - if (p->need_revalidate) { - /* Shouldn't happen, but if it does just go around again. */ - VLOG_DBG_RL(&rl, "need revalidate in ofproto_wait_cb()"); - poll_immediate_wake(); - } else if (p->next_expiration != LLONG_MAX) { - poll_timer_wait(p->next_expiration - time_msec()); - } - for (i = 0; i < p->n_listeners; i++) { - pvconn_wait(p->listeners[i]); - } - for (i = 0; i < p->n_snoops; i++) { - pvconn_wait(p->snoops[i]); - } -} - -void -ofproto_revalidate(struct ofproto *ofproto, tag_type tag) -{ - tag_set_add(&ofproto->revalidate_set, tag); -} - -struct tag_set * -ofproto_get_revalidate_set(struct ofproto *ofproto) -{ - return &ofproto->revalidate_set; -} - -bool -ofproto_is_alive(const struct ofproto *p) -{ - return p->discovery || rconn_is_alive(p->controller->rconn); -} - -int -ofproto_send_packet(struct ofproto *p, const flow_t *flow, - const union ofp_action *actions, size_t n_actions, - const struct ofpbuf *packet) -{ - struct odp_actions odp_actions; - int error; - - error = xlate_actions(actions, n_actions, flow, p, packet, &odp_actions, - NULL, NULL); - if (error) { - return error; - } - - /* XXX Should we translate the dpif_execute() errno value into an OpenFlow - * error code? */ - dpif_execute(p->dpif, flow->in_port, odp_actions.actions, - odp_actions.n_actions, packet); - return 0; -} - -void -ofproto_add_flow(struct ofproto *p, - const flow_t *flow, uint32_t wildcards, unsigned int priority, - const union ofp_action *actions, size_t n_actions, - int idle_timeout) -{ - struct rule *rule; - rule = rule_create(NULL, actions, n_actions, - idle_timeout >= 0 ? idle_timeout : 5 /* XXX */, 0); - cls_rule_from_flow(&rule->cr, flow, wildcards, priority); - rule_insert(p, rule, NULL, 0); -} - -void -ofproto_delete_flow(struct ofproto *ofproto, const flow_t *flow, - uint32_t wildcards, unsigned int priority) -{ - struct rule *rule; - - rule = rule_from_cls_rule(classifier_find_rule_exactly(&ofproto->cls, - flow, wildcards, - priority)); - if (rule) { - rule_remove(ofproto, rule); - } -} - -static void -destroy_rule(struct cls_rule *rule_, void *ofproto_) -{ - struct rule *rule = rule_from_cls_rule(rule_); - struct ofproto *ofproto = ofproto_; - - /* Mark the flow as not installed, even though it might really be - * installed, so that rule_remove() doesn't bother trying to uninstall it. - * There is no point in uninstalling it individually since we are about to - * blow away all the flows with dpif_flow_flush(). */ - rule->installed = false; - - rule_remove(ofproto, rule); -} - -void -ofproto_flush_flows(struct ofproto *ofproto) -{ - COVERAGE_INC(ofproto_flush); - classifier_for_each(&ofproto->cls, CLS_INC_ALL, destroy_rule, ofproto); - dpif_flow_flush(ofproto->dpif); - if (ofproto->in_band) { - in_band_flushed(ofproto->in_band); - } - if (ofproto->fail_open) { - fail_open_flushed(ofproto->fail_open); - } -} - -static void -reinit_ports(struct ofproto *p) -{ - struct svec devnames; - struct ofport *ofport; - unsigned int port_no; - struct odp_port *odp_ports; - size_t n_odp_ports; - size_t i; - - svec_init(&devnames); - PORT_ARRAY_FOR_EACH (ofport, &p->ports, port_no) { - svec_add (&devnames, (char *) ofport->opp.name); - } - dpif_port_list(p->dpif, &odp_ports, &n_odp_ports); - for (i = 0; i < n_odp_ports; i++) { - svec_add (&devnames, odp_ports[i].devname); - } - free(odp_ports); - - svec_sort_unique(&devnames); - for (i = 0; i < devnames.n; i++) { - update_port(p, devnames.names[i]); - } - svec_destroy(&devnames); -} - -static void -refresh_port_group(struct ofproto *p, unsigned int group) -{ - uint16_t *ports; - size_t n_ports; - struct ofport *port; - unsigned int port_no; - - assert(group == DP_GROUP_ALL || group == DP_GROUP_FLOOD); - - ports = xmalloc(port_array_count(&p->ports) * sizeof *ports); - n_ports = 0; - PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) { - if (group == DP_GROUP_ALL || !(port->opp.config & OFPPC_NO_FLOOD)) { - ports[n_ports++] = port_no; - } - } - dpif_port_group_set(p->dpif, group, ports, n_ports); - free(ports); -} - -static void -refresh_port_groups(struct ofproto *p) -{ - refresh_port_group(p, DP_GROUP_FLOOD); - refresh_port_group(p, DP_GROUP_ALL); -} - -static struct ofport * -make_ofport(const struct odp_port *odp_port) -{ - enum netdev_flags flags; - struct ofport *ofport; - struct netdev *netdev; - bool carrier; - int error; - - error = netdev_open(odp_port->devname, NETDEV_ETH_TYPE_NONE, &netdev); - if (error) { - VLOG_WARN_RL(&rl, "ignoring port %s (%"PRIu16") because netdev %s " - "cannot be opened (%s)", - odp_port->devname, odp_port->port, - odp_port->devname, strerror(error)); - return NULL; - } - - ofport = xmalloc(sizeof *ofport); - ofport->netdev = netdev; - ofport->opp.port_no = odp_port_to_ofp_port(odp_port->port); - memcpy(ofport->opp.hw_addr, netdev_get_etheraddr(netdev), ETH_ALEN); - memcpy(ofport->opp.name, odp_port->devname, - MIN(sizeof ofport->opp.name, sizeof odp_port->devname)); - ofport->opp.name[sizeof ofport->opp.name - 1] = '\0'; - - netdev_get_flags(netdev, &flags); - ofport->opp.config = flags & NETDEV_UP ? 0 : OFPPC_PORT_DOWN; - - netdev_get_carrier(netdev, &carrier); - ofport->opp.state = carrier ? 0 : OFPPS_LINK_DOWN; - - netdev_get_features(netdev, - &ofport->opp.curr, &ofport->opp.advertised, - &ofport->opp.supported, &ofport->opp.peer); - return ofport; -} - -static bool -ofport_conflicts(const struct ofproto *p, const struct odp_port *odp_port) -{ - if (port_array_get(&p->ports, odp_port->port)) { - VLOG_WARN_RL(&rl, "ignoring duplicate port %"PRIu16" in datapath", - odp_port->port); - return true; - } else if (shash_find(&p->port_by_name, odp_port->devname)) { - VLOG_WARN_RL(&rl, "ignoring duplicate device %s in datapath", - odp_port->devname); - return true; - } else { - return false; - } -} - -static int -ofport_equal(const struct ofport *a_, const struct ofport *b_) -{ - const struct ofp_phy_port *a = &a_->opp; - const struct ofp_phy_port *b = &b_->opp; - - BUILD_ASSERT_DECL(sizeof *a == 48); /* Detect ofp_phy_port changes. */ - return (a->port_no == b->port_no - && !memcmp(a->hw_addr, b->hw_addr, sizeof a->hw_addr) - && !strcmp((char *) a->name, (char *) b->name) - && a->state == b->state - && a->config == b->config - && a->curr == b->curr - && a->advertised == b->advertised - && a->supported == b->supported - && a->peer == b->peer); -} - -static void -send_port_status(struct ofproto *p, const struct ofport *ofport, - uint8_t reason) -{ - /* XXX Should limit the number of queued port status change messages. */ - struct ofconn *ofconn; - LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { - struct ofp_port_status *ops; - struct ofpbuf *b; - - ops = make_openflow_xid(sizeof *ops, OFPT_PORT_STATUS, 0, &b); - ops->reason = reason; - ops->desc = ofport->opp; - hton_ofp_phy_port(&ops->desc); - queue_tx(b, ofconn, NULL); - } - if (p->ofhooks->port_changed_cb) { - p->ofhooks->port_changed_cb(reason, &ofport->opp, p->aux); - } -} - -static void -ofport_install(struct ofproto *p, struct ofport *ofport) -{ - netdev_monitor_add(p->netdev_monitor, ofport->netdev); - port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no), - ofport); - shash_add(&p->port_by_name, (char *) ofport->opp.name, ofport); -} - -static void -ofport_remove(struct ofproto *p, struct ofport *ofport) -{ - netdev_monitor_remove(p->netdev_monitor, ofport->netdev); - port_array_set(&p->ports, ofp_port_to_odp_port(ofport->opp.port_no), NULL); - shash_delete(&p->port_by_name, - shash_find(&p->port_by_name, (char *) ofport->opp.name)); -} - -static void -ofport_free(struct ofport *ofport) -{ - if (ofport) { - netdev_close(ofport->netdev); - free(ofport); - } -} - -static void -update_port(struct ofproto *p, const char *devname) -{ - struct odp_port odp_port; - struct ofport *ofport; - int error; - - COVERAGE_INC(ofproto_update_port); - ofport = shash_find_data(&p->port_by_name, devname); - error = dpif_port_query_by_name(p->dpif, devname, &odp_port); - if (!error) { - if (!ofport) { - /* New port. */ - if (!ofport_conflicts(p, &odp_port)) { - ofport = make_ofport(&odp_port); - if (ofport) { - ofport_install(p, ofport); - send_port_status(p, ofport, OFPPR_ADD); - } - } - } else { - /* Modified port. */ - struct ofport *new_ofport = make_ofport(&odp_port); - if (!new_ofport) { - return; - } - - new_ofport->opp.config &= OFPPC_PORT_DOWN; - new_ofport->opp.config |= ofport->opp.config & ~OFPPC_PORT_DOWN; - if (ofport_equal(ofport, new_ofport)) { - /* False alarm--no change. */ - ofport_free(new_ofport); - } else { - ofport_remove(p, ofport); - ofport_install(p, new_ofport); - ofport_free(ofport); - send_port_status(p, new_ofport, OFPPR_MODIFY); - } - } - } else if (error == ENOENT || error == ENODEV) { - /* Deleted port. */ - if (ofport) { - send_port_status(p, ofport, OFPPR_DELETE); - ofport_remove(p, ofport); - ofport_free(ofport); - } - } else { - VLOG_WARN_RL(&rl, "dpif_port_query_by_name returned unexpected error " - "%s", strerror(error)); - return; - } - refresh_port_groups(p); -} - -static int -init_ports(struct ofproto *p) -{ - struct odp_port *ports; - size_t n_ports; - size_t i; - int error; - - error = dpif_port_list(p->dpif, &ports, &n_ports); - if (error) { - return error; - } - - for (i = 0; i < n_ports; i++) { - const struct odp_port *odp_port = &ports[i]; - if (!ofport_conflicts(p, odp_port)) { - struct ofport *ofport = make_ofport(odp_port); - if (ofport) { - ofport_install(p, ofport); - } - } - } - free(ports); - refresh_port_groups(p); - return 0; -} - -static struct ofconn * -ofconn_create(struct ofproto *p, struct rconn *rconn) -{ - struct ofconn *ofconn = xmalloc(sizeof *ofconn); - list_push_back(&p->all_conns, &ofconn->node); - ofconn->rconn = rconn; - ofconn->pktbuf = NULL; - ofconn->send_flow_exp = false; - ofconn->miss_send_len = 0; - ofconn->packet_in_counter = rconn_packet_counter_create (); - ofconn->reply_counter = rconn_packet_counter_create (); - return ofconn; -} - -static void -ofconn_destroy(struct ofconn *ofconn, struct ofproto *p) -{ - if (p->executer) { - executer_rconn_closing(p->executer, ofconn->rconn); - } - - list_remove(&ofconn->node); - rconn_destroy(ofconn->rconn); - rconn_packet_counter_destroy(ofconn->packet_in_counter); - rconn_packet_counter_destroy(ofconn->reply_counter); - pktbuf_destroy(ofconn->pktbuf); - free(ofconn); -} - -static void -ofconn_run(struct ofconn *ofconn, struct ofproto *p) -{ - int iteration; - - rconn_run(ofconn->rconn); - - if (rconn_packet_counter_read (ofconn->reply_counter) < OFCONN_REPLY_MAX) { - /* Limit the number of iterations to prevent other tasks from - * starving. */ - for (iteration = 0; iteration < 50; iteration++) { - struct ofpbuf *of_msg = rconn_recv(ofconn->rconn); - if (!of_msg) { - break; - } - handle_openflow(ofconn, p, of_msg); - ofpbuf_delete(of_msg); - } - } - - if (ofconn != p->controller && !rconn_is_alive(ofconn->rconn)) { - ofconn_destroy(ofconn, p); - } -} - -static void -ofconn_wait(struct ofconn *ofconn) -{ - rconn_run_wait(ofconn->rconn); - if (rconn_packet_counter_read (ofconn->reply_counter) < OFCONN_REPLY_MAX) { - rconn_recv_wait(ofconn->rconn); - } else { - COVERAGE_INC(ofproto_ofconn_stuck); - } -} - -/* Caller is responsible for initializing the 'cr' member of the returned - * rule. */ -static struct rule * -rule_create(struct rule *super, - const union ofp_action *actions, size_t n_actions, - uint16_t idle_timeout, uint16_t hard_timeout) -{ - struct rule *rule = xcalloc(1, sizeof *rule); - rule->idle_timeout = idle_timeout; - rule->hard_timeout = hard_timeout; - rule->used = rule->created = time_msec(); - rule->super = super; - if (super) { - list_push_back(&super->list, &rule->list); - } else { - list_init(&rule->list); - } - rule->n_actions = n_actions; - rule->actions = xmemdup(actions, n_actions * sizeof *actions); - return rule; -} - -static struct rule * -rule_from_cls_rule(const struct cls_rule *cls_rule) -{ - return cls_rule ? CONTAINER_OF(cls_rule, struct rule, cr) : NULL; -} - -static void -rule_free(struct rule *rule) -{ - free(rule->actions); - free(rule->odp_actions); - free(rule); -} - -/* Destroys 'rule'. If 'rule' is a subrule, also removes it from its - * super-rule's list of subrules. If 'rule' is a super-rule, also iterates - * through all of its subrules and revalidates them, destroying any that no - * longer has a super-rule (which is probably all of them). - * - * Before calling this function, the caller must make have removed 'rule' from - * the classifier. If 'rule' is an exact-match rule, the caller is also - * responsible for ensuring that it has been uninstalled from the datapath. */ -static void -rule_destroy(struct ofproto *ofproto, struct rule *rule) -{ - if (!rule->super) { - struct rule *subrule, *next; - LIST_FOR_EACH_SAFE (subrule, next, struct rule, list, &rule->list) { - revalidate_rule(ofproto, subrule); - } - } else { - list_remove(&rule->list); - } - rule_free(rule); -} - -static bool -rule_has_out_port(const struct rule *rule, uint16_t out_port) -{ - const union ofp_action *oa; - struct actions_iterator i; - - if (out_port == htons(OFPP_NONE)) { - return true; - } - for (oa = actions_first(&i, rule->actions, rule->n_actions); oa; - oa = actions_next(&i)) { - if (oa->type == htons(OFPAT_OUTPUT) && oa->output.port == out_port) { - return true; - } - } - return false; -} - -/* Executes the actions indicated by 'rule' on 'packet', which is in flow - * 'flow' and is considered to have arrived on ODP port 'in_port'. - * - * The flow that 'packet' actually contains does not need to actually match - * 'rule'; the actions in 'rule' will be applied to it either way. Likewise, - * the packet and byte counters for 'rule' will be credited for the packet sent - * out whether or not the packet actually matches 'rule'. - * - * If 'rule' is an exact-match rule and 'flow' actually equals the rule's flow, - * the caller must already have accurately composed ODP actions for it given - * 'packet' using rule_make_actions(). If 'rule' is a wildcard rule, or if - * 'rule' is an exact-match rule but 'flow' is not the rule's flow, then this - * function will compose a set of ODP actions based on 'rule''s OpenFlow - * actions and apply them to 'packet'. */ -static void -rule_execute(struct ofproto *ofproto, struct rule *rule, - struct ofpbuf *packet, const flow_t *flow) -{ - const union odp_action *actions; - size_t n_actions; - struct odp_actions a; - - /* Grab or compose the ODP actions. - * - * The special case for an exact-match 'rule' where 'flow' is not the - * rule's flow is important to avoid, e.g., sending a packet out its input - * port simply because the ODP actions were composed for the wrong - * scenario. */ - if (rule->cr.wc.wildcards || !flow_equal(flow, &rule->cr.flow)) { - struct rule *super = rule->super ? rule->super : rule; - if (xlate_actions(super->actions, super->n_actions, flow, ofproto, - packet, &a, NULL, 0)) { - return; - } - actions = a.actions; - n_actions = a.n_actions; - } else { - actions = rule->odp_actions; - n_actions = rule->n_odp_actions; - } - - /* Execute the ODP actions. */ - if (!dpif_execute(ofproto->dpif, flow->in_port, - actions, n_actions, packet)) { - struct odp_flow_stats stats; - flow_extract_stats(flow, packet, &stats); - update_stats(rule, &stats); - rule->used = time_msec(); - } -} - -static void -rule_insert(struct ofproto *p, struct rule *rule, struct ofpbuf *packet, - uint16_t in_port) -{ - struct rule *displaced_rule; - - /* Insert the rule in the classifier. */ - displaced_rule = rule_from_cls_rule(classifier_insert(&p->cls, &rule->cr)); - if (!rule->cr.wc.wildcards) { - rule_make_actions(p, rule, packet); - } - - /* Send the packet and credit it to the rule. */ - if (packet) { - flow_t flow; - flow_extract(packet, in_port, &flow); - rule_execute(p, rule, packet, &flow); - } - - /* Install the rule in the datapath only after sending the packet, to - * avoid packet reordering. */ - if (rule->cr.wc.wildcards) { - COVERAGE_INC(ofproto_add_wc_flow); - p->need_revalidate = true; - } else { - rule_install(p, rule, displaced_rule); - } - - /* Free the rule that was displaced, if any. */ - if (displaced_rule) { - rule_destroy(p, displaced_rule); - } -} - -static struct rule * -rule_create_subrule(struct ofproto *ofproto, struct rule *rule, - const flow_t *flow) -{ - struct rule *subrule = rule_create(rule, NULL, 0, - rule->idle_timeout, rule->hard_timeout); - COVERAGE_INC(ofproto_subrule_create); - cls_rule_from_flow(&subrule->cr, flow, 0, - (rule->cr.priority <= UINT16_MAX ? UINT16_MAX - : rule->cr.priority)); - classifier_insert_exact(&ofproto->cls, &subrule->cr); - - return subrule; -} - -static void -rule_remove(struct ofproto *ofproto, struct rule *rule) -{ - if (rule->cr.wc.wildcards) { - COVERAGE_INC(ofproto_del_wc_flow); - ofproto->need_revalidate = true; - } else { - rule_uninstall(ofproto, rule); - } - classifier_remove(&ofproto->cls, &rule->cr); - rule_destroy(ofproto, rule); -} - -/* Returns true if the actions changed, false otherwise. */ -static bool -rule_make_actions(struct ofproto *p, struct rule *rule, - const struct ofpbuf *packet) -{ - const struct rule *super; - struct odp_actions a; - size_t actions_len; - - assert(!rule->cr.wc.wildcards); - - super = rule->super ? rule->super : rule; - rule->tags = 0; - xlate_actions(super->actions, super->n_actions, &rule->cr.flow, p, - packet, &a, &rule->tags, &rule->may_install); - - actions_len = a.n_actions * sizeof *a.actions; - if (rule->n_odp_actions != a.n_actions - || memcmp(rule->odp_actions, a.actions, actions_len)) { - COVERAGE_INC(ofproto_odp_unchanged); - free(rule->odp_actions); - rule->n_odp_actions = a.n_actions; - rule->odp_actions = xmemdup(a.actions, actions_len); - return true; - } else { - return false; - } -} - -static int -do_put_flow(struct ofproto *ofproto, struct rule *rule, int flags, - struct odp_flow_put *put) -{ - memset(&put->flow.stats, 0, sizeof put->flow.stats); - put->flow.key = rule->cr.flow; - put->flow.actions = rule->odp_actions; - put->flow.n_actions = rule->n_odp_actions; - put->flags = flags; - return dpif_flow_put(ofproto->dpif, put); -} - -static void -rule_install(struct ofproto *p, struct rule *rule, struct rule *displaced_rule) -{ - assert(!rule->cr.wc.wildcards); - - if (rule->may_install) { - struct odp_flow_put put; - if (!do_put_flow(p, rule, - ODPPF_CREATE | ODPPF_MODIFY | ODPPF_ZERO_STATS, - &put)) { - rule->installed = true; - if (displaced_rule) { - update_stats(rule, &put.flow.stats); - rule_post_uninstall(p, displaced_rule); - } - } - } else if (displaced_rule) { - rule_uninstall(p, displaced_rule); - } -} - -static void -rule_reinstall(struct ofproto *ofproto, struct rule *rule) -{ - if (rule->installed) { - struct odp_flow_put put; - COVERAGE_INC(ofproto_dp_missed); - do_put_flow(ofproto, rule, ODPPF_CREATE | ODPPF_MODIFY, &put); - } else { - rule_install(ofproto, rule, NULL); - } -} - -static void -rule_update_actions(struct ofproto *ofproto, struct rule *rule) -{ - bool actions_changed = rule_make_actions(ofproto, rule, NULL); - if (rule->may_install) { - if (rule->installed) { - if (actions_changed) { - /* XXX should really do rule_post_uninstall() for the *old* set - * of actions, and distinguish the old stats from the new. */ - struct odp_flow_put put; - do_put_flow(ofproto, rule, ODPPF_CREATE | ODPPF_MODIFY, &put); - } - } else { - rule_install(ofproto, rule, NULL); - } - } else { - rule_uninstall(ofproto, rule); - } -} - -static void -rule_account(struct ofproto *ofproto, struct rule *rule, uint64_t extra_bytes) -{ - uint64_t total_bytes = rule->byte_count + extra_bytes; - - if (ofproto->ofhooks->account_flow_cb - && total_bytes > rule->accounted_bytes) - { - ofproto->ofhooks->account_flow_cb( - &rule->cr.flow, rule->odp_actions, rule->n_odp_actions, - total_bytes - rule->accounted_bytes, ofproto->aux); - rule->accounted_bytes = total_bytes; - } -} - -static void -rule_uninstall(struct ofproto *p, struct rule *rule) -{ - assert(!rule->cr.wc.wildcards); - if (rule->installed) { - struct odp_flow odp_flow; - - odp_flow.key = rule->cr.flow; - odp_flow.actions = NULL; - odp_flow.n_actions = 0; - if (!dpif_flow_del(p->dpif, &odp_flow)) { - update_stats(rule, &odp_flow.stats); - } - rule->installed = false; - - rule_post_uninstall(p, rule); - } -} - -static void -rule_post_uninstall(struct ofproto *ofproto, struct rule *rule) -{ - struct rule *super = rule->super; - - rule_account(ofproto, rule, 0); - if (ofproto->netflow) { - struct ofexpired expired; - expired.flow = rule->cr.flow; - expired.packet_count = rule->packet_count; - expired.byte_count = rule->byte_count; - expired.used = rule->used; - expired.created = rule->created; - expired.tcp_flags = rule->tcp_flags; - expired.ip_tos = rule->ip_tos; - netflow_expire(ofproto->netflow, &expired); - } - if (super) { - super->packet_count += rule->packet_count; - super->byte_count += rule->byte_count; - super->tcp_flags |= rule->tcp_flags; - if (rule->packet_count) { - super->ip_tos = rule->ip_tos; - } - } - - /* Reset counters to prevent double counting if the rule ever gets - * reinstalled. */ - rule->packet_count = 0; - rule->byte_count = 0; - rule->accounted_bytes = 0; - rule->tcp_flags = 0; - rule->ip_tos = 0; -} - -static void -queue_tx(struct ofpbuf *msg, const struct ofconn *ofconn, - struct rconn_packet_counter *counter) -{ - update_openflow_length(msg); - if (rconn_send(ofconn->rconn, msg, counter)) { - ofpbuf_delete(msg); - } -} - -static void -send_error(const struct ofconn *ofconn, const struct ofp_header *oh, - int error, const void *data, size_t len) -{ - struct ofpbuf *buf; - struct ofp_error_msg *oem; - - if (!(error >> 16)) { - VLOG_WARN_RL(&rl, "not sending bad error code %d to controller", - error); - return; - } - - COVERAGE_INC(ofproto_error); - oem = make_openflow_xid(len + sizeof *oem, OFPT_ERROR, - oh ? oh->xid : 0, &buf); - oem->type = htons((unsigned int) error >> 16); - oem->code = htons(error & 0xffff); - memcpy(oem->data, data, len); - queue_tx(buf, ofconn, ofconn->reply_counter); -} - -static void -send_error_oh(const struct ofconn *ofconn, const struct ofp_header *oh, - int error) -{ - size_t oh_length = ntohs(oh->length); - send_error(ofconn, oh, error, oh, MIN(oh_length, 64)); -} - -static void -hton_ofp_phy_port(struct ofp_phy_port *opp) -{ - opp->port_no = htons(opp->port_no); - opp->config = htonl(opp->config); - opp->state = htonl(opp->state); - opp->curr = htonl(opp->curr); - opp->advertised = htonl(opp->advertised); - opp->supported = htonl(opp->supported); - opp->peer = htonl(opp->peer); -} - -static int -handle_echo_request(struct ofconn *ofconn, struct ofp_header *oh) -{ - struct ofp_header *rq = oh; - queue_tx(make_echo_reply(rq), ofconn, ofconn->reply_counter); - return 0; -} - -static int -handle_features_request(struct ofproto *p, struct ofconn *ofconn, - struct ofp_header *oh) -{ - struct ofp_switch_features *osf; - struct ofpbuf *buf; - unsigned int port_no; - struct ofport *port; - - osf = make_openflow_xid(sizeof *osf, OFPT_FEATURES_REPLY, oh->xid, &buf); - osf->datapath_id = htonll(p->datapath_id); - osf->n_buffers = htonl(pktbuf_capacity()); - osf->n_tables = 2; - osf->capabilities = htonl(OFPC_FLOW_STATS | OFPC_TABLE_STATS | - OFPC_PORT_STATS | OFPC_MULTI_PHY_TX); - osf->actions = htonl((1u << OFPAT_OUTPUT) | - (1u << OFPAT_SET_VLAN_VID) | - (1u << OFPAT_SET_VLAN_PCP) | - (1u << OFPAT_STRIP_VLAN) | - (1u << OFPAT_SET_DL_SRC) | - (1u << OFPAT_SET_DL_DST) | - (1u << OFPAT_SET_NW_SRC) | - (1u << OFPAT_SET_NW_DST) | - (1u << OFPAT_SET_TP_SRC) | - (1u << OFPAT_SET_TP_DST)); - - PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) { - hton_ofp_phy_port(ofpbuf_put(buf, &port->opp, sizeof port->opp)); - } - - queue_tx(buf, ofconn, ofconn->reply_counter); - return 0; -} - -static int -handle_get_config_request(struct ofproto *p, struct ofconn *ofconn, - struct ofp_header *oh) -{ - struct ofpbuf *buf; - struct ofp_switch_config *osc; - uint16_t flags; - bool drop_frags; - - /* Figure out flags. */ - dpif_get_drop_frags(p->dpif, &drop_frags); - flags = drop_frags ? OFPC_FRAG_DROP : OFPC_FRAG_NORMAL; - if (ofconn->send_flow_exp) { - flags |= OFPC_SEND_FLOW_EXP; - } - - /* Send reply. */ - osc = make_openflow_xid(sizeof *osc, OFPT_GET_CONFIG_REPLY, oh->xid, &buf); - osc->flags = htons(flags); - osc->miss_send_len = htons(ofconn->miss_send_len); - queue_tx(buf, ofconn, ofconn->reply_counter); - - return 0; -} - -static int -handle_set_config(struct ofproto *p, struct ofconn *ofconn, - struct ofp_switch_config *osc) -{ - uint16_t flags; - int error; - - error = check_ofp_message(&osc->header, OFPT_SET_CONFIG, sizeof *osc); - if (error) { - return error; - } - flags = ntohs(osc->flags); - - ofconn->send_flow_exp = (flags & OFPC_SEND_FLOW_EXP) != 0; - - if (ofconn == p->controller) { - switch (flags & OFPC_FRAG_MASK) { - case OFPC_FRAG_NORMAL: - dpif_set_drop_frags(p->dpif, false); - break; - case OFPC_FRAG_DROP: - dpif_set_drop_frags(p->dpif, true); - break; - default: - VLOG_WARN_RL(&rl, "requested bad fragment mode (flags=%"PRIx16")", - osc->flags); - break; - } - } - - if ((ntohs(osc->miss_send_len) != 0) != (ofconn->miss_send_len != 0)) { - if (ntohs(osc->miss_send_len) != 0) { - ofconn->pktbuf = pktbuf_create(); - } else { - pktbuf_destroy(ofconn->pktbuf); - } - } - - ofconn->miss_send_len = ntohs(osc->miss_send_len); - - return 0; -} - -static void -add_output_group_action(struct odp_actions *actions, uint16_t group) -{ - odp_actions_add(actions, ODPAT_OUTPUT_GROUP)->output_group.group = group; -} - -static void -add_controller_action(struct odp_actions *actions, - const struct ofp_action_output *oao) -{ - union odp_action *a = odp_actions_add(actions, ODPAT_CONTROLLER); - a->controller.arg = oao->max_len ? ntohs(oao->max_len) : UINT32_MAX; -} - -struct action_xlate_ctx { - /* Input. */ - const flow_t *flow; /* Flow to which these actions correspond. */ - int recurse; /* Recursion level, via xlate_table_action. */ - struct ofproto *ofproto; - const struct ofpbuf *packet; /* The packet corresponding to 'flow', or a - * null pointer if we are revalidating - * without a packet to refer to. */ - - /* Output. */ - struct odp_actions *out; /* Datapath actions. */ - tag_type *tags; /* Tags associated with OFPP_NORMAL actions. */ - bool may_setup_flow; /* True ordinarily; false if the actions must - * be reassessed for every packet. */ -}; - -static void do_xlate_actions(const union ofp_action *in, size_t n_in, - struct action_xlate_ctx *ctx); - -static void -add_output_action(struct action_xlate_ctx *ctx, uint16_t port) -{ - const struct ofport *ofport = port_array_get(&ctx->ofproto->ports, port); - if (!ofport || !(ofport->opp.config & OFPPC_NO_FWD)) { - odp_actions_add(ctx->out, ODPAT_OUTPUT)->output.port = port; - } -} - -static struct rule * -lookup_valid_rule(struct ofproto *ofproto, const flow_t *flow) -{ - struct rule *rule; - rule = rule_from_cls_rule(classifier_lookup(&ofproto->cls, flow)); - - /* The rule we found might not be valid, since we could be in need of - * revalidation. If it is not valid, don't return it. */ - if (rule - && rule->super - && ofproto->need_revalidate - && !revalidate_rule(ofproto, rule)) { - COVERAGE_INC(ofproto_invalidated); - return NULL; - } - - return rule; -} - -static void -xlate_table_action(struct action_xlate_ctx *ctx, uint16_t in_port) -{ - if (!ctx->recurse) { - struct rule *rule; - flow_t flow; - - flow = *ctx->flow; - flow.in_port = in_port; - - rule = lookup_valid_rule(ctx->ofproto, &flow); - if (rule) { - if (rule->super) { - rule = rule->super; - } - - ctx->recurse++; - do_xlate_actions(rule->actions, rule->n_actions, ctx); - ctx->recurse--; - } - } -} - -static void -xlate_output_action(struct action_xlate_ctx *ctx, - const struct ofp_action_output *oao) -{ - uint16_t odp_port; - - switch (ntohs(oao->port)) { - case OFPP_IN_PORT: - add_output_action(ctx, ctx->flow->in_port); - break; - case OFPP_TABLE: - xlate_table_action(ctx, ctx->flow->in_port); - break; - case OFPP_NORMAL: - if (!ctx->ofproto->ofhooks->normal_cb(ctx->flow, ctx->packet, - ctx->out, ctx->tags, - ctx->ofproto->aux)) { - COVERAGE_INC(ofproto_uninstallable); - ctx->may_setup_flow = false; - } - break; - case OFPP_FLOOD: - add_output_group_action(ctx->out, DP_GROUP_FLOOD); - break; - case OFPP_ALL: - add_output_group_action(ctx->out, DP_GROUP_ALL); - break; - case OFPP_CONTROLLER: - add_controller_action(ctx->out, oao); - break; - case OFPP_LOCAL: - add_output_action(ctx, ODPP_LOCAL); - break; - default: - odp_port = ofp_port_to_odp_port(ntohs(oao->port)); - if (odp_port != ctx->flow->in_port) { - add_output_action(ctx, odp_port); - } - break; - } -} - -static void -xlate_nicira_action(struct action_xlate_ctx *ctx, - const struct nx_action_header *nah) -{ - const struct nx_action_resubmit *nar; - int subtype = ntohs(nah->subtype); - - assert(nah->vendor == htonl(NX_VENDOR_ID)); - switch (subtype) { - case NXAST_RESUBMIT: - nar = (const struct nx_action_resubmit *) nah; - xlate_table_action(ctx, ofp_port_to_odp_port(ntohs(nar->in_port))); - break; - - default: - VLOG_DBG_RL(&rl, "unknown Nicira action type %"PRIu16, subtype); - break; - } -} - -static void -do_xlate_actions(const union ofp_action *in, size_t n_in, - struct action_xlate_ctx *ctx) -{ - struct actions_iterator iter; - const union ofp_action *ia; - const struct ofport *port; - - port = port_array_get(&ctx->ofproto->ports, ctx->flow->in_port); - if (port && port->opp.config & (OFPPC_NO_RECV | OFPPC_NO_RECV_STP) && - port->opp.config & (eth_addr_equals(ctx->flow->dl_dst, stp_eth_addr) - ? OFPPC_NO_RECV_STP : OFPPC_NO_RECV)) { - /* Drop this flow. */ - return; - } - - for (ia = actions_first(&iter, in, n_in); ia; ia = actions_next(&iter)) { - uint16_t type = ntohs(ia->type); - union odp_action *oa; - - switch (type) { - case OFPAT_OUTPUT: - xlate_output_action(ctx, &ia->output); - break; - - case OFPAT_SET_VLAN_VID: - oa = odp_actions_add(ctx->out, ODPAT_SET_VLAN_VID); - oa->vlan_vid.vlan_vid = ia->vlan_vid.vlan_vid; - break; - - case OFPAT_SET_VLAN_PCP: - oa = odp_actions_add(ctx->out, ODPAT_SET_VLAN_PCP); - oa->vlan_pcp.vlan_pcp = ia->vlan_pcp.vlan_pcp; - break; - - case OFPAT_STRIP_VLAN: - odp_actions_add(ctx->out, ODPAT_STRIP_VLAN); - break; - - case OFPAT_SET_DL_SRC: - oa = odp_actions_add(ctx->out, ODPAT_SET_DL_SRC); - memcpy(oa->dl_addr.dl_addr, - ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN); - break; - - case OFPAT_SET_DL_DST: - oa = odp_actions_add(ctx->out, ODPAT_SET_DL_DST); - memcpy(oa->dl_addr.dl_addr, - ((struct ofp_action_dl_addr *) ia)->dl_addr, ETH_ADDR_LEN); - break; - - case OFPAT_SET_NW_SRC: - oa = odp_actions_add(ctx->out, ODPAT_SET_NW_SRC); - oa->nw_addr.nw_addr = ia->nw_addr.nw_addr; - break; - - case OFPAT_SET_TP_SRC: - oa = odp_actions_add(ctx->out, ODPAT_SET_TP_SRC); - oa->tp_port.tp_port = ia->tp_port.tp_port; - break; - - case OFPAT_VENDOR: - xlate_nicira_action(ctx, (const struct nx_action_header *) ia); - break; - - default: - VLOG_DBG_RL(&rl, "unknown action type %"PRIu16, type); - break; - } - } -} - -static int -xlate_actions(const union ofp_action *in, size_t n_in, - const flow_t *flow, struct ofproto *ofproto, - const struct ofpbuf *packet, - struct odp_actions *out, tag_type *tags, bool *may_setup_flow) -{ - tag_type no_tags = 0; - struct action_xlate_ctx ctx; - COVERAGE_INC(ofproto_ofp2odp); - odp_actions_init(out); - ctx.flow = flow; - ctx.recurse = 0; - ctx.ofproto = ofproto; - ctx.packet = packet; - ctx.out = out; - ctx.tags = tags ? tags : &no_tags; - ctx.may_setup_flow = true; - do_xlate_actions(in, n_in, &ctx); - if (may_setup_flow) { - *may_setup_flow = ctx.may_setup_flow; - } - if (odp_actions_overflow(out)) { - odp_actions_init(out); - return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_TOO_MANY); - } - return 0; -} - -static int -handle_packet_out(struct ofproto *p, struct ofconn *ofconn, - struct ofp_header *oh) -{ - struct ofp_packet_out *opo; - struct ofpbuf payload, *buffer; - struct odp_actions actions; - int n_actions; - uint16_t in_port; - flow_t flow; - int error; - - error = check_ofp_packet_out(oh, &payload, &n_actions, p->max_ports); - if (error) { - return error; - } - opo = (struct ofp_packet_out *) oh; - - COVERAGE_INC(ofproto_packet_out); - if (opo->buffer_id != htonl(UINT32_MAX)) { - error = pktbuf_retrieve(ofconn->pktbuf, ntohl(opo->buffer_id), - &buffer, &in_port); - if (error) { - return error; - } - payload = *buffer; - } else { - buffer = NULL; - } - - flow_extract(&payload, ofp_port_to_odp_port(ntohs(opo->in_port)), &flow); - error = xlate_actions((const union ofp_action *) opo->actions, n_actions, - &flow, p, &payload, &actions, NULL, NULL); - if (error) { - return error; - } - - dpif_execute(p->dpif, flow.in_port, actions.actions, actions.n_actions, - &payload); - ofpbuf_delete(buffer); - - return 0; -} - -static void -update_port_config(struct ofproto *p, struct ofport *port, - uint32_t config, uint32_t mask) -{ - mask &= config ^ port->opp.config; - if (mask & OFPPC_PORT_DOWN) { - if (config & OFPPC_PORT_DOWN) { - netdev_turn_flags_off(port->netdev, NETDEV_UP, true); - } else { - netdev_turn_flags_on(port->netdev, NETDEV_UP, true); - } - } -#define REVALIDATE_BITS (OFPPC_NO_RECV | OFPPC_NO_RECV_STP | OFPPC_NO_FWD) - if (mask & REVALIDATE_BITS) { - COVERAGE_INC(ofproto_costly_flags); - port->opp.config ^= mask & REVALIDATE_BITS; - p->need_revalidate = true; - } -#undef REVALIDATE_BITS - if (mask & OFPPC_NO_FLOOD) { - port->opp.config ^= OFPPC_NO_FLOOD; - refresh_port_group(p, DP_GROUP_FLOOD); - } - if (mask & OFPPC_NO_PACKET_IN) { - port->opp.config ^= OFPPC_NO_PACKET_IN; - } -} - -static int -handle_port_mod(struct ofproto *p, struct ofp_header *oh) -{ - const struct ofp_port_mod *opm; - struct ofport *port; - int error; - - error = check_ofp_message(oh, OFPT_PORT_MOD, sizeof *opm); - if (error) { - return error; - } - opm = (struct ofp_port_mod *) oh; - - port = port_array_get(&p->ports, - ofp_port_to_odp_port(ntohs(opm->port_no))); - if (!port) { - return ofp_mkerr(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_PORT); - } else if (memcmp(port->opp.hw_addr, opm->hw_addr, OFP_ETH_ALEN)) { - return ofp_mkerr(OFPET_PORT_MOD_FAILED, OFPPMFC_BAD_HW_ADDR); - } else { - update_port_config(p, port, ntohl(opm->config), ntohl(opm->mask)); - if (opm->advertise) { - netdev_set_advertisements(port->netdev, ntohl(opm->advertise)); - } - } - return 0; -} - -static struct ofpbuf * -make_stats_reply(uint32_t xid, uint16_t type, size_t body_len) -{ - struct ofp_stats_reply *osr; - struct ofpbuf *msg; - - msg = ofpbuf_new(MIN(sizeof *osr + body_len, UINT16_MAX)); - osr = put_openflow_xid(sizeof *osr, OFPT_STATS_REPLY, xid, msg); - osr->type = type; - osr->flags = htons(0); - return msg; -} - -static struct ofpbuf * -start_stats_reply(const struct ofp_stats_request *request, size_t body_len) -{ - return make_stats_reply(request->header.xid, request->type, body_len); -} - -static void * -append_stats_reply(size_t nbytes, struct ofconn *ofconn, struct ofpbuf **msgp) -{ - struct ofpbuf *msg = *msgp; - assert(nbytes <= UINT16_MAX - sizeof(struct ofp_stats_reply)); - if (nbytes + msg->size > UINT16_MAX) { - struct ofp_stats_reply *reply = msg->data; - reply->flags = htons(OFPSF_REPLY_MORE); - *msgp = make_stats_reply(reply->header.xid, reply->type, nbytes); - queue_tx(msg, ofconn, ofconn->reply_counter); - } - return ofpbuf_put_uninit(*msgp, nbytes); -} - -static int -handle_desc_stats_request(struct ofproto *p, struct ofconn *ofconn, - struct ofp_stats_request *request) -{ - struct ofp_desc_stats *ods; - struct ofpbuf *msg; - - msg = start_stats_reply(request, sizeof *ods); - ods = append_stats_reply(sizeof *ods, ofconn, &msg); - strncpy(ods->mfr_desc, p->manufacturer, sizeof ods->mfr_desc); - strncpy(ods->hw_desc, p->hardware, sizeof ods->hw_desc); - strncpy(ods->sw_desc, p->software, sizeof ods->sw_desc); - strncpy(ods->serial_num, p->serial, sizeof ods->serial_num); - queue_tx(msg, ofconn, ofconn->reply_counter); - - return 0; -} - -static void -count_subrules(struct cls_rule *cls_rule, void *n_subrules_) -{ - struct rule *rule = rule_from_cls_rule(cls_rule); - int *n_subrules = n_subrules_; - - if (rule->super) { - (*n_subrules)++; - } -} - -static int -handle_table_stats_request(struct ofproto *p, struct ofconn *ofconn, - struct ofp_stats_request *request) -{ - struct ofp_table_stats *ots; - struct ofpbuf *msg; - struct odp_stats dpstats; - int n_exact, n_subrules, n_wild; - - msg = start_stats_reply(request, sizeof *ots * 2); - - /* Count rules of various kinds. */ - n_subrules = 0; - classifier_for_each(&p->cls, CLS_INC_EXACT, count_subrules, &n_subrules); - n_exact = classifier_count_exact(&p->cls) - n_subrules; - n_wild = classifier_count(&p->cls) - classifier_count_exact(&p->cls); - - /* Hash table. */ - dpif_get_dp_stats(p->dpif, &dpstats); - ots = append_stats_reply(sizeof *ots, ofconn, &msg); - memset(ots, 0, sizeof *ots); - ots->table_id = TABLEID_HASH; - strcpy(ots->name, "hash"); - ots->wildcards = htonl(0); - ots->max_entries = htonl(dpstats.max_capacity); - ots->active_count = htonl(n_exact); - ots->lookup_count = htonll(dpstats.n_frags + dpstats.n_hit + - dpstats.n_missed); - ots->matched_count = htonll(dpstats.n_hit); /* XXX */ - - /* Classifier table. */ - ots = append_stats_reply(sizeof *ots, ofconn, &msg); - memset(ots, 0, sizeof *ots); - ots->table_id = TABLEID_CLASSIFIER; - strcpy(ots->name, "classifier"); - ots->wildcards = htonl(OFPFW_ALL); - ots->max_entries = htonl(65536); - ots->active_count = htonl(n_wild); - ots->lookup_count = htonll(0); /* XXX */ - ots->matched_count = htonll(0); /* XXX */ - - queue_tx(msg, ofconn, ofconn->reply_counter); - return 0; -} - -static int -handle_port_stats_request(struct ofproto *p, struct ofconn *ofconn, - struct ofp_stats_request *request) -{ - struct ofp_port_stats *ops; - struct ofpbuf *msg; - struct ofport *port; - unsigned int port_no; - - msg = start_stats_reply(request, sizeof *ops * 16); - PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) { - struct netdev_stats stats; - - /* Intentionally ignore return value, since errors will set 'stats' to - * all-1s, which is correct for OpenFlow, and netdev_get_stats() will - * log errors. */ - netdev_get_stats(port->netdev, &stats); - - ops = append_stats_reply(sizeof *ops, ofconn, &msg); - ops->port_no = htons(odp_port_to_ofp_port(port_no)); - memset(ops->pad, 0, sizeof ops->pad); - ops->rx_packets = htonll(stats.rx_packets); - ops->tx_packets = htonll(stats.tx_packets); - ops->rx_bytes = htonll(stats.rx_bytes); - ops->tx_bytes = htonll(stats.tx_bytes); - ops->rx_dropped = htonll(stats.rx_dropped); - ops->tx_dropped = htonll(stats.tx_dropped); - ops->rx_errors = htonll(stats.rx_errors); - ops->tx_errors = htonll(stats.tx_errors); - ops->rx_frame_err = htonll(stats.rx_frame_errors); - ops->rx_over_err = htonll(stats.rx_over_errors); - ops->rx_crc_err = htonll(stats.rx_crc_errors); - ops->collisions = htonll(stats.collisions); - } - - queue_tx(msg, ofconn, ofconn->reply_counter); - return 0; -} - -struct flow_stats_cbdata { - struct ofproto *ofproto; - struct ofconn *ofconn; - uint16_t out_port; - struct ofpbuf *msg; -}; - -static void -query_stats(struct ofproto *p, struct rule *rule, - uint64_t *packet_countp, uint64_t *byte_countp) -{ - uint64_t packet_count, byte_count; - struct rule *subrule; - struct odp_flow *odp_flows; - size_t n_odp_flows; - - n_odp_flows = rule->cr.wc.wildcards ? list_size(&rule->list) : 1; - odp_flows = xcalloc(1, n_odp_flows * sizeof *odp_flows); - if (rule->cr.wc.wildcards) { - size_t i = 0; - LIST_FOR_EACH (subrule, struct rule, list, &rule->list) { - odp_flows[i++].key = subrule->cr.flow; - } - } else { - odp_flows[0].key = rule->cr.flow; - } - - packet_count = rule->packet_count; - byte_count = rule->byte_count; - if (!dpif_flow_get_multiple(p->dpif, odp_flows, n_odp_flows)) { - size_t i; - for (i = 0; i < n_odp_flows; i++) { - struct odp_flow *odp_flow = &odp_flows[i]; - packet_count += odp_flow->stats.n_packets; - byte_count += odp_flow->stats.n_bytes; - } - } - free(odp_flows); - - *packet_countp = packet_count; - *byte_countp = byte_count; -} - -static void -flow_stats_cb(struct cls_rule *rule_, void *cbdata_) -{ - struct rule *rule = rule_from_cls_rule(rule_); - struct flow_stats_cbdata *cbdata = cbdata_; - struct ofp_flow_stats *ofs; - uint64_t packet_count, byte_count; - size_t act_len, len; - - if (rule_is_hidden(rule) || !rule_has_out_port(rule, cbdata->out_port)) { - return; - } - - act_len = sizeof *rule->actions * rule->n_actions; - len = offsetof(struct ofp_flow_stats, actions) + act_len; - - query_stats(cbdata->ofproto, rule, &packet_count, &byte_count); - - ofs = append_stats_reply(len, cbdata->ofconn, &cbdata->msg); - ofs->length = htons(len); - ofs->table_id = rule->cr.wc.wildcards ? TABLEID_CLASSIFIER : TABLEID_HASH; - ofs->pad = 0; - flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofs->match); - ofs->duration = htonl((time_msec() - rule->created) / 1000); - ofs->priority = htons(rule->cr.priority); - ofs->idle_timeout = htons(rule->idle_timeout); - ofs->hard_timeout = htons(rule->hard_timeout); - memset(ofs->pad2, 0, sizeof ofs->pad2); - ofs->packet_count = htonll(packet_count); - ofs->byte_count = htonll(byte_count); - memcpy(ofs->actions, rule->actions, act_len); -} - -static int -table_id_to_include(uint8_t table_id) -{ - return (table_id == TABLEID_HASH ? CLS_INC_EXACT - : table_id == TABLEID_CLASSIFIER ? CLS_INC_WILD - : table_id == 0xff ? CLS_INC_ALL - : 0); -} - -static int -handle_flow_stats_request(struct ofproto *p, struct ofconn *ofconn, - const struct ofp_stats_request *osr, - size_t arg_size) -{ - struct ofp_flow_stats_request *fsr; - struct flow_stats_cbdata cbdata; - struct cls_rule target; - - if (arg_size != sizeof *fsr) { - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); - } - fsr = (struct ofp_flow_stats_request *) osr->body; - - COVERAGE_INC(ofproto_flows_req); - cbdata.ofproto = p; - cbdata.ofconn = ofconn; - cbdata.out_port = fsr->out_port; - cbdata.msg = start_stats_reply(osr, 1024); - cls_rule_from_match(&target, &fsr->match, 0); - classifier_for_each_match(&p->cls, &target, - table_id_to_include(fsr->table_id), - flow_stats_cb, &cbdata); - queue_tx(cbdata.msg, ofconn, ofconn->reply_counter); - return 0; -} - -struct aggregate_stats_cbdata { - struct ofproto *ofproto; - uint16_t out_port; - uint64_t packet_count; - uint64_t byte_count; - uint32_t n_flows; -}; - -static void -aggregate_stats_cb(struct cls_rule *rule_, void *cbdata_) -{ - struct rule *rule = rule_from_cls_rule(rule_); - struct aggregate_stats_cbdata *cbdata = cbdata_; - uint64_t packet_count, byte_count; - - if (rule_is_hidden(rule) || !rule_has_out_port(rule, cbdata->out_port)) { - return; - } - - query_stats(cbdata->ofproto, rule, &packet_count, &byte_count); - - cbdata->packet_count += packet_count; - cbdata->byte_count += byte_count; - cbdata->n_flows++; -} - -static int -handle_aggregate_stats_request(struct ofproto *p, struct ofconn *ofconn, - const struct ofp_stats_request *osr, - size_t arg_size) -{ - struct ofp_aggregate_stats_request *asr; - struct ofp_aggregate_stats_reply *reply; - struct aggregate_stats_cbdata cbdata; - struct cls_rule target; - struct ofpbuf *msg; - - if (arg_size != sizeof *asr) { - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); - } - asr = (struct ofp_aggregate_stats_request *) osr->body; - - COVERAGE_INC(ofproto_agg_request); - cbdata.ofproto = p; - cbdata.out_port = asr->out_port; - cbdata.packet_count = 0; - cbdata.byte_count = 0; - cbdata.n_flows = 0; - cls_rule_from_match(&target, &asr->match, 0); - classifier_for_each_match(&p->cls, &target, - table_id_to_include(asr->table_id), - aggregate_stats_cb, &cbdata); - - msg = start_stats_reply(osr, sizeof *reply); - reply = append_stats_reply(sizeof *reply, ofconn, &msg); - reply->flow_count = htonl(cbdata.n_flows); - reply->packet_count = htonll(cbdata.packet_count); - reply->byte_count = htonll(cbdata.byte_count); - queue_tx(msg, ofconn, ofconn->reply_counter); - return 0; -} - -static int -handle_stats_request(struct ofproto *p, struct ofconn *ofconn, - struct ofp_header *oh) -{ - struct ofp_stats_request *osr; - size_t arg_size; - int error; - - error = check_ofp_message_array(oh, OFPT_STATS_REQUEST, sizeof *osr, - 1, &arg_size); - if (error) { - return error; - } - osr = (struct ofp_stats_request *) oh; - - switch (ntohs(osr->type)) { - case OFPST_DESC: - return handle_desc_stats_request(p, ofconn, osr); - - case OFPST_FLOW: - return handle_flow_stats_request(p, ofconn, osr, arg_size); - - case OFPST_AGGREGATE: - return handle_aggregate_stats_request(p, ofconn, osr, arg_size); - - case OFPST_TABLE: - return handle_table_stats_request(p, ofconn, osr); - - case OFPST_PORT: - return handle_port_stats_request(p, ofconn, osr); - - case OFPST_VENDOR: - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR); - - default: - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_STAT); - } -} - -static long long int -msec_from_nsec(uint64_t sec, uint32_t nsec) -{ - return !sec ? 0 : sec * 1000 + nsec / 1000000; -} - -static void -update_time(struct rule *rule, const struct odp_flow_stats *stats) -{ - long long int used = msec_from_nsec(stats->used_sec, stats->used_nsec); - if (used > rule->used) { - rule->used = used; - } -} - -static void -update_stats(struct rule *rule, const struct odp_flow_stats *stats) -{ - update_time(rule, stats); - rule->packet_count += stats->n_packets; - rule->byte_count += stats->n_bytes; - rule->tcp_flags |= stats->tcp_flags; - if (stats->n_packets) { - rule->ip_tos = stats->ip_tos; - } -} - -static int -add_flow(struct ofproto *p, struct ofconn *ofconn, - struct ofp_flow_mod *ofm, size_t n_actions) -{ - struct ofpbuf *packet; - struct rule *rule; - uint16_t in_port; - int error; - - rule = rule_create(NULL, (const union ofp_action *) ofm->actions, - n_actions, ntohs(ofm->idle_timeout), - ntohs(ofm->hard_timeout)); - cls_rule_from_match(&rule->cr, &ofm->match, ntohs(ofm->priority)); - - packet = NULL; - error = 0; - if (ofm->buffer_id != htonl(UINT32_MAX)) { - error = pktbuf_retrieve(ofconn->pktbuf, ntohl(ofm->buffer_id), - &packet, &in_port); - } - - rule_insert(p, rule, packet, in_port); - ofpbuf_delete(packet); - return error; -} - -static int -modify_flow(struct ofproto *p, const struct ofp_flow_mod *ofm, - size_t n_actions, uint16_t command, struct rule *rule) -{ - if (rule_is_hidden(rule)) { - return 0; - } - - if (command == OFPFC_DELETE) { - rule_remove(p, rule); - } else { - size_t actions_len = n_actions * sizeof *rule->actions; - - if (n_actions == rule->n_actions - && !memcmp(ofm->actions, rule->actions, actions_len)) - { - return 0; - } - - free(rule->actions); - rule->actions = xmemdup(ofm->actions, actions_len); - rule->n_actions = n_actions; - - if (rule->cr.wc.wildcards) { - COVERAGE_INC(ofproto_mod_wc_flow); - p->need_revalidate = true; - } else { - rule_update_actions(p, rule); - } - } - - return 0; -} - -static int -modify_flows_strict(struct ofproto *p, const struct ofp_flow_mod *ofm, - size_t n_actions, uint16_t command) -{ - struct rule *rule; - uint32_t wildcards; - flow_t flow; - - flow_from_match(&flow, &wildcards, &ofm->match); - rule = rule_from_cls_rule(classifier_find_rule_exactly( - &p->cls, &flow, wildcards, - ntohs(ofm->priority))); - - if (rule) { - if (command == OFPFC_DELETE - && ofm->out_port != htons(OFPP_NONE) - && !rule_has_out_port(rule, ofm->out_port)) { - return 0; - } - - modify_flow(p, ofm, n_actions, command, rule); - } - return 0; -} - -struct modify_flows_cbdata { - struct ofproto *ofproto; - const struct ofp_flow_mod *ofm; - uint16_t out_port; - size_t n_actions; - uint16_t command; -}; - -static void -modify_flows_cb(struct cls_rule *rule_, void *cbdata_) -{ - struct rule *rule = rule_from_cls_rule(rule_); - struct modify_flows_cbdata *cbdata = cbdata_; - - if (cbdata->out_port != htons(OFPP_NONE) - && !rule_has_out_port(rule, cbdata->out_port)) { - return; - } - - modify_flow(cbdata->ofproto, cbdata->ofm, cbdata->n_actions, - cbdata->command, rule); -} - -static int -modify_flows_loose(struct ofproto *p, const struct ofp_flow_mod *ofm, - size_t n_actions, uint16_t command) -{ - struct modify_flows_cbdata cbdata; - struct cls_rule target; - - cbdata.ofproto = p; - cbdata.ofm = ofm; - cbdata.out_port = (command == OFPFC_DELETE ? ofm->out_port - : htons(OFPP_NONE)); - cbdata.n_actions = n_actions; - cbdata.command = command; - - cls_rule_from_match(&target, &ofm->match, 0); - - classifier_for_each_match(&p->cls, &target, CLS_INC_ALL, - modify_flows_cb, &cbdata); - return 0; -} - -static int -handle_flow_mod(struct ofproto *p, struct ofconn *ofconn, - struct ofp_flow_mod *ofm) -{ - size_t n_actions; - int error; - - error = check_ofp_message_array(&ofm->header, OFPT_FLOW_MOD, sizeof *ofm, - sizeof *ofm->actions, &n_actions); - if (error) { - return error; - } - - normalize_match(&ofm->match); - if (!ofm->match.wildcards) { - ofm->priority = htons(UINT16_MAX); - } - - error = validate_actions((const union ofp_action *) ofm->actions, - n_actions, p->max_ports); - if (error) { - return error; - } - - switch (ntohs(ofm->command)) { - case OFPFC_ADD: - return add_flow(p, ofconn, ofm, n_actions); - - case OFPFC_MODIFY: - return modify_flows_loose(p, ofm, n_actions, OFPFC_MODIFY); - - case OFPFC_MODIFY_STRICT: - return modify_flows_strict(p, ofm, n_actions, OFPFC_MODIFY); - - case OFPFC_DELETE: - return modify_flows_loose(p, ofm, n_actions, OFPFC_DELETE); - - case OFPFC_DELETE_STRICT: - return modify_flows_strict(p, ofm, n_actions, OFPFC_DELETE); - - default: - return ofp_mkerr(OFPET_FLOW_MOD_FAILED, OFPFMFC_BAD_COMMAND); - } -} - -static void -send_capability_reply(struct ofproto *p, struct ofconn *ofconn, uint32_t xid) -{ - struct ofmp_capability_reply *ocr; - struct ofpbuf *b; - char capabilities[] = "com.nicira.mgmt.manager=false\n"; - - ocr = make_openflow_xid(sizeof(*ocr), OFPT_VENDOR, xid, &b); - ocr->header.header.vendor = htonl(NX_VENDOR_ID); - ocr->header.header.subtype = htonl(NXT_MGMT); - ocr->header.type = htons(OFMPT_CAPABILITY_REPLY); - - ocr->format = htonl(OFMPCOF_SIMPLE); - ocr->mgmt_id = htonll(p->mgmt_id); - - ofpbuf_put(b, capabilities, strlen(capabilities)); - - queue_tx(b, ofconn, ofconn->reply_counter); -} - -static int -handle_ofmp(struct ofproto *p, struct ofconn *ofconn, - struct ofmp_header *ofmph) -{ - size_t msg_len = ntohs(ofmph->header.header.length); - if (msg_len < sizeof(*ofmph)) { - VLOG_WARN_RL(&rl, "dropping short managment message: %d\n", msg_len); - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); - } - - if (ofmph->type == htons(OFMPT_CAPABILITY_REQUEST)) { - struct ofmp_capability_request *ofmpcr; - - if (msg_len < sizeof(struct ofmp_capability_request)) { - VLOG_WARN_RL(&rl, "dropping short capability request: %d\n", - msg_len); - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); - } - - ofmpcr = (struct ofmp_capability_request *)ofmph; - if (ofmpcr->format != htonl(OFMPCAF_SIMPLE)) { - /* xxx Find a better type than bad subtype */ - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); - } - - send_capability_reply(p, ofconn, ofmph->header.header.xid); - return 0; - } else { - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); - } -} - -static int -handle_vendor(struct ofproto *p, struct ofconn *ofconn, void *msg) -{ - struct ofp_vendor_header *ovh = msg; - struct nicira_header *nh; - - if (ntohs(ovh->header.length) < sizeof(struct ofp_vendor_header)) { - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); - } - if (ovh->vendor != htonl(NX_VENDOR_ID)) { - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR); - } - if (ntohs(ovh->header.length) < sizeof(struct nicira_header)) { - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LENGTH); - } - - nh = msg; - switch (ntohl(nh->subtype)) { - case NXT_STATUS_REQUEST: - return switch_status_handle_request(p->switch_status, ofconn->rconn, - msg); - - case NXT_ACT_SET_CONFIG: - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */ - - case NXT_ACT_GET_CONFIG: - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); /* XXX */ - - case NXT_COMMAND_REQUEST: - if (p->executer) { - return executer_handle_request(p->executer, ofconn->rconn, msg); - } - break; - - case NXT_MGMT: - return handle_ofmp(p, ofconn, msg); - } - - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_SUBTYPE); -} - -static void -handle_openflow(struct ofconn *ofconn, struct ofproto *p, - struct ofpbuf *ofp_msg) -{ - struct ofp_header *oh = ofp_msg->data; - int error; - - COVERAGE_INC(ofproto_recv_openflow); - switch (oh->type) { - case OFPT_ECHO_REQUEST: - error = handle_echo_request(ofconn, oh); - break; - - case OFPT_ECHO_REPLY: - error = 0; - break; - - case OFPT_FEATURES_REQUEST: - error = handle_features_request(p, ofconn, oh); - break; - - case OFPT_GET_CONFIG_REQUEST: - error = handle_get_config_request(p, ofconn, oh); - break; - - case OFPT_SET_CONFIG: - error = handle_set_config(p, ofconn, ofp_msg->data); - break; - - case OFPT_PACKET_OUT: - error = handle_packet_out(p, ofconn, ofp_msg->data); - break; - - case OFPT_PORT_MOD: - error = handle_port_mod(p, oh); - break; - - case OFPT_FLOW_MOD: - error = handle_flow_mod(p, ofconn, ofp_msg->data); - break; - - case OFPT_STATS_REQUEST: - error = handle_stats_request(p, ofconn, oh); - break; - - case OFPT_VENDOR: - error = handle_vendor(p, ofconn, ofp_msg->data); - break; - - default: - if (VLOG_IS_WARN_ENABLED()) { - char *s = ofp_to_string(oh, ntohs(oh->length), 2); - VLOG_DBG_RL(&rl, "OpenFlow message ignored: %s", s); - free(s); - } - error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_TYPE); - break; - } - - if (error) { - send_error_oh(ofconn, ofp_msg->data, error); - } -} - -static void -handle_odp_msg(struct ofproto *p, struct ofpbuf *packet) -{ - struct odp_msg *msg = packet->data; - uint16_t in_port = odp_port_to_ofp_port(msg->port); - struct rule *rule; - struct ofpbuf payload; - flow_t flow; - - /* Handle controller actions. */ - if (msg->type == _ODPL_ACTION_NR) { - COVERAGE_INC(ofproto_ctlr_action); - pinsched_send(p->action_sched, in_port, packet, - send_packet_in_action, p); - return; - } - - payload.data = msg + 1; - payload.size = msg->length - sizeof *msg; - flow_extract(&payload, msg->port, &flow); - - rule = lookup_valid_rule(p, &flow); - if (!rule) { - /* Don't send a packet-in if OFPPC_NO_PACKET_IN asserted. */ - struct ofport *port = port_array_get(&p->ports, msg->port); - if (port) { - if (port->opp.config & OFPPC_NO_PACKET_IN) { - COVERAGE_INC(ofproto_no_packet_in); - /* XXX install 'drop' flow entry */ - ofpbuf_delete(packet); - return; - } - } else { - VLOG_WARN_RL(&rl, "packet-in on unknown port %"PRIu16, msg->port); - } - - COVERAGE_INC(ofproto_packet_in); - pinsched_send(p->miss_sched, in_port, packet, send_packet_in_miss, p); - return; - } - - if (rule->cr.wc.wildcards) { - rule = rule_create_subrule(p, rule, &flow); - rule_make_actions(p, rule, packet); - } else { - if (!rule->may_install) { - /* The rule is not installable, that is, we need to process every - * packet, so process the current packet and set its actions into - * 'subrule'. */ - rule_make_actions(p, rule, packet); - } else { - /* XXX revalidate rule if it needs it */ - } - } - - rule_execute(p, rule, &payload, &flow); - rule_reinstall(p, rule); - ofpbuf_delete(packet); -} - -static void -revalidate_cb(struct cls_rule *sub_, void *cbdata_) -{ - struct rule *sub = rule_from_cls_rule(sub_); - struct revalidate_cbdata *cbdata = cbdata_; - - if (cbdata->revalidate_all - || (cbdata->revalidate_subrules && sub->super) - || (tag_set_intersects(&cbdata->revalidate_set, sub->tags))) { - revalidate_rule(cbdata->ofproto, sub); - } -} - -static bool -revalidate_rule(struct ofproto *p, struct rule *rule) -{ - const flow_t *flow = &rule->cr.flow; - - COVERAGE_INC(ofproto_revalidate_rule); - if (rule->super) { - struct rule *super; - super = rule_from_cls_rule(classifier_lookup_wild(&p->cls, flow)); - if (!super) { - rule_remove(p, rule); - return false; - } else if (super != rule->super) { - COVERAGE_INC(ofproto_revalidate_moved); - list_remove(&rule->list); - list_push_back(&super->list, &rule->list); - rule->super = super; - rule->hard_timeout = super->hard_timeout; - rule->idle_timeout = super->idle_timeout; - rule->created = super->created; - rule->used = 0; - } - } - - rule_update_actions(p, rule); - return true; -} - -static struct ofpbuf * -compose_flow_exp(const struct rule *rule, long long int now, uint8_t reason) -{ - struct ofp_flow_expired *ofe; - struct ofpbuf *buf; - - ofe = make_openflow(sizeof *ofe, OFPT_FLOW_EXPIRED, &buf); - flow_to_match(&rule->cr.flow, rule->cr.wc.wildcards, &ofe->match); - ofe->priority = htons(rule->cr.priority); - ofe->reason = reason; - ofe->duration = (now - rule->created) / 1000; - ofe->packet_count = rule->packet_count; - ofe->byte_count = rule->byte_count; - - return buf; -} - -static void -send_flow_exp(struct ofproto *p, struct rule *rule, - long long int now, uint8_t reason) -{ - struct ofconn *ofconn; - struct ofconn *prev; - struct ofpbuf *buf; - - /* We limit the maximum number of queued flow expirations it by accounting - * them under the counter for replies. That works because preventing - * OpenFlow requests from being processed also prevents new flows from - * being added (and expiring). (It also prevents processing OpenFlow - * requests that would not add new flows, so it is imperfect.) */ - - prev = NULL; - LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { - if (ofconn->send_flow_exp && rconn_is_connected(ofconn->rconn)) { - if (prev) { - queue_tx(ofpbuf_clone(buf), prev, ofconn->reply_counter); - } else { - buf = compose_flow_exp(rule, now, reason); - } - prev = ofconn; - } - } - if (prev) { - queue_tx(buf, prev, ofconn->reply_counter); - } -} - -static void -uninstall_idle_flow(struct ofproto *ofproto, struct rule *rule) -{ - assert(rule->installed); - assert(!rule->cr.wc.wildcards); - - if (rule->super) { - rule_remove(ofproto, rule); - } else { - rule_uninstall(ofproto, rule); - } -} - -static void -expire_rule(struct cls_rule *cls_rule, void *p_) -{ - struct ofproto *p = p_; - struct rule *rule = rule_from_cls_rule(cls_rule); - long long int hard_expire, idle_expire, expire, now; - - hard_expire = (rule->hard_timeout - ? rule->created + rule->hard_timeout * 1000 - : LLONG_MAX); - idle_expire = (rule->idle_timeout - && (rule->super || list_is_empty(&rule->list)) - ? rule->used + rule->idle_timeout * 1000 - : LLONG_MAX); - expire = MIN(hard_expire, idle_expire); - if (expire == LLONG_MAX) { - if (rule->installed && time_msec() >= rule->used + 5000) { - uninstall_idle_flow(p, rule); - } - return; - } - - now = time_msec(); - if (now < expire) { - if (rule->installed && now >= rule->used + 5000) { - uninstall_idle_flow(p, rule); - } - return; - } - - COVERAGE_INC(ofproto_expired); - if (rule->cr.wc.wildcards) { - /* Update stats. (This code will be a no-op if the rule expired - * due to an idle timeout, because in that case the rule has no - * subrules left.) */ - struct rule *subrule, *next; - LIST_FOR_EACH_SAFE (subrule, next, struct rule, list, &rule->list) { - rule_remove(p, subrule); - } - } - - send_flow_exp(p, rule, now, - (now >= hard_expire - ? OFPER_HARD_TIMEOUT : OFPER_IDLE_TIMEOUT)); - rule_remove(p, rule); -} - -static void -update_used(struct ofproto *p) -{ - struct odp_flow *flows; - size_t n_flows; - size_t i; - int error; - - error = dpif_flow_list_all(p->dpif, &flows, &n_flows); - if (error) { - return; - } - - for (i = 0; i < n_flows; i++) { - struct odp_flow *f = &flows[i]; - struct rule *rule; - - rule = rule_from_cls_rule( - classifier_find_rule_exactly(&p->cls, &f->key, 0, UINT16_MAX)); - if (!rule || !rule->installed) { - COVERAGE_INC(ofproto_unexpected_rule); - dpif_flow_del(p->dpif, f); - continue; - } - - update_time(rule, &f->stats); - rule_account(p, rule, f->stats.n_bytes); - } - free(flows); -} - -static void -do_send_packet_in(struct ofconn *ofconn, uint32_t buffer_id, - const struct ofpbuf *packet, int send_len) -{ - struct ofp_packet_in *opi; - struct ofpbuf payload, *buf; - struct odp_msg *msg; - - msg = packet->data; - payload.data = msg + 1; - payload.size = msg->length - sizeof *msg; - - send_len = MIN(send_len, payload.size); - buf = ofpbuf_new(sizeof *opi + send_len); - opi = put_openflow_xid(offsetof(struct ofp_packet_in, data), - OFPT_PACKET_IN, 0, buf); - opi->buffer_id = htonl(buffer_id); - opi->total_len = htons(payload.size); - opi->in_port = htons(odp_port_to_ofp_port(msg->port)); - opi->reason = msg->type == _ODPL_ACTION_NR ? OFPR_ACTION : OFPR_NO_MATCH; - ofpbuf_put(buf, payload.data, MIN(send_len, payload.size)); - update_openflow_length(buf); - rconn_send_with_limit(ofconn->rconn, buf, ofconn->packet_in_counter, 100); -} - -static void -send_packet_in_action(struct ofpbuf *packet, void *p_) -{ - struct ofproto *p = p_; - struct ofconn *ofconn; - struct odp_msg *msg; - - msg = packet->data; - LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { - if (ofconn == p->controller || ofconn->miss_send_len) { - do_send_packet_in(ofconn, UINT32_MAX, packet, msg->arg); - } - } - ofpbuf_delete(packet); -} - -static void -send_packet_in_miss(struct ofpbuf *packet, void *p_) -{ - struct ofproto *p = p_; - struct ofconn *ofconn; - struct ofpbuf payload; - struct odp_msg *msg; - - msg = packet->data; - payload.data = msg + 1; - payload.size = msg->length - sizeof *msg; - LIST_FOR_EACH (ofconn, struct ofconn, node, &p->all_conns) { - if (ofconn->miss_send_len) { - uint32_t buffer_id = pktbuf_save(ofconn->pktbuf, &payload, - msg->port); - int send_len = (buffer_id != UINT32_MAX ? ofconn->miss_send_len - : UINT32_MAX); - do_send_packet_in(ofconn, buffer_id, packet, send_len); - } - } - ofpbuf_delete(packet); -} - -static uint64_t -pick_datapath_id(struct dpif *dpif, uint64_t fallback_dpid) -{ - char local_name[IF_NAMESIZE]; - uint8_t ea[ETH_ADDR_LEN]; - int error; - - error = dpif_port_get_name(dpif, ODPP_LOCAL, - local_name, sizeof local_name); - if (!error) { - error = netdev_nodev_get_etheraddr(local_name, ea); - if (!error) { - return eth_addr_to_uint64(ea); - } - VLOG_WARN("could not get MAC address for %s (%s)", - local_name, strerror(error)); - } - - return fallback_dpid; -} - -static uint64_t -pick_fallback_dpid(void) -{ - uint8_t ea[ETH_ADDR_LEN]; - eth_addr_random(ea); - ea[0] = 0x00; /* Set Nicira OUI. */ - ea[1] = 0x23; - ea[2] = 0x20; - return eth_addr_to_uint64(ea); -} - -static bool -default_normal_ofhook_cb(const flow_t *flow, const struct ofpbuf *packet, - struct odp_actions *actions, tag_type *tags, - void *ofproto_) -{ - struct ofproto *ofproto = ofproto_; - int out_port; - - /* Drop frames for reserved multicast addresses. */ - if (eth_addr_is_reserved(flow->dl_dst)) { - return true; - } - - /* Learn source MAC (but don't try to learn from revalidation). */ - if (packet != NULL) { - tag_type rev_tag = mac_learning_learn(ofproto->ml, flow->dl_src, - 0, flow->in_port); - if (rev_tag) { - /* The log messages here could actually be useful in debugging, - * so keep the rate limit relatively high. */ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(30, 300); - VLOG_DBG_RL(&rl, "learned that "ETH_ADDR_FMT" is on port %"PRIu16, - ETH_ADDR_ARGS(flow->dl_src), flow->in_port); - ofproto_revalidate(ofproto, rev_tag); - } - } - - /* Determine output port. */ - out_port = mac_learning_lookup_tag(ofproto->ml, flow->dl_dst, 0, tags); - if (out_port < 0) { - add_output_group_action(actions, DP_GROUP_FLOOD); - } else if (out_port != flow->in_port) { - odp_actions_add(actions, ODPAT_OUTPUT)->output.port = out_port; - } else { - /* Drop. */ - } - - return true; -} - -static const struct ofhooks default_ofhooks = { - NULL, - default_normal_ofhook_cb, - NULL, - NULL -}; diff --git a/secchan/ofproto.h b/secchan/ofproto.h deleted file mode 100644 index 44fc023f..00000000 --- a/secchan/ofproto.h +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef OFPROTO_H -#define OFPROTO_H 1 - -#include -#include -#include -#include "flow.h" -#include "tag.h" - -struct odp_actions; -struct ofhooks; -struct ofproto; -struct svec; - -struct ofexpired { - flow_t flow; - uint64_t packet_count; /* Packets from *expired* subrules. */ - uint64_t byte_count; /* Bytes from *expired* subrules. */ - long long int used; /* Last-used time (0 if never used). */ - long long int created; /* Creation time. */ - uint8_t tcp_flags; /* Bitwise-OR of all TCP flags seen. */ - uint8_t ip_tos; /* Last-seen IP type-of-service. */ -}; - -int ofproto_create(const char *datapath, const struct ofhooks *, void *aux, - struct ofproto **ofprotop); -void ofproto_destroy(struct ofproto *); -int ofproto_run(struct ofproto *); -int ofproto_run1(struct ofproto *); -int ofproto_run2(struct ofproto *, bool revalidate_all); -void ofproto_wait(struct ofproto *); -bool ofproto_is_alive(const struct ofproto *); - -/* Configuration. */ -void ofproto_set_datapath_id(struct ofproto *, uint64_t datapath_id); -void ofproto_set_mgmt_id(struct ofproto *, uint64_t mgmt_id); -void ofproto_set_probe_interval(struct ofproto *, int probe_interval); -void ofproto_set_max_backoff(struct ofproto *, int max_backoff); -void ofproto_set_desc(struct ofproto *, - const char *manufacturer, const char *hardware, - const char *software, const char *serial); -int ofproto_set_in_band(struct ofproto *, bool in_band); -int ofproto_set_discovery(struct ofproto *, bool discovery, - const char *accept_controller_re, - bool update_resolv_conf); -int ofproto_set_controller(struct ofproto *, const char *controller); -int ofproto_set_listeners(struct ofproto *, const struct svec *listeners); -int ofproto_set_snoops(struct ofproto *, const struct svec *snoops); -int ofproto_set_netflow(struct ofproto *, const struct svec *collectors, - uint8_t engine_type, uint8_t engine_id, bool add_id_to_iface); -void ofproto_set_failure(struct ofproto *, bool fail_open); -void ofproto_set_rate_limit(struct ofproto *, int rate_limit, int burst_limit); -int ofproto_set_stp(struct ofproto *, bool enable_stp); -int ofproto_set_remote_execution(struct ofproto *, const char *command_acl, - const char *command_dir); - -/* Configuration querying. */ -uint64_t ofproto_get_datapath_id(const struct ofproto *); -int ofproto_get_probe_interval(const struct ofproto *); -int ofproto_get_max_backoff(const struct ofproto *); -bool ofproto_get_in_band(const struct ofproto *); -bool ofproto_get_discovery(const struct ofproto *); -const char *ofproto_get_controller(const struct ofproto *); -void ofproto_get_listeners(const struct ofproto *, struct svec *); -void ofproto_get_snoops(const struct ofproto *, struct svec *); - -/* Functions for use by ofproto implementation modules, not by clients. */ -int ofproto_send_packet(struct ofproto *, const flow_t *, - const union ofp_action *, size_t n_actions, - const struct ofpbuf *); -void ofproto_add_flow(struct ofproto *, const flow_t *, uint32_t wildcards, - unsigned int priority, - const union ofp_action *, size_t n_actions, - int idle_timeout); -void ofproto_delete_flow(struct ofproto *, const flow_t *, uint32_t wildcards, - unsigned int priority); -void ofproto_flush_flows(struct ofproto *); - -/* Hooks for ovs-vswitchd. */ -struct ofhooks { - void (*port_changed_cb)(enum ofp_port_reason, const struct ofp_phy_port *, - void *aux); - bool (*normal_cb)(const flow_t *, const struct ofpbuf *packet, - struct odp_actions *, tag_type *, void *aux); - void (*account_flow_cb)(const flow_t *, const union odp_action *, - size_t n_actions, unsigned long long int n_bytes, - void *aux); - void (*account_checkpoint_cb)(void *aux); -}; -void ofproto_revalidate(struct ofproto *, tag_type); -struct tag_set *ofproto_get_revalidate_set(struct ofproto *); - -#endif /* ofproto.h */ diff --git a/secchan/pinsched.c b/secchan/pinsched.c deleted file mode 100644 index 0afd22ff..00000000 --- a/secchan/pinsched.c +++ /dev/null @@ -1,284 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "pinsched.h" -#include -#include -#include "ofpbuf.h" -#include "openflow/openflow.h" -#include "poll-loop.h" -#include "port-array.h" -#include "queue.h" -#include "random.h" -#include "rconn.h" -#include "status.h" -#include "timeval.h" -#include "vconn.h" - -struct pinsched { - /* Client-supplied parameters. */ - int rate_limit; /* Packets added to bucket per second. */ - int burst_limit; /* Maximum token bucket size, in packets. */ - - /* One queue per physical port. */ - struct port_array queues; /* Array of "struct ovs_queue *". */ - int n_queued; /* Sum over queues[*].n. */ - unsigned int last_tx_port; /* Last port checked in round-robin. */ - - /* Token bucket. - * - * It costs 1000 tokens to send a single packet_in message. A single token - * per message would be more straightforward, but this choice lets us avoid - * round-off error in refill_bucket()'s calculation of how many tokens to - * add to the bucket, since no division step is needed. */ - long long int last_fill; /* Time at which we last added tokens. */ - int tokens; /* Current number of tokens. */ - - /* Transmission queue. */ - int n_txq; /* No. of packets waiting in rconn for tx. */ - - /* Statistics reporting. */ - unsigned long long n_normal; /* # txed w/o rate limit queuing. */ - unsigned long long n_limited; /* # queued for rate limiting. */ - unsigned long long n_queue_dropped; /* # dropped due to queue overflow. */ - - /* Switch status. */ - struct status_category *ss_cat; -}; - -static struct ofpbuf * -dequeue_packet(struct pinsched *ps, struct ovs_queue *q, - unsigned int port_no) -{ - struct ofpbuf *packet = queue_pop_head(q); - if (!q->n) { - free(q); - port_array_set(&ps->queues, port_no, NULL); - } - ps->n_queued--; - return packet; -} - -/* Drop a packet from the longest queue in 'ps'. */ -static void -drop_packet(struct pinsched *ps) -{ - struct ovs_queue *longest; /* Queue currently selected as longest. */ - int n_longest; /* # of queues of same length as 'longest'. */ - unsigned int longest_port_no; - unsigned int port_no; - struct ovs_queue *q; - - ps->n_queue_dropped++; - - longest = port_array_first(&ps->queues, &port_no); - longest_port_no = port_no; - n_longest = 1; - while ((q = port_array_next(&ps->queues, &port_no)) != NULL) { - if (longest->n < q->n) { - longest = q; - n_longest = 1; - } else if (longest->n == q->n) { - n_longest++; - - /* Randomly select one of the longest queues, with a uniform - * distribution (Knuth algorithm 3.4.2R). */ - if (!random_range(n_longest)) { - longest = q; - longest_port_no = port_no; - } - } - } - - /* FIXME: do we want to pop the tail instead? */ - ofpbuf_delete(dequeue_packet(ps, longest, longest_port_no)); -} - -/* Remove and return the next packet to transmit (in round-robin order). */ -static struct ofpbuf * -get_tx_packet(struct pinsched *ps) -{ - struct ovs_queue *q = port_array_next(&ps->queues, &ps->last_tx_port); - if (!q) { - q = port_array_first(&ps->queues, &ps->last_tx_port); - } - return dequeue_packet(ps, q, ps->last_tx_port); -} - -/* Add tokens to the bucket based on elapsed time. */ -static void -refill_bucket(struct pinsched *ps) -{ - long long int now = time_msec(); - long long int tokens = (now - ps->last_fill) * ps->rate_limit + ps->tokens; - if (tokens >= 1000) { - ps->last_fill = now; - ps->tokens = MIN(tokens, ps->burst_limit * 1000); - } -} - -/* Attempts to remove enough tokens from 'ps' to transmit a packet. Returns - * true if successful, false otherwise. (In the latter case no tokens are - * removed.) */ -static bool -get_token(struct pinsched *ps) -{ - if (ps->tokens >= 1000) { - ps->tokens -= 1000; - return true; - } else { - return false; - } -} - -void -pinsched_send(struct pinsched *ps, uint16_t port_no, - struct ofpbuf *packet, pinsched_tx_cb *cb, void *aux) -{ - if (!ps) { - cb(packet, aux); - } else if (!ps->n_queued && get_token(ps)) { - /* In the common case where we are not constrained by the rate limit, - * let the packet take the normal path. */ - ps->n_normal++; - cb(packet, aux); - } else { - /* Otherwise queue it up for the periodic callback to drain out. */ - struct ovs_queue *q; - - /* We are called with a buffer obtained from dpif_recv() that has much - * more allocated space than actual content most of the time. Since - * we're going to store the packet for some time, free up that - * otherwise wasted space. */ - ofpbuf_trim(packet); - - if (ps->n_queued >= ps->burst_limit) { - drop_packet(ps); - } - q = port_array_get(&ps->queues, port_no); - if (!q) { - q = xmalloc(sizeof *q); - queue_init(q); - port_array_set(&ps->queues, port_no, q); - } - queue_push_tail(q, packet); - ps->n_queued++; - ps->n_limited++; - } -} - -static void -pinsched_status_cb(struct status_reply *sr, void *ps_) -{ - struct pinsched *ps = ps_; - - status_reply_put(sr, "normal=%llu", ps->n_normal); - status_reply_put(sr, "limited=%llu", ps->n_limited); - status_reply_put(sr, "queue-dropped=%llu", ps->n_queue_dropped); -} - -void -pinsched_run(struct pinsched *ps, pinsched_tx_cb *cb, void *aux) -{ - if (ps) { - int i; - - /* Drain some packets out of the bucket if possible, but limit the - * number of iterations to allow other code to get work done too. */ - refill_bucket(ps); - for (i = 0; ps->n_queued && get_token(ps) && i < 50; i++) { - cb(get_tx_packet(ps), aux); - } - } -} - -void -pinsched_wait(struct pinsched *ps) -{ - if (ps && ps->n_queued) { - if (ps->tokens >= 1000) { - /* We can transmit more packets as soon as we're called again. */ - poll_immediate_wake(); - } else { - /* We have to wait for the bucket to re-fill. We could calculate - * the exact amount of time here for increased smoothness. */ - poll_timer_wait(TIME_UPDATE_INTERVAL / 2); - } - } -} - -/* Creates and returns a scheduler for sending packet-in messages. */ -struct pinsched * -pinsched_create(int rate_limit, int burst_limit, struct switch_status *ss) -{ - struct pinsched *ps; - - ps = xcalloc(1, sizeof *ps); - port_array_init(&ps->queues); - ps->n_queued = 0; - ps->last_tx_port = PORT_ARRAY_SIZE; - ps->last_fill = time_msec(); - ps->tokens = rate_limit * 100; - ps->n_txq = 0; - ps->n_normal = 0; - ps->n_limited = 0; - ps->n_queue_dropped = 0; - pinsched_set_limits(ps, rate_limit, burst_limit); - - if (ss) { - ps->ss_cat = switch_status_register(ss, "rate-limit", - pinsched_status_cb, ps); - } - - return ps; -} - -void -pinsched_destroy(struct pinsched *ps) -{ - if (ps) { - struct ovs_queue *queue; - unsigned int port_no; - - PORT_ARRAY_FOR_EACH (queue, &ps->queues, port_no) { - queue_destroy(queue); - free(queue); - } - port_array_destroy(&ps->queues); - switch_status_unregister(ps->ss_cat); - free(ps); - } -} - -void -pinsched_set_limits(struct pinsched *ps, int rate_limit, int burst_limit) -{ - if (rate_limit <= 0) { - rate_limit = 1000; - } - if (burst_limit <= 0) { - burst_limit = rate_limit / 4; - } - burst_limit = MAX(burst_limit, 1); - burst_limit = MIN(burst_limit, INT_MAX / 1000); - - ps->rate_limit = rate_limit; - ps->burst_limit = burst_limit; - while (ps->n_queued > burst_limit) { - drop_packet(ps); - } -} diff --git a/secchan/pinsched.h b/secchan/pinsched.h deleted file mode 100644 index aead7a45..00000000 --- a/secchan/pinsched.h +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PINSCHED_H -#define PINSCHED_H_H 1 - -#include - -struct ofpbuf; -struct switch_status; - -typedef void pinsched_tx_cb(struct ofpbuf *, void *aux); -struct pinsched *pinsched_create(int rate_limit, int burst_limit, - struct switch_status *); -void pinsched_set_limits(struct pinsched *, int rate_limit, int burst_limit); -void pinsched_destroy(struct pinsched *); -void pinsched_send(struct pinsched *, uint16_t port_no, struct ofpbuf *, - pinsched_tx_cb *, void *aux); -void pinsched_run(struct pinsched *, pinsched_tx_cb *, void *aux); -void pinsched_wait(struct pinsched *); - -#endif /* pinsched.h */ diff --git a/secchan/pktbuf.c b/secchan/pktbuf.c deleted file mode 100644 index b4198a8f..00000000 --- a/secchan/pktbuf.c +++ /dev/null @@ -1,150 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "pktbuf.h" -#include -#include -#include "coverage.h" -#include "ofpbuf.h" -#include "timeval.h" -#include "util.h" -#include "vconn.h" - -#define THIS_MODULE VLM_pktbuf -#include "vlog.h" - -/* Buffers are identified by a 32-bit opaque ID. We divide the ID - * into a buffer number (low bits) and a cookie (high bits). The buffer number - * is an index into an array of buffers. The cookie distinguishes between - * different packets that have occupied a single buffer. Thus, the more - * buffers we have, the lower-quality the cookie... */ -#define PKTBUF_BITS 8 -#define PKTBUF_MASK (PKTBUF_CNT - 1) -#define PKTBUF_CNT (1u << PKTBUF_BITS) - -#define COOKIE_BITS (32 - PKTBUF_BITS) -#define COOKIE_MAX ((1u << COOKIE_BITS) - 1) - -#define OVERWRITE_MSECS 5000 - -struct packet { - struct ofpbuf *buffer; - uint32_t cookie; - long long int timeout; - uint16_t in_port; -}; - -struct pktbuf { - struct packet packets[PKTBUF_CNT]; - unsigned int buffer_idx; -}; - -int -pktbuf_capacity(void) -{ - return PKTBUF_CNT; -} - -struct pktbuf * -pktbuf_create(void) -{ - return xcalloc(1, sizeof *pktbuf_create()); -} - -void -pktbuf_destroy(struct pktbuf *pb) -{ - if (pb) { - size_t i; - - for (i = 0; i < PKTBUF_CNT; i++) { - ofpbuf_delete(pb->packets[i].buffer); - } - free(pb); - } -} - -uint32_t -pktbuf_save(struct pktbuf *pb, struct ofpbuf *buffer, uint16_t in_port) -{ - struct packet *p = &pb->packets[pb->buffer_idx]; - pb->buffer_idx = (pb->buffer_idx + 1) & PKTBUF_MASK; - if (p->buffer) { - if (time_msec() < p->timeout) { - return UINT32_MAX; - } - ofpbuf_delete(p->buffer); - } - - /* Don't use maximum cookie value since all-1-bits ID is special. */ - if (++p->cookie >= COOKIE_MAX) { - p->cookie = 0; - } - p->buffer = ofpbuf_clone(buffer); - p->timeout = time_msec() + OVERWRITE_MSECS; - p->in_port = in_port; - return (p - pb->packets) | (p->cookie << PKTBUF_BITS); -} - -int -pktbuf_retrieve(struct pktbuf *pb, uint32_t id, struct ofpbuf **bufferp, - uint16_t *in_port) -{ - static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 20); - struct packet *p; - int error; - - if (!pb) { - VLOG_WARN_RL(&rl, "attempt to send buffered packet via connection " - "without buffers"); - return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_COOKIE); - } - - p = &pb->packets[id & PKTBUF_MASK]; - if (p->cookie == id >> PKTBUF_BITS) { - struct ofpbuf *buffer = p->buffer; - if (buffer) { - *bufferp = buffer; - *in_port = p->in_port; - p->buffer = NULL; - COVERAGE_INC(pktbuf_retrieved); - return 0; - } else { - COVERAGE_INC(pktbuf_reuse_error); - VLOG_WARN_RL(&rl, "attempt to reuse buffer %08"PRIx32, id); - error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BUFFER_EMPTY); - } - } else { - COVERAGE_INC(pktbuf_bad_cookie); - VLOG_WARN_RL(&rl, "cookie mismatch: %08"PRIx32" != %08"PRIx32, - id, (id & PKTBUF_MASK) | (p->cookie << PKTBUF_BITS)); - error = ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_COOKIE); - } - *bufferp = NULL; - *in_port = -1; - return error; -} - -void -pktbuf_discard(struct pktbuf *pb, uint32_t id) -{ - struct packet *p = &pb->packets[id & PKTBUF_MASK]; - if (p->cookie == id >> PKTBUF_BITS) { - ofpbuf_delete(p->buffer); - p->buffer = NULL; - } -} diff --git a/secchan/pktbuf.h b/secchan/pktbuf.h deleted file mode 100644 index b27b7490..00000000 --- a/secchan/pktbuf.h +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef PKTBUF_H -#define PKTBUF_H 1 - -#include - -struct pktbuf; -struct ofpbuf; - -int pktbuf_capacity(void); - -struct pktbuf *pktbuf_create(void); -void pktbuf_destroy(struct pktbuf *); -uint32_t pktbuf_save(struct pktbuf *, struct ofpbuf *buffer, uint16_t in_port); -int pktbuf_retrieve(struct pktbuf *, uint32_t id, struct ofpbuf **bufferp, - uint16_t *in_port); -void pktbuf_discard(struct pktbuf *, uint32_t id); - -#endif /* pktbuf.h */ diff --git a/secchan/secchan.8.in b/secchan/secchan.8.in deleted file mode 100644 index 3b781aeb..00000000 --- a/secchan/secchan.8.in +++ /dev/null @@ -1,469 +0,0 @@ -.TH secchan 8 "March 2009" "Open vSwitch" "Open vSwitch Manual" -.ds PN secchan - -.SH NAME -secchan \- OpenFlow switch implementation - -.SH SYNOPSIS -.B secchan -[\fIoptions\fR] \fIdatapath\fR [\fIcontroller\fR] - -.SH DESCRIPTION -The \fBsecchan\fR program implements an OpenFlow switch using a -flow-based datapath. \fBsecchan\fR connects to an OpenFlow controller -over TCP or SSL. - -The mandatory \fIdatapath\fR argument argument specifies the local datapath -to relay. It takes one of the following forms: - -.so lib/dpif.man - -.PP -The optional \fIcontroller\fR argument specifies how to connect to -the OpenFlow controller. It takes one of the following forms: - -.RS -.TP -\fBssl:\fIhost\fR[\fB:\fIport\fR] -The specified SSL \fIport\fR (default: 6633) on the given remote -\fIhost\fR. The \fB--private-key\fR, \fB--certificate\fR, and -\fB--ca-cert\fR options are mandatory when this form is used. - -.TP -\fBtcp:\fIhost\fR[\fB:\fIport\fR] -The specified TCP \fIport\fR (default: 6633) on the given remote -\fIhost\fR. - -.TP -\fBunix:\fIfile\fR -The Unix domain server socket named \fIfile\fR. -.RE - -.PP -If \fIcontroller\fR is omitted, \fBsecchan\fR attempts to discover the -location of the controller automatically (see below). - -.SS "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: - -.IP out-of-band -In this configuration, OpenFlow traffic uses a network separate from -the data traffic that it controls, that is, the switch does not use -any of the network devices added to the datapath with \fBovs\-dpctl -add\-if\fR in its communication with the controller. - -To use \fBsecchan\fR in a network with out-of-band control, specify -\fB--out-of-band\fR on the \fBsecchan\fR command line. The control -network must be configured separately, before or after \fBsecchan\fR -is started. - -.IP in-band -In this configuration, 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 added to the datapath with \fBovs\-dpctl -add\-if\fR. This configuration is often more convenient than -out-of-band control, because it is not necessary to maintain two -independent networks. - -In-band control is the default for \fBsecchan\fR, so no special -command-line option is required. - -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 \fBovs\-dpctl add\-if\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" -To configure in-band control manually, specify the location of the -controller on the \fBsecchan\fR command line as the \fIcontroller\fR -argument. You must also configure the network device for the OpenFlow -``local port'' to allow \fBsecchan\fR to connect to that controller. -The OpenFlow local port is a virtual network port that \fBsecchan\fR -bridges to the physical switch ports. The name of the local port for -a given \fIdatapath\fR may be seen by running \fBovs\-dpctl show -\fIdatapath\fR; the local port is listed as port 0 in \fBshow\fR's -output. - -.IP -Before \fBsecchan\fR starts, the local port network device is not -bridged to any physical network, so the next step depends on whether -connectivity is required to configure the device's IP address. If the -switch has a static IP address, you may configure its IP address now -with a command such as -.B ifconfig of0 192.168.1.1 -and then invoke \fBsecchan\fR. - -On the other hand, if the switch does not have a static IP address, -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 -the local port network device, and start the DHCP client afterward. -.RE - -.SH OPTIONS -.SS "Controller Discovery 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^tcp:.*\fR otherwise -(meaning that only TCP controller connections 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. - -.SS "Networking Options" -.TP -\fB--datapath-id=\fIdpid\fR -Sets \fIdpid\fR, which must consist of exactly 12 hexadecimal digits, -as the datapath ID that the switch will use to identify itself to the -OpenFlow controller. - -If this option is omitted, the default datapath ID is taken from the -Ethernet address of the datapath's local port (which is typically -randomly generated). - -.TP -\fB--mgmt-id=\fImgmtid\fR -Sets \fImgmtid\fR, which must consist of exactly 12 hexadecimal -digits, as the switch's management ID. - -If this option is omitted, the management ID defaults to 0, signaling -to the controller that management is supported but not configured. - -.TP -\fB--fail=\fR[\fBopen\fR|\fBclosed\fR] -The controller is, ordinarily, responsible for setting up all flows on -the OpenFlow switch. Thus, if the connection to the controller fails, -no new network connections can be set up. If the connection to the -controller stays down long enough, no packets can pass through the -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 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. - -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--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. - -When fail-open mode is configured, changing the inactivity probe -interval also changes the interval before entering fail-open mode (see -above). - -.TP -\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=closed\fR and \fB--out-of-band\fR are -specified, 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 \fBovs\-ofctl\fR. The \fImethod\fR -must be given as one of the passive OpenFlow connection methods listed -below. This option may be specified multiple times to listen to -multiple connection methods. - -.RS -.TP -\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR] -Listens for SSL connections on \fIport\fR (default: 6633). The -\fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR options -are mandatory when this form is used. -By default, \fB\*(PN\fR listens for connections to any local IP -address, but \fIip\fR may be specified to listen only for connections -to the given \fIip\fR. - -.TP -\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR] -Listens for TCP connections on \fIport\fR (default: 6633). -By default, \fB\*(PN\fR listens for connections to any local IP -address, but \fIip\fR may be specified to listen only for connections -to the given \fIip\fR. - -.TP -\fBpunix:\fIfile\fR -Listens for connections on Unix domain server socket named \fIfile\fR. -.RE - -.TP -\fB--snoop=\fImethod\fR -Configures the switch to additionally listen for incoming OpenFlow -connections for controller connection snooping. The \fImethod\fR must -be given as one of the passive OpenFlow connection methods listed -under the \fB--listen\fR option above. This option may be specified -multiple times to listen to multiple connection methods. - -If \fBovs\-ofctl monitor\fR is used to connect to \fImethod\fR specified on -\fB--snoop\fR, it will display all the OpenFlow messages traveling -between the switch and its controller on the primary OpenFlow -connection. This can be useful for debugging switch and controller -problems. - -.TP -\fB--in-band\fR, \fB--out-of-band\fR -Configures \fBsecchan\fR to operate in in-band or out-of-band control -mode (see \fBContacting the Controller\fR above). When neither option -is given, the default is in-band control. - -.TP -\fB--netflow=\fIhost\fB:\fIport\fR -Configures the given UDP \fIport\fR on the specified IP \fIhost\fR as -a recipient of NetFlow messages for expired flows. - -This option may be specified multiple times to configure additional -NetFlow collectors. - -.SS "Rate-Limiting Options" - -These options configure how the switch applies a ``token bucket'' to -limit the rate at which packets in unknown flows are forwarded to an -OpenFlow controller for flow-setup processing. This feature prevents -a single OpenFlow switch from overwhelming a controller. - -.TP -\fB--rate-limit\fR[\fB=\fIrate\fR] -. -Limits the maximum rate at which packets will be forwarded to the -OpenFlow controller to \fIrate\fR packets per second. If \fIrate\fR -is not specified then the default of 1,000 packets per second is used. - -If \fB--rate-limit\fR is not used, then the switch does not limit the -rate at which packets are forwarded to the controller. - -.TP -\fB--burst-limit=\fIburst\fR -. -Sets the maximum number of unused packet credits that the switch will -allow to accumulate during time in which no packets are being -forwarded to the OpenFlow controller to \fIburst\fR (measured in -packets). The default \fIburst\fR is one-quarter of the \fIrate\fR -specified on \fB--rate-limit\fR. - -This option takes effect only when \fB--rate-limit\fR is also specified. - -.SS "Remote Command Execution Options" - -.TP -\fB--command-acl=\fR[\fB!\fR]\fIglob\fR[\fB,\fR[\fB!\fR]\fIglob\fR...] -Configures the commands that remote OpenFlow connections are allowed -to invoke using (e.g.) \fBovs\-ofctl execute\fR. The argument is a -comma-separated sequence of shell glob patterns. A glob pattern -specified without a leading \fB!\fR is a ``whitelist'' that specifies -a set of commands that are that may be invoked, whereas a pattern that -does begin with \fB!\fR is a ``blacklist'' that specifies commands -that may not be invoked. To be permitted, a command name must be -whitelisted and must not be blacklisted; -e.g. \fB--command-acl=up*,!upgrade\fR would allow any command whose name -begins with \fBup\fR except for the command named \fBupgrade\fR. -Command names that include characters other than upper- and lower-case -English letters, digits, and the underscore and hyphen characters are -unconditionally disallowed. - -When the whitelist and blacklist permit a command name, \fBsecchan\fR -looks for a program with the same name as the command in the commands -directory (see below). Other directories are not searched. - -.TP -\fB--command-dir=\fIdirectory\fR -Sets the directory searched for remote command execution to -\fBdirectory\fR. The default directory is -\fB@pkgdatadir@/commands\fR. - -.SS "Daemon Options" -.so lib/daemon.man - -.SS "Public Key Infrastructure Options" - -.TP -\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR -Specifies a PEM file containing the private key used as the switch's -identity for SSL connections to the controller. - -.TP -\fB-c\fR, \fB--certificate=\fIcert.pem\fR -Specifies a PEM file containing a certificate, signed by the -controller's certificate authority (CA), that certifies the switch's -private key to identify a trustworthy switch. - -.TP -\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR -Specifies a PEM file containing the CA certificate used to verify that -the switch is connected to a trustworthy controller. - -.TP -\fB--bootstrap-ca-cert=\fIcacert.pem\fR -When \fIcacert.pem\fR exists, this option has the same effect as -\fB-C\fR or \fB--ca-cert\fR. If it does not exist, then \fBsecchan\fR -will attempt to obtain the CA certificate from the controller on its -first SSL connection and save it to the named PEM file. If it is -successful, it will immediately drop the connection and reconnect, and -from then on all SSL connections must be authenticated by a -certificate signed by the CA certificate thus obtained. - -\fBThis option exposes the SSL connection to a man-in-the-middle -attack obtaining the initial CA certificate\fR, but it may be useful -for bootstrapping. - -This option is only useful if the controller sends its CA certificate -as part of the SSL certificate chain. The SSL protocol does not -require the controller to send the CA certificate, but -\fBcontroller\fR(8) can be configured to do so with the -\fB--peer-ca-cert\fR option. - -.SS "Logging Options" -.so lib/vlog.man -.SS "Other Options" -.so lib/common.man -.so lib/leak-checker.man - -.SH "SEE ALSO" - -.BR ovs\-appctl (8), -.BR ovs\-controller (8), -.BR ovs\-discover (8), -.BR ovs\-dpctl (8), -.BR ovs\-ofctl (8), -.BR ovs\-pki (8), -.BR ovs\-vswitchd.conf (5) diff --git a/secchan/status.c b/secchan/status.c deleted file mode 100644 index c7598f37..00000000 --- a/secchan/status.c +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#include -#include "status.h" -#include -#include -#include -#include -#include -#include "dynamic-string.h" -#include "list.h" -#include "ofpbuf.h" -#include "ofproto.h" -#include "openflow/nicira-ext.h" -#include "rconn.h" -#include "svec.h" -#include "timeval.h" -#include "vconn.h" - -#define THIS_MODULE VLM_status -#include "vlog.h" - -struct status_category { - struct list node; - char *name; - void (*cb)(struct status_reply *, void *aux); - void *aux; -}; - -struct switch_status { - time_t booted; - struct status_category *config_cat; - struct status_category *switch_cat; - struct list categories; -}; - -struct status_reply { - struct status_category *category; - struct ds request; - struct ds output; -}; - -int -switch_status_handle_request(struct switch_status *ss, struct rconn *rconn, - struct nicira_header *request) -{ - struct status_category *c; - struct nicira_header *reply; - struct status_reply sr; - struct ofpbuf *b; - int retval; - - sr.request.string = (void *) (request + 1); - sr.request.length = ntohs(request->header.length) - sizeof *request; - ds_init(&sr.output); - LIST_FOR_EACH (c, struct status_category, node, &ss->categories) { - if (!memcmp(c->name, sr.request.string, - MIN(strlen(c->name), sr.request.length))) { - sr.category = c; - c->cb(&sr, c->aux); - } - } - reply = make_openflow_xid(sizeof *reply + sr.output.length, - OFPT_VENDOR, request->header.xid, &b); - reply->vendor = htonl(NX_VENDOR_ID); - reply->subtype = htonl(NXT_STATUS_REPLY); - memcpy(reply + 1, sr.output.string, sr.output.length); - retval = rconn_send(rconn, b, NULL); - if (retval && retval != EAGAIN) { - VLOG_WARN("send failed (%s)", strerror(retval)); - } - ds_destroy(&sr.output); - return 0; -} - -void -rconn_status_cb(struct status_reply *sr, void *rconn_) -{ - struct rconn *rconn = rconn_; - time_t now = time_now(); - - status_reply_put(sr, "name=%s", rconn_get_name(rconn)); - status_reply_put(sr, "state=%s", rconn_get_state(rconn)); - status_reply_put(sr, "backoff=%d", rconn_get_backoff(rconn)); - status_reply_put(sr, "probe-interval=%d", rconn_get_probe_interval(rconn)); - status_reply_put(sr, "is-connected=%s", - rconn_is_connected(rconn) ? "true" : "false"); - status_reply_put(sr, "sent-msgs=%u", rconn_packets_sent(rconn)); - status_reply_put(sr, "received-msgs=%u", rconn_packets_received(rconn)); - status_reply_put(sr, "attempted-connections=%u", - rconn_get_attempted_connections(rconn)); - status_reply_put(sr, "successful-connections=%u", - rconn_get_successful_connections(rconn)); - status_reply_put(sr, "last-connection=%ld", - (long int) (now - rconn_get_last_connection(rconn))); - status_reply_put(sr, "last-received=%ld", - (long int) (now - rconn_get_last_received(rconn))); - status_reply_put(sr, "time-connected=%lu", - rconn_get_total_time_connected(rconn)); - status_reply_put(sr, "state-elapsed=%u", rconn_get_state_elapsed(rconn)); -} - -static void -config_status_cb(struct status_reply *sr, void *ofproto_) -{ - const struct ofproto *ofproto = ofproto_; - struct svec listeners; - int probe_interval, max_backoff; - size_t i; - - svec_init(&listeners); - ofproto_get_listeners(ofproto, &listeners); - for (i = 0; i < listeners.n; i++) { - status_reply_put(sr, "management%zu=%s", i, listeners.names[i]); - } - svec_destroy(&listeners); - - probe_interval = ofproto_get_probe_interval(ofproto); - if (probe_interval) { - status_reply_put(sr, "probe-interval=%d", probe_interval); - } - - max_backoff = ofproto_get_max_backoff(ofproto); - if (max_backoff) { - status_reply_put(sr, "max-backoff=%d", max_backoff); - } -} - -static void -switch_status_cb(struct status_reply *sr, void *ss_) -{ - struct switch_status *ss = ss_; - time_t now = time_now(); - - status_reply_put(sr, "now=%ld", (long int) now); - status_reply_put(sr, "uptime=%ld", (long int) (now - ss->booted)); - status_reply_put(sr, "pid=%ld", (long int) getpid()); -} - -struct switch_status * -switch_status_create(const struct ofproto *ofproto) -{ - struct switch_status *ss = xcalloc(1, sizeof *ss); - ss->booted = time_now(); - list_init(&ss->categories); - ss->config_cat = switch_status_register(ss, "config", config_status_cb, - (void *) ofproto); - ss->switch_cat = switch_status_register(ss, "switch", switch_status_cb, - ss); - return ss; -} - -void -switch_status_destroy(struct switch_status *ss) -{ - if (ss) { - /* Orphan any remaining categories, so that unregistering them later - * won't write to bad memory. */ - struct status_category *c, *next; - LIST_FOR_EACH_SAFE (c, next, - struct status_category, node, &ss->categories) { - list_init(&c->node); - } - switch_status_unregister(ss->config_cat); - switch_status_unregister(ss->switch_cat); - free(ss); - } -} - -struct status_category * -switch_status_register(struct switch_status *ss, - const char *category, - status_cb_func *cb, void *aux) -{ - struct status_category *c = xmalloc(sizeof *c); - c->cb = cb; - c->aux = aux; - c->name = xstrdup(category); - list_push_back(&ss->categories, &c->node); - return c; -} - -void -switch_status_unregister(struct status_category *c) -{ - if (c) { - if (!list_is_empty(&c->node)) { - list_remove(&c->node); - } - free(c->name); - free(c); - } -} - -void -status_reply_put(struct status_reply *sr, const char *content, ...) -{ - size_t old_length = sr->output.length; - size_t added; - va_list args; - - /* Append the status reply to the output. */ - ds_put_format(&sr->output, "%s.", sr->category->name); - va_start(args, content); - ds_put_format_valist(&sr->output, content, args); - va_end(args); - if (ds_last(&sr->output) != '\n') { - ds_put_char(&sr->output, '\n'); - } - - /* Drop what we just added if it doesn't match the request. */ - added = sr->output.length - old_length; - if (added < sr->request.length - || memcmp(&sr->output.string[old_length], - sr->request.string, sr->request.length)) { - ds_truncate(&sr->output, old_length); - } -} diff --git a/secchan/status.h b/secchan/status.h deleted file mode 100644 index 7856674b..00000000 --- a/secchan/status.h +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (c) 2008, 2009 Nicira Networks. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at: - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -#ifndef STATUS_H -#define STATUS_H 1 - -#include "compiler.h" - -struct nicira_header; -struct rconn; -struct secchan; -struct ofproto; -struct status_reply; - -struct switch_status *switch_status_create(const struct ofproto *); -void switch_status_destroy(struct switch_status *); - -int switch_status_handle_request(struct switch_status *, struct rconn *, - struct nicira_header *); - -typedef void status_cb_func(struct status_reply *, void *aux); -struct status_category *switch_status_register(struct switch_status *, - const char *category, - status_cb_func *, void *aux); -void switch_status_unregister(struct status_category *); - -void status_reply_put(struct status_reply *, const char *, ...) - PRINTF_FORMAT(2, 3); - -void rconn_status_cb(struct status_reply *, void *rconn_); - -#endif /* status.h */ diff --git a/tests/test-dhcp-client.c b/tests/test-dhcp-client.c index 7ea476ca..e4471c7b 100644 --- a/tests/test-dhcp-client.c +++ b/tests/test-dhcp-client.c @@ -176,8 +176,8 @@ usage(void) "\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" + " --vendor-class=STRING use STRING as vendor class; use\n" + " OpenFlow to imitate ovs-openflowd\n" " --no-resolv-conf do not update /etc/resolv.conf\n", program_name, program_name); vlog_usage(); diff --git a/utilities/.gitignore b/utilities/.gitignore index 32a7f2eb..ebbd6916 100644 --- a/utilities/.gitignore +++ b/utilities/.gitignore @@ -15,6 +15,8 @@ /ovs-kill.8 /ovs-ofctl /ovs-ofctl.8 +/ovs-openflowd +/ovs-openflowd.8 /ovs-parse-leaks /ovs-pki /ovs-pki-cgi diff --git a/utilities/automake.mk b/utilities/automake.mk index 97b827ac..5bf3cbb7 100644 --- a/utilities/automake.mk +++ b/utilities/automake.mk @@ -6,6 +6,7 @@ bin_PROGRAMS += \ utilities/ovs-dpctl \ utilities/ovs-kill \ utilities/ovs-ofctl \ + utilities/ovs-openflowd \ utilities/ovs-wdt noinst_PROGRAMS += utilities/nlmon bin_SCRIPTS += utilities/ovs-pki @@ -20,6 +21,7 @@ EXTRA_DIST += \ utilities/ovs-dpctl.8.in \ utilities/ovs-kill.8.in \ utilities/ovs-ofctl.8.in \ + utilities/ovs-openflowd.8.in \ utilities/ovs-parse-leaks.in \ utilities/ovs-pki-cgi.in \ utilities/ovs-pki.8.in \ @@ -32,6 +34,7 @@ DISTCLEANFILES += \ utilities/ovs-dpctl.8 \ utilities/ovs-kill.8 \ utilities/ovs-ofctl.8 \ + utilities/ovs-openflowd.8 \ utilities/ovs-parse-leaks \ utilities/ovs-pki \ utilities/ovs-pki.8 \ @@ -45,6 +48,7 @@ man_MANS += \ utilities/ovs-dpctl.8 \ utilities/ovs-kill.8 \ utilities/ovs-ofctl.8 \ + utilities/ovs-openflowd.8 \ utilities/ovs-pki.8 utilities_ovs_appctl_SOURCES = utilities/ovs-appctl.c @@ -68,6 +72,13 @@ utilities_ovs_kill_LDADD = lib/libopenvswitch.a utilities_ovs_ofctl_SOURCES = utilities/ovs-ofctl.c utilities_ovs_ofctl_LDADD = lib/libopenvswitch.a $(FAULT_LIBS) $(SSL_LIBS) +utilities_ovs_openflowd_SOURCES = utilities/ovs-openflowd.c +utilities_ovs_openflowd_LDADD = \ + ofproto/libofproto.a \ + lib/libopenvswitch.a \ + $(FAULT_LIBS) \ + $(SSL_LIBS) + utilities_ovs_wdt_SOURCES = utilities/ovs-wdt.c utilities_nlmon_SOURCES = utilities/nlmon.c diff --git a/utilities/ovs-appctl.8.in b/utilities/ovs-appctl.8.in index 9bf97fd2..d5e6b82e 100644 --- a/utilities/ovs-appctl.8.in +++ b/utilities/ovs-appctl.8.in @@ -79,7 +79,7 @@ expanded as follows: .RS .IP \fB%A\fR -The name of the application logging the message, e.g. \fBsecchan\fR. +The name of the application logging the message, e.g. \fBovs-vswitchd\fR. .IP \fB%c\fR The name of the module (as shown by \fBovs\-appctl --list\fR) logging @@ -163,4 +163,4 @@ error occurs. Use \fB-e help\fR to print a list of available commands. .BR ovs\-controller (8), .BR ovs\-dpctl (8), -.BR secchan (8) +.BR ovs\-openflowd (8) diff --git a/utilities/ovs-controller.8.in b/utilities/ovs-controller.8.in index 380ddeca..b6b05d07 100644 --- a/utilities/ovs-controller.8.in +++ b/utilities/ovs-controller.8.in @@ -82,7 +82,7 @@ already have the controller CA certificate for it to have any confidence in the controller's identity. However, this option allows a newly installed switch to obtain the controller CA certificate on first boot using, e.g., the \fB--bootstrap-ca-cert\fR option to -\fBsecchan\fR(8). +\fBovs\-openflowd\fR(8). .IP "\fB-n\fR, \fB--noflow\fR" By default, \fBovs\-controller\fR sets up a flow in each OpenFlow switch @@ -103,7 +103,7 @@ 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 switch can set up some flows on its own. To set the idle time for those flows, pass -\fB--max-idle\fR to \fBsecchan\fR (on the switch). +\fB--max-idle\fR to \fBovs\-openflowd\fR (on the switch). 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). @@ -133,6 +133,6 @@ To bind locally to port 6633 (the default) and wait for incoming connections fro .SH "SEE ALSO" -.BR secchan (8), +.BR ovs\-openflowd (8), .BR ovs\-appctl (8), .BR ovs\-dpctl (8) diff --git a/utilities/ovs-discover.8.in b/utilities/ovs-discover.8.in index eb83a5f8..fcb579ed 100644 --- a/utilities/ovs-discover.8.in +++ b/utilities/ovs-discover.8.in @@ -17,7 +17,7 @@ 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). +the \fBovs\-openflowd\fR command line (e.g. \fBssl:192.168.0.1\fR). When \fBovs\-discover\fR receives an acceptable response, it prints the details of the response on \fBstdout\fR. Then, by default, it @@ -115,5 +115,5 @@ arriving IP packets, will not. .SH "SEE ALSO" -.BR secchan (8), -.BR ovs-pki (8) +.BR ovs\-openflowd (8), +.BR ovs\-pki (8) diff --git a/utilities/ovs-dpctl.8.in b/utilities/ovs-dpctl.8.in index 0a1d6702..3d8854b5 100644 --- a/utilities/ovs-dpctl.8.in +++ b/utilities/ovs-dpctl.8.in @@ -110,9 +110,10 @@ up may be confused about their disappearance. .IP "\fBdump-groups \fIdp\fR" Prints to the console the sets of port groups maintained by datapath \fIdp\fR. Ordinarily there are at least 2 port groups in a datapath -that \fBsecchan\fR or \fBvswitch\fR is controlling: group 0 contains +that \fBovs\-openflowd\fR or \fBovs\-vswitch\fR is controlling: group +0 contains all ports except those disabled by STP, and group 1 contains all -ports. Additional groups might be used in the future. +ports. Additional or different groups might be used in the future. This command is primarily useful for debugging Open vSwitch. OpenFlow does not have a concept of port groups. @@ -141,7 +142,7 @@ Creates datapath number 0. Adds two network devices to the new datapath. .PP -At this point one would ordinarily start \fBsecchan\fR(8) on +At this point one would ordinarily start \fBovs\-openflowd\fR(8) on \fBdp0\fR, transforming \fBdp0\fR into an OpenFlow switch. Then, when the switch and the datapath is no longer needed: @@ -155,6 +156,6 @@ Deletes the datapath. .SH "SEE ALSO" -.BR secchan (8), .BR ovs\-appctl (8), +.BR ovs\-openflowd (8), .BR ovs\-vswitchd (8) diff --git a/utilities/ovs-monitor b/utilities/ovs-monitor index 215032ae..40ad64b3 100755 --- a/utilities/ovs-monitor +++ b/utilities/ovs-monitor @@ -16,8 +16,8 @@ PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin -SECCHAN_PID=/var/run/secchan.pid -SECCHAN_SOCK=/var/run/secchan.mgmt +OPENFLOWD_PID=/var/run/ovs-openflowd.pid +OPENFLOWD_SOCK=/var/run/ovs-openflowd.mgmt LOG_FILE=/var/log/openflow/monitor INTERVAL=1 FAIL_THRESH=3 @@ -27,8 +27,8 @@ usage() { echo echo "OPTIONS:" echo " -h Show this message" - echo " -p PID file for secchan (default: $SECCHAN_PID)" - echo " -s Unix socket for secchan (default: $SECCHAN_SOCK)" + echo " -p PID file for ovs-openflowd (default: $OPENFLOWD_PID)" + echo " -s Unix socket for ovs-openflowd (default: $OPENFLOWD_SOCK)" echo " -l File to log messages (default: $LOG_FILE)" echo " -i Interval to send probes in seconds (default: $INTERVAL)" echo " -c Number of failed probes before reboot (default: $FAIL_THRESH)" @@ -48,11 +48,11 @@ while getopts "hp:s:l:i:c:" OPTION; do ;; p) - SECCHAN_PID=$OPTARG + OPENFLOWD_PID=$OPTARG ;; s) - SECCHAN_SOCK=$OPTARG + OPENFLOWD_SOCK=$OPTARG ;; l) @@ -73,14 +73,14 @@ while getopts "hp:s:l:i:c:" OPTION; do done -if [ ! -f $SECCHAN_PID ]; then - log "No secchan pid file: ${SECCHAN_PID}" - echo "No secchan pid file: ${SECCHAN_PID}" +if [ ! -f $OPENFLOWD_PID ]; then + log "No ovs-openflowd pid file: ${OPENFLOWD_PID}" + echo "No ovs-openflowd pid file: ${OPENFLOWD_PID}" fi -if [ ! -S $SECCHAN_SOCK ]; then - log "No secchan sock file: ${SECCHAN_SOCK}" - echo "No secchan sock file: ${SECCHAN_SOCK}" +if [ ! -S $OPENFLOWD_SOCK ]; then + log "No ovs-openflowd sock file: ${OPENFLOWD_SOCK}" + echo "No ovs-openflowd sock file: ${OPENFLOWD_SOCK}" fi if [ ! -d `dirname $LOG_FILE` ]; then @@ -88,17 +88,17 @@ if [ ! -d `dirname $LOG_FILE` ]; then fi let DP_DOWN=0 -let SECCHAN_DOWN=0 +let OPENFLOWD_DOWN=0 log "===== Starting Monitor ====" while `/bin/true`; do - # Only check for liveness if the secchan's PID file exists. The PID - # file is removed when secchan is brought down gracefully. - if [ -f $SECCHAN_PID ]; then - pid=`cat $SECCHAN_PID` + # Only check for liveness if ovs-openflowd's PID file exists. The PID + # file is removed when ovs-openflowd is brought down gracefully. + if [ -f $OPENFLOWD_PID ]; then + pid=`cat $OPENFLOWD_PID` if [ -d /proc/$pid ]; then - # Check if the secchan and datapath still can communicate - if [ -S $SECCHAN_SOCK ]; then - ovs-ofctl probe -t 2 unix:$SECCHAN_SOCK + # Check if the ovs-openflowd and datapath still can communicate + if [ -S $OPENFLOWD_SOCK ]; then + ovs-ofctl probe -t 2 unix:$OPENFLOWD_SOCK if [ $? -ne 0 ]; then log "datapath probe failed" let DP_DOWN++ @@ -106,15 +106,15 @@ while `/bin/true`; do let DP_DOWN=0 fi fi - let SECCHAN_DOWN=0 + let OPENFLOWD_DOWN=0 else - log "secchan probe failed" - let SECCHAN_DOWN++ + log "ovs-openflowd probe failed" + let OPENFLOWD_DOWN++ fi fi - if [ $SECCHAN_DOWN -ge $FAIL_THRESH ]; then - log "Failed to probe secchan after ${SECCHAN_DOWN} tries...rebooting!" + if [ $OPENFLOWD_DOWN -ge $FAIL_THRESH ]; then + log "Failed to probe ovs-openflowd after ${OPENFLOWD_DOWN} tries...rebooting!" reboot fi diff --git a/utilities/ovs-ofctl.8.in b/utilities/ovs-ofctl.8.in index eb98043c..fab61e4f 100644 --- a/utilities/ovs-ofctl.8.in +++ b/utilities/ovs-ofctl.8.in @@ -147,7 +147,7 @@ syntax of \fIflows\fR. \fBmonitor \fIswitch\fR [\fImiss-len\fR [\fIsend-exp]] Connects to \fIswitch\fR and prints to the console all OpenFlow messages received. Usually, \fIswitch\fR should specify a connection -named on \fBsecchan\fR(8)'s \fB-l\fR or \fB--listen\fR command line +named on \fBovs\-openflowd\fR(8)'s \fB-l\fR or \fB--listen\fR command line option. If \fImiss-len\fR is provided, \fBovs\-ofctl\fR sends an OpenFlow ``set @@ -327,7 +327,7 @@ 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). +\fBovs\-openflowd\fR(8) for information on the \fBof\fIn\fR network device). .IP \fBdrop\fR Discards the packet, so no further processing or forwarding takes place. @@ -471,7 +471,7 @@ The following examples assume that an OpenFlow switch on the local host has been configured to listen for management connections on a Unix domain socket named \fB@RUNDIR@/openflow.sock\fR, e.g. by specifying \fB--listen=punix:@RUNDIR@/openflow.sock\fR on the -\fBsecchan\fR(8) command line. +\fBovs\-openflowd\fR(8) command line. .TP \fBovs\-ofctl dump-tables unix:@RUNDIR@/openflow.sock\fR diff --git a/utilities/ovs-openflowd.8.in b/utilities/ovs-openflowd.8.in new file mode 100644 index 00000000..3684fab4 --- /dev/null +++ b/utilities/ovs-openflowd.8.in @@ -0,0 +1,469 @@ +.TH ovs\-openflowd 8 "March 2009" "Open vSwitch" "Open vSwitch Manual" +.ds PN ovs\-openflowd + +.SH NAME +ovs\-openflowd \- OpenFlow switch implementation + +.SH SYNOPSIS +.B ovs\-openflowd +[\fIoptions\fR] \fIdatapath\fR [\fIcontroller\fR] + +.SH DESCRIPTION +The \fBovs\-openflowd\fR program implements an OpenFlow switch using a +flow-based datapath. \fBovs\-openflowd\fR connects to an OpenFlow controller +over TCP or SSL. + +The mandatory \fIdatapath\fR argument argument specifies the local datapath +to relay. It takes one of the following forms: + +.so lib/dpif.man + +.PP +The optional \fIcontroller\fR argument specifies how to connect to +the OpenFlow controller. It takes one of the following forms: + +.RS +.TP +\fBssl:\fIhost\fR[\fB:\fIport\fR] +The specified SSL \fIport\fR (default: 6633) on the given remote +\fIhost\fR. The \fB--private-key\fR, \fB--certificate\fR, and +\fB--ca-cert\fR options are mandatory when this form is used. + +.TP +\fBtcp:\fIhost\fR[\fB:\fIport\fR] +The specified TCP \fIport\fR (default: 6633) on the given remote +\fIhost\fR. + +.TP +\fBunix:\fIfile\fR +The Unix domain server socket named \fIfile\fR. +.RE + +.PP +If \fIcontroller\fR is omitted, \fBovs\-openflowd\fR attempts to discover the +location of the controller automatically (see below). + +.SS "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: + +.IP out-of-band +In this configuration, OpenFlow traffic uses a network separate from +the data traffic that it controls, that is, the switch does not use +any of the network devices added to the datapath with \fBovs\-dpctl +add\-if\fR in its communication with the controller. + +To use \fBovs\-openflowd\fR in a network with out-of-band control, specify +\fB--out-of-band\fR on the \fBovs\-openflowd\fR command line. The control +network must be configured separately, before or after \fBovs\-openflowd\fR +is started. + +.IP in-band +In this configuration, 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 added to the datapath with \fBovs\-dpctl +add\-if\fR. This configuration is often more convenient than +out-of-band control, because it is not necessary to maintain two +independent networks. + +In-band control is the default for \fBovs\-openflowd\fR, so no special +command-line option is required. + +With in-band control, the location of the controller can be configured +manually or discovered automatically: + +.RS +.IP "controller discovery" +To make \fBovs\-openflowd\fR discover the location of the controller +automatically, do not specify the location of the controller on the +\fBovs\-openflowd\fR command line. + +In this mode, \fBovs\-openflowd\fR will broadcast a DHCP request with vendor +class identifier \fBOpenFlow\fR across the network devices added to +the datapath with \fBovs\-dpctl add\-if\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 \fBovs\-openflowd\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; \fBovs\-openflowd\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" +To configure in-band control manually, specify the location of the +controller on the \fBovs\-openflowd\fR command line as the \fIcontroller\fR +argument. You must also configure the network device for the OpenFlow +``local port'' to allow \fBovs\-openflowd\fR to connect to that controller. +The OpenFlow local port is a virtual network port that \fBovs\-openflowd\fR +bridges to the physical switch ports. The name of the local port for +a given \fIdatapath\fR may be seen by running \fBovs\-dpctl show +\fIdatapath\fR; the local port is listed as port 0 in \fBshow\fR's +output. + +.IP +Before \fBovs\-openflowd\fR starts, the local port network device is not +bridged to any physical network, so the next step depends on whether +connectivity is required to configure the device's IP address. If the +switch has a static IP address, you may configure its IP address now +with a command such as +.B ifconfig of0 192.168.1.1 +and then invoke \fBovs\-openflowd\fR. + +On the other hand, if the switch does not have a static IP address, +e.g. it obtains its IP address dynamically via DHCP, the DHCP client +will not be able to contact the DHCP server until the OpenFlow switch +has started up. Thus, start \fBovs\-openflowd\fR without configuring +the local port network device, and start the DHCP client afterward. +.RE + +.SH OPTIONS +.SS "Controller Discovery Options" +.TP +\fB--accept-vconn=\fIregex\fR +When \fBovs\-openflowd\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^tcp:.*\fR otherwise +(meaning that only TCP controller connections 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 \fBovs\-openflowd\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, \fBovs\-openflowd\fR +will not modify \fB/etc/resolv.conf\fR. + +\fBovs\-openflowd\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. + +.SS "Networking Options" +.TP +\fB--datapath-id=\fIdpid\fR +Sets \fIdpid\fR, which must consist of exactly 12 hexadecimal digits, +as the datapath ID that the switch will use to identify itself to the +OpenFlow controller. + +If this option is omitted, the default datapath ID is taken from the +Ethernet address of the datapath's local port (which is typically +randomly generated). + +.TP +\fB--mgmt-id=\fImgmtid\fR +Sets \fImgmtid\fR, which must consist of exactly 12 hexadecimal +digits, as the switch's management ID. + +If this option is omitted, the management ID defaults to 0, signaling +to the controller that management is supported but not configured. + +.TP +\fB--fail=\fR[\fBopen\fR|\fBclosed\fR] +The controller is, ordinarily, responsible for setting up all flows on +the OpenFlow switch. Thus, if the connection to the controller fails, +no new network connections can be set up. If the connection to the +controller stays down long enough, no packets can pass through the +switch at all. + +If this option is set to \fBopen\fR (the default), \fBovs\-openflowd\fR will +take over responsibility for setting up flows in the local datapath +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, \fBovs\-openflowd\fR causes the datapath to act +like an ordinary MAC-learning switch. \fBovs\-openflowd\fR will continue to +retry connection to the controller in the background and, when the +connection succeeds, it discontinues its fail-open behavior. + +If this option is set to \fBclosed\fR, then \fBovs\-openflowd\fR will not +set up flows on its own when the controller connection fails. + +.TP +\fB--inactivity-probe=\fIsecs\fR +When the OpenFlow switch is connected to the controller, the +switch 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 switch +assumes that the connection has been broken and attempts to reconnect. +The default is 15 seconds, and the minimum value is 5 seconds. + +When fail-open mode is configured, changing the inactivity probe +interval also changes the interval before entering fail-open mode (see +above). + +.TP +\fB--max-idle=\fIsecs\fR|\fBpermanent\fR +Sets \fIsecs\fR as the number of seconds that a flow set up by the +OpenFlow switch 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 switch will never +expire. The default is 15 seconds. + +Most flows are set up by the OpenFlow controller, not by the +switch. This option affects only the following flows, which the +OpenFlow switch sets up itself: + +.RS +.IP \(bu +When \fB--fail=open\fR is specified, flows set up when the +switch 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=closed\fR and \fB--out-of-band\fR are +specified, 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 \fBovs\-ofctl\fR. The \fImethod\fR +must be given as one of the passive OpenFlow connection methods listed +below. This option may be specified multiple times to listen to +multiple connection methods. + +.RS +.TP +\fBpssl:\fR[\fIport\fR][\fB:\fIip\fR] +Listens for SSL connections on \fIport\fR (default: 6633). The +\fB--private-key\fR, \fB--certificate\fR, and \fB--ca-cert\fR options +are mandatory when this form is used. +By default, \fB\*(PN\fR listens for connections to any local IP +address, but \fIip\fR may be specified to listen only for connections +to the given \fIip\fR. + +.TP +\fBptcp:\fR[\fIport\fR][\fB:\fIip\fR] +Listens for TCP connections on \fIport\fR (default: 6633). +By default, \fB\*(PN\fR listens for connections to any local IP +address, but \fIip\fR may be specified to listen only for connections +to the given \fIip\fR. + +.TP +\fBpunix:\fIfile\fR +Listens for connections on Unix domain server socket named \fIfile\fR. +.RE + +.TP +\fB--snoop=\fImethod\fR +Configures the switch to additionally listen for incoming OpenFlow +connections for controller connection snooping. The \fImethod\fR must +be given as one of the passive OpenFlow connection methods listed +under the \fB--listen\fR option above. This option may be specified +multiple times to listen to multiple connection methods. + +If \fBovs\-ofctl monitor\fR is used to connect to \fImethod\fR specified on +\fB--snoop\fR, it will display all the OpenFlow messages traveling +between the switch and its controller on the primary OpenFlow +connection. This can be useful for debugging switch and controller +problems. + +.TP +\fB--in-band\fR, \fB--out-of-band\fR +Configures \fBovs\-openflowd\fR to operate in in-band or out-of-band control +mode (see \fBContacting the Controller\fR above). When neither option +is given, the default is in-band control. + +.TP +\fB--netflow=\fIhost\fB:\fIport\fR +Configures the given UDP \fIport\fR on the specified IP \fIhost\fR as +a recipient of NetFlow messages for expired flows. + +This option may be specified multiple times to configure additional +NetFlow collectors. + +.SS "Rate-Limiting Options" + +These options configure how the switch applies a ``token bucket'' to +limit the rate at which packets in unknown flows are forwarded to an +OpenFlow controller for flow-setup processing. This feature prevents +a single OpenFlow switch from overwhelming a controller. + +.TP +\fB--rate-limit\fR[\fB=\fIrate\fR] +. +Limits the maximum rate at which packets will be forwarded to the +OpenFlow controller to \fIrate\fR packets per second. If \fIrate\fR +is not specified then the default of 1,000 packets per second is used. + +If \fB--rate-limit\fR is not used, then the switch does not limit the +rate at which packets are forwarded to the controller. + +.TP +\fB--burst-limit=\fIburst\fR +. +Sets the maximum number of unused packet credits that the switch will +allow to accumulate during time in which no packets are being +forwarded to the OpenFlow controller to \fIburst\fR (measured in +packets). The default \fIburst\fR is one-quarter of the \fIrate\fR +specified on \fB--rate-limit\fR. + +This option takes effect only when \fB--rate-limit\fR is also specified. + +.SS "Remote Command Execution Options" + +.TP +\fB--command-acl=\fR[\fB!\fR]\fIglob\fR[\fB,\fR[\fB!\fR]\fIglob\fR...] +Configures the commands that remote OpenFlow connections are allowed +to invoke using (e.g.) \fBovs\-ofctl execute\fR. The argument is a +comma-separated sequence of shell glob patterns. A glob pattern +specified without a leading \fB!\fR is a ``whitelist'' that specifies +a set of commands that are that may be invoked, whereas a pattern that +does begin with \fB!\fR is a ``blacklist'' that specifies commands +that may not be invoked. To be permitted, a command name must be +whitelisted and must not be blacklisted; +e.g. \fB--command-acl=up*,!upgrade\fR would allow any command whose name +begins with \fBup\fR except for the command named \fBupgrade\fR. +Command names that include characters other than upper- and lower-case +English letters, digits, and the underscore and hyphen characters are +unconditionally disallowed. + +When the whitelist and blacklist permit a command name, \fBovs\-openflowd\fR +looks for a program with the same name as the command in the commands +directory (see below). Other directories are not searched. + +.TP +\fB--command-dir=\fIdirectory\fR +Sets the directory searched for remote command execution to +\fBdirectory\fR. The default directory is +\fB@pkgdatadir@/commands\fR. + +.SS "Daemon Options" +.so lib/daemon.man + +.SS "Public Key Infrastructure Options" + +.TP +\fB-p\fR, \fB--private-key=\fIprivkey.pem\fR +Specifies a PEM file containing the private key used as the switch's +identity for SSL connections to the controller. + +.TP +\fB-c\fR, \fB--certificate=\fIcert.pem\fR +Specifies a PEM file containing a certificate, signed by the +controller's certificate authority (CA), that certifies the switch's +private key to identify a trustworthy switch. + +.TP +\fB-C\fR, \fB--ca-cert=\fIcacert.pem\fR +Specifies a PEM file containing the CA certificate used to verify that +the switch is connected to a trustworthy controller. + +.TP +\fB--bootstrap-ca-cert=\fIcacert.pem\fR +When \fIcacert.pem\fR exists, this option has the same effect as +\fB-C\fR or \fB--ca-cert\fR. If it does not exist, then \fBovs\-openflowd\fR +will attempt to obtain the CA certificate from the controller on its +first SSL connection and save it to the named PEM file. If it is +successful, it will immediately drop the connection and reconnect, and +from then on all SSL connections must be authenticated by a +certificate signed by the CA certificate thus obtained. + +\fBThis option exposes the SSL connection to a man-in-the-middle +attack obtaining the initial CA certificate\fR, but it may be useful +for bootstrapping. + +This option is only useful if the controller sends its CA certificate +as part of the SSL certificate chain. The SSL protocol does not +require the controller to send the CA certificate, but +\fBcontroller\fR(8) can be configured to do so with the +\fB--peer-ca-cert\fR option. + +.SS "Logging Options" +.so lib/vlog.man +.SS "Other Options" +.so lib/common.man +.so lib/leak-checker.man + +.SH "SEE ALSO" + +.BR ovs\-appctl (8), +.BR ovs\-controller (8), +.BR ovs\-discover (8), +.BR ovs\-dpctl (8), +.BR ovs\-ofctl (8), +.BR ovs\-pki (8), +.BR ovs\-vswitchd.conf (5) diff --git a/utilities/ovs-openflowd.c b/utilities/ovs-openflowd.c new file mode 100644 index 00000000..f60dea5a --- /dev/null +++ b/utilities/ovs-openflowd.c @@ -0,0 +1,564 @@ +/* + * Copyright (c) 2008, 2009 Nicira Networks. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at: + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "command-line.h" +#include "compiler.h" +#include "daemon.h" +#include "dirs.h" +#include "dpif.h" +#include "fault.h" +#include "leak-checker.h" +#include "list.h" +#include "netdev.h" +#include "ofpbuf.h" +#include "ofproto/ofproto.h" +#include "openflow/openflow.h" +#include "packets.h" +#include "poll-loop.h" +#include "rconn.h" +#include "svec.h" +#include "timeval.h" +#include "unixctl.h" +#include "util.h" +#include "vconn-ssl.h" +#include "vconn.h" + +#include "vlog.h" +#define THIS_MODULE VLM_openflowd + +/* 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 ofsettings { + /* Overall mode of operation. */ + bool discovery; /* Discover the controller automatically? */ + bool in_band; /* Connect to controller in-band? */ + + /* Datapath. */ + uint64_t datapath_id; /* Datapath ID. */ + const char *dp_name; /* Name of local datapath. */ + + /* Description strings. */ + const char *mfr_desc; /* Manufacturer. */ + const char *hw_desc; /* Hardware. */ + const char *sw_desc; /* Software version. */ + const char *serial_desc; /* Serial number. */ + + /* Related vconns and network devices. */ + const char *controller_name; /* Controller (if not discovery mode). */ + struct svec listeners; /* Listen for management connections. */ + struct svec snoops; /* Listen for controller snooping conns. */ + + /* 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. */ + + /* Packet-in rate-limiting. */ + int rate_limit; /* Tokens added to bucket per second. */ + int burst_limit; /* Maximum number token bucket size. */ + + /* Discovery behavior. */ + const char *accept_controller_re; /* Controller vconns to accept. */ + bool update_resolv_conf; /* Update /etc/resolv.conf? */ + + /* Spanning tree protocol. */ + bool enable_stp; + + /* Remote command execution. */ + char *command_acl; /* Command white/blacklist, as shell globs. */ + char *command_dir; /* Directory that contains commands. */ + + /* Management. */ + uint64_t mgmt_id; /* Management ID. */ + + /* NetFlow. */ + struct svec netflow; /* NetFlow targets. */ +}; + +static void parse_options(int argc, char *argv[], struct ofsettings *); +static void usage(void) NO_RETURN; + +int +main(int argc, char *argv[]) +{ + struct unixctl_server *unixctl; + struct ofproto *ofproto; + struct ofsettings s; + int error; + + set_program_name(argv[0]); + register_fault_handlers(); + time_init(); + vlog_init(); + parse_options(argc, argv, &s); + signal(SIGPIPE, SIG_IGN); + + die_if_already_running(); + daemonize(); + + /* Start listening for ovs-appctl requests. */ + error = unixctl_server_create(NULL, &unixctl); + if (error) { + ovs_fatal(error, "Could not listen for unixctl connections"); + } + + VLOG_INFO("Open vSwitch version %s", VERSION BUILDNR); + VLOG_INFO("OpenFlow protocol version 0x%02x", OFP_VERSION); + + /* Start OpenFlow processing. */ + error = ofproto_create(s.dp_name, NULL, NULL, &ofproto); + if (error) { + ovs_fatal(error, "could not initialize openflow switch"); + } + error = ofproto_set_in_band(ofproto, s.in_band); + if (error) { + ovs_fatal(error, "failed to configure in-band control"); + } + error = ofproto_set_discovery(ofproto, s.discovery, s.accept_controller_re, + s.update_resolv_conf); + if (error) { + ovs_fatal(error, "failed to configure controller discovery"); + } + if (s.datapath_id) { + ofproto_set_datapath_id(ofproto, s.datapath_id); + } + if (s.mgmt_id) { + ofproto_set_mgmt_id(ofproto, s.mgmt_id); + } + ofproto_set_desc(ofproto, s.mfr_desc, s.hw_desc, s.sw_desc, s.serial_desc); + error = ofproto_set_listeners(ofproto, &s.listeners); + if (error) { + ovs_fatal(error, "failed to configure management connections"); + } + error = ofproto_set_snoops(ofproto, &s.snoops); + if (error) { + ovs_fatal(error, + "failed to configure controller snooping connections"); + } + error = ofproto_set_netflow(ofproto, &s.netflow, 0, 0, false); + if (error) { + ovs_fatal(error, "failed to configure NetFlow collectors"); + } + ofproto_set_failure(ofproto, s.fail_mode == FAIL_OPEN); + ofproto_set_probe_interval(ofproto, s.probe_interval); + ofproto_set_max_backoff(ofproto, s.max_backoff); + ofproto_set_rate_limit(ofproto, s.rate_limit, s.burst_limit); + error = ofproto_set_stp(ofproto, s.enable_stp); + if (error) { + ovs_fatal(error, "failed to configure STP"); + } + error = ofproto_set_remote_execution(ofproto, s.command_acl, + s.command_dir); + if (error) { + ovs_fatal(error, "failed to configure remote command execution"); + } + if (!s.discovery) { + error = ofproto_set_controller(ofproto, s.controller_name); + if (error) { + ovs_fatal(error, "failed to configure controller"); + } + } + + while (ofproto_is_alive(ofproto)) { + error = ofproto_run(ofproto); + if (error) { + ovs_fatal(error, "unrecoverable datapath error"); + } + unixctl_server_run(unixctl); + dp_run(); + + ofproto_wait(ofproto); + unixctl_server_wait(unixctl); + dp_wait(); + poll_block(); + } + + return 0; +} + +/* User interface. */ + +static void +parse_options(int argc, char *argv[], struct ofsettings *s) +{ + enum { + OPT_DATAPATH_ID = UCHAR_MAX + 1, + OPT_MANUFACTURER, + OPT_HARDWARE, + OPT_SOFTWARE, + OPT_SERIAL, + OPT_ACCEPT_VCONN, + OPT_NO_RESOLV_CONF, + OPT_BR_NAME, + OPT_FAIL_MODE, + OPT_INACTIVITY_PROBE, + OPT_MAX_IDLE, + OPT_MAX_BACKOFF, + OPT_SNOOP, + OPT_RATE_LIMIT, + OPT_BURST_LIMIT, + OPT_BOOTSTRAP_CA_CERT, + OPT_STP, + OPT_NO_STP, + OPT_OUT_OF_BAND, + OPT_IN_BAND, + OPT_COMMAND_ACL, + OPT_COMMAND_DIR, + OPT_NETFLOW, + OPT_MGMT_ID, + VLOG_OPTION_ENUMS, + LEAK_CHECKER_OPTION_ENUMS + }; + static struct option long_options[] = { + {"datapath-id", required_argument, 0, OPT_DATAPATH_ID}, + {"manufacturer", required_argument, 0, OPT_MANUFACTURER}, + {"hardware", required_argument, 0, OPT_HARDWARE}, + {"software", required_argument, 0, OPT_SOFTWARE}, + {"serial", required_argument, 0, OPT_SERIAL}, + {"accept-vconn", required_argument, 0, OPT_ACCEPT_VCONN}, + {"no-resolv-conf", no_argument, 0, OPT_NO_RESOLV_CONF}, + {"config", required_argument, 0, 'F'}, + {"br-name", required_argument, 0, OPT_BR_NAME}, + {"fail", required_argument, 0, OPT_FAIL_MODE}, + {"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'}, + {"snoop", required_argument, 0, OPT_SNOOP}, + {"rate-limit", optional_argument, 0, OPT_RATE_LIMIT}, + {"burst-limit", required_argument, 0, OPT_BURST_LIMIT}, + {"stp", no_argument, 0, OPT_STP}, + {"no-stp", no_argument, 0, OPT_NO_STP}, + {"out-of-band", no_argument, 0, OPT_OUT_OF_BAND}, + {"in-band", no_argument, 0, OPT_IN_BAND}, + {"command-acl", required_argument, 0, OPT_COMMAND_ACL}, + {"command-dir", required_argument, 0, OPT_COMMAND_DIR}, + {"netflow", required_argument, 0, OPT_NETFLOW}, + {"mgmt-id", required_argument, 0, OPT_MGMT_ID}, + {"verbose", optional_argument, 0, 'v'}, + {"help", no_argument, 0, 'h'}, + {"version", no_argument, 0, 'V'}, + DAEMON_LONG_OPTIONS, + VLOG_LONG_OPTIONS, + LEAK_CHECKER_LONG_OPTIONS, +#ifdef HAVE_OPENSSL + VCONN_SSL_LONG_OPTIONS + {"bootstrap-ca-cert", required_argument, 0, OPT_BOOTSTRAP_CA_CERT}, +#endif + {0, 0, 0, 0}, + }; + char *short_options = long_options_to_short_options(long_options); + + /* Set defaults that we can figure out before parsing options. */ + s->datapath_id = 0; + s->mfr_desc = NULL; + s->hw_desc = NULL; + s->sw_desc = NULL; + s->serial_desc = NULL; + svec_init(&s->listeners); + svec_init(&s->snoops); + s->fail_mode = FAIL_OPEN; + s->max_idle = 0; + s->probe_interval = 0; + s->max_backoff = 15; + s->update_resolv_conf = true; + s->rate_limit = 0; + s->burst_limit = 0; + s->accept_controller_re = NULL; + s->enable_stp = false; + s->in_band = true; + s->command_acl = ""; + s->command_dir = NULL; + svec_init(&s->netflow); + s->mgmt_id = 0; + for (;;) { + int c; + + c = getopt_long(argc, argv, short_options, long_options, NULL); + if (c == -1) { + break; + } + + switch (c) { + case OPT_DATAPATH_ID: + if (strlen(optarg) != 12 + || strspn(optarg, "0123456789abcdefABCDEF") != 12) { + ovs_fatal(0, "argument to --datapath-id must be " + "exactly 12 hex digits"); + } + s->datapath_id = strtoll(optarg, NULL, 16); + if (!s->datapath_id) { + ovs_fatal(0, "argument to --datapath-id must be nonzero"); + } + break; + + case OPT_MANUFACTURER: + s->mfr_desc = optarg; + break; + + case OPT_HARDWARE: + s->hw_desc = optarg; + break; + + case OPT_SOFTWARE: + s->sw_desc = optarg; + break; + + case OPT_SERIAL: + s->serial_desc = optarg; + break; + + case OPT_ACCEPT_VCONN: + s->accept_controller_re = optarg; + break; + + case OPT_NO_RESOLV_CONF: + s->update_resolv_conf = false; + break; + + case OPT_FAIL_MODE: + if (!strcmp(optarg, "open")) { + s->fail_mode = FAIL_OPEN; + } else if (!strcmp(optarg, "closed")) { + s->fail_mode = FAIL_CLOSED; + } else { + ovs_fatal(0, "-f or --fail argument must be \"open\" " + "or \"closed\""); + } + break; + + case OPT_INACTIVITY_PROBE: + s->probe_interval = atoi(optarg); + if (s->probe_interval < 5) { + ovs_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) { + ovs_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) { + ovs_fatal(0, "--max-backoff argument must be at least 1"); + } else if (s->max_backoff > 3600) { + s->max_backoff = 3600; + } + break; + + case OPT_RATE_LIMIT: + if (optarg) { + s->rate_limit = atoi(optarg); + if (s->rate_limit < 1) { + ovs_fatal(0, "--rate-limit argument must be at least 1"); + } + } else { + s->rate_limit = 1000; + } + break; + + case OPT_BURST_LIMIT: + s->burst_limit = atoi(optarg); + if (s->burst_limit < 1) { + ovs_fatal(0, "--burst-limit argument must be at least 1"); + } + break; + + case OPT_STP: + s->enable_stp = true; + break; + + case OPT_NO_STP: + s->enable_stp = false; + break; + + case OPT_OUT_OF_BAND: + s->in_band = false; + break; + + case OPT_IN_BAND: + s->in_band = true; + break; + + case OPT_COMMAND_ACL: + s->command_acl = (s->command_acl[0] + ? xasprintf("%s,%s", s->command_acl, optarg) + : optarg); + break; + + case OPT_COMMAND_DIR: + s->command_dir = optarg; + break; + + case OPT_NETFLOW: + svec_add(&s->netflow, optarg); + break; + + case OPT_MGMT_ID: + if (strlen(optarg) != 12 + || strspn(optarg, "0123456789abcdefABCDEF") != 12) { + ovs_fatal(0, "argument to --mgmt-id must be " + "exactly 12 hex digits"); + } + s->mgmt_id = strtoll(optarg, NULL, 16); + if (!s->mgmt_id) { + ovs_fatal(0, "argument to --mgmt-id must be nonzero"); + } + break; + + case 'l': + svec_add(&s->listeners, optarg); + break; + + case OPT_SNOOP: + svec_add(&s->snoops, optarg); + break; + + case 'h': + usage(); + + case 'V': + OVS_PRINT_VERSION(OFP_VERSION, OFP_VERSION); + exit(EXIT_SUCCESS); + + DAEMON_OPTION_HANDLERS + + VLOG_OPTION_HANDLERS + + LEAK_CHECKER_OPTION_HANDLERS + +#ifdef HAVE_OPENSSL + VCONN_SSL_OPTION_HANDLERS + + case OPT_BOOTSTRAP_CA_CERT: + vconn_ssl_set_ca_cert_file(optarg, true); + break; +#endif + + case '?': + exit(EXIT_FAILURE); + + default: + abort(); + } + } + free(short_options); + + argc -= optind; + argv += optind; + if (argc < 1 || argc > 2) { + ovs_fatal(0, "need one or two non-option arguments; " + "use --help for usage"); + } + + /* Local and remote vconns. */ + s->dp_name = argv[0]; + s->controller_name = argc > 1 ? xstrdup(argv[1]) : NULL; + + /* Set accept_controller_regex. */ + if (!s->accept_controller_re) { + s->accept_controller_re + = vconn_ssl_is_configured() ? "^ssl:.*" : "^tcp:.*"; + } + + /* Mode of operation. */ + s->discovery = s->controller_name == NULL; + if (s->discovery && !s->in_band) { + ovs_fatal(0, "Cannot perform discovery with out-of-band control"); + } + + /* Rate limiting. */ + if (s->rate_limit && s->rate_limit < 100) { + VLOG_WARN("Rate limit set to unusually low value %d", s->rate_limit); + } +} + +static void +usage(void) +{ + printf("%s: an OpenFlow switch implementation.\n" + "usage: %s [OPTIONS] DATAPATH [CONTROLLER]\n" + "DATAPATH is a local datapath (e.g. \"dp0\").\n" + "CONTROLLER is an active OpenFlow connection method; if it is\n" + "omitted, then ovs-openflowd performs controller discovery.\n", + program_name, program_name); + vconn_usage(true, true, true); + printf("\nOpenFlow options:\n" + " -d, --datapath-id=ID Use ID as the OpenFlow switch ID\n" + " (ID must consist of 12 hex digits)\n" + " --mgmt-id=ID Use ID as the management ID\n" + " (ID must consist of 12 hex digits)\n" + " --manufacturer=MFR Identify manufacturer as MFR\n" + " --hardware=HW Identify hardware as HW\n" + " --software=SW Identify software as SW\n" + " --serial=SERIAL Identify serial number as SERIAL\n" + "\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" + " --fail=open|closed when controller connection fails:\n" + " closed: drop all packets\n" + " open (default): act as learning switch\n" + " --inactivity-probe=SECS time between inactivity probes\n" + " --max-idle=SECS max idle for flows set up by switch\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" + " --snoop=METHOD allow controller snooping on METHOD\n" + " (a passive OpenFlow connection method)\n" + " --out-of-band controller connection is out-of-band\n" + " --netflow=HOST:PORT configure NetFlow output target\n" + "\nRate-limiting of \"packet-in\" messages to the controller:\n" + " --rate-limit[=PACKETS] max rate, in packets/s (default: 1000)\n" + " --burst-limit=BURST limit on packet credit for idle time\n" + "\nRemote command execution options:\n" + " --command-acl=[!]GLOB[,[!]GLOB...] set allowed/denied commands\n" + " --command-dir=DIR set command dir (default: %s/commands)\n", + ovs_pkgdatadir); + daemon_usage(); + vlog_usage(); + printf("\nOther options:\n" + " -h, --help display this help message\n" + " -V, --version display version information\n"); + leak_checker_usage(); + exit(EXIT_SUCCESS); +} diff --git a/utilities/ovs-pki.8.in b/utilities/ovs-pki.8.in index 27dfccfc..0f1c4540 100644 --- a/utilities/ovs-pki.8.in +++ b/utilities/ovs-pki.8.in @@ -325,6 +325,6 @@ Prints a help usage message and exits. .SH "SEE ALSO" -.BR controller (8), -.BR ovs\-pki\-cgi (8), -.BR secchan (8) +.BR ovs\-controller (8), +.BR ovs\-openflowd (8), +.BR ovs\-pki\-cgi (8) diff --git a/vswitchd/automake.mk b/vswitchd/automake.mk index 6883731e..e3e6ea3a 100644 --- a/vswitchd/automake.mk +++ b/vswitchd/automake.mk @@ -22,7 +22,7 @@ vswitchd_ovs_vswitchd_SOURCES = \ vswitchd/xenserver.c \ vswitchd/xenserver.h vswitchd_ovs_vswitchd_LDADD = \ - secchan/libsecchan.a \ + ofproto/libofproto.a \ lib/libopenvswitch.a \ $(FAULT_LIBS) \ $(SSL_LIBS) diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c index cadefeeb..ab55658f 100644 --- a/vswitchd/bridge.c +++ b/vswitchd/bridge.c @@ -43,12 +43,12 @@ #include "odp-util.h" #include "ofp-print.h" #include "ofpbuf.h" +#include "ofproto/ofproto.h" #include "packets.h" #include "poll-loop.h" #include "port-array.h" #include "proc-net-compat.h" #include "process.h" -#include "secchan/ofproto.h" #include "socket-util.h" #include "stp.h" #include "svec.h" diff --git a/vswitchd/ovs-vswitchd.conf.5.in b/vswitchd/ovs-vswitchd.conf.5.in index d82a08af..b9bf9688 100644 --- a/vswitchd/ovs-vswitchd.conf.5.in +++ b/vswitchd/ovs-vswitchd.conf.5.in @@ -396,7 +396,7 @@ switch will perform all configured bridging and switching locally. .TP \fBdiscover\fR Use controller discovery to find the local OpenFlow controller. -Refer to \fBsecchan\fR(8) for information on how to configure a DHCP +Refer to \fB\ovs\-openflowd\fR(8) for information on how to configure a DHCP server to support controller discovery. The following additional options control the discovery process: . @@ -454,7 +454,7 @@ not in use, the following additional settings are honored: By default, or if this is set to \fBtrue\fR, \fBovs\-vswitchd\fR connects to the controller in-band. If this is set to \fBfalse\fR, \fBovs\-vswitchd\fR connects to the controller out-of-band. Refer to -\fBsecchan\fR(8) for a description of in-band and out-of-band control. +\fBovs\-openflowd\fR(8) for a description of in-band and out-of-band control. .IP "\fBbridge.\fIname\fB.controller.ip=\fIip\fR" If specified, the IP address to configure on the bridge's local port. .IP "\fBbridge.\fIname\fB.controller.netmask=\fInetmask\fR" @@ -476,7 +476,7 @@ When the switch is connected to the controller, it 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 +\fIsecs\fR seconds, \fBovs-vswitchd\fR assumes that the connection has been broken and attempts to reconnect. .IP Changing the inactivity probe interval also changes the interval diff --git a/xenserver/vswitch-xen.spec b/xenserver/vswitch-xen.spec index e660027c..373bfb29 100644 --- a/xenserver/vswitch-xen.spec +++ b/xenserver/vswitch-xen.spec @@ -80,17 +80,17 @@ rm -rf \ $RPM_BUILD_ROOT/root/vswitch/bin/ovs-controller \ $RPM_BUILD_ROOT/root/vswitch/bin/ovs-discover \ $RPM_BUILD_ROOT/root/vswitch/bin/ovs-kill \ + $RPM_BUILD_ROOT/root/vswitch/bin/ovs-openflowd \ $RPM_BUILD_ROOT/root/vswitch/bin/ovs-pki \ $RPM_BUILD_ROOT/root/vswitch/bin/ovs-switchui \ $RPM_BUILD_ROOT/root/vswitch/bin/ovs-wdt \ - $RPM_BUILD_ROOT/root/vswitch/bin/secchan \ $RPM_BUILD_ROOT/root/vswitch/kernel_modules/veth_mod.ko \ $RPM_BUILD_ROOT/root/vswitch/sbin/ovs-monitor \ $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-controller.8 \ $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-discover.8 \ $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-kill.8 \ + $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-openflowd.8 \ $RPM_BUILD_ROOT/root/vswitch/share/man/man8/ovs-pki.8 \ - $RPM_BUILD_ROOT/root/vswitch/share/man/man8/secchan.8 \ $RPM_BUILD_ROOT/root/vswitch/share/openvswitch %clean