diff --git a/examples/kernel/kernel b/examples/kernel/kernel
new file mode 100755
index 0000000000000000000000000000000000000000..2d2539712ff92936264c2e613e2bf56b180d5293
Binary files /dev/null and b/examples/kernel/kernel differ
diff --git a/examples/kernel/main.go b/examples/kernel/main.go
new file mode 100644
index 0000000000000000000000000000000000000000..714e4b9f264c76ba684f878782d46a67ca518f5e
--- /dev/null
+++ b/examples/kernel/main.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+	"os"
+	"time"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/protocols/kernel"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/routingtable/vrf"
+	log "github.com/sirupsen/logrus"
+)
+
+func main() {
+	vrf, err := vrf.New("inet.0")
+	if err != nil {
+		log.Errorf("Unable to create VRF: %v", err)
+		os.Exit(1)
+	}
+
+	rib4 := vrf.IPv4UnicastRIB()
+	rib4.AddPath(bnet.NewPfx(bnet.IPv4FromOctets(8, 8, 8, 0), 24), &route.Path{
+		Type: route.StaticPathType,
+		StaticPath: &route.StaticPath{
+			NextHop: bnet.IPv4FromOctets(127, 0, 0, 1),
+		},
+	})
+
+	k, err := kernel.New()
+	if err != nil {
+		log.Errorf("Unable to create protocol kernel: %v", err)
+		os.Exit(1)
+	}
+	defer k.Dispose()
+
+	rib4.Register(k)
+
+	time.Sleep(time.Second * 10)
+}
diff --git a/protocols/kernel/kernel.go b/protocols/kernel/kernel.go
new file mode 100644
index 0000000000000000000000000000000000000000..dd525c3b670c45282228eda939cad7c669667ba9
--- /dev/null
+++ b/protocols/kernel/kernel.go
@@ -0,0 +1,64 @@
+package kernel
+
+import (
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/routingtable"
+)
+
+type Kernel struct {
+	osKernel osKernel
+}
+
+type osKernel interface {
+	AddPath(pfx net.Prefix, path *route.Path) error
+	RemovePath(pfx net.Prefix, path *route.Path) bool
+	uninit() error
+}
+
+func New() (*Kernel, error) {
+	k := &Kernel{}
+	err := k.init()
+	if err != nil {
+		return nil, err
+	}
+
+	return k, nil
+}
+
+func (k *Kernel) AddPath(pfx net.Prefix, path *route.Path) error {
+	return k.osKernel.AddPath(pfx, path)
+}
+
+func (k *Kernel) RemovePath(pfx net.Prefix, path *route.Path) bool {
+	return k.osKernel.RemovePath(pfx, path)
+}
+
+func (k *Kernel) UpdateNewClient(routingtable.RouteTableClient) error {
+	return nil
+}
+
+func (k *Kernel) Register(routingtable.RouteTableClient) {
+}
+
+func (k *Kernel) RegisterWithOptions(routingtable.RouteTableClient, routingtable.ClientOptions) {
+}
+
+func (k *Kernel) Unregister(routingtable.RouteTableClient) {
+}
+
+func (k *Kernel) RouteCount() int64 {
+	return -1
+}
+
+func (k *Kernel) ClientCount() uint64 {
+	return 0
+}
+
+func (k *Kernel) Dump() []*route.Route {
+	return nil
+}
+
+func (k *Kernel) Dispose() {
+	k.osKernel.uninit()
+}
diff --git a/protocols/kernel/kernel_linux.go b/protocols/kernel/kernel_linux.go
new file mode 100644
index 0000000000000000000000000000000000000000..c94fab318d13ad66dc810035448792ef97314089
--- /dev/null
+++ b/protocols/kernel/kernel_linux.go
@@ -0,0 +1,127 @@
+package kernel
+
+import (
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/pkg/errors"
+	"github.com/vishvananda/netlink"
+
+	bnet "github.com/bio-routing/bio-rd/net"
+)
+
+const (
+	protoBio = 45
+)
+
+func (k *Kernel) init() error {
+	lk, err := newLinuxKernel()
+	if err != nil {
+		return errors.Wrap(err, "Unable to initialize linux kernel")
+	}
+
+	err = lk.init()
+	if err != nil {
+		return errors.Wrap(err, "Init failed")
+	}
+	k.osKernel = lk
+	return nil
+}
+
+func (k *Kernel) uninit() error {
+	return k.osKernel.uninit()
+}
+
+type linuxKernel struct {
+	h      *netlink.Handle
+	routes map[bnet.Prefix]struct{}
+}
+
+func newLinuxKernel() (*linuxKernel, error) {
+	h, err := netlink.NewHandle()
+	if err != nil {
+		return nil, errors.Wrap(err, "Unable to get Netlink handle")
+	}
+
+	return &linuxKernel{
+		h:      h,
+		routes: make(map[bnet.Prefix]struct{}),
+	}, nil
+}
+
+func (lk *linuxKernel) init() error {
+	err := lk.cleanup()
+	if err != nil {
+		return errors.Wrap(err, "Cleanup failed")
+	}
+
+	return nil
+}
+
+func (lk *linuxKernel) uninit() error {
+	return lk.cleanup()
+}
+
+func (lk *linuxKernel) cleanup() error {
+	filter := &netlink.Route{
+		Protocol: protoBio,
+	}
+
+	routes, err := lk.h.RouteListFiltered(0, filter, netlink.RT_FILTER_PROTOCOL)
+	if err != nil {
+		return errors.Wrap(err, "Unable to get routes")
+	}
+
+	for i := range routes {
+		err = lk.h.RouteDel(&routes[i])
+		if err != nil {
+			return errors.Wrap(err, "Unable to remove route")
+		}
+	}
+
+	return nil
+}
+
+func (lk *linuxKernel) AddPath(pfx net.Prefix, path *route.Path) error {
+	r := &netlink.Route{
+		Protocol: protoBio,
+		Dst:      pfx.GetIPNet(),
+		Gw:       path.NextHop().ToNetIP(),
+	}
+
+	if _, found := lk.routes[pfx]; !found {
+		err := lk.h.RouteAdd(r)
+		if err != nil {
+			return errors.Wrap(err, "Unable to add route")
+		}
+
+		lk.routes[pfx] = struct{}{}
+		return nil
+	}
+
+	err := lk.h.RouteReplace(r)
+	if err != nil {
+		return errors.Wrap(err, "Unable to replace route")
+	}
+
+	return nil
+}
+
+func (lk *linuxKernel) RemovePath(pfx net.Prefix, path *route.Path) bool {
+	if _, found := lk.routes[pfx]; !found {
+		return false
+	}
+
+	r := &netlink.Route{
+		Protocol: protoBio,
+		Dst:      pfx.GetIPNet(),
+		Gw:       path.NextHop().ToNetIP(),
+	}
+
+	err := lk.h.RouteDel(r)
+	if err != nil {
+		return false
+	}
+
+	delete(lk.routes, pfx)
+	return true
+}