2
0
mirror of https://github.com/openvswitch/ovs synced 2025-08-30 22:05:19 +00:00

netdev-linux: Support for SFQ, FQ_CoDel and CoDel qdiscs.

This patch adds support for SFQ, CoDel and FQ_CoDel classless qdiscs to Open vSwitch. It also removes the requirement for a QoS to have at least one Queue (as this makes no sense when using classless qdiscs). I have also not implemented class_{get,set,delete,get_stats,dump_stats} because they are meant for qdiscs with classes.

Signed-off-by: Jonathan Vestin <jonavest@kau.se>
[blp@nicira.com mostly applied stylistic changes]
Signed-off-by: Ben Pfaff <blp@nicira.com>
This commit is contained in:
Jonathan Vestin
2015-03-18 17:13:01 +01:00
committed by Ben Pfaff
parent 14691214d1
commit 677d9158fc
5 changed files with 673 additions and 2 deletions

View File

@@ -85,6 +85,7 @@ Jesse Gross jesse@nicira.com
Jing Ai jinga@google.com
Joe Perches joe@perches.com
Joe Stringer joestringer@nicira.com
Jonathan Vestin jonavest@kau.se
Jun Nakajima jun.nakajima@intel.com
Justin Pettit jpettit@nicira.com
Keith Amidon keith@nicira.com
@@ -268,7 +269,6 @@ Joan Cirer joan@ev0.net
John Darrington john@darrington.wattle.id.au
John Galgay john@galgay.net
John Hurley john.hurley@netronome.com
Jonathan Vestin jonavest@kau.se
K 華 k940545@hotmail.com
Kevin Mancuso kevin.mancuso@rackspace.com
Kiran Shanbhog kiran@vmware.com

1
NEWS
View File

@@ -1,5 +1,6 @@
Post-v2.3.0
---------------------
- Added support for SFQ, FQ_CoDel and CoDel qdiscs.
- Add bash command-line completion support for ovs-vsctl Please check
utilities/ovs-command-compgen.INSTALL.md for how to use.
- The MAC learning feature now includes per-port fairness to mitigate

View File

