diff --git a/include/netlink-private/types.h b/include/netlink-private/types.h
index 07f00fa..de9382d 100644
--- a/include/netlink-private/types.h
+++ b/include/netlink-private/types.h
@@ -626,6 +626,20 @@ struct rtnl_sfq
 	uint32_t	qs_mask;
 };
 
+struct rtnl_sfb
+{
+	uint32_t	qsb_rehash_interval;
+	uint32_t	qsb_warmup_time;
+	uint32_t	qsb_limit;
+	uint32_t	qsb_target;
+	uint32_t	qsb_max;
+	uint32_t	qsb_increment;
+	uint32_t	qsb_decrement;
+	uint32_t	qsb_penalty_rate;
+	uint32_t	qsb_penalty_burst;
+	uint32_t	qsb_mask;
+};
+
 struct rtnl_netem_corr
 {
 	uint32_t	nmc_delay;
diff --git a/include/netlink/route/qdisc/sfb.h b/include/netlink/route/qdisc/sfb.h
new file mode 100644
index 0000000..2899ab2
--- /dev/null
+++ b/include/netlink/route/qdisc/sfb.h
@@ -0,0 +1,60 @@
+/*
+ * netlink/route/sch/sfb.c	SFB Qdisc
+ */
+/*
+ **************************************************************************
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+#ifndef NETLINK_SFB_H_
+#define NETLINK_SFB_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern void	rtnl_sfb_set_rehash_interval(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_rehash_interval(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_warmup_time(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_warmup_time(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_max(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_max(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_bin_size(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_bin_size(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_increment(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_increment(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_decrement(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_decrement(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_limit(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_limit(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_penalty_rate(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_penalty_rate(struct rtnl_qdisc *);
+
+extern void	rtnl_sfb_set_penalty_burst(struct rtnl_qdisc *, int);
+extern int	rtnl_sfq_get_penalty_burst(struct rtnl_qdisc *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/Makefile.am b/lib/Makefile.am
index b619198..a1b5ddb 100644
--- a/lib/Makefile.am
+++ b/lib/Makefile.am
@@ -79,6 +79,7 @@ libnl_route_3_la_SOURCES = \
 	route/qdisc/codel.c \
 	route/qdisc/hfsc.c \
 	route/qdisc/drr.c \
+	route/qdisc/sfb.c \
 	\
 	fib_lookup/lookup.c fib_lookup/request.c \
 	\
diff --git a/lib/route/qdisc/sfb.c b/lib/route/qdisc/sfb.c
new file mode 100644
index 0000000..015e6a2
--- /dev/null
+++ b/lib/route/qdisc/sfb.c
@@ -0,0 +1,522 @@
+/*
+ * lib/route/qdisc/sfb.c		Stochastic Fair Blue Qdisc
+ */
+/*
+ **************************************************************************
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ * Permission to use, copy, modify, and/or distribute this software for
+ * any purpose with or without fee is hereby granted, provided that the
+ * above copyright notice and this permission notice appear in all copies.
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
+ * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ **************************************************************************
+ */
+
+/**
+ * @ingroup qdisc
+ * @defgroup qdisc_sfb Stochastic Fair Blue (SFB)
+ * @brief
+ * Sch_sfb is an implementation of the Stochastic Fair Blue (SFB) queue
+ * management algorithm for Linux.
+ *
+ * SFB aims to keep your queues short while avoiding packet drops,
+ * maximising link utilisation and preserving fairness between flows.
+ * SFB will detect flows that do not respond to congestion indications,
+ * and limit them to a constant share of the link's capacity.
+ *
+ * SFB only deals with marking and/or droping packets.  Reordering of
+ * packets is handled by sfb's child qdisc; by default, this is pfifo,
+ * meaning that no reordering will happen.  You may want to use something
+ * like prio or sfq as sfb's child.
+ *
+ * Unlike most other fair queueing policies, SFB doesn't actually keep
+ * per-flow state; it manages flow information using a Bloom filter,
+ * which means that hash collisions are reduced to a minimum while using
+ * fairly little memory.
+ * @{
+ */
+
+#include <netlink-private/netlink.h>
+#include <netlink-private/tc.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink-private/route/tc-api.h>
+#include <netlink/route/qdisc.h>
+#include <netlink/route/qdisc/sfb.h>
+
+/** @cond SKIP */
+#define SCH_SFB_ATTR_REHASH	0x001
+#define SCH_SFB_ATTR_WARMUP	0x002
+#define SCH_SFB_ATTR_LIMIT	0x004
+#define SCH_SFB_ATTR_TARGET	0x008
+#define SCH_SFB_ATTR_MAX	0x010
+#define SCH_SFB_ATTR_INCREMENT	0x020
+#define SCH_SFB_ATTR_DECREMENT	0x040
+#define SCH_SFB_ATTR_PEN_RATE	0x080
+#define SCH_SFB_ATTR_PEN_BURST	0x100
+/** @endcond */
+
+static int sfb_msg_parser(struct rtnl_tc *tc, void *data)
+{
+	struct rtnl_sfb *sfb = data;
+	struct tc_sfb_qopt *opts;
+
+	if (!(tc->ce_mask & TCA_ATTR_OPTS))
+		return 0;
+
+	if (tc->tc_opts->d_size < sizeof(*opts))
+		return -NLE_INVAL;
+
+	opts = (struct tc_sfb_qopt *) tc->tc_opts->d_data;
+
+	sfb->qsb_rehash_interval	= opts->rehash_interval;
+	sfb->qsb_warmup_time		= opts->warmup_time;
+	sfb->qsb_limit			= opts->limit;
+	sfb->qsb_target			= opts->bin_size;
+	sfb->qsb_max			= opts->max;
+	sfb->qsb_increment		= opts->increment;
+	sfb->qsb_decrement		= opts->decrement;
+	sfb->qsb_penalty_rate		= opts->penalty_rate;
+	sfb->qsb_penalty_burst		= opts->penalty_burst;
+
+	sfb->qsb_mask = (SCH_SFB_ATTR_REHASH | SCH_SFB_ATTR_WARMUP |
+			 SCH_SFB_ATTR_LIMIT | SCH_SFB_ATTR_MAX |
+			 SCH_SFB_ATTR_TARGET |
+			 SCH_SFB_ATTR_INCREMENT | SCH_SFB_ATTR_DECREMENT |
+			 SCH_SFB_ATTR_PEN_RATE | SCH_SFB_ATTR_PEN_BURST);
+
+	return 0;
+}
+
+static void sfb_dump_line(struct rtnl_tc *tc, void *data,
+			  struct nl_dump_params *p)
+{
+	struct rtnl_sfb *sfb = data;
+
+	if (sfb)
+		nl_dump(p, " limit %u target %u max %u" ,
+		        sfb->qsb_limit, sfb->qsb_target, sfb->qsb_max);
+}
+
+static void sfb_dump_details(struct rtnl_tc *tc, void *data,
+			     struct nl_dump_params *p)
+{
+	struct rtnl_sfb *sfb = data;
+
+	if (sfb)
+		nl_dump(p, "rehash_interval %us warmup_time %us "
+			   "increment %u decrement %u "
+			   "penalty_rate %up/s penalty_burst %up",
+			sfb->qsb_rehash_interval, sfb->qsb_warmup_time,
+			sfb->qsb_increment, sfb->qsb_decrement,
+			sfb->qsb_penalty_rate, sfb->qsb_penalty_burst);
+}
+
+static int sfb_msg_fill(struct rtnl_tc *tc, void *data, struct nl_msg *msg)
+{
+	struct rtnl_sfb *sfb = data;
+	struct tc_sfb_qopt opts = {0};
+
+	if (!sfb)
+		BUG();
+
+	opts.rehash_interval	= sfb->qsb_rehash_interval;
+	opts.warmup_time	= sfb->qsb_warmup_time;
+	opts.limit		= sfb->qsb_limit;
+	opts.bin_size 		= sfb->qsb_target;
+	opts.max		= sfb->qsb_max;
+	opts.increment		= sfb->qsb_increment;
+	opts.decrement		= sfb->qsb_decrement;
+	opts.penalty_rate	= sfb->qsb_penalty_rate;
+	opts.penalty_burst	= sfb->qsb_penalty_burst;
+
+	return nlmsg_append(msg, &opts, sizeof(opts), NL_DONTPAD);
+}
+
+/**
+ * @name Attribute Access
+ * @{
+ */
+
+/**
+ * Set the rehash interval of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg interval_secs	Specifies how often, in seconds, before we will switch
+ * to a fresh Bloom filter and a different set of hash functions.  Since Bloom
+ * filters are more resistent to hash collisions that hash tables, this may be
+ * set to a fairly large value.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_rehash_interval(struct rtnl_qdisc *qdisc, int interval_secs)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_rehash_interval = interval_secs;
+	sfb->qsb_mask |= SCH_SFB_ATTR_REHASH;
+}
+
+/**
+ * Get rehash interval of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Rehash interval in seconds, or a negative error code.
+ */
+int rtnl_sfb_get_rehash_interval(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_REHASH)
+		return sfb->qsb_rehash_interval;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the warmup time of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg warmup_secs	In seconds, how long we will perform double buffering
+ * before switching to a new bloom filter.  This should be long enough to make
+ * sure that the new filter is ``primed'' before it is used, a few tens of
+ * seconds should be enough.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_warmup_time(struct rtnl_qdisc *qdisc, int warmup_secs)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_warmup_time = warmup_secs;
+	sfb->qsb_mask |= SCH_SFB_ATTR_WARMUP;
+}
+
+/**
+ * Get warmup time of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Warmup time in seconds, or a negative error code.
+ */
+int rtnl_sfb_get_warmup_time(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_WARMUP)
+		return sfb->qsb_warmup_time;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the total packet limit of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg interval	The hard limit on the number of packets in all of sfb's
+ * aggregates (total qdisc limit).  Set it to some large value, it should never
+ * be reached anyway.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_limit(struct rtnl_qdisc *qdisc, int interval)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_limit = interval;
+	sfb->qsb_mask |= SCH_SFB_ATTR_REHASH;
+}
+
+/**
+ * Get rehash interval of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Limit in packets, or a negative error code.
+ */
+int rtnl_sfb_get_limit(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_REHASH)
+		return sfb->qsb_limit;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the target number of packets per aggregate of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg target_packets	the target per-flow queue size in packets.  sfb will
+ * try to keep each per-aggregate queue between 0 and target.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_target_packets(struct rtnl_qdisc *qdisc, int target_packets)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_target = target_packets;
+	sfb->qsb_mask |= SCH_SFB_ATTR_TARGET;
+}
+
+/**
+ * Get rehash interval of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Target packets per aggregate, or a negative error code.
+ */
+int rtnl_sfb_get_target_packets(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_TARGET)
+		return sfb->qsb_target;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the max packets per aggregate of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg max_packets	The maximum number of packets queued for a given
+ * aggregate.  It should be very slightly larger than target, in order to
+ * absorb transient bursts.  Setting this to more than roughly 1.5 times target
+ * will cause instabilities with which Blue is not designed to cope.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_max_packets(struct rtnl_qdisc *qdisc, int max_packets)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_max = max_packets;
+	sfb->qsb_mask |= SCH_SFB_ATTR_MAX;
+}
+
+/**
+ * Get max packets per aggregate of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Max packets, or a negative error code.
+ */
+int rtnl_sfb_get_max_packets(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_MAX)
+		return sfb->qsb_max;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the underflow increment of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg increment	16-bit fixed point integer that represents the value by
+ * which per-flow dropping probability is decreased on queue underflow.  This
+ * should be a small fraction of a percent, and increment should be a few times
+ * smaller than decrement.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_underflow_increment(struct rtnl_qdisc *qdisc, int increment)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_increment = increment;
+	sfb->qsb_mask |= SCH_SFB_ATTR_INCREMENT;
+}
+
+/**
+ * Get underflow increment of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Underflow increment in 16-bit fixed point, or a negative error code.
+ */
+int rtnl_sfb_get_underflow_increment(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_INCREMENT)
+		return sfb->qsb_increment;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the overflow decrement of SFB qdisc.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg decrement	16-bit fixed point integer that represents the value by
+ * which per-flow dropping probability is increased on queue overflow.  This
+ * should be a small fraction of a percent, and decrement should be a few times
+ * larger than increment.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_overflow_decrement(struct rtnl_qdisc *qdisc, int decrement)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_decrement = decrement;
+	sfb->qsb_mask |= SCH_SFB_ATTR_DECREMENT;
+}
+
+/**
+ * Get overflow decrement of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Overflow decrement in 16-bit fixed point, or a negative error code.
+ */
+int rtnl_sfb_get_overflow_decrement(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_DECREMENT)
+		return sfb->qsb_decrement;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the penalty rate of SFB qdisc.
+ *
+ * When a flow doesn't back off in response to congestion indication, sfb will
+ * categorise it as ``non-reactive'' and will rate-limit it.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg packets_per_sec	Total throughput that non-reactive flows are allowed to
+ * use in packets per second.  You should set penalty_rate to some reasonable
+ * fraction of your up-link throughput (the default values are probably too
+ * small).
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_penalty_rate(struct rtnl_qdisc *qdisc, int packets_per_sec)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_penalty_rate = packets_per_sec;
+	sfb->qsb_mask |= SCH_SFB_ATTR_PEN_RATE;
+}
+
+/**
+ * Get the penalty rate of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Penalty rate in packets per second, or a negative error code.
+ */
+int rtnl_sfb_get_penalty_rate(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_PEN_RATE)
+		return sfb->qsb_penalty_rate;
+	else
+		return -NLE_NOATTR;
+}
+
+/**
+ * Set the penalty burst of SFB qdisc.
+ *
+ * When a flow doesn't back off in response to congestion indication, sfb will
+ * categorise it as ``non-reactive'' and will rate-limit it.
+ *
+ * @arg qdisc		SFB qdisc to be modified.
+ * @arg burst_packets   Number of packets in the token bucket for penalty rate
+ * limiting.
+ * @return 0 on success or a negative error code.
+ */
+void rtnl_sfb_set_penalty_burst(struct rtnl_qdisc *qdisc, int burst_packets)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	sfb->qsb_penalty_burst = burst_packets;
+	sfb->qsb_mask |= SCH_SFB_ATTR_PEN_BURST;
+}
+
+/**
+ * Get the penalty burst of SFB qdisc.
+ * @arg qdisc		SFB qdisc.
+ * @return Penalty burst size in packets, or a negative error code.
+ */
+int rtnl_sfb_get_penalty_burst(struct rtnl_qdisc *qdisc)
+{
+	struct rtnl_sfb *sfb;
+
+	if (!(sfb = rtnl_tc_data(TC_CAST(qdisc))))
+		BUG();
+
+	if (sfb->qsb_mask & SCH_SFB_ATTR_PEN_RATE)
+		return sfb->qsb_penalty_burst;
+	else
+		return -NLE_NOATTR;
+}
+
+/** @} */
+
+static struct rtnl_tc_ops sfb_ops = {
+	.to_kind		= "sfb",
+	.to_type		= RTNL_TC_TYPE_QDISC,
+	.to_size		= sizeof(struct rtnl_sfb),
+	.to_msg_parser		= sfb_msg_parser,
+	.to_dump = {
+	    [NL_DUMP_LINE]	= sfb_dump_line,
+	    [NL_DUMP_DETAILS]	= sfb_dump_details,
+	},
+	.to_msg_fill		= sfb_msg_fill,
+};
+
+static void __init sfb_init(void)
+{
+	rtnl_tc_register(&sfb_ops);
+}
+
+static void __exit sfb_exit(void)
+{
+	rtnl_tc_unregister(&sfb_ops);
+}
+
+/** @} */
