diff --git a/route/route.go b/route/route.go
index b537da336474d668b493c56e8ab84b0adfcd07e0..b3de6203022600719a165b58e31f92f00d27f305 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 `rm` from route `r`. Returns length of path list after removing path `rm`
 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..446d71e02982a6bd903b9890c15e8501af6f19a1
--- /dev/null
+++ b/routingtable/adjRIBIn/adj_rib_in.go
@@ -0,0 +1,46 @@
+package adjRIBIn
+
+import (
+	"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
+}
+
+// NewAdjRIBIn creates a new Adjacency RIB In
+func NewAdjRIBIn() *AdjRIBIn {
+	return &AdjRIBIn{
+		rt: routingtable.NewRoutingTable(),
+	}
+}
+
+// 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 {
+	oldPaths := a.rt.ReplacePath(pfx, p)
+
+	for _, oldPath := range oldPaths {
+		for _, client := range a.ClientManager.Clients() {
+			client.RemovePath(pfx, oldPath)
+		}
+	}
+
+	return nil
+}
+
+// RemovePath removes the path for prefix `pfx`
+func (a *AdjRIBIn) RemovePath(pfx net.Prefix, p *route.Path) error {
+	if !a.rt.RemovePath(pfx, p) {
+		return nil
+	}
+
+	for _, client := range a.ClientManager.Clients() {
+		client.RemovePath(pfx, p)
+	}
+
+	return nil
+}
diff --git a/routingtable/adjRIBIn/adj_rib_in_test.go b/routingtable/adjRIBIn/adj_rib_in_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..a6c8989ee760a17ac1c2556533ade447cbcfdb45
--- /dev/null
+++ b/routingtable/adjRIBIn/adj_rib_in_test.go
@@ -0,0 +1,220 @@
+package adjRIBIn
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/bio-routing/bio-rd/net"
+	"github.com/bio-routing/bio-rd/route"
+)
+
+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
+}
+
+// RemovePath removes the path for prefix `pfx`
+func (m *RTMockClient) RemovePath(pfx net.Prefix, p *route.Path) error {
+	m.removePathParams.pfx = pfx
+	m.removePathParams.path = p
+	return nil
+}
+
+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())
+			}
+
+			if mc.removePathParams.path != test.removePath {
+				t.Errorf("Test %q failed: Call to RemovePath did not propagate path properly: Got: %v Want: %v", test.name, mc.removePathParams.path, test.removePath)
+			}
+		} else {
+			if mc.removePathParams.pfx != net.NewPfx(0, 0) || mc.removePathParams.path != nil {
+				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..7c235c7a668f03d7b9bdf05dab95db01fb7f9eb2 100644
--- a/routingtable/client_interface.go
+++ b/routingtable/client_interface.go
@@ -7,6 +7,6 @@ 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) error
 }
diff --git a/routingtable/client_manager.go b/routingtable/client_manager.go
index 08e2587aa0d9f50bcf59054765754cfe84166af0..8f93a94ba8bdca3bf577ec405f75017d8d081012 100644
--- a/routingtable/client_manager.go
+++ b/routingtable/client_manager.go
@@ -1,10 +1,12 @@
 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
 }
 
+// Register registers a client for updates
 func (c *ClientManager) Register(client RouteTableClient) {
 	if c.clients == nil {
 		c.clients = make(map[RouteTableClient]struct{}, 0)
@@ -13,9 +15,19 @@ func (c *ClientManager) Register(client RouteTableClient) {
 	//c.routingTable.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() (ret []RouteTableClient) {
+	for rtc := range c.clients {
+		ret = append(ret, rtc)
+	}
+
+	return 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) {