@@ -377,12 +377,18 @@ tc_destroy(struct tc *tc)
static const struct tc_ops tc_ops_htb;
static const struct tc_ops tc_ops_hfsc;
static const struct tc_ops tc_ops_codel;
static const struct tc_ops tc_ops_fqcodel;
static const struct tc_ops tc_ops_sfq;
static const struct tc_ops tc_ops_default;
static const struct tc_ops tc_ops_other;
static const struct tc_ops *const tcs[] = {
&tc_ops_htb, /* Hierarchy token bucket (see tc-htb(8)). */
&tc_ops_hfsc, /* Hierarchical fair service curve. */
&tc_ops_codel, /* Controlled delay */
&tc_ops_fqcodel, /* Fair queue controlled delay */
&tc_ops_sfq, /* Stochastic fair queueing */
&tc_ops_default, /* Default qdisc (see tc-pfifo_fast(8)). */
&tc_ops_other, /* Some other qdisc. */
NULL
@@ -2832,6 +2838,643 @@ const struct netdev_class netdev_internal_class =
NULL, /* get_features */
netdev_internal_get_status);
#define CODEL_N_QUEUES 0x0000
struct codel {
struct tc tc;
uint32_t target;
uint32_t limit;
uint32_t interval;
};
static struct codel *
codel_get__(const struct netdev *netdev_)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
return CONTAINER_OF(netdev->tc, struct codel, tc);
}
static void
codel_install__(struct netdev *netdev_, uint32_t target, uint32_t limit,
uint32_t interval)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
struct codel *codel;
codel = xmalloc(sizeof *codel);
tc_init(&codel->tc, &tc_ops_codel);
codel->target = target;
codel->limit = limit;
codel->interval = interval;
netdev->tc = &codel->tc;
}
static int
codel_setup_qdisc__(struct netdev *netdev, uint32_t target, uint32_t limit,
uint32_t interval)
{
size_t opt_offset;
struct ofpbuf request;
struct tcmsg *tcmsg;
uint32_t otarget, olimit, ointerval;
int error;
tc_del_qdisc(netdev);
tcmsg = tc_make_request(netdev, RTM_NEWQDISC,
NLM_F_EXCL | NLM_F_CREATE, &request);
if (!tcmsg) {
return ENODEV;
}
tcmsg->tcm_handle = tc_make_handle(1, 0);
tcmsg->tcm_parent = TC_H_ROOT;
otarget = target ? target : 5000;
olimit = limit ? limit : 10240;
ointerval = interval ? interval : 100000;
nl_msg_put_string(&request, TCA_KIND, "codel");
opt_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
nl_msg_put_u32(&request, TCA_CODEL_TARGET, otarget);
nl_msg_put_u32(&request, TCA_CODEL_LIMIT, olimit);
nl_msg_put_u32(&request, TCA_CODEL_INTERVAL, ointerval);
nl_msg_end_nested(&request, opt_offset);
error = tc_transact(&request, NULL);
if (error) {
VLOG_WARN_RL(&rl, "failed to replace %s qdisc, "
"target %u, limit %u, interval %u error %d(%s)",
netdev_get_name(netdev),
otarget, olimit, ointerval,
error, ovs_strerror(error));
}
return error;
}
static void
codel_parse_qdisc_details__(struct netdev *netdev OVS_UNUSED,
const struct smap *details, struct codel *codel)
{
const char *target_s;
const char *limit_s;
const char *interval_s;
target_s = smap_get(details, "target");
limit_s = smap_get(details, "limit");
interval_s = smap_get(details, "interval");
codel->target = target_s ? strtoull(target_s, NULL, 10) : 0;
codel->limit = limit_s ? strtoull(limit_s, NULL, 10) : 0;
codel->interval = interval_s ? strtoull(interval_s, NULL, 10) : 0;
if (!codel->target) {
codel->target = 5000;
}
if (!codel->limit) {
codel->limit = 10240;
}
if (!codel->interval) {
codel->interval = 100000;
}
}
static int
codel_tc_install(struct netdev *netdev, const struct smap *details)
{
int error;
struct codel codel;
codel_parse_qdisc_details__(netdev, details, &codel);
error = codel_setup_qdisc__(netdev, codel.target, codel.limit,
codel.interval);
if (!error) {
codel_install__(netdev, codel.target, codel.limit, codel.interval);
}
return error;
}
static int
codel_parse_tca_options__(struct nlattr *nl_options, struct codel *codel)
{
static const struct nl_policy tca_codel_policy[] = {
[TCA_CODEL_TARGET] = { .type = NL_A_U32 },
[TCA_CODEL_LIMIT] = { .type = NL_A_U32 },
[TCA_CODEL_INTERVAL] = { .type = NL_A_U32 }
};
struct nlattr *attrs[ARRAY_SIZE(tca_codel_policy)];
if (!nl_parse_nested(nl_options, tca_codel_policy,
attrs, ARRAY_SIZE(tca_codel_policy))) {
VLOG_WARN_RL(&rl, "failed to parse CoDel class options");
return EPROTO;
}
codel->target = nl_attr_get_u32(attrs[TCA_CODEL_TARGET]);
codel->limit = nl_attr_get_u32(attrs[TCA_CODEL_LIMIT]);
codel->interval = nl_attr_get_u32(attrs[TCA_CODEL_INTERVAL]);
return 0;
}
static int
codel_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg)
{
struct nlattr *nlattr;
const char * kind;
int error;
struct codel codel;
error = tc_parse_qdisc(nlmsg, &kind, &nlattr);
if (error != 0) {
return error;
}
error = codel_parse_tca_options__(nlattr, &codel);
if (error != 0) {
return error;
}
codel_install__(netdev, codel.target, codel.limit, codel.interval);
return 0;
}
static void
codel_tc_destroy(struct tc *tc)
{
struct codel *codel = CONTAINER_OF(tc, struct codel, tc);
tc_destroy(tc);
free(codel);
}
static int
codel_qdisc_get(const struct netdev *netdev, struct smap *details)
{
const struct codel *codel = codel_get__(netdev);
smap_add_format(details, "target", "%u", codel->target);
smap_add_format(details, "limit", "%u", codel->limit);
smap_add_format(details, "interval", "%u", codel->interval);
return 0;
}
static int
codel_qdisc_set(struct netdev *netdev, const struct smap *details)
{
struct codel codel;
codel_parse_qdisc_details__(netdev, details, &codel);
codel_install__(netdev, codel.target, codel.limit, codel.interval);
codel_get__(netdev)->target = codel.target;
codel_get__(netdev)->limit = codel.limit;
codel_get__(netdev)->interval = codel.interval;
return 0;
}
static const struct tc_ops tc_ops_codel = {
"codel", /* linux_name */
"linux-codel", /* ovs_name */
CODEL_N_QUEUES, /* n_queues */
codel_tc_install,
codel_tc_load,
codel_tc_destroy,
codel_qdisc_get,
codel_qdisc_set,
NULL,
NULL,
NULL,
NULL,
NULL
};
/* FQ-CoDel traffic control class. */
#define FQCODEL_N_QUEUES 0x0000
struct fqcodel {
struct tc tc;
uint32_t target;
uint32_t limit;
uint32_t interval;
uint32_t flows;
uint32_t quantum;
};
static struct fqcodel *
fqcodel_get__(const struct netdev *netdev_)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
return CONTAINER_OF(netdev->tc, struct fqcodel, tc);
}
static void
fqcodel_install__(struct netdev *netdev_, uint32_t target, uint32_t limit,
uint32_t interval, uint32_t flows, uint32_t quantum)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
struct fqcodel *fqcodel;
fqcodel = xmalloc(sizeof *fqcodel);
tc_init(&fqcodel->tc, &tc_ops_fqcodel);
fqcodel->target = target;
fqcodel->limit = limit;
fqcodel->interval = interval;
fqcodel->flows = flows;
fqcodel->quantum = quantum;
netdev->tc = &fqcodel->tc;
}
static int
fqcodel_setup_qdisc__(struct netdev *netdev, uint32_t target, uint32_t limit,
uint32_t interval, uint32_t flows, uint32_t quantum)
{
size_t opt_offset;
struct ofpbuf request;
struct tcmsg *tcmsg;
uint32_t otarget, olimit, ointerval, oflows, oquantum;
int error;
tc_del_qdisc(netdev);
tcmsg = tc_make_request(netdev, RTM_NEWQDISC,
NLM_F_EXCL | NLM_F_CREATE, &request);
if (!tcmsg) {
return ENODEV;
}
tcmsg->tcm_handle = tc_make_handle(1, 0);
tcmsg->tcm_parent = TC_H_ROOT;
otarget = target ? target : 5000;
olimit = limit ? limit : 10240;
ointerval = interval ? interval : 100000;
oflows = flows ? flows : 1024;
oquantum = quantum ? quantum : 1514; /* fq_codel default quantum is 1514
not mtu */
nl_msg_put_string(&request, TCA_KIND, "fq_codel");
opt_offset = nl_msg_start_nested(&request, TCA_OPTIONS);
nl_msg_put_u32(&request, TCA_FQ_CODEL_TARGET, otarget);
nl_msg_put_u32(&request, TCA_FQ_CODEL_LIMIT, olimit);
nl_msg_put_u32(&request, TCA_FQ_CODEL_INTERVAL, ointerval);
nl_msg_put_u32(&request, TCA_FQ_CODEL_FLOWS, oflows);
nl_msg_put_u32(&request, TCA_FQ_CODEL_QUANTUM, oquantum);
nl_msg_end_nested(&request, opt_offset);
error = tc_transact(&request, NULL);
if (error) {
VLOG_WARN_RL(&rl, "failed to replace %s qdisc, "
"target %u, limit %u, interval %u, flows %u, quantum %u error %d(%s)",
netdev_get_name(netdev),
otarget, olimit, ointerval, oflows, oquantum,
error, ovs_strerror(error));
}
return error;
}
static void
fqcodel_parse_qdisc_details__(struct netdev *netdev OVS_UNUSED,
const struct smap *details, struct fqcodel *fqcodel)
{
const char *target_s;
const char *limit_s;
const char *interval_s;
const char *flows_s;
const char *quantum_s;
target_s = smap_get(details, "target");
limit_s = smap_get(details, "limit");
interval_s = smap_get(details, "interval");
flows_s = smap_get(details, "flows");
quantum_s = smap_get(details, "quantum");
fqcodel->target = target_s ? strtoull(target_s, NULL, 10) : 0;
fqcodel->limit = limit_s ? strtoull(limit_s, NULL, 10) : 0;
fqcodel->interval = interval_s ? strtoull(interval_s, NULL, 10) : 0;
fqcodel->flows = flows_s ? strtoull(flows_s, NULL, 10) : 0;
fqcodel->quantum = quantum_s ? strtoull(quantum_s, NULL, 10) : 0;
if (!fqcodel->target) {
fqcodel->target = 5000;
}
if (!fqcodel->limit) {
fqcodel->limit = 10240;
}
if (!fqcodel->interval) {
fqcodel->interval = 1000000;
}
if (!fqcodel->flows) {
fqcodel->flows = 1024;
}
if (!fqcodel->quantum) {
fqcodel->quantum = 1514;
}
}
static int
fqcodel_tc_install(struct netdev *netdev, const struct smap *details)
{
int error;
struct fqcodel fqcodel;
fqcodel_parse_qdisc_details__(netdev, details, &fqcodel);
error = fqcodel_setup_qdisc__(netdev, fqcodel.target, fqcodel.limit,
fqcodel.interval, fqcodel.flows,
fqcodel.quantum);
if (!error) {
fqcodel_install__(netdev, fqcodel.target, fqcodel.limit,
fqcodel.interval, fqcodel.flows, fqcodel.quantum);
}
return error;
}
static int
fqcodel_parse_tca_options__(struct nlattr *nl_options, struct fqcodel *fqcodel)
{
static const struct nl_policy tca_fqcodel_policy[] = {
[TCA_FQ_CODEL_TARGET] = { .type = NL_A_U32 },
[TCA_FQ_CODEL_LIMIT] = { .type = NL_A_U32 },
[TCA_FQ_CODEL_INTERVAL] = { .type = NL_A_U32 },
[TCA_FQ_CODEL_FLOWS] = { .type = NL_A_U32 },
[TCA_FQ_CODEL_QUANTUM] = { .type = NL_A_U32 }
};
struct nlattr *attrs[ARRAY_SIZE(tca_fqcodel_policy)];
if (!nl_parse_nested(nl_options, tca_fqcodel_policy,
attrs, ARRAY_SIZE(tca_fqcodel_policy))) {
VLOG_WARN_RL(&rl, "failed to parse FQ_CoDel class options");
return EPROTO;
}
fqcodel->target = nl_attr_get_u32(attrs[TCA_FQ_CODEL_TARGET]);
fqcodel->limit = nl_attr_get_u32(attrs[TCA_FQ_CODEL_LIMIT]);
fqcodel->interval =nl_attr_get_u32(attrs[TCA_FQ_CODEL_INTERVAL]);
fqcodel->flows = nl_attr_get_u32(attrs[TCA_FQ_CODEL_FLOWS]);
fqcodel->quantum = nl_attr_get_u32(attrs[TCA_FQ_CODEL_QUANTUM]);
return 0;
}
static int
fqcodel_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg)
{
struct nlattr *nlattr;
const char * kind;
int error;
struct fqcodel fqcodel;
error = tc_parse_qdisc(nlmsg, &kind, &nlattr);
if (error != 0) {
return error;
}
error = fqcodel_parse_tca_options__(nlattr, &fqcodel);
if (error != 0) {
return error;
}
fqcodel_install__(netdev, fqcodel.target, fqcodel.limit, fqcodel.interval,
fqcodel.flows, fqcodel.quantum);
return 0;
}
static void
fqcodel_tc_destroy(struct tc *tc)
{
struct fqcodel *fqcodel = CONTAINER_OF(tc, struct fqcodel, tc);
tc_destroy(tc);
free(fqcodel);
}
static int
fqcodel_qdisc_get(const struct netdev *netdev, struct smap *details)
{
const struct fqcodel *fqcodel = fqcodel_get__(netdev);
smap_add_format(details, "target", "%u", fqcodel->target);
smap_add_format(details, "limit", "%u", fqcodel->limit);
smap_add_format(details, "interval", "%u", fqcodel->interval);
smap_add_format(details, "flows", "%u", fqcodel->flows);
smap_add_format(details, "quantum", "%u", fqcodel->quantum);
return 0;
}
static int
fqcodel_qdisc_set(struct netdev *netdev, const struct smap *details)
{
struct fqcodel fqcodel;
fqcodel_parse_qdisc_details__(netdev, details, &fqcodel);
fqcodel_install__(netdev, fqcodel.target, fqcodel.limit, fqcodel.interval,
fqcodel.flows, fqcodel.quantum);
fqcodel_get__(netdev)->target = fqcodel.target;
fqcodel_get__(netdev)->limit = fqcodel.limit;
fqcodel_get__(netdev)->interval = fqcodel.interval;
fqcodel_get__(netdev)->flows = fqcodel.flows;
fqcodel_get__(netdev)->quantum = fqcodel.quantum;
return 0;
}
static const struct tc_ops tc_ops_fqcodel = {
"fq_codel", /* linux_name */
"linux-fq_codel", /* ovs_name */
FQCODEL_N_QUEUES, /* n_queues */
fqcodel_tc_install,
fqcodel_tc_load,
fqcodel_tc_destroy,
fqcodel_qdisc_get,
fqcodel_qdisc_set,
NULL,
NULL,
NULL,
NULL,
NULL
};
/* SFQ traffic control class. */
#define SFQ_N_QUEUES 0x0000
struct sfq {
struct tc tc;
uint32_t quantum;
uint32_t perturb;
};
static struct sfq *
sfq_get__(const struct netdev *netdev_)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
return CONTAINER_OF(netdev->tc, struct sfq, tc);
}
static void
sfq_install__(struct netdev *netdev_, uint32_t quantum, uint32_t perturb)
{
struct netdev_linux *netdev = netdev_linux_cast(netdev_);
struct sfq *sfq;
sfq = xmalloc(sizeof *sfq);
tc_init(&sfq->tc, &tc_ops_sfq);
sfq->perturb = perturb;
sfq->quantum = quantum;
netdev->tc = &sfq->tc;
}
static int
sfq_setup_qdisc__(struct netdev *netdev, uint32_t quantum, uint32_t perturb)
{
struct tc_sfq_qopt opt;
struct ofpbuf request;
struct tcmsg *tcmsg;
int mtu;
int mtu_error, error;
mtu_error = netdev_linux_get_mtu__(netdev_linux_cast(netdev), &mtu);
tc_del_qdisc(netdev);
tcmsg = tc_make_request(netdev, RTM_NEWQDISC,
NLM_F_EXCL | NLM_F_CREATE, &request);
if (!tcmsg) {
return ENODEV;
}
tcmsg->tcm_handle = tc_make_handle(1, 0);
tcmsg->tcm_parent = TC_H_ROOT;
memset(&opt, 0, sizeof opt);
if (!quantum) {
if (!mtu_error) {
opt.quantum = mtu; /* if we cannot find mtu, use default */
}
} else {
opt.quantum = quantum;
}
if (!perturb) {
opt.perturb_period = 10;
} else {
opt.perturb_period = perturb;
}
nl_msg_put_string(&request, TCA_KIND, "sfq");
nl_msg_put_unspec(&request, TCA_OPTIONS, &opt, sizeof opt);
error = tc_transact(&request, NULL);
if (error) {
VLOG_WARN_RL(&rl, "failed to replace %s qdisc, "
"quantum %u, perturb %u error %d(%s)",
netdev_get_name(netdev),
opt.quantum, opt.perturb_period,
error, ovs_strerror(error));
}
return error;
}
static void
sfq_parse_qdisc_details__(struct netdev *netdev,
const struct smap *details, struct sfq *sfq)
{
const char *perturb_s;
const char *quantum_s;
int mtu;
int mtu_error;
perturb_s = smap_get(details, "perturb");
quantum_s = smap_get(details, "quantum");
sfq->perturb = perturb_s ? strtoull(perturb_s, NULL, 10) : 0;
sfq->quantum = quantum_s ? strtoull(quantum_s, NULL, 10) : 0;
if (!sfq->perturb) {
sfq->perturb = 10;
}
if (!sfq->quantum) {
mtu_error = netdev_linux_get_mtu__(netdev_linux_cast(netdev), &mtu);
if (!mtu_error) {
sfq->quantum = mtu;
} else {
VLOG_WARN_RL(&rl, "when using SFQ, you must specify quantum on a "
"device without mtu");
return;
}
}
}
static int
sfq_tc_install(struct netdev *netdev, const struct smap *details)
{
int error;
struct sfq sfq;
sfq_parse_qdisc_details__(netdev, details, &sfq);
error = sfq_setup_qdisc__(netdev, sfq.quantum, sfq.perturb);
if (!error) {
sfq_install__(netdev, sfq.quantum, sfq.perturb);
}
return error;
}
static int
sfq_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg)
{
const struct tc_sfq_qopt *sfq;
struct nlattr *nlattr;
const char * kind;
int error;
error = tc_parse_qdisc(nlmsg, &kind, &nlattr);
if (error == 0) {
sfq = nl_attr_get(nlattr);
sfq_install__(netdev, sfq->perturb_period, sfq->quantum);
return 0;
}
return error;
}
static void
sfq_tc_destroy(struct tc *tc)
{
struct sfq *sfq = CONTAINER_OF(tc, struct sfq, tc);
tc_destroy(tc);
free(sfq);
}
static int
sfq_qdisc_get(const struct netdev *netdev, struct smap *details)
{
const struct sfq *sfq = sfq_get__(netdev);
smap_add_format(details, "quantum", "%u", sfq->quantum);
smap_add_format(details, "perturb", "%u", sfq->perturb);
return 0;
}
static int
sfq_qdisc_set(struct netdev *netdev, const struct smap *details)
{
struct sfq sfq;
sfq_parse_qdisc_details__(netdev, details, &sfq);
sfq_install__(netdev, sfq.quantum, sfq.perturb);
sfq_get__(netdev)->quantum = sfq.quantum;
sfq_get__(netdev)->perturb = sfq.perturb;
return 0;
}
static const struct tc_ops tc_ops_sfq = {
"sfq", /* linux_name */
"linux-sfq", /* ovs_name */
SFQ_N_QUEUES, /* n_queues */
sfq_tc_install,
sfq_tc_load,
sfq_tc_destroy,
sfq_qdisc_get,
sfq_qdisc_set,
NULL,
NULL,
NULL,
NULL,
NULL
};
/* HTB traffic control class. */
#define HTB_N_QUEUES 0xf000

