aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Pfaff <blp@nicira.com>2010-06-17 15:04:12 -0700
committerBen Pfaff <blp@nicira.com>2010-06-17 15:04:12 -0700
commitc1c9c9c4b636ab2acf2f75024c282a9a497ca9a9 (patch)
tree02d8e5c448f1b75126f965ee5173ddde5111d63c
parenta90b56b7708a17e8bd689d4a3558348f04cb8dcb (diff)
Implement QoS framework.
ovs-vswitchd doesn't declare its QoS capabilities in the database yet, so the controller has to know what they are. We can add that later. The linux-htb QoS class has been tested to the extent that I can see that it sets up the queues I expect when I run "tc qdisc show" and "tc class show". I haven't tested that the effects on flows are what we expect them to be. I am sure that there will be problems in that area that we will have to fix.
-rw-r--r--datapath/actions.c9
-rw-r--r--include/openflow/openflow.h39
-rw-r--r--include/openvswitch/datapath-protocol.h12
-rw-r--r--lib/netdev-gre.c11
-rw-r--r--lib/netdev-linux.c1616
-rw-r--r--lib/netdev-patch.c11
-rw-r--r--lib/netdev-provider.h149
-rw-r--r--lib/netdev.c279
-rw-r--r--lib/netdev.h57
-rw-r--r--lib/ofp-util.c58
-rw-r--r--lib/ofp-util.h2
-rw-r--r--ofproto/ofproto.c163
-rw-r--r--ovsdb/ovsdbmonitor/MainWindow.ui20
-rw-r--r--ovsdb/ovsdbmonitor/Ui_MainWindow.py125
-rw-r--r--utilities/ovs-vsctl.c8
-rw-r--r--vswitchd/bridge.c90
-rw-r--r--vswitchd/vswitch.ovsschema35
-rw-r--r--vswitchd/vswitch.xml135
18 files changed, 2725 insertions, 94 deletions
diff --git a/datapath/actions.c b/datapath/actions.c
index fed9830f..ff67372a 100644
--- a/datapath/actions.c
+++ b/datapath/actions.c
@@ -444,6 +444,7 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
* then freeing the original skbuff is wasteful. So the following code
* is slightly obscure just to avoid that. */
int prev_port = -1;
+ u32 priority = skb->priority;
int err;
if (dp->sflow_probability) {
@@ -516,6 +517,14 @@ int execute_actions(struct datapath *dp, struct sk_buff *skb,
case ODPAT_SET_TP_DST:
skb = set_tp_port(skb, key, &a->tp_port, gfp);
break;
+
+ case ODPAT_SET_PRIORITY:
+ skb->priority = a->priority.priority;
+ break;
+
+ case ODPAT_POP_PRIORITY:
+ skb->priority = priority;
+ break;
}
if (!skb)
return -ENOMEM;
diff --git a/include/openflow/openflow.h b/include/openflow/openflow.h
index b77cd701..f84fd020 100644
--- a/include/openflow/openflow.h
+++ b/include/openflow/openflow.h
@@ -419,6 +419,18 @@ struct ofp_action_header {
};
OFP_ASSERT(sizeof(struct ofp_action_header) == 8);
+/* OFPAT_ENQUEUE action struct: send packets to given queue on port. */
+struct ofp_action_enqueue {
+ uint16_t type; /* OFPAT_ENQUEUE. */
+ uint16_t len; /* Len is 16. */
+ uint16_t port; /* Port that queue belongs. Should
+ refer to a valid physical port
+ (i.e. < OFPP_MAX) or OFPP_IN_PORT. */
+ uint8_t pad[6]; /* Pad for 64-bit alignment. */
+ uint32_t queue_id; /* Where to enqueue the packets. */
+};
+OFP_ASSERT(sizeof(struct ofp_action_enqueue) == 16);
+
union ofp_action {
uint16_t type;
struct ofp_action_header header;
@@ -713,8 +725,8 @@ enum ofp_stats_types {
OFPST_PORT,
/* Queue statistics for a port
- * The request body defines the port
- * The reply body is an array of struct ofp_queue_stats */
+ * The request body is struct ofp_queue_stats_request.
+ * The reply body is an array of struct ofp_queue_stats. */
OFPST_QUEUE,
/* Vendor extension.
@@ -859,6 +871,29 @@ struct ofp_port_stats {
};
OFP_ASSERT(sizeof(struct ofp_port_stats) == 104);
+/* All ones is used to indicate all queues in a port (for stats retrieval). */
+#define OFPQ_ALL 0xffffffff
+
+/* Body for ofp_stats_request of type OFPST_QUEUE. */
+struct ofp_queue_stats_request {
+ uint16_t port_no; /* All ports if OFPP_ALL. */
+ uint8_t pad[2]; /* Align to 32-bits. */
+ uint32_t queue_id; /* All queues if OFPQ_ALL. */
+};
+OFP_ASSERT(sizeof(struct ofp_queue_stats_request) == 8);
+
+/* Body for ofp_stats_reply of type OFPST_QUEUE consists of an array of this
+ * structure type. */
+struct ofp_queue_stats {
+ uint16_t port_no;
+ uint8_t pad[2]; /* Align to 32-bits. */
+ uint32_t queue_id; /* Queue id. */
+ uint64_t tx_bytes; /* Number of transmitted bytes. */
+ uint64_t tx_packets; /* Number of transmitted packets. */
+ uint64_t tx_errors; /* Number of packets dropped due to overrun. */
+};
+OFP_ASSERT(sizeof(struct ofp_queue_stats) == 32);
+
/* Vendor extension. */
struct ofp_vendor_header {
struct ofp_header header; /* Type OFPT_VENDOR. */
diff --git a/include/openvswitch/datapath-protocol.h b/include/openvswitch/datapath-protocol.h
index 4b2168cb..13aa9225 100644
--- a/include/openvswitch/datapath-protocol.h
+++ b/include/openvswitch/datapath-protocol.h
@@ -279,7 +279,9 @@ struct odp_flowvec {
#define ODPAT_SET_TP_SRC 11 /* TCP/UDP source port. */
#define ODPAT_SET_TP_DST 12 /* TCP/UDP destination port. */
#define ODPAT_SET_TUNNEL 13 /* Set the encapsulating tunnel ID. */
-#define ODPAT_N_ACTIONS 14
+#define ODPAT_SET_PRIORITY 14 /* Set skb->priority. */
+#define ODPAT_POP_PRIORITY 15 /* Restore original skb->priority. */
+#define ODPAT_N_ACTIONS 16
struct odp_action_output {
uint16_t type; /* ODPAT_OUTPUT. */
@@ -353,6 +355,13 @@ struct odp_action_tp_port {
uint16_t reserved2;
};
+/* Action structure for ODPAT_SET_PRIORITY. */
+struct odp_action_priority {
+ uint16_t type; /* ODPAT_SET_PRIORITY. */
+ uint16_t reserved;
+ uint32_t priority; /* skb->priority value. */
+};
+
union odp_action {
uint16_t type;
struct odp_action_output output;
@@ -365,6 +374,7 @@ union odp_action {
struct odp_action_nw_addr nw_addr;
struct odp_action_nw_tos nw_tos;
struct odp_action_tp_port tp_port;
+ struct odp_action_priority priority;
};
struct odp_execute {
diff --git a/lib/netdev-gre.c b/lib/netdev-gre.c
index 0aa587a2..45b574ff 100644
--- a/lib/netdev-gre.c
+++ b/lib/netdev-gre.c
@@ -256,7 +256,18 @@ const struct netdev_class netdev_gre_class = {
NULL, /* get_features */
NULL, /* set_advertisements */
NULL, /* get_vlan_vid */
+
NULL, /* set_policing */
+ NULL, /* get_qos_types */
+ NULL, /* get_qos_capabilities */
+ NULL, /* get_qos */
+ NULL, /* set_qos */
+ NULL, /* get_queue */
+ NULL, /* set_queue */
+ NULL, /* delete_queue */
+ NULL, /* get_queue_stats */
+ NULL, /* dump_queues */
+ NULL, /* dump_queue_stats */
NULL, /* get_in4 */
NULL, /* set_in4 */
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index 5510a954..8cd40cd2 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -20,6 +20,7 @@
#include <fcntl.h>
#include <arpa/inet.h>
#include <inttypes.h>
+#include <linux/gen_stats.h>
#include <linux/if_tun.h>
#include <linux/ip.h>
#include <linux/types.h>
@@ -55,6 +56,7 @@
#include "openvswitch/gre.h"
#include "packets.h"
#include "poll-loop.h"
+#include "port-array.h"
#include "rtnetlink.h"
#include "socket-util.h"
#include "shash.h"
@@ -72,6 +74,12 @@
#define ADVERTISED_Asym_Pause (1 << 14)
#endif
+/* This was introduced in Linux 2.6.25, so it might be missing if we have old
+ * headers. */
+#ifndef TC_RTAB_SIZE
+#define TC_RTAB_SIZE 1024
+#endif
+
static struct rtnetlink_notifier netdev_linux_cache_notifier;
static int cache_notifier_refcount;
@@ -91,7 +99,224 @@ struct tap_state {
int fd;
bool opened;
};
+
+/* Traffic control. */
+
+/* An instance of a traffic control class. Always associated with a particular
+ * network device. */
+struct tc {
+ const struct tc_ops *ops;
+
+ /* Maps from queue ID to tc-specific data.
+ *
+ * The generic netdev TC layer uses this to the following extent: if an
+ * entry is nonnull, then the queue whose ID is the index is assumed to
+ * exist; if an entry is null, then that queue is assumed not to exist.
+ * Implementations must adhere to this scheme, although they may store
+ * whatever they like as data.
+ */
+ struct port_array queues;
+};
+
+/* A particular kind of traffic control. Each implementation generally maps to
+ * one particular Linux qdisc class.
+ *
+ * The functions below return 0 if successful or a positive errno value on
+ * failure, except where otherwise noted. All of them must be provided, except
+ * where otherwise noted. */
+struct tc_ops {
+ /* Name used by kernel in the TCA_KIND attribute of tcmsg, e.g. "htb".
+ * This is null for tc_ops_default and tc_ops_other, for which there are no
+ * appropriate values. */
+ const char *linux_name;
+
+ /* Name used in OVS database, e.g. "linux-htb". Must be nonnull. */
+ const char *ovs_name;
+
+ /* Number of supported OpenFlow queues, 0 for qdiscs that have no
+ * queues. The queues are numbered 0 through n_queues - 1. */
+ unsigned int n_queues;
+
+ /* Called to install this TC class on 'netdev'. The implementation should
+ * make the Netlink calls required to set up 'netdev' with the right qdisc
+ * and configure it according to 'details'. The implementation may assume
+ * that the current qdisc is the default; that is, there is no need for it
+ * to delete the current qdisc before installing itself.
+ *
+ * The contents of 'details' should be documented as valid for 'ovs_name'
+ * in the "other_config" column in the "QoS" table in vswitchd/vswitch.xml
+ * (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * This function must return 0 if and only if it sets 'netdev->tc' to an
+ * initialized 'struct tc'.
+ *
+ * (This function is null for tc_ops_other, which cannot be installed. For
+ * other TC classes it should always be nonnull.) */
+ int (*tc_install)(struct netdev *netdev, const struct shash *details);
+
+ /* Called when the netdev code determines (through a Netlink query) that
+ * this TC class's qdisc is installed on 'netdev', but we didn't install
+ * it ourselves and so don't know any of the details.
+ *
+ * 'nlmsg' is the kernel reply to a RTM_GETQDISC Netlink message for
+ * 'netdev'. The TCA_KIND attribute of 'nlmsg' is 'linux_name'. The
+ * implementation should parse the other attributes of 'nlmsg' as
+ * necessary to determine its configuration. If necessary it should also
+ * use Netlink queries to determine the configuration of queues on
+ * 'netdev'.
+ *
+ * This function must return 0 if and only if it sets 'netdev->tc' to an
+ * initialized 'struct tc'. */
+ int (*tc_load)(struct netdev *netdev, struct ofpbuf *nlmsg);
+
+ /* Destroys the data structures allocated by the implementation as part of
+ * 'tc'. (This includes destroying 'tc->queues' by calling
+ * tc_destroy(tc).
+ *
+ * The implementation should not need to perform any Netlink calls. If
+ * desirable, the caller is responsible for deconfiguring the kernel qdisc.
+ * (But it may not be desirable.)
+ *
+ * This function may be null if 'tc' is trivial. */
+ void (*tc_destroy)(struct tc *tc);
+
+ /* Retrieves details of 'netdev->tc' configuration into 'details'.
+ *
+ * The implementation should not need to perform any Netlink calls, because
+ * the 'tc_install' or 'tc_load' that instantiated 'netdev->tc' should have
+ * cached the configuration.
+ *
+ * The contents of 'details' should be documented as valid for 'ovs_name'
+ * in the "other_config" column in the "QoS" table in vswitchd/vswitch.xml
+ * (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * This function may be null if 'tc' is not configurable.
+ */
+ int (*qdisc_get)(const struct netdev *netdev, struct shash *details);
+
+ /* Reconfigures 'netdev->tc' according to 'details', performing any
+ * required Netlink calls to complete the reconfiguration.
+ *
+ * The contents of 'details' should be documented as valid for 'ovs_name'
+ * in the "other_config" column in the "QoS" table in vswitchd/vswitch.xml
+ * (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * This function may be null if 'tc' is not configurable.
+ */
+ int (*qdisc_set)(struct netdev *, const struct shash *details);
+
+ /* Retrieves details of 'queue_id' on 'netdev->tc' into 'details'. The
+ * caller ensures that 'queues' has a nonnull value for index 'queue_id.
+ *
+ * The contents of 'details' should be documented as valid for 'ovs_name'
+ * in the "other_config" column in the "Queue" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * The implementation should not need to perform any Netlink calls, because
+ * the 'tc_install' or 'tc_load' that instantiated 'netdev->tc' should have
+ * cached the queue configuration.
+ *
+ * This function may be null if 'tc' does not have queues ('n_queues' is
+ * 0). */
+ int (*class_get)(const struct netdev *netdev, unsigned int queue_id,
+ struct shash *details);
+
+ /* Configures or reconfigures 'queue_id' on 'netdev->tc' according to
+ * 'details', perfoming any required Netlink calls to complete the
+ * reconfiguration. The caller ensures that 'queue_id' is less than
+ * 'n_queues'.
+ *
+ * The contents of 'details' should be documented as valid for 'ovs_name'
+ * in the "other_config" column in the "Queue" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * This function may be null if 'tc' does not have queues or its queues are
+ * not configurable. */
+ int (*class_set)(struct netdev *, unsigned int queue_id,
+ const struct shash *details);
+
+ /* Deletes 'queue_id' from 'netdev->tc'. The caller ensures that 'queues'
+ * has a nonnull value for index 'queue_id.
+ *
+ * This function may be null if 'tc' does not have queues or its queues
+ * cannot be deleted. */
+ int (*class_delete)(struct netdev *, unsigned int queue_id);
+
+ /* Obtains stats for 'queue' from 'netdev->tc'. The caller ensures that
+ * 'queues' has a nonnull value for index 'queue_id.
+ *
+ * On success, initializes '*stats'.
+ *
+ * This function may be null if 'tc' does not have queues or if it cannot
+ * report queue statistics. */
+ int (*class_get_stats)(const struct netdev *netdev, unsigned int queue_id,
+ struct netdev_queue_stats *stats);
+
+ /* Extracts queue stats from 'nlmsg', which is a response to a
+ * RTM_GETTCLASS message, and passes them to 'cb' along with 'aux'.
+ *
+ * This function may be null if 'tc' does not have queues or if it cannot
+ * report queue statistics. */
+ int (*class_dump_stats)(const struct netdev *netdev,
+ const struct ofpbuf *nlmsg,
+ netdev_dump_queue_stats_cb *cb, void *aux);
+};
+static void
+tc_init(struct tc *tc, const struct tc_ops *ops)
+{
+ tc->ops = ops;
+ port_array_init(&tc->queues);
+}
+
+static void
+tc_destroy(struct tc *tc)
+{
+ port_array_destroy(&tc->queues);
+}
+
+static const struct tc_ops tc_ops_htb;
+static const struct tc_ops tc_ops_default;
+static const struct tc_ops tc_ops_other;
+
+static const struct tc_ops *tcs[] = {
+ &tc_ops_htb, /* Hierarchy token bucket (see tc-htb(8)). */
+ &tc_ops_default, /* Default qdisc (see tc-pfifo_fast(8)). */
+ &tc_ops_other, /* Some other qdisc. */
+ NULL
+};
+
+static unsigned int tc_make_handle(unsigned int major, unsigned int minor);
+static unsigned int tc_get_major(unsigned int handle);
+static unsigned int tc_get_minor(unsigned int handle);
+
+static unsigned int tc_ticks_to_bytes(unsigned int rate, unsigned int ticks);
+static unsigned int tc_bytes_to_ticks(unsigned int rate, unsigned int size);
+static unsigned int tc_buffer_per_jiffy(unsigned int rate);
+
+static struct tcmsg *tc_make_request(const struct netdev *, int type,
+ unsigned int flags, struct ofpbuf *);
+static int tc_transact(struct ofpbuf *request, struct ofpbuf **replyp);
+
+static int tc_parse_qdisc(const struct ofpbuf *, const char **kind,
+ struct nlattr **options);
+static int tc_parse_class(const struct ofpbuf *, unsigned int *queue_id,
+ struct nlattr **options,
+ struct netdev_queue_stats *);
+static int tc_query_class(const struct netdev *,
+ unsigned int handle, unsigned int parent,
+ struct ofpbuf **replyp);
+static int tc_delete_class(const struct netdev *, unsigned int handle);
+
+static int tc_del_qdisc(struct netdev *netdev);
+static int tc_query_qdisc(const struct netdev *netdev);
+
+static int tc_calc_cell_log(unsigned int mtu);
+static void tc_fill_rate(struct tc_ratespec *rate, uint64_t bps, int mtu);
+static void tc_put_rtab(struct ofpbuf *, uint16_t type,
+ const struct tc_ratespec *rate);
+static int tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes);
+
struct netdev_dev_linux {
struct netdev_dev netdev_dev;
@@ -111,6 +336,7 @@ struct netdev_dev_linux {
uint32_t kbits_rate; /* Policing data. */
uint32_t kbits_burst;
bool have_vport_stats;
+ struct tc *tc;
union {
struct tap_state tap;
@@ -354,6 +580,10 @@ netdev_linux_destroy(struct netdev_dev *netdev_dev_)
struct netdev_dev_linux *netdev_dev = netdev_dev_linux_cast(netdev_dev_);
const char *type = netdev_dev_get_type(netdev_dev_);
+ if (netdev_dev->tc && netdev_dev->tc->ops->tc_destroy) {
+ netdev_dev->tc->ops->tc_destroy(netdev_dev->tc);
+ }
+
if (!strcmp(type, "system")) {
cache_notifier_refcount--;
@@ -1127,28 +1357,16 @@ netdev_linux_remove_policing(struct netdev *netdev)
const char *netdev_name = netdev_get_name(netdev);
struct ofpbuf request;
- struct ofpbuf *reply;
struct tcmsg *tcmsg;
- int ifindex;
int error;
- error = get_ifindex(netdev, &ifindex);
- if (error) {
- return error;
- }
-
- ofpbuf_init(&request, 0);
- nl_msg_put_nlmsghdr(&request, sizeof *tcmsg, RTM_DELQDISC, NLM_F_REQUEST);
- tcmsg = ofpbuf_put_zeros(&request, sizeof *tcmsg);
- tcmsg->tcm_family = AF_UNSPEC;
- tcmsg->tcm_ifindex = ifindex;
- tcmsg->tcm_handle = 0xffff0000;
+ tcmsg = tc_make_request(netdev, RTM_DELQDISC, 0, &request);
+ tcmsg->tcm_handle = tc_make_handle(0xffff, 0);
tcmsg->tcm_parent = TC_H_INGRESS;
nl_msg_put_string(&request, TCA_KIND, "ingress");
nl_msg_put_unspec(&request, TCA_OPTIONS, NULL, 0);
- error = nl_sock_transact(rtnl_sock, &request, &reply);
- ofpbuf_uninit(&request);
- ofpbuf_delete(reply);
+
+ error = tc_transact(&request, NULL);
if (error && error != ENOENT && error != EINVAL) {
VLOG_WARN_RL(&rl, "%s: removing policing failed: %s",
netdev_name, strerror(error));
@@ -1209,6 +1427,277 @@ netdev_linux_set_policing(struct netdev *netdev,
}
static int
+netdev_linux_get_qos_types(const struct netdev *netdev OVS_UNUSED,
+ struct svec *types)
+{
+ const struct tc_ops **opsp;
+
+ for (opsp = tcs; *opsp != NULL; opsp++) {
+ const struct tc_ops *ops = *opsp;
+ if (ops->tc_install && ops->ovs_name[0] != '\0') {
+ svec_add(types, ops->ovs_name);
+ }
+ }
+ return 0;
+}
+
+static const struct tc_ops *
+tc_lookup_ovs_name(const char *name)
+{
+ const struct tc_ops **opsp;
+
+ for (opsp = tcs; *opsp != NULL; opsp++) {
+ const struct tc_ops *ops = *opsp;
+ if (!strcmp(name, ops->ovs_name)) {
+ return ops;
+ }
+ }
+ return NULL;
+}
+
+static const struct tc_ops *
+tc_lookup_linux_name(const char *name)
+{
+ const struct tc_ops **opsp;
+
+ for (opsp = tcs; *opsp != NULL; opsp++) {
+ const struct tc_ops *ops = *opsp;
+ if (ops->linux_name && !strcmp(name, ops->linux_name)) {
+ return ops;
+ }
+ }
+ return NULL;
+}
+
+static int
+netdev_linux_get_qos_capabilities(const struct netdev *netdev OVS_UNUSED,
+ const char *type,
+ struct netdev_qos_capabilities *caps)
+{
+ const struct tc_ops *ops = tc_lookup_ovs_name(type);
+ if (!ops) {
+ return EOPNOTSUPP;
+ }
+ caps->n_queues = ops->n_queues;
+ return 0;
+}
+
+static int
+netdev_linux_get_qos(const struct netdev *netdev,
+ const char **typep, struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ }
+
+ *typep = netdev_dev->tc->ops->ovs_name;
+ return (netdev_dev->tc->ops->qdisc_get
+ ? netdev_dev->tc->ops->qdisc_get(netdev, details)
+ : 0);
+}
+
+static int
+netdev_linux_set_qos(struct netdev *netdev,
+ const char *type, const struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ const struct tc_ops *new_ops;
+ int error;
+
+ new_ops = tc_lookup_ovs_name(type);
+ if (!new_ops || !new_ops->tc_install) {
+ return EOPNOTSUPP;
+ }
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ }
+
+ if (new_ops == netdev_dev->tc->ops) {
+ return new_ops->qdisc_set ? new_ops->qdisc_set(netdev, details) : 0;
+ } else {
+ /* Delete existing qdisc. */
+ error = tc_del_qdisc(netdev);
+ if (error) {
+ return error;
+ }
+ assert(netdev_dev->tc == NULL);
+
+ /* Install new qdisc. */
+ error = new_ops->tc_install(netdev, details);
+ assert((error == 0) == (netdev_dev->tc != NULL));
+
+ return error;
+ }
+}
+
+static int
+netdev_linux_get_queue(const struct netdev *netdev,
+ unsigned int queue_id, struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (queue_id > UINT16_MAX
+ || !port_array_get(&netdev_dev->tc->queues, queue_id)) {
+ return ENOENT;
+ }
+
+ return netdev_dev->tc->ops->class_get(netdev, queue_id, details);
+}
+
+static int
+netdev_linux_set_queue(struct netdev *netdev,
+ unsigned int queue_id, const struct shash *details)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (queue_id >= netdev_dev->tc->ops->n_queues
+ || !netdev_dev->tc->ops->class_set) {
+ return EINVAL;
+ }
+
+ return netdev_dev->tc->ops->class_set(netdev, queue_id, details);
+}
+
+static int
+netdev_linux_delete_queue(struct netdev *netdev, unsigned int queue_id)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_delete) {
+ return EINVAL;
+ } else if (queue_id > UINT16_MAX
+ || !port_array_get(&netdev_dev->tc->queues, queue_id)) {
+ return ENOENT;
+ }
+
+ return netdev_dev->tc->ops->class_delete(netdev, queue_id);
+}
+
+static int
+netdev_linux_get_queue_stats(const struct netdev *netdev,
+ unsigned int queue_id,
+ struct netdev_queue_stats *stats)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (queue_id > UINT16_MAX
+ || !port_array_get(&netdev_dev->tc->queues, queue_id)) {
+ return ENOENT;
+ } else if (!netdev_dev->tc->ops->class_get_stats) {
+ return EOPNOTSUPP;
+ }
+
+ return netdev_dev->tc->ops->class_get_stats(netdev, queue_id, stats);
+}
+
+static void
+start_queue_dump(const struct netdev *netdev, struct nl_dump *dump)
+{
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+
+ tcmsg = tc_make_request(netdev, RTM_GETTCLASS, 0, &request);
+ tcmsg->tcm_parent = TC_H_ROOT;
+ nl_dump_start(dump, rtnl_sock, &request);
+ ofpbuf_uninit(&request);
+}
+
+static int
+netdev_linux_dump_queues(const struct netdev *netdev,
+ netdev_dump_queues_cb *cb, void *aux)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ unsigned int queue_id;
+ struct shash details;
+ int last_error;
+ void *queue;
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_get) {
+ return EOPNOTSUPP;
+ }
+
+ last_error = 0;
+ shash_init(&details);
+ PORT_ARRAY_FOR_EACH (queue, &netdev_dev->tc->queues, queue_id) {
+ shash_clear(&details);
+
+ error = netdev_dev->tc->ops->class_get(netdev, queue_id, &details);
+ if (!error) {
+ (*cb)(queue_id, &details, aux);
+ } else {
+ last_error = error;
+ }
+ }
+ shash_destroy(&details);
+
+ return last_error;
+}
+
+static int
+netdev_linux_dump_queue_stats(const struct netdev *netdev,
+ netdev_dump_queue_stats_cb *cb, void *aux)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct nl_dump dump;
+ struct ofpbuf msg;
+ int last_error;
+ int error;
+
+ error = tc_query_qdisc(netdev);
+ if (error) {
+ return error;
+ } else if (!netdev_dev->tc->ops->class_dump_stats) {
+ return EOPNOTSUPP;
+ }
+
+ last_error = 0;
+ start_queue_dump(netdev, &dump);
+ while (nl_dump_next(&dump, &msg)) {
+ error = netdev_dev->tc->ops->class_dump_stats(netdev, &msg, cb, aux);
+ if (error) {
+ last_error = error;
+ }
+ }
+
+ error = nl_dump_done(&dump);
+ return error ? error : last_error;
+}
+
+static int
netdev_linux_get_in4(const struct netdev *netdev_,
struct in_addr *address, struct in_addr *netmask)
{
@@ -1607,7 +2096,18 @@ const struct netdev_class netdev_linux_class = {
netdev_linux_get_features,
netdev_linux_set_advertisements,
netdev_linux_get_vlan_vid,
+
netdev_linux_set_policing,
+ netdev_linux_get_qos_types,
+ netdev_linux_get_qos_capabilities,
+ netdev_linux_get_qos,
+ netdev_linux_set_qos,
+ netdev_linux_get_queue,
+ netdev_linux_set_queue,
+ netdev_linux_delete_queue,
+ netdev_linux_get_queue_stats,
+ netdev_linux_dump_queues,
+ netdev_linux_dump_queue_stats,
netdev_linux_get_in4,
netdev_linux_set_in4,
@@ -1656,7 +2156,18 @@ const struct netdev_class netdev_tap_class = {
netdev_linux_get_features,
netdev_linux_set_advertisements,
netdev_linux_get_vlan_vid,
+
netdev_linux_set_policing,
+ netdev_linux_get_qos_types,
+ netdev_linux_get_qos_capabilities,
+ netdev_linux_get_qos,
+ netdev_linux_set_qos,
+ netdev_linux_get_queue,
+ netdev_linux_set_queue,
+ netdev_linux_delete_queue,
+ netdev_linux_get_queue_stats,
+ netdev_linux_dump_queues,
+ netdev_linux_dump_queue_stats,
netdev_linux_get_in4,
netdev_linux_set_in4,
@@ -1670,8 +2181,1079 @@ const struct netdev_class netdev_tap_class = {
netdev_linux_poll_add,
netdev_linux_poll_remove,
};
+
+/* HTB traffic control class. */
+
+#define HTB_N_QUEUES 0xf000
+
+struct htb {
+ struct tc tc;
+ unsigned int max_rate; /* In bytes/s. */
+};
+
+struct htb_class {
+ unsigned int min_rate; /* In bytes/s. */
+ unsigned int max_rate; /* In bytes/s. */
+ unsigned int burst; /* In bytes. */
+ unsigned int priority; /* Lower values are higher priorities. */
+};
+
+static struct htb *
+htb_get__(const struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ return CONTAINER_OF(netdev_dev->tc, struct htb, tc);
+}
+
+static struct htb *
+htb_install__(struct netdev *netdev, uint64_t max_rate)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct htb *htb;
+
+ htb = xmalloc(sizeof *htb);
+ tc_init(&htb->tc, &tc_ops_htb);
+ htb->max_rate = max_rate;
+ netdev_dev->tc = &htb->tc;
+
+ return htb;
+}
+
+/* Create an HTB qdisc.
+ *
+ * Equivalent to "tc qdisc add dev <dev> root handle 1: htb default
+ * 0". */
+static int
+htb_setup_qdisc__(struct netdev *netdev)
+{
+ size_t opt_offset;
+ struct tc_htb_glob opt;
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+
+ tc_del_qdisc(netdev);
+
+ tcmsg = tc_make_request(netdev, RTM_NEWQDISC,
+ NLM_F_EXCL | NLM_F_CREATE, &request);
+ tcmsg->tcm_handle = tc_make_handle(1, 0);
+ tcmsg->tcm_parent = TC_H_ROOT;
+
+ nl_msg_put_string(&request, TCA_KIND, "htb");
+
+ memset(&opt, 0, sizeof opt);
+ opt.rate2quantum = 10;
+ opt.version = 3;
+ opt.defcls = 0;
+
+ opt_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
+ nl_msg_put_unspec(&request, TCA_HTB_INIT, &opt, sizeof opt);
+ nl_msg_end_nested(&request, opt_offset);
+
+ return tc_transact(&request, NULL);
+}
+
+/* Equivalent to "tc class replace <dev> classid <handle> parent <parent> htb
+ * rate <min_rate>bps ceil <max_rate>bps burst <burst>b prio <priority>". */
+static int
+htb_setup_class__(struct netdev *netdev, unsigned int handle,
+ unsigned int parent, struct htb_class *class)
+{
+ size_t opt_offset;
+ struct tc_htb_opt opt;
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+ int mtu;
+
+ netdev_get_mtu(netdev, &mtu);
+
+ memset(&opt, 0, sizeof opt);
+ tc_fill_rate(&opt.rate, class->min_rate, mtu);
+ tc_fill_rate(&opt.ceil, class->max_rate, mtu);
+ opt.buffer = tc_calc_buffer(opt.rate.rate, mtu, class->burst);
+ opt.cbuffer = tc_calc_buffer(opt.ceil.rate, mtu, class->burst);
+ opt.prio = class->priority;
+
+ tcmsg = tc_make_request(netdev, RTM_NEWTCLASS, NLM_F_CREATE, &request);
+ tcmsg->tcm_handle = handle;
+ tcmsg->tcm_parent = parent;
+
+ nl_msg_put_string(&request, TCA_KIND, "htb");
+ opt_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
+ nl_msg_put_unspec(&request, TCA_HTB_PARMS, &opt, sizeof opt);
+ tc_put_rtab(&request, TCA_HTB_RTAB, &opt.rate);
+ tc_put_rtab(&request, TCA_HTB_CTAB, &opt.ceil);
+ nl_msg_end_nested(&request, opt_offset);
+
+ error = tc_transact(&request, NULL);
+ if (error) {
+ VLOG_WARN_RL(&rl, "failed to replace %s class %u:%u, parent %u:%u, "
+ "min_rate=%u max_rate=%u burst=%u prio=%u (%s)",
+ netdev_get_name(netdev),
+ tc_get_major(handle), tc_get_minor(handle),
+ tc_get_major(parent), tc_get_minor(parent),
+ class->min_rate, class->max_rate,
+ class->burst, class->priority, strerror(error));
+ }
+ return error;
+}
+
+/* Parses Netlink attributes in 'options' for HTB parameters and stores a
+ * description of them into 'details'. The description complies with the
+ * specification given in the vswitch database documentation for linux-htb
+ * queue details. */
+static int
+htb_parse_tca_options__(struct nlattr *nl_options, struct htb_class *class)
+{
+ static const struct nl_policy tca_htb_policy[] = {
+ [TCA_HTB_PARMS] = { .type = NL_A_UNSPEC, .optional = false,
+ .min_len = sizeof(struct tc_htb_opt) },
+ };
+
+ struct nlattr *attrs[ARRAY_SIZE(tca_htb_policy)];
+ const struct tc_htb_opt *htb;
+
+ if (!nl_parse_nested(nl_options, tca_htb_policy,
+ attrs, ARRAY_SIZE(tca_htb_policy))) {
+ VLOG_WARN_RL(&rl, "failed to parse HTB class options");
+ return EPROTO;
+ }
+
+ htb = nl_attr_get(attrs[TCA_HTB_PARMS]);
+ class->min_rate = htb->rate.rate;
+ class->max_rate = htb->ceil.rate;
+ class->burst = tc_ticks_to_bytes(htb->rate.rate, htb->buffer);
+ class->priority = htb->prio;
+ return 0;
+}
+
+static int
+htb_parse_tcmsg__(struct ofpbuf *tcmsg, unsigned int *queue_id,
+ struct htb_class *options,
+ struct netdev_queue_stats *stats)
+{
+ struct nlattr *nl_options;
+ unsigned int handle;
+ int error;
+
+ error = tc_parse_class(tcmsg, &handle, &nl_options, stats);
+ if (!error && queue_id) {
+ if (tc_get_major(handle) == 1 && tc_get_minor(handle) < HTB_N_QUEUES) {
+ *queue_id = tc_get_minor(handle);
+ } else {
+ error = EPROTO;
+ }
+ }
+ if (!error && options) {
+ error = htb_parse_tca_options__(nl_options, options);
+ }
+ return error;
+}
+
+static void
+htb_parse_qdisc_details__(struct netdev *netdev,
+ const struct shash *details, struct htb_class *hc)
+{
+ const char *max_rate_s;
+
+ max_rate_s = shash_find_data(details, "max-rate");
+ hc->max_rate = max_rate_s ? strtoull(max_rate_s, NULL, 10) / 8 : 0;
+ if (!hc->max_rate) {
+ uint32_t current;
+
+ netdev_get_features(netdev, &current, NULL, NULL, NULL);
+ hc->max_rate = netdev_features_to_bps(current) / 8;
+ }
+ hc->min_rate = hc->max_rate;
+ hc->burst = 0;
+ hc->priority = 0;
+}
+
+static int
+htb_parse_class_details__(struct netdev *netdev,
+ const struct shash *details, struct htb_class *hc)
+{
+ const struct htb *htb = htb_get__(netdev);
+ const char *min_rate_s = shash_find_data(details, "min-rate");
+ const char *max_rate_s = shash_find_data(details, "max-rate");
+ const char *burst_s = shash_find_data(details, "burst");
+ const char *priority_s = shash_find_data(details, "priority");
+ int mtu;
+
+ /* min-rate */
+ if (!min_rate_s) {
+ /* min-rate is required. */
+ return EINVAL;
+ }
+ hc->min_rate = strtoull(min_rate_s, NULL, 10) / 8;
+ hc->min_rate = MAX(hc->min_rate, 0);
+ hc->min_rate = MIN(hc->min_rate, htb->max_rate);
+
+ /* max-rate */
+ hc->max_rate = (max_rate_s
+ ? strtoull(max_rate_s, NULL, 10) / 8
+ : htb->max_rate);
+ hc->max_rate = MAX(hc->max_rate, hc->min_rate);
+ hc->max_rate = MIN(hc->max_rate, htb->max_rate);
+
+ /* burst
+ *
+ * According to hints in the documentation that I've read, it is important
+ * that 'burst' be at least as big as the largest frame that might be
+ * transmitted. Also, making 'burst' a bit bigger than necessary is OK,
+ * but having it a bit too small is a problem. Since netdev_get_mtu()
+ * doesn't include the Ethernet header, we need to add at least 14 (18?) to
+ * the MTU. We actually add 64, instead of 14, as a guard against
+ * additional headers get tacked on somewhere that we're not aware of. */
+ netdev_get_mtu(netdev, &mtu);
+ hc->burst = burst_s ? strtoull(burst_s, NULL, 10) / 8 : 0;
+ hc->burst = MAX(hc->burst, mtu + 64);
+
+ /* priority */
+ hc->priority = priority_s ? strtoul(priority_s, NULL, 10) : 0;
+
+ return 0;
+}
+
+static int
+htb_query_class__(const struct netdev *netdev, unsigned int handle,
+ unsigned int parent, struct htb_class *options,
+ struct netdev_queue_stats *stats)
+{
+ struct ofpbuf *reply;
+ int error;
+
+ error = tc_query_class(netdev, handle, parent, &reply);
+ if (!error) {
+ error = htb_parse_tcmsg__(reply, NULL, options, stats);
+ ofpbuf_delete(reply);
+ }
+ return error;
+}
+
+static int
+htb_tc_install(struct netdev *netdev, const struct shash *details)
+{
+ int error;
+
+ error = htb_setup_qdisc__(netdev);
+ if (!error) {
+ struct htb_class hc;
+
+ htb_parse_qdisc_details__(netdev, details, &hc);
+ error = htb_setup_class__(netdev, tc_make_handle(1, 0xfffe),
+ tc_make_handle(1, 0), &hc);
+ if (!error) {
+ htb_install__(netdev, hc.max_rate);
+ }
+ }
+ return error;
+}
+
+static void
+htb_update_queue__(struct netdev *netdev, unsigned int queue_id,
+ const struct htb_class *hc)
+{
+ struct htb *htb = htb_get__(netdev);
+ struct htb_class *hcp;
+
+ hcp = port_array_get(&htb->tc.queues, queue_id);
+ if (!hcp) {
+ hcp = xmalloc(sizeof *hcp);
+ port_array_set(&htb->tc.queues, queue_id, hcp);
+ }
+ *hcp = *hc;
+}
+
+static int
+htb_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg OVS_UNUSED)
+{
+ struct shash details = SHASH_INITIALIZER(&details);
+ struct ofpbuf msg;
+ struct nl_dump dump;
+ struct htb_class hc;
+ struct htb *htb;
+
+ /* Get qdisc options. */
+ hc.max_rate = 0;
+ htb_query_class__(netdev, tc_make_handle(1, 0xfffe), 0, &hc, NULL);
+ htb = htb_install__(netdev, hc.max_rate);
+
+ /* Get queues. */
+ start_queue_dump(netdev, &dump);
+ shash_init(&details);
+ while (nl_dump_next(&dump, &msg)) {
+ unsigned int queue_id;
+
+ if (!htb_parse_tcmsg__(&msg, &queue_id, &hc, NULL)) {
+ htb_update_queue__(netdev, queue_id, &hc);
+ }
+ }
+ nl_dump_done(&dump);
+
+ return 0;
+}
+
+static void
+htb_tc_destroy(struct tc *tc)
+{
+ struct htb *htb = CONTAINER_OF(tc, struct htb, tc);
+ unsigned int queue_id;
+ struct htb_class *hc;
+
+ PORT_ARRAY_FOR_EACH (hc, &htb->tc.queues, queue_id) {
+ free(hc);
+ }
+ tc_destroy(tc);
+ free(htb);
+}
+
+static int
+htb_qdisc_get(const struct netdev *netdev, struct shash *details)
+{
+ const struct htb *htb = htb_get__(netdev);
+ shash_add(details, "max-rate", xasprintf("%llu", 8ULL * htb->max_rate));
+ return 0;
+}
+
+static int
+htb_qdisc_set(struct netdev *netdev, const struct shash *details)
+{
+ struct htb_class hc;
+ int error;
+
+ htb_parse_qdisc_details__(netdev, details, &hc);
+ error = htb_setup_class__(netdev, tc_make_handle(1, 0xfffe),
+ tc_make_handle(1, 0), &hc);
+ if (!error) {
+ htb_get__(netdev)->max_rate = hc.max_rate;
+ }
+ return error;
+}
+
+static int
+htb_class_get(const struct netdev *netdev, unsigned int queue_id,
+ struct shash *details)
+{
+ const struct htb *htb = htb_get__(netdev);
+ const struct htb_class *hc;
+
+ hc = port_array_get(&htb->tc.queues, queue_id);
+ assert(hc != NULL);
+
+ shash_add(details, "min-rate", xasprintf("%llu", 8ULL * hc->min_rate));
+ if (hc->min_rate != hc->max_rate) {
+ shash_add(details, "max-rate", xasprintf("%llu", 8ULL * hc->max_rate));
+ }
+ shash_add(details, "burst", xasprintf("%llu", 8ULL * hc->burst));
+ if (hc->priority) {
+ shash_add(details, "priority", xasprintf("%u", hc->priority));
+ }
+ return 0;
+}
+
+static int
+htb_class_set(struct netdev *netdev, unsigned int queue_id,
+ const struct shash *details)
+{
+ struct htb_class hc;
+ int error;
+
+ error = htb_parse_class_details__(netdev, details, &hc);
+ if (error) {
+ return error;
+ }
+
+ error = htb_setup_class__(netdev, tc_make_handle(1, queue_id),
+ tc_make_handle(1, 0xfffe), &hc);
+ if (error) {
+ return error;
+ }
+
+ htb_update_queue__(netdev, queue_id, &hc);
+ return 0;
+}
+
+static int
+htb_class_delete(struct netdev *netdev, unsigned int queue_id)
+{
+ struct htb *htb = htb_get__(netdev);
+ struct htb_class *hc;
+ int error;
+
+ hc = port_array_get(&htb->tc.queues, queue_id);
+ assert(hc != NULL);
+
+ error = tc_delete_class(netdev, tc_make_handle(1, queue_id));
+ if (!error) {
+ free(hc);
+ port_array_delete(&htb->tc.queues, queue_id);
+ }
+ return error;
+}
+
+static int
+htb_class_get_stats(const struct netdev *netdev, unsigned int queue_id,
+ struct netdev_queue_stats *stats)
+{
+ return htb_query_class__(netdev, tc_make_handle(1, queue_id),
+ tc_make_handle(1, 0xfffe), NULL, stats);
+}
+
+static int
+htb_class_dump_stats(const struct netdev *netdev OVS_UNUSED,
+ const struct ofpbuf *nlmsg,
+ netdev_dump_queue_stats_cb *cb, void *aux)
+{
+ struct netdev_queue_stats stats;
+ unsigned int handle;
+ int error;
+
+ error = tc_parse_class(nlmsg, &handle, NULL, &stats);
+ if (error) {
+ return error;
+ }
+
+ if (tc_get_major(handle) == 1 && tc_get_minor(handle) < HTB_N_QUEUES) {
+ (*cb)(tc_get_minor(handle), &stats, aux);
+ }
+ return 0;
+}
+
+static const struct tc_ops tc_ops_htb = {
+ "htb", /* linux_name */
+ "linux-htb", /* ovs_name */
+ HTB_N_QUEUES, /* n_queues */
+ htb_tc_install,
+ htb_tc_load,
+ htb_tc_destroy,
+ htb_qdisc_get,
+ htb_qdisc_set,
+ htb_class_get,
+ htb_class_set,
+ htb_class_delete,
+ htb_class_get_stats,
+ htb_class_dump_stats
+};
+/* "linux-default" traffic control class.
+ *
+ * This class represents the default, unnamed Linux qdisc. It corresponds to
+ * the "" (empty string) QoS type in the OVS database. */
+
+static void
+default_install__(struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ static struct tc *tc;
+
+ if (!tc) {
+ tc = xmalloc(sizeof *tc);
+ tc_init(tc, &tc_ops_default);
+ }
+ netdev_dev->tc = tc;
+}
+
+static int
+default_tc_install(struct netdev *netdev,
+ const struct shash *details OVS_UNUSED)
+{
+ default_install__(netdev);
+ return 0;
+}
+
+static int
+default_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg OVS_UNUSED)
+{
+ default_install__(netdev);
+ return 0;
+}
+
+static const struct tc_ops tc_ops_default = {
+ NULL, /* linux_name */
+ "", /* ovs_name */
+ 0, /* n_queues */
+ default_tc_install,
+ default_tc_load,
+ NULL, /* tc_destroy */
+ NULL, /* qdisc_get */
+ NULL, /* qdisc_set */
+ NULL, /* class_get */
+ NULL, /* class_set */
+ NULL, /* class_delete */
+ NULL, /* class_get_stats */
+ NULL /* class_dump_stats */
+};
+
+/* "linux-other" traffic control class.
+ *
+ * */
+
+static int
+other_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg OVS_UNUSED)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ static struct tc *tc;
+
+ if (!tc) {
+ tc = xmalloc(sizeof *tc);
+ tc_init(tc, &tc_ops_other);
+ }
+ netdev_dev->tc = tc;
+ return 0;
+}
+
+static const struct tc_ops tc_ops_other = {
+ NULL, /* linux_name */
+ "linux-other", /* ovs_name */
+ 0, /* n_queues */
+ NULL, /* tc_install */
+ other_tc_load,
+ NULL, /* tc_destroy */
+ NULL, /* qdisc_get */
+ NULL, /* qdisc_set */
+ NULL, /* class_get */
+ NULL, /* class_set */
+ NULL, /* class_delete */
+ NULL, /* class_get_stats */
+ NULL /* class_dump_stats */
+};
+
+/* Traffic control. */
+
+/* Number of kernel "tc" ticks per second. */
+static double ticks_per_s;
+
+/* Number of kernel "jiffies" per second. This is used for the purpose of
+ * computing buffer sizes. Generally kernel qdiscs need to be able to buffer
+ * one jiffy's worth of data.
+ *
+ * There are two possibilities here:
+ *
+ * - 'buffer_hz' is the kernel's real timer tick rate, a small number in the
+ * approximate range of 100 to 1024. That means that we really need to
+ * make sure that the qdisc can buffer that much data.
+ *
+ * - 'buffer_hz' is an absurdly large number. That means that the kernel
+ * has finely granular timers and there's no need to fudge additional room
+ * for buffers. (There's no extra effort needed to implement that: the
+ * large 'buffer_hz' is used as a divisor, so practically any number will
+ * come out as 0 in the division. Small integer results in the case of
+ * really high dividends won't have any real effect anyhow.)
+ */
+static unsigned int buffer_hz;
+
+/* Returns tc handle 'major':'minor'. */
+static unsigned int
+tc_make_handle(unsigned int major, unsigned int minor)
+{
+ return TC_H_MAKE(major << 16, minor);
+}
+
+/* Returns the major number from 'handle'. */
+static unsigned int
+tc_get_major(unsigned int handle)
+{
+ return TC_H_MAJ(handle) >> 16;
+}
+
+/* Returns the minor number from 'handle'. */
+static unsigned int
+tc_get_minor(unsigned int handle)
+{
+ return TC_H_MIN(handle);
+}
+
+static struct tcmsg *
+tc_make_request(const struct netdev *netdev, int type, unsigned int flags,
+ struct ofpbuf *request)
+{
+ struct tcmsg *tcmsg;
+ int ifindex;
+ int error;
+
+ error = get_ifindex(netdev, &ifindex);
+ if (error) {
+ return NULL;
+ }
+
+ ofpbuf_init(request, 512);
+ nl_msg_put_nlmsghdr(request, sizeof *tcmsg, type, NLM_F_REQUEST | flags);
+ tcmsg = ofpbuf_put_zeros(request, sizeof *tcmsg);
+ tcmsg->tcm_family = AF_UNSPEC;
+ tcmsg->tcm_ifindex = ifindex;
+ /* Caller should fill in tcmsg->tcm_handle. */
+ /* Caller should fill in tcmsg->tcm_parent. */
+
+ return tcmsg;
+}
+
+static int
+tc_transact(struct ofpbuf *request, struct ofpbuf **replyp)
+{
+ int error = nl_sock_transact(rtnl_sock, request, replyp);
+ ofpbuf_uninit(request);
+ return error;
+}
+
+static void
+read_psched(void)
+{
+ /* The values in psched are not individually very meaningful, but they are
+ * important. The tables below show some values seen in the wild.
+ *
+ * Some notes:
+ *
+ * - "c" has always been a constant 1000000 since at least Linux 2.4.14.
+ * (Before that, there are hints that it was 1000000000.)
+ *
+ * - "d" can be unrealistically large, see the comment on 'buffer_hz'
+ * above.
+ *
+ * /proc/net/psched
+ * -----------------------------------
+ * [1] 000c8000 000f4240 000f4240 00000064
+ * [2] 000003e8 00000400 000f4240 3b9aca00
+ * [3] 000003e8 00000400 000f4240 3b9aca00
+ * [4] 000003e8 00000400 000f4240 00000064
+ * [5] 000003e8 00000040 000f4240 3b9aca00
+ * [6] 000003e8 00000040 000f4240 000000f9
+ *
+ * a b c d ticks_per_s buffer_hz
+ * ------- --------- ---------- ------------- ----------- -------------
+ * [1] 819,200 1,000,000 1,000,000 100 819,200 100
+ * [2] 1,000 1,024 1,000,000 1,000,000,000 976,562 1,000,000,000
+ * [3] 1,000 1,024 1,000,000 1,000,000,000 976,562 1,000,000,000
+ * [4] 1,000 1,024 1,000,000 100 976,562 100
+ * [5] 1,000 64 1,000,000 1,000,000,000 15,625,000 1,000,000,000
+ * [6] 1,000 64 1,000,000 249 15,625,000 249
+ *
+ * [1] 2.6.18-128.1.6.el5.xs5.5.0.505.1024xen from XenServer 5.5.0-24648p
+ * [2] 2.6.26-1-686-bigmem from Debian lenny
+ * [3] 2.6.26-2-sparc64 from Debian lenny
+ * [4] 2.6.27.42-0.1.1.xs5.6.810.44.111163xen from XenServer 5.6.810-31078p
+ * [5] 2.6.32.21.22 (approx.) from Ubuntu 10.04 on VMware Fusion
+ * [6] 2.6.34 from kernel.org on KVM
+ */
+ static const char fn[] = "/proc/net/psched";
+ unsigned int a, b, c, d;
+ FILE *stream;
+
+ ticks_per_s = 1.0;
+ buffer_hz = 100;
+
+ stream = fopen(fn, "r");
+ if (!stream) {
+ VLOG_WARN("%s: open failed: %s", fn, strerror(errno));
+ return;
+ }
+
+ if (fscanf(stream, "%x %x %x %x", &a, &b, &c, &d) != 4) {
+ VLOG_WARN("%s: read failed", fn);
+ fclose(stream);
+ return;
+ }
+ VLOG_DBG("%s: psched parameters are: %u %u %u %u", fn, a, b, c, d);
+ fclose(stream);
+
+ if (!a || !c) {
+ VLOG_WARN("%s: invalid scheduler parameters", fn);
+ return;
+ }
+
+ ticks_per_s = (double) a * c / b;
+ if (c == 1000000) {
+ buffer_hz = d;
+ } else {
+ VLOG_WARN("%s: unexpected psched parameters: %u %u %u %u",
+ fn, a, b, c, d);
+ }
+ VLOG_DBG("%s: ticks_per_s=%f buffer_hz=%u", fn, ticks_per_s, buffer_hz);
+}
+
+/* Returns the number of bytes that can be transmitted in 'ticks' ticks at a
+ * rate of 'rate' bytes per second. */
+static unsigned int
+tc_ticks_to_bytes(unsigned int rate, unsigned int ticks)
+{
+ if (!buffer_hz) {
+ read_psched();
+ }
+ return (rate * ticks) / ticks_per_s;
+}
+
+/* Returns the number of ticks that it would take to transmit 'size' bytes at a
+ * rate of 'rate' bytes per second. */
+static unsigned int
+tc_bytes_to_ticks(unsigned int rate, unsigned int size)
+{
+ if (!buffer_hz) {
+ read_psched();
+ }
+ return ((unsigned long long int) ticks_per_s * size) / rate;
+}
+
+/* Returns the number of bytes that need to be reserved for qdisc buffering at
+ * a transmission rate of 'rate' bytes per second. */
+static unsigned int
+tc_buffer_per_jiffy(unsigned int rate)
+{
+ if (!buffer_hz) {
+ read_psched();
+ }
+ return rate / buffer_hz;
+}
+
+/* Given Netlink 'msg' that describes a qdisc, extracts the name of the qdisc,
+ * e.g. "htb", into '*kind' (if it is nonnull). If 'options' is nonnull,
+ * extracts 'msg''s TCA_OPTIONS attributes into '*options' if it is present or
+ * stores NULL into it if it is absent.
+ *
+ * '*kind' and '*options' point into 'msg', so they are owned by whoever owns
+ * 'msg'.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+tc_parse_qdisc(const struct ofpbuf *msg, const char **kind,
+ struct nlattr **options)
+{
+ static const struct nl_policy tca_policy[] = {
+ [TCA_KIND] = { .type = NL_A_STRING, .optional = false },
+ [TCA_OPTIONS] = { .type = NL_A_NESTED, .optional = true },
+ };
+ struct nlattr *ta[ARRAY_SIZE(tca_policy)];
+
+ if (!nl_policy_parse(msg, NLMSG_HDRLEN + sizeof(struct tcmsg),
+ tca_policy, ta, ARRAY_SIZE(ta))) {
+ VLOG_WARN_RL(&rl, "failed to parse qdisc message");
+ goto error;
+ }
+
+ if (kind) {
+ *kind = nl_attr_get_string(ta[TCA_KIND]);
+ }
+
+ if (options) {
+ *options = ta[TCA_OPTIONS];
+ }
+
+ return 0;
+
+error:
+ if (kind) {
+ *kind = NULL;
+ }
+ if (options) {
+ *options = NULL;
+ }
+ return EPROTO;
+}
+
+/* Given Netlink 'msg' that describes a class, extracts the queue ID (e.g. the
+ * minor number of its class ID) into '*queue_id', its TCA_OPTIONS attribute
+ * into '*options', and its queue statistics into '*stats'. Any of the output
+ * arguments may be null.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+static int
+tc_parse_class(const struct ofpbuf *msg, unsigned int *handlep,
+ struct nlattr **options, struct netdev_queue_stats *stats)
+{
+ static const struct nl_policy tca_policy[] = {
+ [TCA_OPTIONS] = { .type = NL_A_NESTED, .optional = false },
+ [TCA_STATS2] = { .type = NL_A_NESTED, .optional = false },
+ };
+ struct nlattr *ta[ARRAY_SIZE(tca_policy)];
+
+ if (!nl_policy_parse(msg, NLMSG_HDRLEN + sizeof(struct tcmsg),
+ tca_policy, ta, ARRAY_SIZE(ta))) {
+ VLOG_WARN_RL(&rl, "failed to parse class message");
+ goto error;
+ }
+
+ if (handlep) {
+ struct tcmsg *tc = ofpbuf_at_assert(msg, NLMSG_HDRLEN, sizeof *tc);
+ *handlep = tc->tcm_handle;
+ }
+
+ if (options) {
+ *options = ta[TCA_OPTIONS];
+ }
+
+ if (stats) {
+ const struct gnet_stats_queue *gsq;
+ struct gnet_stats_basic gsb;
+
+ static const struct nl_policy stats_policy[] = {
+ [TCA_STATS_BASIC] = { .type = NL_A_UNSPEC, .optional = false,
+ .min_len = sizeof gsb },
+ [TCA_STATS_QUEUE] = { .type = NL_A_UNSPEC, .optional = false,
+ .min_len = sizeof *gsq },
+ };
+ struct nlattr *sa[ARRAY_SIZE(stats_policy)];
+
+ if (!nl_parse_nested(ta[TCA_STATS2], stats_policy,
+ sa, ARRAY_SIZE(sa))) {
+ VLOG_WARN_RL(&rl, "failed to parse class stats");
+ goto error;
+ }
+
+ /* Alignment issues screw up the length of struct gnet_stats_basic on
+ * some arch/bitsize combinations. Newer versions of Linux have a
+ * struct gnet_stats_basic_packed, but we can't depend on that. The
+ * easiest thing to do is just to make a copy. */
+ memset(&gsb, 0, sizeof gsb);
+ memcpy(&gsb, nl_attr_get(sa[TCA_STATS_BASIC]),
+ MIN(nl_attr_get_size(sa[TCA_STATS_BASIC]), sizeof gsb));
+ stats->tx_bytes = gsb.bytes;
+ stats->tx_packets = gsb.packets;
+
+ gsq = nl_attr_get(sa[TCA_STATS_QUEUE]);
+ stats->tx_errors = gsq->drops;
+ }
+
+ return 0;
+
+error:
+ if (options) {
+ *options = NULL;
+ }
+ if (stats) {
+ memset(stats, 0, sizeof *stats);
+ }
+ return EPROTO;
+}
+
+/* Queries the kernel for class with identifier 'handle' and parent 'parent'
+ * on 'netdev'. */
+static int
+tc_query_class(const struct netdev *netdev,
+ unsigned int handle, unsigned int parent,
+ struct ofpbuf **replyp)
+{
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+
+ tcmsg = tc_make_request(netdev, RTM_GETTCLASS, NLM_F_ECHO, &request);
+ tcmsg->tcm_handle = handle;
+ tcmsg->tcm_parent = parent;
+
+ error = tc_transact(&request, replyp);
+ if (error) {
+ VLOG_WARN_RL(&rl, "query %s class %u:%u (parent %u:%u) failed (%s)",
+ netdev_get_name(netdev),
+ tc_get_major(handle), tc_get_minor(handle),
+ tc_get_major(parent), tc_get_minor(parent),
+ strerror(error));
+ }
+ return error;
+}
+
+/* Equivalent to "tc class del dev <name> handle <handle>". */
+static int
+tc_delete_class(const struct netdev *netdev, unsigned int handle)
+{
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+
+ tcmsg = tc_make_request(netdev, RTM_DELTCLASS, 0, &request);
+ tcmsg->tcm_handle = handle;
+ tcmsg->tcm_parent = 0;
+
+ error = tc_transact(&request, NULL);
+ if (error) {
+ VLOG_WARN_RL(&rl, "delete %s class %u:%u failed (%s)",
+ netdev_get_name(netdev),
+ tc_get_major(handle), tc_get_minor(handle),
+ strerror(error));
+ }
+ return error;
+}
+
+/* Equivalent to "tc qdisc del dev <name> root". */
+static int
+tc_del_qdisc(struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+
+ tcmsg = tc_make_request(netdev, RTM_DELQDISC, 0, &request);
+ tcmsg->tcm_handle = tc_make_handle(1, 0);
+ tcmsg->tcm_parent = TC_H_ROOT;
+
+ error = tc_transact(&request, NULL);
+ if (error == EINVAL) {
+ /* EINVAL probably means that the default qdisc was in use, in which
+ * case we've accomplished our purpose. */
+ error = 0;
+ }
+ if (!error && netdev_dev->tc) {
+ if (netdev_dev->tc->ops->tc_destroy) {
+ netdev_dev->tc->ops->tc_destroy(netdev_dev->tc);
+ }
+ netdev_dev->tc = NULL;
+ }
+ return error;
+}
+
+/* If 'netdev''s qdisc type and parameters are not yet known, queries the
+ * kernel to determine what they are. Returns 0 if successful, otherwise a
+ * positive errno value. */
+static int
+tc_query_qdisc(const struct netdev *netdev)
+{
+ struct netdev_dev_linux *netdev_dev =
+ netdev_dev_linux_cast(netdev_get_dev(netdev));
+ struct ofpbuf request, *qdisc;
+ const struct tc_ops *ops;
+ struct tcmsg *tcmsg;
+ int load_error;
+ int error;
+
+ if (netdev_dev->tc) {
+ return 0;
+ }
+
+ /* This RTM_GETQDISC is crafted to avoid OOPSing kernels that do not have
+ * commit 53b0f08 "net_sched: Fix qdisc_notify()", which is anything before
+ * 2.6.35 without that fix backported to it.
+ *
+ * To avoid the OOPS, we must not make a request that would attempt to dump
+ * a "built-in" qdisc, that is, the default pfifo_fast qdisc or one of a
+ * few others. There are a few ways that I can see to do this, but most of
+ * them seem to be racy (and if you lose the race the kernel OOPSes). The
+ * technique chosen here is to assume that any non-default qdisc that we
+ * create will have a class with handle 1:0. The built-in qdiscs only have
+ * a class with handle 0:0.
+ *
+ * We could check for Linux 2.6.35+ and use a more straightforward method
+ * there. */
+ tcmsg = tc_make_request(netdev, RTM_GETQDISC, NLM_F_ECHO, &request);
+ tcmsg->tcm_handle = tc_make_handle(1, 0);
+ tcmsg->tcm_parent = 0;
+
+ /* Figure out what tc class to instantiate. */
+ error = tc_transact(&request, &qdisc);
+ if (!error) {
+ const char *kind;
+
+ error = tc_parse_qdisc(qdisc, &kind, NULL);
+ if (error) {
+ ops = &tc_ops_other;
+ } else {
+ ops = tc_lookup_linux_name(kind);
+ if (!ops) {
+ static struct vlog_rate_limit rl2 = VLOG_RATE_LIMIT_INIT(1, 1);
+ VLOG_INFO_RL(&rl2, "unknown qdisc \"%s\"", kind);
+
+ ops = &tc_ops_other;
+ }
+ }
+ } else if (error == ENOENT) {
+ /* Either it's a built-in qdisc, or it's a qdisc set up by some
+ * other entity that doesn't have a handle 1:0. We will assume
+ * that it's the system default qdisc. */
+ ops = &tc_ops_default;
+ error = 0;
+ } else {
+ /* Who knows? Maybe the device got deleted. */
+ VLOG_WARN_RL(&rl, "query %s qdisc failed (%s)",
+ netdev_get_name(netdev), strerror(error));
+ ops = &tc_ops_other;
+ }
+
+ /* Instantiate it. */
+ load_error = ops->tc_load((struct netdev *) netdev, qdisc);
+ assert((load_error == 0) == (netdev_dev->tc != NULL));
+ ofpbuf_delete(qdisc);
+
+ return error ? error : load_error;
+}
+
+/* Linux traffic control uses tables with 256 entries ("rtab" tables) to
+ approximate the time to transmit packets of various lengths. For an MTU of
+ 256 or less, each entry is exact; for an MTU of 257 through 512, each entry
+ represents two possible packet lengths; for a MTU of 513 through 1024, four
+ possible lengths; and so on.
+
+ Returns, for the specified 'mtu', the number of bits that packet lengths
+ need to be shifted right to fit within such a 256-entry table. */
+static int
+tc_calc_cell_log(unsigned int mtu)
+{
+ int cell_log;
+
+ if (!mtu) {
+ mtu = ETH_PAYLOAD_MAX;
+ }
+ mtu += ETH_HEADER_LEN + VLAN_HEADER_LEN;
+
+ for (cell_log = 0; mtu >= 256; cell_log++) {
+ mtu >>= 1;
+ }
+
+ return cell_log;
+}
+
+/* Initializes 'rate' properly for a rate of 'Bps' bytes per second with an MTU
+ * of 'mtu'. */
+static void
+tc_fill_rate(struct tc_ratespec *rate, uint64_t Bps, int mtu)
+{
+ memset(rate, 0, sizeof *rate);
+ rate->cell_log = tc_calc_cell_log(mtu);
+ /* rate->overhead = 0; */ /* New in 2.6.24, not yet in some */
+ /* rate->cell_align = 0; */ /* distro headers. */
+ rate->mpu = ETH_TOTAL_MIN;
+ rate->rate = Bps;
+}
+
+/* Appends to 'msg' an "rtab" table for the specified 'rate' as a Netlink
+ * attribute of the specified "type".
+ *
+ * See tc_calc_cell_log() above for a description of "rtab"s. */
+static void
+tc_put_rtab(struct ofpbuf *msg, uint16_t type, const struct tc_ratespec *rate)
+{
+ uint32_t *rtab;
+ unsigned int i;
+
+ rtab = nl_msg_put_unspec_uninit(msg, type, TC_RTAB_SIZE);
+ for (i = 0; i < TC_RTAB_SIZE / sizeof *rtab; i++) {
+ unsigned packet_size = (i + 1) << rate->cell_log;
+ if (packet_size < rate->mpu) {
+ packet_size = rate->mpu;
+ }
+ rtab[i] = tc_bytes_to_ticks(rate->rate, packet_size);
+ }
+}
+
+/* Calculates the proper value of 'buffer' or 'cbuffer' in HTB options given a
+ * rate of 'Bps' bytes per second, the specified 'mtu', and a user-requested
+ * burst size of 'burst_bytes'. (If no value was requested, a 'burst_bytes' of
+ * 0 is fine.)
+ *
+ * This */
+static int
+tc_calc_buffer(unsigned int Bps, int mtu, uint64_t burst_bytes)
+{
+ unsigned int min_burst = tc_buffer_per_jiffy(Bps) + mtu;
+ return tc_bytes_to_ticks(Bps, MAX(burst_bytes, min_burst));
+}
+
+
+/* Utility functions. */
+
static int
get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
{
@@ -1800,7 +3382,7 @@ get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
fclose(stream);
return ENODEV;
}
-
+
static int
get_flags(const struct netdev *netdev, int *flags)
{
diff --git a/lib/netdev-patch.c b/lib/netdev-patch.c
index 17c509f1..ec4d4bd8 100644
--- a/lib/netdev-patch.c
+++ b/lib/netdev-patch.c
@@ -207,7 +207,18 @@ const struct netdev_class netdev_patch_class = {
NULL, /* get_features */
NULL, /* set_advertisements */
NULL, /* get_vlan_vid */
+
NULL, /* set_policing */
+ NULL, /* get_qos_types */
+ NULL, /* get_qos_capabilities */
+ NULL, /* get_qos */
+ NULL, /* set_qos */
+ NULL, /* get_queue */
+ NULL, /* set_queue */
+ NULL, /* delete_queue */
+ NULL, /* get_queue_stats */
+ NULL, /* dump_queues */
+ NULL, /* dump_queue_stats */
NULL, /* get_in4 */
NULL, /* set_in4 */
diff --git a/lib/netdev-provider.h b/lib/netdev-provider.h
index 024b1fb7..52f440fa 100644
--- a/lib/netdev-provider.h
+++ b/lib/netdev-provider.h
@@ -313,6 +313,155 @@ struct netdev_class {
int (*set_policing)(struct netdev *netdev, unsigned int kbits_rate,
unsigned int kbits_burst);
+ /* Adds to 'types' all of the forms of QoS supported by 'netdev', or leaves
+ * it empty if 'netdev' does not support QoS. Any names added to 'types'
+ * should be documented as valid for the "type" column in the "QoS" table
+ * in vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * Every network device must support disabling QoS with a type of "", but
+ * this function must not add "" to 'types'.
+ *
+ * The caller is responsible for initializing 'types' (e.g. with
+ * svec_init()) before calling this function. The caller takes ownership
+ * of the strings added to 'types'.
+ *
+ * May be NULL if 'netdev' does not support QoS at all. */
+ int (*get_qos_types)(const struct netdev *netdev, struct svec *types);
+
+ /* Queries 'netdev' for its capabilities regarding the specified 'type' of
+ * QoS. On success, initializes 'caps' with the QoS capabilities.
+ *
+ * Should return EOPNOTSUPP if 'netdev' does not support 'type'. May be
+ * NULL if 'netdev' does not support QoS at all. */
+ int (*get_qos_capabilities)(const struct netdev *netdev,
+ const char *type,
+ struct netdev_qos_capabilities *caps);
+
+ /* Queries 'netdev' about its currently configured form of QoS. If
+ * successful, stores the name of the current form of QoS into '*typep'
+ * and any details of configuration as string key-value pairs in
+ * 'details'.
+ *
+ * A '*typep' of "" indicates that QoS is currently disabled on 'netdev'.
+ *
+ * The caller initializes 'details' before calling this function. The
+ * caller takes ownership of the string key-values pairs added to
+ * 'details'.
+ *
+ * The netdev retains ownership of '*typep'.
+ *
+ * '*typep' will be one of the types returned by netdev_get_qos_types() for
+ * 'netdev'. The contents of 'details' should be documented as valid for
+ * '*typep' in the "other_config" column in the "QoS" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * May be NULL if 'netdev' does not support QoS at all. */
+ int (*get_qos)(const struct netdev *netdev,
+ const char **typep, struct shash *details);
+
+ /* Attempts to reconfigure QoS on 'netdev', changing the form of QoS to
+ * 'type' with details of configuration from 'details'.
+ *
+ * On error, the previous QoS configuration is retained.
+ *
+ * When this function changes the type of QoS (not just 'details'), this
+ * also resets all queue configuration for 'netdev' to their defaults
+ * (which depend on the specific type of QoS). Otherwise, the queue
+ * configuration for 'netdev' is unchanged.
+ *
+ * 'type' should be "" (to disable QoS) or one of the types returned by
+ * netdev_get_qos_types() for 'netdev'. The contents of 'details' should
+ * be documented as valid for the given 'type' in the "other_config" column
+ * in the "QoS" table in vswitchd/vswitch.xml (which is built as
+ * ovs-vswitchd.conf.db(8)).
+ *
+ * May be NULL if 'netdev' does not support QoS at all. */
+ int (*set_qos)(struct netdev *netdev,
+ const char *type, const struct shash *details);
+
+ /* Queries 'netdev' for information about the queue numbered 'queue_id'.
+ * If successful, adds that information as string key-value pairs to
+ * 'details'. Returns 0 if successful, otherwise a positive errno value.
+ *
+ * Should return EINVAL if 'queue_id' is greater than or equal to the
+ * number of supported queues (as reported in the 'n_queues' member of
+ * struct netdev_qos_capabilities by 'get_qos_capabilities').
+ *
+ * The caller initializes 'details' before calling this function. The
+ * caller takes ownership of the string key-values pairs added to
+ * 'details'.
+ *
+ * The returned contents of 'details' should be documented as valid for the
+ * given 'type' in the "other_config" column in the "Queue" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ */
+ int (*get_queue)(const struct netdev *netdev,
+ unsigned int queue_id, struct shash *details);
+
+ /* Configures the queue numbered 'queue_id' on 'netdev' with the key-value
+ * string pairs in 'details'. The contents of 'details' should be
+ * documented as valid for the given 'type' in the "other_config" column in
+ * the "Queue" table in vswitchd/vswitch.xml (which is built as
+ * ovs-vswitchd.conf.db(8)). Returns 0 if successful, otherwise a positive
+ * errno value. On failure, the given queue's configuration should be
+ * unmodified.
+ *
+ * Should return EINVAL if 'queue_id' is greater than or equal to the
+ * number of supported queues (as reported in the 'n_queues' member of
+ * struct netdev_qos_capabilities by 'get_qos_capabilities'), or if
+ * 'details' is invalid for the type of queue.
+ *
+ * This function does not modify 'details', and the caller retains
+ * ownership of it.
+ *
+ * May be NULL if 'netdev' does not support QoS at all. */
+ int (*set_queue)(struct netdev *netdev,
+ unsigned int queue_id, const struct shash *details);
+
+ /* Attempts to delete the queue numbered 'queue_id' from 'netdev'.
+ *
+ * Should return EINVAL if 'queue_id' is greater than or equal to the
+ * number of supported queues (as reported in the 'n_queues' member of
+ * struct netdev_qos_capabilities by 'get_qos_capabilities'). Should
+ * return EOPNOTSUPP if 'queue_id' is valid but may not be deleted (e.g. if
+ * 'netdev' has a fixed set of queues with the current QoS mode).
+ *
+ * May be NULL if 'netdev' does not support QoS at all, or if all of its
+ * QoS modes have fixed sets of queues. */
+ int (*delete_queue)(struct netdev *netdev, unsigned int queue_id);
+
+ /* Obtains statistics about 'queue_id' on 'netdev'. Fills 'stats' with the
+ * queue's statistics. May set individual members of 'stats' to all-1-bits
+ * if the statistic is unavailable.
+ *
+ * May be NULL if 'netdev' does not support QoS at all. */
+ int (*get_queue_stats)(const struct netdev *netdev, unsigned int queue_id,
+ struct netdev_queue_stats *stats);
+
+ /* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's
+ * ID, its configuration, and the 'aux' specified by the caller. The order
+ * of iteration is unspecified, but (when successful) each queue is visited
+ * exactly once.
+ *
+ * 'cb' will not modify or free the 'details' argument passed in. */
+ int (*dump_queues)(const struct netdev *netdev,
+ void (*cb)(unsigned int queue_id,
+ const struct shash *details,
+ void *aux),
+ void *aux);
+
+ /* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's
+ * ID, its statistics, and the 'aux' specified by the caller. The order of
+ * iteration is unspecified, but (when successful) each queue must be
+ * visited exactly once.
+ *
+ * 'cb' will not modify or free the statistics passed in. */
+ int (*dump_queue_stats)(const struct netdev *netdev,
+ void (*cb)(unsigned int queue_id,
+ struct netdev_queue_stats *,
+ void *aux),
+ void *aux);
+
/* If 'netdev' has an assigned IPv4 address, sets '*address' to that
* address and '*netmask' to the associated netmask.
*
diff --git a/lib/netdev.c b/lib/netdev.c
index a15315a7..dc27fd74 100644
--- a/lib/netdev.c
+++ b/lib/netdev.c
@@ -963,6 +963,285 @@ netdev_set_policing(struct netdev *netdev, uint32_t kbits_rate,
: EOPNOTSUPP);
}
+/* Adds to 'types' all of the forms of QoS supported by 'netdev', or leaves it
+ * empty if 'netdev' does not support QoS. Any names added to 'types' should
+ * be documented as valid for the "type" column in the "QoS" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * Every network device supports disabling QoS with a type of "", but this type
+ * will not be added to 'types'.
+ *
+ * The caller must initialize 'types' (e.g. with svec_init()) before calling
+ * this function. The caller is responsible for destroying 'types' (e.g. with
+ * svec_destroy()) when it is no longer needed.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. */
+int
+netdev_get_qos_types(const struct netdev *netdev, struct svec *types)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ return (class->get_qos_types
+ ? class->get_qos_types(netdev, types)
+ : 0);
+}
+
+/* Queries 'netdev' for its capabilities regarding the specified 'type' of QoS,
+ * which should be "" or one of the types returned by netdev_get_qos_types()
+ * for 'netdev'. Returns 0 if successful, otherwise a positive errno value.
+ * On success, initializes 'caps' with the QoS capabilities; on failure, clears
+ * 'caps' to all zeros. */
+int
+netdev_get_qos_capabilities(const struct netdev *netdev, const char *type,
+ struct netdev_qos_capabilities *caps)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+
+ if (*type) {
+ int retval = (class->get_qos_capabilities
+ ? class->get_qos_capabilities(netdev, type, caps)
+ : EOPNOTSUPP);
+ if (retval) {
+ memset(caps, 0, sizeof *caps);
+ }
+ return retval;
+ } else {
+ /* Every netdev supports turning off QoS. */
+ memset(caps, 0, sizeof *caps);
+ return 0;
+ }
+}
+
+/* Obtains the number of queues supported by 'netdev' for the specified 'type'
+ * of QoS. Returns 0 if successful, otherwise a positive errno value. Stores
+ * the number of queues (zero on failure) in '*n_queuesp'.
+ *
+ * This is just a simple wrapper around netdev_get_qos_capabilities(). */
+int
+netdev_get_n_queues(const struct netdev *netdev,
+ const char *type, unsigned int *n_queuesp)
+{
+ struct netdev_qos_capabilities caps;
+ int retval;
+
+ retval = netdev_get_qos_capabilities(netdev, type, &caps);
+ *n_queuesp = caps.n_queues;
+ return retval;
+}
+
+/* Queries 'netdev' about its currently configured form of QoS. If successful,
+ * stores the name of the current form of QoS into '*typep', stores any details
+ * of configuration as string key-value pairs in 'details', and returns 0. On
+ * failure, sets '*typep' to NULL and returns a positive errno value.
+ *
+ * A '*typep' of "" indicates that QoS is currently disabled on 'netdev'.
+ *
+ * The caller must initialize 'details' as an empty shash (e.g. with
+ * shash_init()) before calling this function. The caller must free 'details',
+ * including 'data' members, when it is no longer needed (e.g. with
+ * shash_destroy_free_data()).
+ *
+ * The caller must not modify or free '*typep'.
+ *
+ * '*typep' will be one of the types returned by netdev_get_qos_types() for
+ * 'netdev'. The contents of 'details' should be documented as valid for
+ * '*typep' in the "other_config" column in the "QoS" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)). */
+int
+netdev_get_qos(const struct netdev *netdev,
+ const char **typep, struct shash *details)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ int retval;
+
+ if (class->get_qos) {
+ retval = class->get_qos(netdev, typep, details);
+ if (retval) {
+ *typep = NULL;
+ shash_clear_free_data(details);
+ }
+ return retval;
+ } else {
+ /* 'netdev' doesn't support QoS, so report that QoS is disabled. */
+ *typep = "";
+ return 0;
+ }
+}
+
+/* Attempts to reconfigure QoS on 'netdev', changing the form of QoS to 'type'
+ * with details of configuration from 'details'. Returns 0 if successful,
+ * otherwise a positive errno value. On error, the previous QoS configuration
+ * is retained.
+ *
+ * When this function changes the type of QoS (not just 'details'), this also
+ * resets all queue configuration for 'netdev' to their defaults (which depend
+ * on the specific type of QoS). Otherwise, the queue configuration for
+ * 'netdev' is unchanged.
+ *
+ * 'type' should be "" (to disable QoS) or one of the types returned by
+ * netdev_get_qos_types() for 'netdev'. The contents of 'details' should be
+ * documented as valid for the given 'type' in the "other_config" column in the
+ * "QoS" table in vswitchd/vswitch.xml (which is built as
+ * ovs-vswitchd.conf.db(8)).
+ *
+ * NULL may be specified for 'details' if there are no configuration
+ * details. */
+int
+netdev_set_qos(struct netdev *netdev,
+ const char *type, const struct shash *details)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+
+ if (!type) {
+ type = "";
+ }
+
+ if (class->set_qos) {
+ if (!details) {
+ static struct shash empty = SHASH_INITIALIZER(&empty);
+ details = &empty;
+ }
+ return class->set_qos(netdev, type, details);
+ } else {
+ return *type ? EOPNOTSUPP : 0;
+ }
+}
+
+/* Queries 'netdev' for information about the queue numbered 'queue_id'. If
+ * successful, adds that information as string key-value pairs to 'details'.
+ * Returns 0 if successful, otherwise a positive errno value.
+ *
+ * 'queue_id' must be less than the number of queues supported by 'netdev' for
+ * the current form of QoS (e.g. as returned by netdev_get_n_queues(netdev)).
+ *
+ * The returned contents of 'details' should be documented as valid for the
+ * given 'type' in the "other_config" column in the "Queue" table in
+ * vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ *
+ * The caller must initialize 'details' (e.g. with shash_init()) before calling
+ * this function. The caller must free 'details', including 'data' members,
+ * when it is no longer needed (e.g. with shash_destroy_free_data()). */
+int
+netdev_get_queue(const struct netdev *netdev,
+ unsigned int queue_id, struct shash *details)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ int retval;
+
+ retval = (class->get_queue
+ ? class->get_queue(netdev, queue_id, details)
+ : EOPNOTSUPP);
+ if (retval) {
+ shash_clear_free_data(details);
+ }
+ return retval;
+}
+
+/* Configures the queue numbered 'queue_id' on 'netdev' with the key-value
+ * string pairs in 'details'. The contents of 'details' should be documented
+ * as valid for the given 'type' in the "other_config" column in the "Queue"
+ * table in vswitchd/vswitch.xml (which is built as ovs-vswitchd.conf.db(8)).
+ * Returns 0 if successful, otherwise a positive errno value. On failure, the
+ * given queue's configuration should be unmodified.
+ *
+ * 'queue_id' must be less than the number of queues supported by 'netdev' for
+ * the current form of QoS (e.g. as returned by netdev_get_n_queues(netdev)).
+ *
+ * This function does not modify 'details', and the caller retains ownership of
+ * it.
+ */
+int
+netdev_set_queue(struct netdev *netdev,
+ unsigned int queue_id, const struct shash *details)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ return (class->set_queue
+ ? class->set_queue(netdev, queue_id, details)
+ : EOPNOTSUPP);
+}
+
+/* Attempts to delete the queue numbered 'queue_id' from 'netdev'. Some kinds
+ * of QoS may have a fixed set of queues, in which case attempts to delete them
+ * will fail with EOPNOTSUPP.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. On failure, the
+ * given queue will be unmodified.
+ *
+ * 'queue_id' must be less than the number of queues supported by 'netdev' for
+ * the current form of QoS (e.g. as returned by
+ * netdev_get_n_queues(netdev)). */
+int
+netdev_delete_queue(struct netdev *netdev, unsigned int queue_id)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ return (class->delete_queue
+ ? class->delete_queue(netdev, queue_id)
+ : EOPNOTSUPP);
+}
+
+/* Obtains statistics about 'queue_id' on 'netdev'. On success, returns 0 and
+ * fills 'stats' with the queue's statistics; individual members of 'stats' may
+ * be set to all-1-bits if the statistic is unavailable. On failure, returns a
+ * positive errno value and fills 'stats' with all-1-bits. */
+int
+netdev_get_queue_stats(const struct netdev *netdev, unsigned int queue_id,
+ struct netdev_queue_stats *stats)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ int retval;
+
+ retval = (class->get_queue_stats
+ ? class->get_queue_stats(netdev, queue_id, stats)
+ : EOPNOTSUPP);
+ if (retval) {
+ memset(stats, 0xff, sizeof *stats);
+ }
+ return retval;
+}
+
+/* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's ID,
+ * its configuration, and the 'aux' specified by the caller. The order of
+ * iteration is unspecified, but (when successful) each queue is visited
+ * exactly once.
+ *
+ * Calling this function may be more efficient than calling netdev_get_queue()
+ * for every queue.
+ *
+ * 'cb' must not modify or free the 'details' argument passed in.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. On error, some
+ * configured queues may not have been included in the iteration. */
+int
+netdev_dump_queues(const struct netdev *netdev,
+ netdev_dump_queues_cb *cb, void *aux)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ return (class->dump_queues
+ ? class->dump_queues(netdev, cb, aux)
+ : EOPNOTSUPP);
+}
+
+/* Iterates over all of 'netdev''s queues, calling 'cb' with the queue's ID,
+ * its statistics, and the 'aux' specified by the caller. The order of
+ * iteration is unspecified, but (when successful) each queue is visited
+ * exactly once.
+ *
+ * Calling this function may be more efficient than calling
+ * netdev_get_queue_stats() for every queue.
+ *
+ * 'cb' must not modify or free the statistics passed in.
+ *
+ * Returns 0 if successful, otherwise a positive errno value. On error, some
+ * configured queues may not have been included in the iteration. */
+int
+netdev_dump_queue_stats(const struct netdev *netdev,
+ netdev_dump_queue_stats_cb *cb, void *aux)
+{
+ const struct netdev_class *class = netdev_get_dev(netdev)->netdev_class;
+ return (class->dump_queue_stats
+ ? class->dump_queue_stats(netdev, cb, aux)
+ : EOPNOTSUPP);
+}
+
/* If 'netdev' is a VLAN network device (e.g. one created with vconfig(8)),
* sets '*vlan_vid' to the VLAN VID associated with that device and returns 0.
* Otherwise returns a errno value (specifically ENOENT if 'netdev_name' is the
diff --git a/lib/netdev.h b/lib/netdev.h
index 5dca24cf..cd5c8c30 100644
--- a/lib/netdev.h
+++ b/lib/netdev.h
@@ -97,6 +97,7 @@ int netdev_register_provider(const struct netdev_class *);
int netdev_unregister_provider(const char *type);
void netdev_enumerate_types(struct svec *types);
+/* Open and close. */
int netdev_open(struct netdev_options *, struct netdev **);
int netdev_open_default(const char *name, struct netdev **);
int netdev_reconfigure(struct netdev *, const struct shash *args);
@@ -107,11 +108,13 @@ bool netdev_is_open(const char *name);
int netdev_enumerate(struct svec *);
+/* Basic properties. */
const char *netdev_get_name(const struct netdev *);
const char *netdev_get_type(const struct netdev *);
int netdev_get_mtu(const struct netdev *, int *mtup);
int netdev_get_ifindex(const struct netdev *);
+/* Packet send and receive. */
int netdev_recv(struct netdev *, struct ofpbuf *);
void netdev_recv_wait(struct netdev *);
int netdev_drain(struct netdev *);
@@ -119,9 +122,11 @@ int netdev_drain(struct netdev *);
int netdev_send(struct netdev *, const struct ofpbuf *);
void netdev_send_wait(struct netdev *);
+/* Hardware address. */
int netdev_set_etheraddr(struct netdev *, const uint8_t mac[6]);
int netdev_get_etheraddr(const struct netdev *, uint8_t mac[6]);
+/* PHY interface. */
int netdev_get_carrier(const struct netdev *, bool *carrier);
int netdev_get_features(struct netdev *,
uint32_t *current, uint32_t *advertised,
@@ -130,6 +135,7 @@ uint64_t netdev_features_to_bps(uint32_t features);
bool netdev_features_is_full_duplex(uint32_t features);
int netdev_set_advertisements(struct netdev *, uint32_t advertise);
+/* TCP/IP stack interface. */
int netdev_get_in4(const struct netdev *, struct in_addr *address,
struct in_addr *netmask);
int netdev_set_in4(struct netdev *, struct in_addr addr, struct in_addr mask);
@@ -143,15 +149,62 @@ int netdev_get_flags(const struct netdev *, enum netdev_flags *);
int netdev_set_flags(struct netdev *, enum netdev_flags, bool permanent);
int netdev_turn_flags_on(struct netdev *, enum netdev_flags, bool permanent);
int netdev_turn_flags_off(struct netdev *, enum netdev_flags, bool permanent);
+struct netdev *netdev_find_dev_by_in4(const struct in_addr *);
+/* Statistics. */
int netdev_get_stats(const struct netdev *, struct netdev_stats *);
int netdev_set_stats(struct netdev *, const struct netdev_stats *);
-int netdev_set_policing(struct netdev *, uint32_t kbits_rate,
+
+/* Quality of service. */
+struct netdev_qos_capabilities {
+ unsigned int n_queues;
+};
+
+struct netdev_queue_stats {
+ /* Values of unsupported statistics are set to all-1-bits (UINT64_MAX). */
+ uint64_t tx_bytes;
+ uint64_t tx_packets;
+ uint64_t tx_errors;
+};
+
+int netdev_set_policing(struct netdev *, uint32_t kbits_rate,
uint32_t kbits_burst);
+int netdev_get_qos_types(const struct netdev *, struct svec *types);
+int netdev_get_qos_capabilities(const struct netdev *,
+ const char *type,
+ struct netdev_qos_capabilities *);
+int netdev_get_n_queues(const struct netdev *,
+ const char *type, unsigned int *n_queuesp);
+
+int netdev_get_qos(const struct netdev *,
+ const char **typep, struct shash *details);
+int netdev_set_qos(struct netdev *,
+ const char *type, const struct shash *details);
+
+int netdev_get_queue(const struct netdev *,
+ unsigned int queue_id, struct shash *details);
+int netdev_set_queue(struct netdev *,
+ unsigned int queue_id, const struct shash *details);
+int netdev_delete_queue(struct netdev *, unsigned int queue_id);
+int netdev_get_queue_stats(const struct netdev *, unsigned int queue_id,
+ struct netdev_queue_stats *);
+
+typedef void netdev_dump_queues_cb(unsigned int queue_id,
+ const struct shash *details, void *aux);
+int netdev_dump_queues(const struct netdev *,
+ netdev_dump_queues_cb *, void *aux);
+
+typedef void netdev_dump_queue_stats_cb(unsigned int queue_id,
+ struct netdev_queue_stats *,
+ void *aux);
+int netdev_dump_queue_stats(const struct netdev *,
+ netdev_dump_queue_stats_cb *, void *aux);
+
+/* Linux stuff. */
int netdev_get_vlan_vid(const struct netdev *, int *vlan_vid);
-struct netdev *netdev_find_dev_by_in4(const struct in_addr *);
+/* Monitoring for changes in network device status. */
struct netdev_monitor *netdev_monitor_create(void);
void netdev_monitor_destroy(struct netdev_monitor *);
int netdev_monitor_add(struct netdev_monitor *, struct netdev *);
diff --git a/lib/ofp-util.c b/lib/ofp-util.c
index d773a3fa..e990f0f4 100644
--- a/lib/ofp-util.c
+++ b/lib/ofp-util.c
@@ -22,6 +22,7 @@
#include "ofpbuf.h"
#include "packets.h"
#include "random.h"
+#include "xtoxll.h"
#define THIS_MODULE VLM_ofp_util
#include "vlog.h"
@@ -489,8 +490,11 @@ check_action_exact_len(const union ofp_action *a, unsigned int len,
return 0;
}
+/* Checks that 'port' is a valid output port for the OFPAT_OUTPUT action, given
+ * that the switch will never have more than 'max_ports' ports. Returns 0 if
+ * 'port' is valid, otherwise an ofp_mkerr() return code. */
static int
-check_action_port(int port, int max_ports)
+check_output_port(uint16_t port, int max_ports)
{
switch (port) {
case OFPP_IN_PORT:
@@ -503,7 +507,7 @@ check_action_port(int port, int max_ports)
return 0;
default:
- if (port >= 0 && port < max_ports) {
+ if (port < max_ports) {
return 0;
}
VLOG_WARN_RL(&bad_ofmsg_rl, "unknown output port %x", port);
@@ -511,6 +515,31 @@ check_action_port(int port, int max_ports)
}
}
+/* Checks that 'action' is a valid OFPAT_ENQUEUE action, given that the switch
+ * will never have more than 'max_ports' ports. Returns 0 if 'port' is valid,
+ * otherwise an ofp_mkerr() return code. */
+static int
+check_enqueue_action(const union ofp_action *a, unsigned int len,
+ int max_ports)
+{
+ const struct ofp_action_enqueue *oae;
+ uint16_t port;
+ int error;
+
+ error = check_action_exact_len(a, len, 16);
+ if (error) {
+ return error;
+ }
+
+ oae = (const struct ofp_action_enqueue *) a;
+ port = ntohs(oae->port);
+ if (port < max_ports || port == OFPP_IN_PORT) {
+ return 0;
+ }
+ VLOG_WARN_RL(&bad_ofmsg_rl, "unknown enqueue port %x", port);
+ return ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_OUT_PORT);
+}
+
static int
check_nicira_action(const union ofp_action *a, unsigned int len)
{
@@ -539,8 +568,11 @@ check_action(const union ofp_action *a, unsigned int len, int max_ports)
switch (ntohs(a->type)) {
case OFPAT_OUTPUT:
- error = check_action_port(ntohs(a->output.port), max_ports);
- return error ? error : check_action_exact_len(a, len, 8);
+ error = check_action_exact_len(a, len, 8);
+ if (error) {
+ return error;
+ }
+ return check_output_port(ntohs(a->output.port), max_ports);
case OFPAT_SET_VLAN_VID:
case OFPAT_SET_VLAN_PCP:
@@ -561,6 +593,9 @@ check_action(const union ofp_action *a, unsigned int len, int max_ports)
? check_nicira_action(a, len)
: ofp_mkerr(OFPET_BAD_ACTION, OFPBAC_BAD_VENDOR));
+ case OFPAT_ENQUEUE:
+ return check_enqueue_action(a, len, max_ports);
+
default:
VLOG_WARN_RL(&bad_ofmsg_rl, "unknown action type %"PRIu16,
ntohs(a->type));
@@ -603,6 +638,21 @@ validate_actions(const union ofp_action *actions, size_t n_actions,
return 0;
}
+/* Returns true if 'action' outputs to 'port' (which must be in network byte
+ * order), false otherwise. */
+bool
+action_outputs_to_port(const union ofp_action *action, uint16_t port)
+{
+ switch (ntohs(action->type)) {
+ case OFPAT_OUTPUT:
+ return action->output.port == port;
+ case OFPAT_ENQUEUE:
+ return ((const struct ofp_action_enqueue *) action)->port == port;
+ default:
+ return false;
+ }
+}
+
/* The set of actions must either come from a trusted source or have been
* previously validated with validate_actions(). */
const union ofp_action *
diff --git a/lib/ofp-util.h b/lib/ofp-util.h
index 0d141a68..2c6b2f91 100644
--- a/lib/ofp-util.h
+++ b/lib/ofp-util.h
@@ -18,6 +18,7 @@
#define OFP_UTIL_H 1
#include <assert.h>
+#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "flow.h"
@@ -77,6 +78,7 @@ const union ofp_action *actions_first(struct actions_iterator *,
const union ofp_action *actions_next(struct actions_iterator *);
int validate_actions(const union ofp_action *, size_t n_actions,
int max_ports);
+bool action_outputs_to_port(const union ofp_action *, uint16_t port);
void normalize_match(struct ofp_match *);
diff --git a/ofproto/ofproto.c b/ofproto/ofproto.c
index d877d36a..5232cfad 100644
--- a/ofproto/ofproto.c
+++ b/ofproto/ofproto.c
@@ -59,6 +59,9 @@
#include "vconn.h"
#include "xtoxll.h"
+#include <linux/types.h> /* XXX */
+#include <linux/pkt_sched.h> /* XXX */
+
#define THIS_MODULE VLM_ofproto
#include "vlog.h"
@@ -1798,7 +1801,7 @@ rule_has_out_port(const struct rule *rule, uint16_t out_port)
}
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) {
+ if (action_outputs_to_port(oa, out_port)) {
return true;
}
}
@@ -2070,15 +2073,11 @@ is_controller_rule(struct rule *rule)
* NetFlow expiration messages since it is just part of the control
* logic for the network and not real traffic. */
- if (rule && rule->super) {
- struct rule *super = rule->super;
-
- return super->n_actions == 1 &&
- super->actions[0].type == htons(OFPAT_OUTPUT) &&
- super->actions[0].output.port == htons(OFPP_CONTROLLER);
- }
-
- return false;
+ return (rule
+ && rule->super
+ && rule->super->n_actions == 1
+ && action_outputs_to_port(&rule->super->actions[0],
+ htons(OFPP_CONTROLLER)));
}
static void
@@ -2195,7 +2194,8 @@ handle_features_request(struct ofproto *p, struct ofconn *ofconn,
(1u << OFPAT_SET_NW_DST) |
(1u << OFPAT_SET_NW_TOS) |
(1u << OFPAT_SET_TP_SRC) |
- (1u << OFPAT_SET_TP_DST));
+ (1u << OFPAT_SET_TP_DST) |
+ (1u << OFPAT_ENQUEUE));
PORT_ARRAY_FOR_EACH (port, &p->ports, port_no) {
hton_ofp_phy_port(ofpbuf_put(buf, &port->opp, sizeof port->opp));
@@ -2423,6 +2423,48 @@ xlate_output_action(struct action_xlate_ctx *ctx,
}
}
+/* If the final ODP action in 'ctx' is "pop priority", drop it, as an
+ * optimization, because we're going to add another action that sets the
+ * priority immediately after, or because there are no actions following the
+ * pop. */
+static void
+remove_pop_action(struct action_xlate_ctx *ctx)
+{
+ size_t n = ctx->out->n_actions;
+ if (n > 0 && ctx->out->actions[n - 1].type == ODPAT_POP_PRIORITY) {
+ ctx->out->n_actions--;
+ }
+}
+
+static void
+xlate_enqueue_action(struct action_xlate_ctx *ctx,
+ const struct ofp_action_enqueue *oae)
+{
+ uint16_t ofp_port, odp_port;
+
+ /* Figure out ODP output port. */
+ ofp_port = ntohs(oae->port);
+ if (ofp_port != OFPP_IN_PORT) {
+ odp_port = ofp_port_to_odp_port(ofp_port);
+ } else {
+ odp_port = ctx->flow.in_port;
+ }
+
+ /* Add ODP actions. */
+ remove_pop_action(ctx);
+ odp_actions_add(ctx->out, ODPAT_SET_PRIORITY)->priority.priority
+ = TC_H_MAKE(1, ntohl(oae->queue_id)); /* XXX */
+ add_output_action(ctx, odp_port);
+ odp_actions_add(ctx->out, ODPAT_POP_PRIORITY);
+
+ /* Update NetFlow output port. */
+ if (ctx->nf_output_iface == NF_OUT_DROP) {
+ ctx->nf_output_iface = odp_port;
+ } else if (ctx->nf_output_iface != NF_OUT_FLOOD) {
+ ctx->nf_output_iface = NF_OUT_MULTI;
+ }
+}
+
static void
xlate_nicira_action(struct action_xlate_ctx *ctx,
const struct nx_action_header *nah)
@@ -2446,7 +2488,7 @@ xlate_nicira_action(struct action_xlate_ctx *ctx,
break;
/* If you add a new action here that modifies flow data, don't forget to
- * update the flow key in ctx->flow in the same key. */
+ * update the flow key in ctx->flow at the same time. */
default:
VLOG_DBG_RL(&rl, "unknown Nicira action type %"PRIu16, subtype);
@@ -2540,6 +2582,10 @@ do_xlate_actions(const union ofp_action *in, size_t n_in,
xlate_nicira_action(ctx, (const struct nx_action_header *) ia);
break;
+ case OFPAT_ENQUEUE:
+ xlate_enqueue_action(ctx, (const struct ofp_action_enqueue *) ia);
+ break;
+
default:
VLOG_DBG_RL(&rl, "unknown action type %"PRIu16, type);
break;
@@ -2567,6 +2613,7 @@ xlate_actions(const union ofp_action *in, size_t n_in,
ctx.may_set_up_flow = true;
ctx.nf_output_iface = NF_OUT_DROP;
do_xlate_actions(in, n_in, &ctx);
+ remove_pop_action(&ctx);
/* Check with in-band control to see if we're allowed to set up this
* flow. */
@@ -3144,6 +3191,95 @@ handle_aggregate_stats_request(struct ofproto *p, struct ofconn *ofconn,
return 0;
}
+struct queue_stats_cbdata {
+ struct ofconn *ofconn;
+ struct ofpbuf *msg;
+ uint16_t port_no;
+};
+
+static void
+put_queue_stats(struct queue_stats_cbdata *cbdata, uint16_t queue_id,
+ const struct netdev_queue_stats *stats)
+{
+ struct ofp_queue_stats *reply;
+
+ reply = append_stats_reply(sizeof *reply, cbdata->ofconn, &cbdata->msg);
+ reply->port_no = htons(cbdata->port_no);
+ memset(reply->pad, 0, sizeof reply->pad);
+ reply->queue_id = htonl(queue_id);
+ reply->tx_bytes = htonll(stats->tx_bytes);
+ reply->tx_packets = htonll(stats->tx_packets);
+ reply->tx_errors = htonll(stats->tx_errors);
+}
+
+static void
+handle_queue_stats_dump_cb(unsigned int queue_id,
+ struct netdev_queue_stats *stats,
+ void *cbdata_)
+{
+ struct queue_stats_cbdata *cbdata = cbdata_;
+
+ put_queue_stats(cbdata, queue_id, stats);
+}
+
+static void
+handle_queue_stats_for_port(struct ofport *port, uint16_t port_no,
+ uint16_t queue_id,
+ struct queue_stats_cbdata *cbdata)
+{
+ cbdata->port_no = port_no;
+ if (queue_id == OFPQ_ALL) {
+ netdev_dump_queue_stats(port->netdev,
+ handle_queue_stats_dump_cb, cbdata);
+ } else {
+ struct netdev_queue_stats stats;
+
+ netdev_get_queue_stats(port->netdev, queue_id, &stats);
+ put_queue_stats(cbdata, queue_id, &stats);
+ }
+}
+
+static int
+handle_queue_stats_request(struct ofproto *ofproto, struct ofconn *ofconn,
+ const struct ofp_stats_request *osr,
+ size_t arg_size)
+{
+ struct ofp_queue_stats_request *qsr;
+ struct queue_stats_cbdata cbdata;
+ struct ofport *port;
+ unsigned int port_no;
+ uint32_t queue_id;
+
+ if (arg_size != sizeof *qsr) {
+ return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_LEN);
+ }
+ qsr = (struct ofp_queue_stats_request *) osr->body;
+
+ COVERAGE_INC(ofproto_queue_req);
+
+ cbdata.ofconn = ofconn;
+ cbdata.msg = start_stats_reply(osr, 128);
+
+ port_no = ntohs(qsr->port_no);
+ queue_id = ntohl(qsr->queue_id);
+ if (port_no == OFPP_ALL) {
+ PORT_ARRAY_FOR_EACH (port, &ofproto->ports, port_no) {
+ handle_queue_stats_for_port(port, port_no, queue_id, &cbdata);
+ }
+ } else if (port_no < ofproto->max_ports) {
+ port = port_array_get(&ofproto->ports, port_no);
+ if (port) {
+ handle_queue_stats_for_port(port, port_no, queue_id, &cbdata);
+ }
+ } else {
+ ofpbuf_delete(cbdata.msg);
+ return ofp_mkerr(OFPET_QUEUE_OP_FAILED, OFPQOFC_BAD_PORT);
+ }
+ queue_tx(cbdata.msg, ofconn, ofconn->reply_counter);
+
+ return 0;
+}
+
static int
handle_stats_request(struct ofproto *p, struct ofconn *ofconn,
struct ofp_header *oh)
@@ -3175,6 +3311,9 @@ handle_stats_request(struct ofproto *p, struct ofconn *ofconn,
case OFPST_PORT:
return handle_port_stats_request(p, ofconn, osr, arg_size);
+ case OFPST_QUEUE:
+ return handle_queue_stats_request(p, ofconn, osr, arg_size);
+
case OFPST_VENDOR:
return ofp_mkerr(OFPET_BAD_REQUEST, OFPBRC_BAD_VENDOR);
diff --git a/ovsdb/ovsdbmonitor/MainWindow.ui b/ovsdb/ovsdbmonitor/MainWindow.ui
index cda1943a..b49c85e5 100644
--- a/ovsdb/ovsdbmonitor/MainWindow.ui
+++ b/ovsdb/ovsdbmonitor/MainWindow.ui
@@ -92,6 +92,26 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="QoS">
+ <attribute name="title">
+ <string>QoS</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_10">
+ <item row="0" column="0">
+ <widget class="QTableWidget" name="QoSTable"/>
+ </item>
+ </layout>
+ </widget>
+ <widget class="QWidget" name="Queue">
+ <attribute name="title">
+ <string>Queue</string>
+ </attribute>
+ <layout class="QGridLayout" name="gridLayout_11">
+ <item row="0" column="0">
+ <widget class="QTableWidget" name="QueueTable"/>
+ </item>
+ </layout>
+ </widget>
<widget class="QWidget" name="sFlow">
<attribute name="title">
<string>sFlow</string>
diff --git a/ovsdb/ovsdbmonitor/Ui_MainWindow.py b/ovsdb/ovsdbmonitor/Ui_MainWindow.py
index 393fc7f7..ee578058 100644
--- a/ovsdb/ovsdbmonitor/Ui_MainWindow.py
+++ b/ovsdb/ovsdbmonitor/Ui_MainWindow.py
@@ -1,15 +1,17 @@
# -*- coding: utf-8 -*-
-# Form implementation generated from reading ui file 'MainWindow.ui'
+# Form implementation generated from reading ui file '../ovsdb/ovsdbmonitor/MainWindow.ui'
#
-# Created: Fri May 7 17:20:33 2010
-# by: PyQt4 UI code generator 4.4.2
+# Created: Mon May 17 16:23:47 2010
+# by: PyQt4 UI code generator 4.7.3
#
# WARNING! All changes made in this file will be lost!
+
try:
from OVEStandard import globalForcePySide
- if globalForcePySide: raise Exception()
+ if globalForcePySide:
+ raise Exception()
from PyQt4 import QtCore, QtGui
except:
from PySide import QtCore, QtGui
@@ -17,7 +19,7 @@ except:
class Ui_MainWindow(object):
def setupUi(self, MainWindow):
MainWindow.setObjectName("MainWindow")
- MainWindow.resize(800,600)
+ MainWindow.resize(800, 600)
self.centralwidget = QtGui.QWidget(MainWindow)
self.centralwidget.setObjectName("centralwidget")
self.gridLayout = QtGui.QGridLayout(self.centralwidget)
@@ -32,72 +34,110 @@ class Ui_MainWindow(object):
self.gridLayout_2.setObjectName("gridLayout_2")
self.BridgeTable = QtGui.QTableWidget(self.Bridge)
self.BridgeTable.setObjectName("BridgeTable")
- self.gridLayout_2.addWidget(self.BridgeTable,0,0,1,1)
- self.tabWidget.addTab(self.Bridge,"")
+ self.BridgeTable.setColumnCount(0)
+ self.BridgeTable.setRowCount(0)
+ self.gridLayout_2.addWidget(self.BridgeTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Bridge, "")
self.Controller = QtGui.QWidget()
self.Controller.setObjectName("Controller")
self.gridLayout_3 = QtGui.QGridLayout(self.Controller)
self.gridLayout_3.setObjectName("gridLayout_3")
self.ControllerTable = QtGui.QTableWidget(self.Controller)
self.ControllerTable.setObjectName("ControllerTable")
- self.gridLayout_3.addWidget(self.ControllerTable,0,0,1,1)
- self.tabWidget.addTab(self.Controller,"")
+ self.ControllerTable.setColumnCount(0)
+ self.ControllerTable.setRowCount(0)
+ self.gridLayout_3.addWidget(self.ControllerTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Controller, "")
self.Interface = QtGui.QWidget()
self.Interface.setObjectName("Interface")
self.gridLayout_4 = QtGui.QGridLayout(self.Interface)
self.gridLayout_4.setObjectName("gridLayout_4")
self.InterfaceTable = QtGui.QTableWidget(self.Interface)
self.InterfaceTable.setObjectName("InterfaceTable")
- self.gridLayout_4.addWidget(self.InterfaceTable,0,0,1,1)
- self.tabWidget.addTab(self.Interface,"")
+ self.InterfaceTable.setColumnCount(0)
+ self.InterfaceTable.setRowCount(0)
+ self.gridLayout_4.addWidget(self.InterfaceTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Interface, "")
self.Mirror = QtGui.QWidget()
self.Mirror.setObjectName("Mirror")
self.gridLayout_5 = QtGui.QGridLayout(self.Mirror)
self.gridLayout_5.setObjectName("gridLayout_5")
self.MirrorTable = QtGui.QTableWidget(self.Mirror)
self.MirrorTable.setObjectName("MirrorTable")
- self.gridLayout_5.addWidget(self.MirrorTable,0,0,1,1)
- self.tabWidget.addTab(self.Mirror,"")
+ self.MirrorTable.setColumnCount(0)
+ self.MirrorTable.setRowCount(0)
+ self.gridLayout_5.addWidget(self.MirrorTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Mirror, "")
self.NetFlow = QtGui.QWidget()
self.NetFlow.setObjectName("NetFlow")
self.gridLayout_6 = QtGui.QGridLayout(self.NetFlow)
self.gridLayout_6.setObjectName("gridLayout_6")
self.NetFlowTable = QtGui.QTableWidget(self.NetFlow)
self.NetFlowTable.setObjectName("NetFlowTable")
- self.gridLayout_6.addWidget(self.NetFlowTable,0,0,1,1)
- self.tabWidget.addTab(self.NetFlow,"")
+ self.NetFlowTable.setColumnCount(0)
+ self.NetFlowTable.setRowCount(0)
+ self.gridLayout_6.addWidget(self.NetFlowTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.NetFlow, "")
self.Open_vSwitch = QtGui.QWidget()
self.Open_vSwitch.setObjectName("Open_vSwitch")
self.gridLayout_7 = QtGui.QGridLayout(self.Open_vSwitch)
self.gridLayout_7.setObjectName("gridLayout_7")
self.Open_vSwitchTable = QtGui.QTableWidget(self.Open_vSwitch)
self.Open_vSwitchTable.setObjectName("Open_vSwitchTable")
- self.gridLayout_7.addWidget(self.Open_vSwitchTable,0,0,1,1)
- self.tabWidget.addTab(self.Open_vSwitch,"")
+ self.Open_vSwitchTable.setColumnCount(0)
+ self.Open_vSwitchTable.setRowCount(0)
+ self.gridLayout_7.addWidget(self.Open_vSwitchTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Open_vSwitch, "")
self.Port = QtGui.QWidget()
self.Port.setObjectName("Port")
self.gridLayout_8 = QtGui.QGridLayout(self.Port)
self.gridLayout_8.setObjectName("gridLayout_8")
self.PortTable = QtGui.QTableWidget(self.Port)
self.PortTable.setObjectName("PortTable")
- self.gridLayout_8.addWidget(self.PortTable,0,0,1,1)
- self.tabWidget.addTab(self.Port,"")
+ self.PortTable.setColumnCount(0)
+ self.PortTable.setRowCount(0)
+ self.gridLayout_8.addWidget(self.PortTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Port, "")
+ self.QoS = QtGui.QWidget()
+ self.QoS.setObjectName("QoS")
+ self.gridLayout_10 = QtGui.QGridLayout(self.QoS)
+ self.gridLayout_10.setObjectName("gridLayout_10")
+ self.QoSTable = QtGui.QTableWidget(self.QoS)
+ self.QoSTable.setObjectName("QoSTable")
+ self.QoSTable.setColumnCount(0)
+ self.QoSTable.setRowCount(0)
+ self.gridLayout_10.addWidget(self.QoSTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.QoS, "")
+ self.Queue = QtGui.QWidget()
+ self.Queue.setObjectName("Queue")
+ self.gridLayout_11 = QtGui.QGridLayout(self.Queue)
+ self.gridLayout_11.setObjectName("gridLayout_11")
+ self.QueueTable = QtGui.QTableWidget(self.Queue)
+ self.QueueTable.setObjectName("QueueTable")
+ self.QueueTable.setColumnCount(0)
+ self.QueueTable.setRowCount(0)
+ self.gridLayout_11.addWidget(self.QueueTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.Queue, "")
self.sFlow = QtGui.QWidget()
self.sFlow.setObjectName("sFlow")
self.gridLayout_9 = QtGui.QGridLayout(self.sFlow)
self.gridLayout_9.setObjectName("gridLayout_9")
self.sFlowTable = QtGui.QTableWidget(self.sFlow)
self.sFlowTable.setObjectName("sFlowTable")
- self.gridLayout_9.addWidget(self.sFlowTable,0,0,1,1)
- self.tabWidget.addTab(self.sFlow,"")
+ self.sFlowTable.setColumnCount(0)
+ self.sFlowTable.setRowCount(0)
+ self.gridLayout_9.addWidget(self.sFlowTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.sFlow, "")
self.SSL = QtGui.QWidget()
self.SSL.setObjectName("SSL")
- self.gridLayout_10 = QtGui.QGridLayout(self.SSL)
- self.gridLayout_10.setObjectName("gridLayout_10")
+ self.gridLayout_101 = QtGui.QGridLayout(self.SSL)
+ self.gridLayout_101.setObjectName("gridLayout_101")
self.SSLTable = QtGui.QTableWidget(self.SSL)
self.SSLTable.setObjectName("SSLTable")
- self.gridLayout_10.addWidget(self.SSLTable,0,0,1,1)
- self.tabWidget.addTab(self.SSL,"")
+ self.SSLTable.setColumnCount(0)
+ self.SSLTable.setRowCount(0)
+ self.gridLayout_101.addWidget(self.SSLTable, 0, 0, 1, 1)
+ self.tabWidget.addTab(self.SSL, "")
self.verticalLayout.addWidget(self.tabWidget)
self.horizontalLayout = QtGui.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
@@ -116,16 +156,16 @@ class Ui_MainWindow(object):
self.intervalSpinBox.setMaximum(1000000)
self.intervalSpinBox.setObjectName("intervalSpinBox")
self.horizontalLayout.addWidget(self.intervalSpinBox)
- spacerItem = QtGui.QSpacerItem(40,20,QtGui.QSizePolicy.Expanding,QtGui.QSizePolicy.Minimum)
+ spacerItem = QtGui.QSpacerItem(40, 20, QtGui.QSizePolicy.Expanding, QtGui.QSizePolicy.Minimum)
self.horizontalLayout.addItem(spacerItem)
self.fetchButton = QtGui.QPushButton(self.centralwidget)
self.fetchButton.setObjectName("fetchButton")
self.horizontalLayout.addWidget(self.fetchButton)
self.verticalLayout.addLayout(self.horizontalLayout)
- self.gridLayout.addLayout(self.verticalLayout,0,0,1,1)
+ self.gridLayout.addLayout(self.verticalLayout, 0, 0, 1, 1)
MainWindow.setCentralWidget(self.centralwidget)
self.menubar = QtGui.QMenuBar(MainWindow)
- self.menubar.setGeometry(QtCore.QRect(0,0,800,28))
+ self.menubar.setGeometry(QtCore.QRect(0, 0, 800, 28))
self.menubar.setObjectName("menubar")
self.menuFile = QtGui.QMenu(self.menubar)
self.menuFile.setObjectName("menuFile")
@@ -158,41 +198,16 @@ class Ui_MainWindow(object):
def retranslateUi(self, MainWindow):
MainWindow.setWindowTitle(QtGui.QApplication.translate("MainWindow", "OVSDB Monitor", None, QtGui.QApplication.UnicodeUTF8))
- self.BridgeTable.clear()
- self.BridgeTable.setColumnCount(0)
- self.BridgeTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Bridge), QtGui.QApplication.translate("MainWindow", "Bridge", None, QtGui.QApplication.UnicodeUTF8))
- self.ControllerTable.clear()
- self.ControllerTable.setColumnCount(0)
- self.ControllerTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Controller), QtGui.QApplication.translate("MainWindow", "Controller", None, QtGui.QApplication.UnicodeUTF8))
- self.InterfaceTable.clear()
- self.InterfaceTable.setColumnCount(0)
- self.InterfaceTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Interface), QtGui.QApplication.translate("MainWindow", "Interface", None, QtGui.QApplication.UnicodeUTF8))
- self.MirrorTable.clear()
- self.MirrorTable.setColumnCount(0)
- self.MirrorTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Mirror), QtGui.QApplication.translate("MainWindow", "Mirror", None, QtGui.QApplication.UnicodeUTF8))
- self.NetFlowTable.clear()
- self.NetFlowTable.setColumnCount(0)
- self.NetFlowTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.NetFlow), QtGui.QApplication.translate("MainWindow", "NetFlow", None, QtGui.QApplication.UnicodeUTF8))
- self.Open_vSwitchTable.clear()
- self.Open_vSwitchTable.setColumnCount(0)
- self.Open_vSwitchTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Open_vSwitch), QtGui.QApplication.translate("MainWindow", "Open_vSwitch", None, QtGui.QApplication.UnicodeUTF8))
- self.PortTable.clear()
- self.PortTable.setColumnCount(0)
- self.PortTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.Port), QtGui.QApplication.translate("MainWindow", "Port", None, QtGui.QApplication.UnicodeUTF8))
- self.sFlowTable.clear()
- self.sFlowTable.setColumnCount(0)
- self.sFlowTable.setRowCount(0)
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.QoS), QtGui.QApplication.translate("MainWindow", "QoS", None, QtGui.QApplication.UnicodeUTF8))
+ self.tabWidget.setTabText(self.tabWidget.indexOf(self.Queue), QtGui.QApplication.translate("MainWindow", "Queue", None, QtGui.QApplication.UnicodeUTF8))
self.tabWidget.setTabText(self.tabWidget.indexOf(self.sFlow), QtGui.QApplication.translate("MainWindow", "sFlow", None, QtGui.QApplication.UnicodeUTF8))
- self.SSLTable.clear()
- self.SSLTable.setColumnCount(0)
- self.SSLTable.setRowCount(0)
self.tabWidget.setTabText(self.tabWidget.indexOf(self.SSL), QtGui.QApplication.translate("MainWindow", "SSL", None, QtGui.QApplication.UnicodeUTF8))
self.hostLabel.setText(QtGui.QApplication.translate("MainWindow", "Host", None, QtGui.QApplication.UnicodeUTF8))
self.intervalCheckBox.setText(QtGui.QApplication.translate("MainWindow", "Auto-refetch every", None, QtGui.QApplication.UnicodeUTF8))
diff --git a/utilities/ovs-vsctl.c b/utilities/ovs-vsctl.c
index 76a3831f..aea317ca 100644
--- a/utilities/ovs-vsctl.c
+++ b/utilities/ovs-vsctl.c
@@ -1904,6 +1904,14 @@ static const struct vsctl_table_class tables[] = {
{{&ovsrec_table_port, &ovsrec_port_col_name, NULL},
{NULL, NULL, NULL}}},
+ {&ovsrec_table_qos,
+ {{&ovsrec_table_port, &ovsrec_port_col_name, &ovsrec_port_col_qos},
+ {NULL, NULL, NULL}}},
+
+ {&ovsrec_table_queue,
+ {{NULL, NULL, NULL},
+ {NULL, NULL, NULL}}},
+
{&ovsrec_table_ssl,
{{&ovsrec_table_open_vswitch, NULL, &ovsrec_open_vswitch_col_ssl}}},
diff --git a/vswitchd/bridge.c b/vswitchd/bridge.c
index f6079928..47f269f9 100644
--- a/vswitchd/bridge.c
+++ b/vswitchd/bridge.c
@@ -253,6 +253,7 @@ static struct iface *iface_from_dp_ifidx(const struct bridge *,
uint16_t dp_ifidx);
static bool iface_is_internal(const struct bridge *, const char *name);
static void iface_set_mac(struct iface *);
+static void iface_update_qos(struct iface *, const struct ovsrec_qos *);
/* Hooks into ofproto processing. */
static struct ofhooks bridge_ofhooks;
@@ -832,9 +833,14 @@ bridge_reconfigure(const struct ovsrec_open_vswitch *ovs_cfg)
LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
for (i = 0; i < br->n_ports; i++) {
struct port *port = br->ports[i];
+ int j;
port_update_vlan_compat(port);
port_update_bonding(port);
+
+ for (j = 0; j < port->n_ifaces; j++) {
+ iface_update_qos(port->ifaces[j], port->cfg->qos);
+ }
}
}
LIST_FOR_EACH (br, struct bridge, node, &all_bridges) {
@@ -3644,6 +3650,90 @@ iface_set_mac(struct iface *iface)
}
}
}
+
+static void
+shash_from_ovs_idl_map(char **keys, char **values, size_t n,
+ struct shash *shash)
+{
+ size_t i;
+
+ shash_init(shash);
+ for (i = 0; i < n; i++) {
+ shash_add(shash, keys[i], values[i]);
+ }
+}
+
+struct iface_delete_queues_cbdata {
+ struct netdev *netdev;
+ const int64_t *queue_ids;
+ size_t n_queue_ids;
+};
+
+static bool
+queue_ids_include(const int64_t *ids, size_t n, int64_t target)
+{
+ size_t low = 0;
+ size_t high = n;
+
+ while (low < high) {
+ size_t mid = low + (high - low) / 2;
+ if (target > ids[mid]) {
+ high = mid;
+ } else if (target < ids[mid]) {
+ low = mid + 1;
+ } else {
+ return true;
+ }
+ }
+ return false;
+}
+
+static void
+iface_delete_queues(unsigned int queue_id,
+ const struct shash *details OVS_UNUSED, void *cbdata_)
+{
+ struct iface_delete_queues_cbdata *cbdata = cbdata_;
+
+ if (!queue_ids_include(cbdata->queue_ids, cbdata->n_queue_ids, queue_id)) {
+ netdev_delete_queue(cbdata->netdev, queue_id);
+ }
+}
+
+static void
+iface_update_qos(struct iface *iface, const struct ovsrec_qos *qos)
+{
+ if (!qos || qos->type[0] == '\0') {
+ netdev_set_qos(iface->netdev, NULL, NULL);
+ } else {
+ struct iface_delete_queues_cbdata cbdata;
+ struct shash details;
+ size_t i;
+
+ /* Configure top-level Qos for 'iface'. */
+ shash_from_ovs_idl_map(qos->key_other_config, qos->value_other_config,
+ qos->n_other_config, &details);
+ netdev_set_qos(iface->netdev, qos->type, &details);
+ shash_destroy(&details);
+
+ /* Deconfigure queues that were deleted. */
+ cbdata.netdev = iface->netdev;
+ cbdata.queue_ids = qos->key_queues;
+ cbdata.n_queue_ids = qos->n_queues;
+ netdev_dump_queues(iface->netdev, iface_delete_queues, &cbdata);
+
+ /* Configure queues for 'iface'. */
+ for (i = 0; i < qos->n_queues; i++) {
+ const struct ovsrec_queue *queue = qos->value_queues[i];
+ unsigned int queue_id = qos->key_queues[i];
+
+ shash_from_ovs_idl_map(queue->key_other_config,
+ queue->value_other_config,
+ queue->n_other_config, &details);
+ netdev_set_queue(iface->netdev, queue_id, &details);
+ shash_destroy(&details);
+ }
+ }
+}
/* Port mirroring. */
diff --git a/vswitchd/vswitch.ovsschema b/vswitchd/vswitch.ovsschema
index 9e3573fb..8b6fb8ba 100644
--- a/vswitchd/vswitch.ovsschema
+++ b/vswitchd/vswitch.ovsschema
@@ -22,8 +22,18 @@
"next_cfg": {
"type": "integer"},
"cur_cfg": {
- "type": "integer"}},
+ "type": "integer"},
+ "capabilities": {
+ "type": {"key": "string",
+ "value": {"type": "uuid",
+ "refTable": "Capability"},
+ "min": 0, "max": "unlimited"}}},
"maxRows": 1},
+ "Capability": {
+ "columns": {
+ "details": {
+ "type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}}}},
"Bridge": {
"columns": {
"name": {
@@ -80,6 +90,10 @@
"minInteger": 0,
"maxInteger": 4095},
"min": 0, "max": 1}},
+ "qos": {
+ "type": {"key": {"type": "uuid",
+ "refTable": "QoS"},
+ "min": 0, "max": 1}},
"mac": {
"type": {"key": {"type": "string"},
"min": 0, "max": 1}},
@@ -117,6 +131,25 @@
"ofport": {
"type": {"key": "integer", "min": 0, "max": 1},
"ephemeral": true}}},
+ "QoS": {
+ "columns": {
+ "type": {
+ "type": "string"},
+ "queues": {
+ "type": {"key": {"type": "integer",
+ "minInteger": 0,
+ "maxInteger": 4294967295},
+ "value": {"type": "uuid",
+ "refTable": "Queue"},
+ "min": 0, "max": "unlimited"}},
+ "other_config": {
+ "type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}}}},
+ "Queue": {
+ "columns": {
+ "other_config": {
+ "type": {"key": "string", "value": "string",
+ "min": 0, "max": "unlimited"}}}},
"Mirror": {
"columns": {
"name": {
diff --git a/vswitchd/vswitch.xml b/vswitchd/vswitch.xml
index d17b0822..cc81643d 100644
--- a/vswitchd/vswitch.xml
+++ b/vswitchd/vswitch.xml
@@ -56,6 +56,14 @@
<ref column="next_cfg"/> after it finishes applying a set of
configuration changes.
</column>
+
+ <column name="capabilities">
+ Describes functionality supported by the hardware and software platform
+ on which this Open vSwitch is based. Clients should not modify this
+ column. See the <ref table="Capability"/> description for defined
+ capability categories and the meaning of associated
+ <ref table="Capability"/> records.
+ </column>
</group>
</table>
@@ -253,6 +261,10 @@
</group>
<group title="Other Features">
+ <column name="qos">
+ Quality of Service configuration for this port.
+ </column>
+
<column name="mac">
The MAC address to use for this port for the purpose of choosing the
bridge's MAC address. This column does not necessarily reflect the
@@ -509,6 +521,87 @@
</group>
</table>
+ <table name="QoS" title="Quality of Service configuration">
+ <p>Quality of Service (QoS) configuration for each Port that
+ references it.</p>
+
+ <column name="type">
+ <p>The type of QoS to implement. The <ref table="Open_vSwitch"
+ column="capabilities"/> column in the <ref table="Open_vSwitch"/> table
+ identifies the types that a switch actually supports. The currently
+ defined types are listed below:</p>
+ <dl>
+ <dt><code>linux-htb</code></dt>
+ <dd>Linux ``hierarchy token bucket'' classifier.</dd>
+ </dl>
+ </column>
+
+ <column name="queues">
+ <p>A map from queue numbers to <ref table="Queue"/> records. The
+ supported range of queue numbers depend on <ref column="type"/>. The
+ queue numbers are the same as the <code>queue_id</code> used in
+ OpenFlow in <code>struct ofp_action_enqueue</code> and other
+ structures. Queue 0 is used by OpenFlow output actions that do not
+ specify a specific queue.</p>
+ </column>
+
+ <column name="other_config">
+ <p>Key-value pairs for configuring QoS features that depend on
+ <ref column="type"/>.</p>
+ <p>The <code>linux-htb</code> class supports the following key-value
+ pairs:</p>
+ <dl>
+ <dt><code>max-rate</code></dt>
+ <dd>Maximum rate shared by all queued traffic, in bit/s.
+ Optional. If not specified, for physical interfaces, the
+ default is the link rate. For other interfaces or if the
+ link rate cannot be determined, the default is currently 100
+ Mbps.</dd>
+ </dl>
+ </column>
+ </table>
+
+ <table name="Queue" title="QoS output queue.">
+ <p>A configuration for a port output queue, used in configuring Quality of
+ Service (QoS) features. May be referenced by <ref column="queues"
+ table="QoS"/> column in <ref table="QoS"/> table.</p>
+
+ <column name="other_config">
+ <p>Key-value pairs for configuring the output queue. The supported
+ key-value pairs and their meanings depend on the <ref column="type"/>
+ of the <ref column="QoS"/> records that reference this row.</p>
+ <p>The key-value pairs defined for <ref table="QoS"/> <ref table="QoS"
+ column="type"/> of <code>min-rate</code> are:</p>
+ <dl>
+ <dt><code>min-rate</code></dt>
+ <dd>Minimum guaranteed bandwidth, in bit/s. Required.</dd>
+ </dl>
+ <p>The key-value pairs defined for <ref table="QoS"/> <ref table="QoS"
+ column="type"/> of <code>linux-htb</code> are:</p>
+ <dl>
+ <dt><code>min-rate</code></dt>
+ <dd>Minimum guaranteed bandwidth, in bit/s. Required.</dd>
+ <dt><code>max-rate</code></dt>
+ <dd>Maximum allowed bandwidth, in bit/s. Optional. If specified, the
+ queue's rate will not be allowed to exceed the specified value, even
+ if excess bandwidth is available. If unspecified, defaults to no
+ limit.</dd>
+ <dt><code>burst</code></dt>
+ <dd>Burst size, in bits. This is the maximum amount of ``credits''
+ that a queue can accumulate while it is idle. Optional. Details of
+ the <code>linux-htb</code> implementation require a minimum burst
+ size, so a too-small <code>burst</code> will be silently
+ ignored.</dd>
+ <dt><code>priority</code></dt>
+ <dd>A nonnegative 32-bit integer. Defaults to 0 if
+ unspecified. A queue with a smaller <code>priority</code>
+ will receive all the excess bandwidth that it can use before
+ a queue with a larger value receives any. Specific priority
+ values are unimportant; only relative ordering matters.</dd>
+ </dl>
+ </column>
+ </table>
+
<table name="Mirror" title="Port mirroring (SPAN/RSPAN).">
<p>A port mirror within a <ref table="Bridge"/>.</p>
<p>A port mirror configures a bridge to send selected frames to special
@@ -920,4 +1013,46 @@
<code><var>ip</var>:<var>port</var></code>.
</column>
</table>
+
+ <table name="Capability">
+ <p>Records in this table describe functionality supported by the hardware
+ and software platform on which this Open vSwitch is based. Clients
+ should not modify this table.</p>
+
+ <p>A record in this table is meaningful only if it is referenced by the
+ <ref table="Open_vSwitch" column="capabilities"/> column in the
+ <ref table="Open_vSwitch"/> table. The key used to reference it, called
+ the record's ``category,'' determines the meanings of the
+ <ref column="details"/> column. The following general forms of
+ categories are currently defined:</p>
+
+ <dl>
+ <dt><code>qos-<var>type</var></code></dt>
+ <dd><var>type</var> is supported as the value for
+ <ref column="type" table="QoS"/> in the <ref table="QoS"/> table.
+ </dd>
+ </dl>
+
+ <column name="details">
+ <p>Key-value pairs that describe capabilities. The meaning of the pairs
+ depends on the category key that the <ref table="Open_vSwitch"
+ column="capabilities"/> column in the <ref table="Open_vSwitch"/> table
+ uses to reference this record, as described above.</p>
+
+ <p>The presence of a record for category <code>qos-<var>type</var></code>
+ indicates that the switch supports <var>type</var> as the value of
+ the <ref table="QoS" column="type"/> column in the <ref table="QoS"/>
+ table. The following key-value pairs are defined to further describe
+ QoS capabilities:</p>
+
+ <dl>
+ <dt><code>n-queues</code></dt>
+ <dd>Number of supported queues, as a positive integer. Keys in the
+ <ref table="QoS" column="queues"/> column for <ref table="QoS"/>
+ records whose <ref table="QoS" column="type"/> value
+ equals <var>type</var> must range between 0 and this value minus one,
+ inclusive.</dd>
+ </dl>
+ </column>
+ </table>
</database>