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

Quality of Service (QoS) configuration for each Port that + references it.

+ + +

The type of QoS to implement. The column in the table + identifies the types that a switch actually supports. The currently + defined types are listed below:

+
+
linux-htb
+
Linux ``hierarchy token bucket'' classifier.
+
+
+ + +

A map from queue numbers to records. The + supported range of queue numbers depend on . The + queue numbers are the same as the queue_id used in + OpenFlow in struct ofp_action_enqueue and other + structures. Queue 0 is used by OpenFlow output actions that do not + specify a specific queue.

+
+ + +

Key-value pairs for configuring QoS features that depend on + .

+

The linux-htb class supports the following key-value + pairs:

+
+
max-rate
+
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.
+
+
+
+ + +

A configuration for a port output queue, used in configuring Quality of + Service (QoS) features. May be referenced by column in table.

+ + +

Key-value pairs for configuring the output queue. The supported + key-value pairs and their meanings depend on the + of the records that reference this row.

+

The key-value pairs defined for of min-rate are:

+
+
min-rate
+
Minimum guaranteed bandwidth, in bit/s. Required.
+
+

The key-value pairs defined for of linux-htb are:

+
+
min-rate
+
Minimum guaranteed bandwidth, in bit/s. Required.
+
max-rate
+
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.
+
burst
+
Burst size, in bits. This is the maximum amount of ``credits'' + that a queue can accumulate while it is idle. Optional. Details of + the linux-htb implementation require a minimum burst + size, so a too-small burst will be silently + ignored.
+
priority
+
A nonnegative 32-bit integer. Defaults to 0 if + unspecified. A queue with a smaller priority + 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.
+
+
+
+

A port mirror within a .

A port mirror configures a bridge to send selected frames to special @@ -920,4 +1013,46 @@ ip:port.

+ + +

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.

+ +

A record in this table is meaningful only if it is referenced by the + column in the + table. The key used to reference it, called + the record's ``category,'' determines the meanings of the + column. The following general forms of + categories are currently defined:

+ +
+
qos-type
+
type is supported as the value for + in the table. +
+
+ + +

Key-value pairs that describe capabilities. The meaning of the pairs + depends on the category key that the column in the table + uses to reference this record, as described above.

+ +

The presence of a record for category qos-type + indicates that the switch supports type as the value of + the column in the + table. The following key-value pairs are defined to further describe + QoS capabilities:

+ +
+
n-queues
+
Number of supported queues, as a positive integer. Keys in the + column for + records whose value + equals type must range between 0 and this value minus one, + inclusive.
+
+
+
-- 2.43.0