+ netdev_get_name(netdev), IP_ARGS(ip),
+ ovs_strerror(retval));
+ }
+ return retval;
+}
+
+static int
+nd_to_iff_flags(enum netdev_flags nd)
+{
+ int iff = 0;
+ if (nd & NETDEV_UP) {
+ iff |= IFF_UP;
+ }
+ if (nd & NETDEV_PROMISC) {
+ iff |= IFF_PROMISC;
+ }
+ if (nd & NETDEV_LOOPBACK) {
+ iff |= IFF_LOOPBACK;
+ }
+ return iff;
+}
+
+static int
+iff_to_nd_flags(int iff)
+{
+ enum netdev_flags nd = 0;
+ if (iff & IFF_UP) {
+ nd |= NETDEV_UP;
+ }
+ if (iff & IFF_PROMISC) {
+ nd |= NETDEV_PROMISC;
+ }
+ if (iff & IFF_LOOPBACK) {
+ nd |= NETDEV_LOOPBACK;
+ }
+ return nd;
+}
+
+static int
+update_flags(struct netdev_linux *netdev, enum netdev_flags off,
+ enum netdev_flags on, enum netdev_flags *old_flagsp)
+ OVS_REQUIRES(netdev->mutex)
+{
+ int old_flags, new_flags;
+ int error = 0;
+
+ old_flags = netdev->ifi_flags;
+ *old_flagsp = iff_to_nd_flags(old_flags);
+ new_flags = (old_flags & ~nd_to_iff_flags(off)) | nd_to_iff_flags(on);
+ if (new_flags != old_flags) {
+ error = set_flags(netdev_get_name(&netdev->up), new_flags);
+ get_flags(&netdev->up, &netdev->ifi_flags);
+ }
+
+ return error;
+}
+
+static int
+netdev_linux_update_flags(struct netdev *netdev_, enum netdev_flags off,
+ enum netdev_flags on, enum netdev_flags *old_flagsp)
+{
+ struct netdev_linux *netdev = netdev_linux_cast(netdev_);
+ int error;
+
+ ovs_mutex_lock(&netdev->mutex);
+ error = update_flags(netdev, off, on, old_flagsp);
+ ovs_mutex_unlock(&netdev->mutex);
+
+ return error;
+}
+
+#define NETDEV_LINUX_CLASS(NAME, CONSTRUCT, GET_STATS, \
+ GET_FEATURES, GET_STATUS) \
+{ \
+ NAME, \
+ \
+ NULL, \
+ netdev_linux_run, \
+ netdev_linux_wait, \
+ \
+ netdev_linux_alloc, \
+ CONSTRUCT, \
+ netdev_linux_destruct, \
+ netdev_linux_dealloc, \
+ NULL, /* get_config */ \
+ NULL, /* set_config */ \
+ NULL, /* get_tunnel_config */ \
+ NULL, /* build header */ \
+ NULL, /* push header */ \
+ NULL, /* pop header */ \
+ NULL, /* get_numa_id */ \
+ NULL, /* set_multiq */ \
+ \
+ netdev_linux_send, \
+ netdev_linux_send_wait, \
+ \
+ netdev_linux_set_etheraddr, \
+ netdev_linux_get_etheraddr, \
+ netdev_linux_get_mtu, \
+ netdev_linux_set_mtu, \
+ netdev_linux_get_ifindex, \
+ netdev_linux_get_carrier, \
+ netdev_linux_get_carrier_resets, \
+ netdev_linux_set_miimon_interval, \
+ GET_STATS, \
+ \
+ GET_FEATURES, \
+ netdev_linux_set_advertisements, \
+ \
+ 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_queue_dump_start, \
+ netdev_linux_queue_dump_next, \
+ netdev_linux_queue_dump_done, \
+ netdev_linux_dump_queue_stats, \
+ \
+ netdev_linux_get_in4, \
+ netdev_linux_set_in4, \
+ netdev_linux_get_in6, \
+ netdev_linux_add_router, \
+ netdev_linux_get_next_hop, \
+ GET_STATUS, \
+ netdev_linux_arp_lookup, \
+ \
+ netdev_linux_update_flags, \
+ \
+ netdev_linux_rxq_alloc, \
+ netdev_linux_rxq_construct, \
+ netdev_linux_rxq_destruct, \
+ netdev_linux_rxq_dealloc, \
+ netdev_linux_rxq_recv, \
+ netdev_linux_rxq_wait, \
+ netdev_linux_rxq_drain, \
+}
+
+const struct netdev_class netdev_linux_class =
+ NETDEV_LINUX_CLASS(
+ "system",
+ netdev_linux_construct,
+ netdev_linux_get_stats,
+ netdev_linux_get_features,
+ netdev_linux_get_status);
+
+const struct netdev_class netdev_tap_class =
+ NETDEV_LINUX_CLASS(
+ "tap",
+ netdev_linux_construct_tap,
+ netdev_tap_get_stats,
+ netdev_linux_get_features,
+ netdev_linux_get_status);
+
+const struct netdev_class netdev_internal_class =
+ NETDEV_LINUX_CLASS(
+ "internal",
+ netdev_linux_construct,
+ netdev_internal_get_stats,
+ NULL, /* get_features */
+ netdev_internal_get_status);
+\f
+
+#define CODEL_N_QUEUES 0x0000
+
+/* In sufficiently new kernel headers these are defined as enums in
+ * <linux/pkt_sched.h>. Define them here as macros to help out with older
+ * kernels. (This overrides any enum definition in the header file but that's
+ * harmless.) */
+#define TCA_CODEL_TARGET 1
+#define TCA_CODEL_LIMIT 2
+#define TCA_CODEL_INTERVAL 3
+
+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
+};
+\f
+/* FQ-CoDel traffic control class. */
+
+#define FQCODEL_N_QUEUES 0x0000
+
+/* In sufficiently new kernel headers these are defined as enums in
+ * <linux/pkt_sched.h>. Define them here as macros to help out with older
+ * kernels. (This overrides any enum definition in the header file but that's
+ * harmless.) */
+#define TCA_FQ_CODEL_TARGET 1
+#define TCA_FQ_CODEL_LIMIT 2
+#define TCA_FQ_CODEL_INTERVAL 3
+#define TCA_FQ_CODEL_ECN 4
+#define TCA_FQ_CODEL_FLOWS 5
+#define TCA_FQ_CODEL_QUANTUM 6
+
+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
+};
+\f
+/* 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;