summaryrefslogtreecommitdiff
path: root/lib/netdev-linux.c
diff options
context:
space:
mode:
authorSharon K <thekafkaf@gmail.com>2019-03-15 01:02:24 +0200
committerBen Pfaff <blp@ovn.org>2019-03-14 19:20:19 -0700
commit2f564bb153f253fb6e2f6432bfe33fb213c0bef9 (patch)
tree499e7930ad6a2e37ee7fce771c3f42914d7514ec /lib/netdev-linux.c
parentf7e46850153c323a80528e556913b8b5968d5f33 (diff)
downloadopenvswitch-2f564bb153f253fb6e2f6432bfe33fb213c0bef9.tar.gz
netdev-linux: netem QoS support
Signed-off-by: Sharon Krendel <thekafkaf@gmail.com> Signed-off-by: Ben Pfaff <blp@ovn.org>
Diffstat (limited to 'lib/netdev-linux.c')
-rw-r--r--lib/netdev-linux.c183
1 files changed, 183 insertions, 0 deletions
diff --git a/lib/netdev-linux.c b/lib/netdev-linux.c
index 0d0d045f7..deedc6954 100644
--- a/lib/netdev-linux.c
+++ b/lib/netdev-linux.c
@@ -24,6 +24,7 @@
#include <netinet/in.h>
#include <arpa/inet.h>
#include <inttypes.h>
+#include <math.h>
#include <linux/filter.h>
#include <linux/gen_stats.h>
#include <linux/if_ether.h>
@@ -437,6 +438,7 @@ 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_netem;
static const struct tc_ops tc_ops_default;
static const struct tc_ops tc_ops_noop;
static const struct tc_ops tc_ops_other;
@@ -447,6 +449,7 @@ static const struct tc_ops *const tcs[] = {
&tc_ops_codel, /* Controlled delay */
&tc_ops_fqcodel, /* Fair queue controlled delay */
&tc_ops_sfq, /* Stochastic fair queueing */
+ &tc_ops_netem, /* Network Emulator */
&tc_ops_noop, /* Non operating qos type. */
&tc_ops_default, /* Default qdisc (see tc-pfifo_fast(8)). */
&tc_ops_other, /* Some other qdisc. */
@@ -456,6 +459,7 @@ static const struct tc_ops *const tcs[] = {
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 uint32_t tc_time_to_ticks(uint32_t time);
static struct tcmsg *netdev_linux_tc_make_request(const struct netdev *,
int type,
@@ -3957,6 +3961,179 @@ static const struct tc_ops tc_ops_sfq = {
.qdisc_set = sfq_qdisc_set,
};
+/* netem traffic control class. */
+
+struct netem {
+ struct tc tc;
+ uint32_t latency;
+ uint32_t limit;
+ uint32_t loss;
+};
+
+static struct netem *
+netem_get__(const struct netdev *netdev_)
+{
+ struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+ return CONTAINER_OF(netdev->tc, struct netem, tc);
+}
+
+static void
+netem_install__(struct netdev *netdev_, uint32_t latency,
+ uint32_t limit, uint32_t loss)
+{
+ struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+ struct netem *netem;
+
+ netem = xmalloc(sizeof *netem);
+ tc_init(&netem->tc, &tc_ops_netem);
+ netem->latency = latency;
+ netem->limit = limit;
+ netem->loss = loss;
+
+ netdev->tc = &netem->tc;
+}
+
+static int
+netem_setup_qdisc__(struct netdev *netdev, uint32_t latency,
+ uint32_t limit, uint32_t loss)
+{
+ struct tc_netem_qopt opt;
+ struct ofpbuf request;
+ struct tcmsg *tcmsg;
+ int error;
+
+ tc_del_qdisc(netdev);
+
+ tcmsg = netdev_linux_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 (!limit) {
+ opt.limit = 1000;
+ } else {
+ opt.limit = limit;
+ }
+
+ if (loss) {
+ if (loss > 100) {
+ VLOG_WARN_RL(&rl,
+ "loss should be a percentage value between 0 to 100, "
+ "loss was %u", loss);
+ return EINVAL;
+ }
+ opt.loss = floor(UINT32_MAX * (loss / 100.0));
+ }
+
+ opt.latency = tc_time_to_ticks(latency);
+
+ nl_msg_put_string(&request, TCA_KIND, "netem");
+ 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, "
+ "latency %u, limit %u, loss %u error %d(%s)",
+ netdev_get_name(netdev),
+ opt.latency, opt.limit, opt.loss,
+ error, ovs_strerror(error));
+ }
+ return error;
+}
+
+static void
+netem_parse_qdisc_details__(struct netdev *netdev OVS_UNUSED,
+ const struct smap *details, struct netem *netem)
+{
+ netem->latency = smap_get_ullong(details, "latency", 0);
+ netem->limit = smap_get_ullong(details, "limit", 0);
+ netem->loss = smap_get_ullong(details, "loss", 0);
+
+ if (!netem->limit) {
+ netem->limit = 1000;
+ }
+}
+
+static int
+netem_tc_install(struct netdev *netdev, const struct smap *details)
+{
+ int error;
+ struct netem netem;
+
+ netem_parse_qdisc_details__(netdev, details, &netem);
+ error = netem_setup_qdisc__(netdev, netem.latency,
+ netem.limit, netem.loss);
+ if (!error) {
+ netem_install__(netdev, netem.latency, netem.limit, netem.loss);
+ }
+ return error;
+}
+
+static int
+netem_tc_load(struct netdev *netdev, struct ofpbuf *nlmsg)
+{
+ const struct tc_netem_qopt *netem;
+ struct nlattr *nlattr;
+ const char *kind;
+ int error;
+
+ error = tc_parse_qdisc(nlmsg, &kind, &nlattr);
+ if (error == 0) {
+ netem = nl_attr_get(nlattr);
+ netem_install__(netdev, netem->latency, netem->limit, netem->loss);
+ return 0;
+ }
+
+ return error;
+}
+
+static void
+netem_tc_destroy(struct tc *tc)
+{
+ struct netem *netem = CONTAINER_OF(tc, struct netem, tc);
+ tc_destroy(tc);
+ free(netem);
+}
+
+static int
+netem_qdisc_get(const struct netdev *netdev, struct smap *details)
+{
+ const struct netem *netem = netem_get__(netdev);
+ smap_add_format(details, "latency", "%u", netem->latency);
+ smap_add_format(details, "limit", "%u", netem->limit);
+ smap_add_format(details, "loss", "%u", netem->loss);
+ return 0;
+}
+
+static int
+netem_qdisc_set(struct netdev *netdev, const struct smap *details)
+{
+ struct netem netem;
+
+ netem_parse_qdisc_details__(netdev, details, &netem);
+ netem_install__(netdev, netem.latency, netem.limit, netem.loss);
+ netem_get__(netdev)->latency = netem.latency;
+ netem_get__(netdev)->limit = netem.limit;
+ netem_get__(netdev)->loss = netem.loss;
+ return 0;
+}
+
+static const struct tc_ops tc_ops_netem = {
+ .linux_name = "netem",
+ .ovs_name = "linux-netem",
+ .n_queues = 0,
+ .tc_install = netem_tc_install,
+ .tc_load = netem_tc_load,
+ .tc_destroy = netem_tc_destroy,
+ .qdisc_get = netem_qdisc_get,
+ .qdisc_set = netem_qdisc_set,
+};
+
/* HTB traffic control class. */
#define HTB_N_QUEUES 0xf000
@@ -5240,6 +5417,12 @@ tc_buffer_per_jiffy(unsigned int rate)
return rate / buffer_hz;
}
+static uint32_t
+tc_time_to_ticks(uint32_t time) {
+ read_psched();
+ return time * (ticks_per_s / 1000000);
+}
+
/* 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