View File

@@ -4382,7 +4382,7 @@ iface_configure_qos(struct iface *iface, const struct ovsrec_qos *qos)
ofpbuf_init(&queues_buf, 0);
if (!qos || qos->type[0] == '\0' || qos->n_queues < 1) {
if (!qos || qos->type[0] == '\0') {
netdev_set_qos(iface->netdev, NULL, NULL);
} else {
const struct ovsdb_datum *queues;

View File

@@ -3149,6 +3149,33 @@
information on how this classifier works.
</dd>
</dl>
<dl>
<dt><code>linux-sfq</code></dt>
<dd>
Linux ``Stochastic Fairness Queueing'' classifier. See
<code>tc-sfq</code>(8) (also at
<code>http://linux.die.net/man/8/tc-sfq</code>) for information on
how this classifier works.
</dd>
</dl>
<dl>
<dt><code>linux-codel</code></dt>
<dd>
Linux ``Controlled Delay'' classifier. See <code>tc-codel</code>(8)
(also at
<code>http://man7.org/linux/man-pages/man8/tc-codel.8.html</code>)
for information on how this classifier works.
</dd>
</dl>
<dl>
<dt><code>linux-fq_codel</code></dt>
<dd>
Linux ``Fair Queuing with Controlled Delay'' classifier. See
<code>tc-fq_codel</code>(8) (also at
<code>http://man7.org/linux/man-pages/man8/tc-fq_codel.8.html</code>)
for information on how this classifier works.
</dd>
</dl>
</column>
<column name="queues">