Implement QoS framework.
authorBen Pfaff <blp@nicira.com>
Thu, 17 Jun 2010 22:04:12 +0000 (15:04 -0700)
committerBen Pfaff <blp@nicira.com>
Thu, 17 Jun 2010 22:04:12 +0000 (15:04 -0700)
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.

18 files changed:
datapath/actions.c
include/openflow/openflow.h
include/openvswitch/datapath-protocol.h
lib/netdev-gre.c
lib/netdev-linux.c
lib/netdev-patch.c
lib/netdev-provider.h
lib/netdev.c
lib/netdev.h
lib/ofp-util.c
lib/ofp-util.h
ofproto/ofproto.c
ovsdb/ovsdbmonitor/MainWindow.ui
ovsdb/ovsdbmonitor/Ui_MainWindow.py
utilities/ovs-vsctl.c
vswitchd/bridge.c
vswitchd/vswitch.ovsschema
vswitchd/vswitch.xml

index fed9830..ff67372 100644 (file)
@@ -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;
index b77cd70..f84fd02 100644 (file)
@@ -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. */
index 4b2168c..13aa922 100644 (file)
@@ -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 {
index 0aa587a..45b574f 100644 (file)
@@ -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 */
index 5510a95..8cd40cd 100644 (file)
@@ -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"
 #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;
 };
+\f
+/* 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);
+\f
 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));
@@ -1208,6 +1426,277 @@ netdev_linux_set_policing(struct netdev *netdev,
     return 0;
 }
 
+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,51 +2181,1122 @@ const struct netdev_class netdev_tap_class = {
     netdev_linux_poll_add,
     netdev_linux_poll_remove,
 };
-
 \f
-static int
-get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
-{
-    /* Policy for RTNLGRP_LINK messages.
-     *
-     * There are *many* more fields in these messages, but currently we only
-     * care about these fields. */
-    static const struct nl_policy rtnlgrp_link_policy[] = {
-        [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
-        [IFLA_STATS] = { .type = NL_A_UNSPEC, .optional = true,
-                         .min_len = sizeof(struct rtnl_link_stats) },
-    };
+/* HTB traffic control class. */
 
-    struct ofpbuf request;
-    struct ofpbuf *reply;
-    struct ifinfomsg *ifi;
-    const struct rtnl_link_stats *rtnl_stats;
-    struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
-    int error;
+#define HTB_N_QUEUES 0xf000
 
-    ofpbuf_init(&request, 0);
-    nl_msg_put_nlmsghdr(&request, sizeof *ifi, RTM_GETLINK, NLM_F_REQUEST);
-    ifi = ofpbuf_put_zeros(&request, sizeof *ifi);
-    ifi->ifi_family = PF_UNSPEC;
-    ifi->ifi_index = ifindex;
-    error = nl_sock_transact(rtnl_sock, &request, &reply);
-    ofpbuf_uninit(&request);
-    if (error) {
-        return error;
-    }
+struct htb {
+    struct tc tc;
+    unsigned int max_rate;      /* In bytes/s. */
+};
 
-    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
-                         rtnlgrp_link_policy,
-                         attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
-        ofpbuf_delete(reply);
-        return EPROTO;
-    }
+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. */
+};
 
-    if (!attrs[IFLA_STATS]) {
-        VLOG_WARN_RL(&rl, "RTM_GETLINK reply lacks stats");
-        ofpbuf_delete(reply);
-        return EPROTO;
-    }
+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
+};
+\f
+/* "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 */
+};
+\f
+/* "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 */
+};
+\f
+/* 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));
+}
+
+\f
+/* Utility functions. */
+
+static int
+get_stats_via_netlink(int ifindex, struct netdev_stats *stats)
+{
+    /* Policy for RTNLGRP_LINK messages.
+     *
+     * There are *many* more fields in these messages, but currently we only
+     * care about these fields. */
+    static const struct nl_policy rtnlgrp_link_policy[] = {
+        [IFLA_IFNAME] = { .type = NL_A_STRING, .optional = false },
+        [IFLA_STATS] = { .type = NL_A_UNSPEC, .optional = true,
+                         .min_len = sizeof(struct rtnl_link_stats) },
+    };
+
+    struct ofpbuf request;
+    struct ofpbuf *reply;
+    struct ifinfomsg *ifi;
+    const struct rtnl_link_stats *rtnl_stats;
+    struct nlattr *attrs[ARRAY_SIZE(rtnlgrp_link_policy)];
+    int error;
+
+    ofpbuf_init(&request, 0);
+    nl_msg_put_nlmsghdr(&request, sizeof *ifi, RTM_GETLINK, NLM_F_REQUEST);
+    ifi = ofpbuf_put_zeros(&request, sizeof *ifi);
+    ifi->ifi_family = PF_UNSPEC;
+    ifi->ifi_index = ifindex;
+    error = nl_sock_transact(rtnl_sock, &request, &reply);
+    ofpbuf_uninit(&request);
+    if (error) {
+        return error;
+    }
+
+    if (!nl_policy_parse(reply, NLMSG_HDRLEN + sizeof(struct ifinfomsg),
+                         rtnlgrp_link_policy,
+                         attrs, ARRAY_SIZE(rtnlgrp_link_policy))) {
+        ofpbuf_delete(reply);
+        return EPROTO;
+    }
+
+    if (!attrs[IFLA_STATS]) {
+        VLOG_WARN_RL(&rl, "RTM_GETLINK reply lacks stats");
+        ofpbuf_delete(reply);
+        return EPROTO;
+    }
 
     rtnl_stats = nl_attr_get(attrs[IFLA_STATS]);
     stats->rx_packets = rtnl_stats->rx_packets;
@@ -1800,7 +3382,7 @@ get_stats_via_proc(const char *netdev_name, struct netdev_stats *stats)
     fclose(stream);
     return ENODEV;
 }
-\f
+
 static int
 get_flags(const struct netdev *netdev, int *flags)
 {
index 17c509f..ec4d4bd 100644 (file)
@@ -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 */
index 024b1fb..52f440f 100644 (file)
@@ -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.
      *
index a15315a..dc27fd7 100644 (file)
@@ -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
index 5dca24c..cd5c8c3 100644 (file)
@@ -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 *);
index d773a3f..e990f0f 100644 (file)
@@ -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 *
index 0d141a6..2c6b2f9 100644 (file)
@@ -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 *);
 
index d877d36..5232cfa 100644 (file)
@@ -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);
 
index cda1943..b49c85e 100644 (file)
           </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>
index 393fc7f..ee57805 100644 (file)
@@ -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))
index 76a3831..aea317c 100644 (file)
@@ -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}}},
 
index f607992..47f269f 100644 (file)
@@ -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);
+        }
+    }
+}
 \f
 /* Port mirroring. */
 
index 9e3573f..8b6fb8b 100644 (file)
        "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": {
                           "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}},
        "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": {
index d17b082..cc81643 100644 (file)
         <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>
 
     </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
     </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
       <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>