diff --git a/route/route.go b/route/route.go
index b537da336474d668b493c56e8ab84b0adfcd07e0..67249f6419e10585175182d945a290600921b28b 100644
--- a/route/route.go
+++ b/route/route.go
@@ -80,7 +80,7 @@ func (r *Route) AddPath(p *Path) {
 	r.paths = append(r.paths, p)
 }
 
-// RemovePath removes path `rm` from route `r`. Returns true if removed path was last one. False otherwise.
+// RemovePath removes path `p` from route `r`. Returns length of path list after removing path `p`
 func (r *Route) RemovePath(p *Path) int {
 	if p == nil {
 		return len(r.paths)
diff --git a/routingtable/adjRIBIn/adj_rib_in.go b/routingtable/adjRIBIn/adj_rib_in.go
new file mode 100644
index 0000000000000000000000000000000000000000..33366d2192887e334549118c53c085846cf3db31
--- /dev/null
+++ b/routingtable/adjRIBIn/adj_rib_in.go
@@ -0,0 +1,81 @@
+package adjRIBIn
+
+import (
+	"sync"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/routingtable"
+)
+
+// AdjRIBIn represents an Adjacency RIB In as described in RFC4271
+type AdjRIBIn struct {
+	rt *routingtable.RoutingTable
+	routingtable.ClientManager
+	mu sync.RWMutex
+}
+
+// NewAdjRIBIn creates a new Adjacency RIB In
+func NewAdjRIBIn() *AdjRIBIn {
+	a := &AdjRIBIn{
+		rt: routingtable.NewRoutingTable(),
+	}
+	a.ClientManager = routingtable.NewClientManager(a)
+	return a
+}
+
+// UpdateNewClient sends current state to a new client
+func (a *AdjRIBIn) UpdateNewClient(client routingtable.RouteTableClient) error {
+	a.mu.RLock()
+	defer a.mu.RUnlock()
+
+	routes := a.rt.Dump()
+	for _, route := range routes {
+		paths := route.Paths()
+		for _, path := range paths {
+			client.AddPath(route.Prefix(), path)
+		}
+	}
+	return nil
+}
+
+// AddPath replaces the path for prefix `pfx`. If the prefix doesn't exist it is added.
+func (a *AdjRIBIn) AddPath(pfx net.Prefix, p *route.Path) error {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+
+	oldPaths := a.rt.ReplacePath(pfx, p)
+	a.removePathsFromClients(pfx, oldPaths)
+
+	for _, client := range a.ClientManager.Clients() {
+		client.AddPath(pfx, p)
+	}
+	return nil
+}
+
+// RemovePath removes the path for prefix `pfx`
+func (a *AdjRIBIn) RemovePath(pfx net.Prefix, p *route.Path) bool {
+	a.mu.Lock()
+	defer a.mu.Unlock()
+
+	r := a.rt.Get(pfx)
+	if r == nil {
+		return false
+	}
+
+	oldPaths := r.Paths()
+	for _, path := range oldPaths {
+		a.rt.RemovePath(pfx, path)
+	}
+
+	a.removePathsFromClients(pfx, oldPaths)
+	return true
+}
+
+func (a *AdjRIBIn) removePathsFromClients(pfx net.Prefix, paths []*route.Path) {
+	for _, path := range paths {
+		for _, client := range a.ClientManager.Clients() {
+			client.RemovePath(pfx, path)
+		}
+	}
+}
diff --git a/routingtable/adjRIBIn/adj_rib_in_test.go b/routingtable/adjRIBIn/adj_rib_in_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..514830fb34bc236f73ea7228168102cba5f165ba
--- /dev/null
+++ b/routingtable/adjRIBIn/adj_rib_in_test.go
@@ -0,0 +1,222 @@
+package adjRIBIn
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+	"github.com/bio-routing/bio-rd/routingtable"
+)
+
+type RTMockClient struct {
+	removePathParams struct {
+		pfx  net.Prefix
+		path *route.Path
+	}
+}
+
+func NewRTMockClient() *RTMockClient {
+	return &RTMockClient{}
+}
+
+func (m *RTMockClient) AddPath(pfx net.Prefix, p *route.Path) error {
+	return nil
+}
+
+func (m *RTMockClient) UpdateNewClient(client routingtable.RouteTableClient) error {
+	return fmt.Errorf("Not implemented")
+}
+
+// RemovePath removes the path for prefix `pfx`
+func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) bool {
+	m.removePathParams.pfx = pfx
+	m.removePathParams.path = p
+	return true
+}
+
+func TestAddPath(t *testing.T) {
+	tests := []struct {
+		name       string
+		routes     []*route.Route
+		removePfx  net.Prefix
+		removePath *route.Path
+		expected   []*route.Route
+	}{
+		{
+			name: "Add route",
+			routes: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+					Type: route.BGPPathType,
+					BGPPath: &route.BGPPath{
+						LocalPref: 100,
+					},
+				}),
+			},
+			removePfx:  net.NewPfx(0, 0),
+			removePath: nil,
+			expected: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+					Type: route.BGPPathType,
+					BGPPath: &route.BGPPath{
+						LocalPref: 100,
+					},
+				}),
+			},
+		},
+		{
+			name: "Overwrite routes",
+			routes: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+					Type: route.BGPPathType,
+					BGPPath: &route.BGPPath{
+						LocalPref: 100,
+					},
+				}),
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+					Type: route.BGPPathType,
+					BGPPath: &route.BGPPath{
+						LocalPref: 200,
+					},
+				}),
+			},
+			removePfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+			removePath: &route.Path{
+				Type: route.BGPPathType,
+				BGPPath: &route.BGPPath{
+					LocalPref: 100,
+				},
+			},
+			expected: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+					Type: route.BGPPathType,
+					BGPPath: &route.BGPPath{
+						LocalPref: 200,
+					},
+				}),
+			},
+		},
+	}
+
+	for _, test := range tests {
+		adjRIBIn := NewAdjRIBIn()
+		mc := NewRTMockClient()
+		adjRIBIn.ClientManager.Register(mc)
+
+		for _, route := range test.routes {
+			adjRIBIn.AddPath(route.Prefix(), route.Paths()[0])
+		}
+
+		if mc.removePathParams.pfx != test.removePfx {
+			t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String())
+		}
+
+		assert.Equal(t, test.removePath, mc.removePathParams.path)
+		assert.Equal(t, test.expected, adjRIBIn.rt.Dump())
+	}
+}
+
+func TestRemovePath(t *testing.T) {
+	tests := []struct {
+		name            string
+		routes          []*route.Route
+		removePfx       net.Prefix
+		removePath      *route.Path
+		expected        []*route.Route
+		wantPropagation bool
+	}{
+		{
+			name: "Remove an existing route",
+			routes: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+				route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+			},
+			removePfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+			removePath: &route.Path{
+				Type:    route.BGPPathType,
+				BGPPath: &route.BGPPath{},
+			},
+			expected: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+				route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+			},
+			wantPropagation: true,
+		},
+		{
+			name: "Remove non existing route",
+			routes: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+				route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+			},
+			removePfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+			removePath: &route.Path{
+				Type:    route.BGPPathType,
+				BGPPath: &route.BGPPath{},
+			},
+			expected: []*route.Route{
+				route.NewRoute(net.NewPfx(strAddr("10.0.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+				route.NewRoute(net.NewPfx(strAddr("10.128.0.0"), 9), &route.Path{
+					Type:    route.BGPPathType,
+					BGPPath: &route.BGPPath{},
+				}),
+			},
+			wantPropagation: false,
+		},
+	}
+
+	for _, test := range tests {
+		adjRIBIn := NewAdjRIBIn()
+		for _, route := range test.routes {
+			adjRIBIn.AddPath(route.Prefix(), route.Paths()[0])
+		}
+
+		mc := NewRTMockClient()
+		adjRIBIn.ClientManager.Register(mc)
+		adjRIBIn.RemovePath(test.removePfx, test.removePath)
+
+		if test.wantPropagation {
+			if mc.removePathParams.pfx != test.removePfx {
+				t.Errorf("Test %q failed: Call to RemovePath did not propagate prefix properly: Got: %s Want: %s", test.name, mc.removePathParams.pfx.String(), test.removePfx.String())
+			}
+			assert.Equal(t, test.removePath, mc.removePathParams.path)
+		} else {
+			if mc.removePathParams.pfx != net.NewPfx(0, 0) {
+				t.Errorf("Test %q failed: Call to RemovePath propagated unexpectedly", test.name)
+			}
+		}
+
+		assert.Equal(t, test.expected, adjRIBIn.rt.Dump())
+	}
+}
+
+func strAddr(s string) uint32 {
+	ret, _ := net.StrToAddr(s)
+	return ret
+}
diff --git a/routingtable/client_interface.go b/routingtable/client_interface.go
index 648bf40ae29738a7c9f85bf068183d2317d66120..63ba81e8d5afa4fb73b0989b2ae519000e56a6b5 100644
--- a/routingtable/client_interface.go
+++ b/routingtable/client_interface.go
@@ -7,6 +7,7 @@ import (
 
 // RouteTableClient is the interface that every type of RIB must implement
 type RouteTableClient interface {
-	AddPath(*net.Prefix, *route.Path) error
-	RemovePath(*net.Prefix, *route.Path) error
+	AddPath(net.Prefix, *route.Path) error
+	RemovePath(net.Prefix, *route.Path) bool
+	UpdateNewClient(RouteTableClient) error
 }
diff --git a/routingtable/client_manager.go b/routingtable/client_manager.go
index 08e2587aa0d9f50bcf59054765754cfe84166af0..bb86111d6f89cd8d98d15396e211050dfe8872ba 100644
--- a/routingtable/client_manager.go
+++ b/routingtable/client_manager.go
@@ -1,21 +1,42 @@
 package routingtable
 
+// ClientManager manages clients of routing tables (observer pattern)
 type ClientManager struct {
-	clients      map[RouteTableClient]struct{} // Ensures a client registers at most once
-	routingTable *RoutingTable
+	clients map[RouteTableClient]struct{} // Ensures a client registers at most once
+	master  RouteTableClient
 }
 
+// NewClientManager creates and initializes a new client manager
+func NewClientManager(master RouteTableClient) ClientManager {
+	return ClientManager{
+		clients: make(map[RouteTableClient]struct{}, 0),
+		master:  master,
+	}
+}
+
+// Register registers a client for updates
 func (c *ClientManager) Register(client RouteTableClient) {
 	if c.clients == nil {
 		c.clients = make(map[RouteTableClient]struct{}, 0)
 	}
 	c.clients[client] = struct{}{}
-	//c.routingTable.updateNewClient(client)
+	c.master.UpdateNewClient(client)
 }
 
+// Unregister unregisters a client
 func (c *ClientManager) Unregister(client RouteTableClient) {
 	if _, ok := c.clients[client]; !ok {
 		return
 	}
 	delete(c.clients, client)
 }
+
+// Clients returns a list of registered clients
+func (c *ClientManager) Clients() []RouteTableClient {
+	ret := make([]RouteTableClient, 0)
+	for rtc := range c.clients {
+		ret = append(ret, rtc)
+	}
+
+	return ret
+}
diff --git a/routingtable/client_manager_test.go b/routingtable/client_manager_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..d05370ce55e0a3d4608e131b0b371b6ef5e6f4fc
--- /dev/null
+++ b/routingtable/client_manager_test.go
@@ -0,0 +1,66 @@
+package routingtable
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+type MockClient struct {
+	foo int
+}
+
+func (m MockClient) AddPath(net.Prefix, *route.Path) error {
+	return nil
+}
+func (m MockClient) RemovePath(net.Prefix, *route.Path) bool {
+	return false
+}
+func (m MockClient) UpdateNewClient(RouteTableClient) error {
+	return nil
+}
+
+func TestClients(t *testing.T) {
+	tests := []struct {
+		name     string
+		clients  []MockClient
+		expected []RouteTableClient
+	}{
+		{
+			name:     "No clients",
+			clients:  []MockClient{},
+			expected: []RouteTableClient{},
+		},
+		{
+			name: "No clients",
+			clients: []MockClient{
+				MockClient{
+					foo: 1,
+				},
+				MockClient{
+					foo: 2,
+				},
+			},
+			expected: []RouteTableClient{
+				MockClient{
+					foo: 1,
+				},
+				MockClient{
+					foo: 2,
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		cm := NewClientManager(MockClient{})
+		for _, client := range test.clients {
+			cm.Register(client)
+		}
+		ret := cm.Clients()
+		assert.Equal(t, test.expected, ret)
+	}
+}
diff --git a/routingtable/table.go b/routingtable/table.go
index 6b8bcb6b39200b0710aa3de0899ce12c248ed846..fce8a4f1a26ac25ca5b124ec2b17810c698fe3ec 100644
--- a/routingtable/table.go
+++ b/routingtable/table.go
@@ -23,6 +23,10 @@ func (rt *RoutingTable) AddPath(pfx net.Prefix, p *route.Path) error {
 	rt.mu.Lock()
 	defer rt.mu.Unlock()
 
+	return rt.addPath(pfx, p)
+}
+
+func (rt *RoutingTable) addPath(pfx net.Prefix, p *route.Path) error {
 	if rt.root == nil {
 		rt.root = newNode(pfx, p, pfx.Pfxlen(), false)
 		return nil
@@ -32,13 +36,55 @@ func (rt *RoutingTable) AddPath(pfx net.Prefix, p *route.Path) error {
 	return nil
 }
 
+// ReplacePath replaces all paths for prefix `pfx` with path `p`
+func (rt *RoutingTable) ReplacePath(pfx net.Prefix, p *route.Path) []*route.Path {
+	rt.mu.Lock()
+	defer rt.mu.Unlock()
+
+	r := rt.get(pfx)
+	if r == nil {
+		rt.addPath(pfx, p)
+		return nil
+	}
+
+	oldPaths := r.Paths()
+	rt.removePaths(pfx, oldPaths)
+
+	rt.addPath(pfx, p)
+	return oldPaths
+}
+
 // RemovePath removes a path from the trie
-func (rt *RoutingTable) RemovePath(pfx net.Prefix, p *route.Path) error {
+func (rt *RoutingTable) RemovePath(pfx net.Prefix, p *route.Path) bool {
 	rt.mu.Lock()
 	defer rt.mu.Unlock()
 
-	rt.root.removePath(pfx, p)
-	return nil
+	return rt.removePath(pfx, p)
+}
+
+func (rt *RoutingTable) removePath(pfx net.Prefix, p *route.Path) bool {
+	return rt.root.removePath(pfx, p)
+}
+
+func (rt *RoutingTable) removePaths(pfx net.Prefix, paths []*route.Path) {
+	for _, p := range paths {
+		rt.removePath(pfx, p)
+	}
+}
+
+// RemovePfx removes all paths for prefix `pfx`
+func (rt *RoutingTable) RemovePfx(pfx net.Prefix) []*route.Path {
+	rt.mu.Lock()
+	defer rt.mu.Unlock()
+
+	r := rt.get(pfx)
+	if r == nil {
+		return nil
+	}
+
+	oldPaths := r.Paths()
+	rt.removePaths(pfx, oldPaths)
+	return oldPaths
 }
 
 // LPM performs a longest prefix match for pfx on lpm
@@ -59,6 +105,10 @@ func (rt *RoutingTable) Get(pfx net.Prefix) *route.Route {
 	rt.mu.RLock()
 	defer rt.mu.RUnlock()
 
+	return rt.get(pfx)
+}
+
+func (rt *RoutingTable) get(pfx net.Prefix) *route.Route {
 	if rt.root == nil {
 		return nil
 	}
diff --git a/routingtable/trie.go b/routingtable/trie.go
index 5fdc4b8cb73357740e777fd6606ffcea129972ba..730d10155284863b49c475185e34fc9db3ca0798 100644
--- a/routingtable/trie.go
+++ b/routingtable/trie.go
@@ -27,9 +27,9 @@ func newNode(pfx net.Prefix, path *route.Path, skip uint8, dummy bool) *node {
 	return n
 }
 
-func (n *node) removePath(pfx net.Prefix, p *route.Path) {
+func (n *node) removePath(pfx net.Prefix, p *route.Path) (success bool) {
 	if n == nil {
-		return
+		return false
 	}
 
 	if n.route.Prefix() == pfx {
@@ -37,22 +37,21 @@ func (n *node) removePath(pfx net.Prefix, p *route.Path) {
 			return
 		}
 
-		n.route.RemovePath(p)
+		nPaths := len(n.route.Paths())
+		nPathsAfterDel := n.route.RemovePath(p)
 		if len(n.route.Paths()) == 0 {
 			// FIXME: Can this node actually be removed from the trie entirely?
 			n.dummy = true
 		}
 
-		return
+		return nPathsAfterDel < nPaths
 	}
 
 	b := getBitUint32(pfx.Addr(), n.route.Pfxlen()+1)
 	if !b {
-		n.l.removePath(pfx, p)
-		return
+		return n.l.removePath(pfx, p)
 	}
-	n.h.removePath(pfx, p)
-	return
+	return n.h.removePath(pfx, p)
 }
 
 func (n *node) lpm(needle net.Prefix, res *[]*route.Route) {