diff --git a/net/prefix.go b/net/prefix.go
index 72ece5d6409521547d39586e4592355cebe9a8af..cb4d525e8dd70e06899f14879122e41e13ccfaa8 100644
--- a/net/prefix.go
+++ b/net/prefix.go
@@ -17,8 +17,8 @@ type Prefix struct {
 }
 
 // NewPfx creates a new Prefix
-func NewPfx(addr uint32, pfxlen uint8) *Prefix {
-	return &Prefix{
+func NewPfx(addr uint32, pfxlen uint8) Prefix {
+	return Prefix{
 		addr:   addr,
 		pfxlen: pfxlen,
 	}
@@ -49,22 +49,22 @@ func StrToAddr(x string) (uint32, error) {
 }
 
 // Addr returns the address of the prefix
-func (pfx *Prefix) Addr() uint32 {
+func (pfx Prefix) Addr() uint32 {
 	return pfx.addr
 }
 
 // Pfxlen returns the length of the prefix
-func (pfx *Prefix) Pfxlen() uint8 {
+func (pfx Prefix) Pfxlen() uint8 {
 	return pfx.pfxlen
 }
 
 // String returns a string representation of pfx
-func (pfx *Prefix) String() string {
+func (pfx Prefix) String() string {
 	return fmt.Sprintf("%s/%d", net.IP(convert.Uint32Byte(pfx.addr)), pfx.pfxlen)
 }
 
 // Contains checks if x is a subnet of or equal to pfx
-func (pfx *Prefix) Contains(x *Prefix) bool {
+func (pfx Prefix) Contains(x Prefix) bool {
 	if x.pfxlen <= pfx.pfxlen {
 		return false
 	}
@@ -74,12 +74,12 @@ func (pfx *Prefix) Contains(x *Prefix) bool {
 }
 
 // Equal checks if pfx and x are equal
-func (pfx *Prefix) Equal(x *Prefix) bool {
-	return *pfx == *x
+func (pfx Prefix) Equal(x Prefix) bool {
+	return pfx == x
 }
 
 // GetSupernet gets the next common supernet of pfx and x
-func (pfx *Prefix) GetSupernet(x *Prefix) *Prefix {
+func (pfx Prefix) GetSupernet(x Prefix) Prefix {
 	maxPfxLen := min(pfx.pfxlen, x.pfxlen) - 1
 	a := pfx.addr >> (32 - maxPfxLen)
 	b := x.addr >> (32 - maxPfxLen)
@@ -90,7 +90,7 @@ func (pfx *Prefix) GetSupernet(x *Prefix) *Prefix {
 		maxPfxLen--
 	}
 
-	return &Prefix{
+	return Prefix{
 		addr:   a << (32 - maxPfxLen),
 		pfxlen: maxPfxLen,
 	}
diff --git a/net/prefix_test.go b/net/prefix_test.go
index 45fb90287a22483b6a49eeda1870b48b791790de..5f5d4f032cc6e7c86dc6ca8368900fe5d21f0ac5 100644
--- a/net/prefix_test.go
+++ b/net/prefix_test.go
@@ -16,7 +16,7 @@ func TestNewPfx(t *testing.T) {
 func TestAddr(t *testing.T) {
 	tests := []struct {
 		name     string
-		pfx      *Prefix
+		pfx      Prefix
 		expected uint32
 	}{
 		{
@@ -37,7 +37,7 @@ func TestAddr(t *testing.T) {
 func TestPfxlen(t *testing.T) {
 	tests := []struct {
 		name     string
-		pfx      *Prefix
+		pfx      Prefix
 		expected uint8
 	}{
 		{
@@ -58,36 +58,36 @@ func TestPfxlen(t *testing.T) {
 func TestGetSupernet(t *testing.T) {
 	tests := []struct {
 		name     string
-		a        *Prefix
-		b        *Prefix
-		expected *Prefix
+		a        Prefix
+		b        Prefix
+		expected Prefix
 	}{
 		{
 			name: "Test 1",
-			a: &Prefix{
+			a: Prefix{
 				addr:   167772160, // 10.0.0.0/8
 				pfxlen: 8,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   191134464, // 11.100.123.0/24
 				pfxlen: 24,
 			},
-			expected: &Prefix{
+			expected: Prefix{
 				addr:   167772160, // 10.0.0.0/7
 				pfxlen: 7,
 			},
 		},
 		{
 			name: "Test 2",
-			a: &Prefix{
+			a: Prefix{
 				addr:   167772160, // 10.0.0.0/8
 				pfxlen: 8,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   3232235520, // 192.168.0.0/24
 				pfxlen: 24,
 			},
-			expected: &Prefix{
+			expected: Prefix{
 				addr:   0, // 0.0.0.0/0
 				pfxlen: 0,
 			},
@@ -103,17 +103,17 @@ func TestGetSupernet(t *testing.T) {
 func TestContains(t *testing.T) {
 	tests := []struct {
 		name     string
-		a        *Prefix
-		b        *Prefix
+		a        Prefix
+		b        Prefix
 		expected bool
 	}{
 		{
 			name: "Test 1",
-			a: &Prefix{
+			a: Prefix{
 				addr:   0,
 				pfxlen: 0,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   100,
 				pfxlen: 24,
 			},
@@ -121,11 +121,11 @@ func TestContains(t *testing.T) {
 		},
 		{
 			name: "Test 2",
-			a: &Prefix{
+			a: Prefix{
 				addr:   100,
 				pfxlen: 24,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   0,
 				pfxlen: 0,
 			},
@@ -133,11 +133,11 @@ func TestContains(t *testing.T) {
 		},
 		{
 			name: "Test 3",
-			a: &Prefix{
+			a: Prefix{
 				addr:   167772160,
 				pfxlen: 8,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   167772160,
 				pfxlen: 9,
 			},
@@ -145,11 +145,11 @@ func TestContains(t *testing.T) {
 		},
 		{
 			name: "Test 4",
-			a: &Prefix{
+			a: Prefix{
 				addr:   167772160,
 				pfxlen: 8,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   174391040,
 				pfxlen: 24,
 			},
@@ -157,11 +157,11 @@ func TestContains(t *testing.T) {
 		},
 		{
 			name: "Test 5",
-			a: &Prefix{
+			a: Prefix{
 				addr:   167772160,
 				pfxlen: 8,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   184549377,
 				pfxlen: 24,
 			},
@@ -169,11 +169,11 @@ func TestContains(t *testing.T) {
 		},
 		{
 			name: "Test 6",
-			a: &Prefix{
+			a: Prefix{
 				addr:   167772160,
 				pfxlen: 8,
 			},
-			b: &Prefix{
+			b: Prefix{
 				addr:   191134464,
 				pfxlen: 24,
 			},
@@ -227,8 +227,8 @@ func TestMin(t *testing.T) {
 func TestEqual(t *testing.T) {
 	tests := []struct {
 		name     string
-		a        *Prefix
-		b        *Prefix
+		a        Prefix
+		b        Prefix
 		expected bool
 	}{
 		{
@@ -256,7 +256,7 @@ func TestEqual(t *testing.T) {
 func TestString(t *testing.T) {
 	tests := []struct {
 		name     string
-		pfx      *Prefix
+		pfx      Prefix
 		expected string
 	}{
 		{
diff --git a/route/bgp.go b/route/bgp.go
new file mode 100644
index 0000000000000000000000000000000000000000..603fa3f6f81b92203f167bca72827833c85b85c9
--- /dev/null
+++ b/route/bgp.go
@@ -0,0 +1,105 @@
+package route
+
+// BGPPath represents a set of BGP path attributes
+type BGPPath struct {
+	PathIdentifier uint32
+	NextHop        uint32
+	LocalPref      uint32
+	ASPath         string
+	ASPathLen      uint16
+	Origin         uint8
+	MED            uint32
+	EBGP           bool
+	BGPIdentifier  uint32
+	Source         uint32
+}
+
+func (r *Route) bgpPathSelection() (best *Path, active []*Path) {
+	// TODO: Implement next hop lookup and compare IGP metrics
+	for _, p := range r.paths {
+		if p.Type != BGPPathType {
+			continue
+		}
+
+		if len(active) == 0 {
+			active = append(active, p)
+			best = p
+			continue
+		}
+
+		if active[0].BGPPath.ecmp(p.BGPPath) {
+			active = append(active, p)
+			if !r.bestPath.BGPPath.better(p.BGPPath) {
+				continue
+			}
+
+			best = p
+			continue
+		}
+
+		if !active[0].BGPPath.betterECMP(p.BGPPath) {
+			continue
+		}
+
+		active = []*Path{p}
+		best = p
+	}
+
+	return best, active
+}
+
+func (b *BGPPath) betterECMP(c *BGPPath) bool {
+	if c.LocalPref < b.LocalPref {
+		return false
+	}
+
+	if c.LocalPref > b.LocalPref {
+		return true
+	}
+
+	if c.ASPathLen > b.ASPathLen {
+		return false
+	}
+
+	if c.ASPathLen < b.ASPathLen {
+		return true
+	}
+
+	if c.Origin > b.Origin {
+		return false
+	}
+
+	if c.Origin < b.Origin {
+		return true
+	}
+
+	if c.MED > b.MED {
+		return false
+	}
+
+	if c.MED < b.MED {
+		return true
+	}
+
+	return false
+}
+
+func (b *BGPPath) better(c *BGPPath) bool {
+	if b.betterECMP(c) {
+		return true
+	}
+
+	if c.BGPIdentifier < b.BGPIdentifier {
+		return true
+	}
+
+	if c.Source < b.Source {
+		return true
+	}
+
+	return false
+}
+
+func (b *BGPPath) ecmp(c *BGPPath) bool {
+	return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.Origin == c.Origin && b.MED == c.MED
+}
diff --git a/route/bgp_path_manager.go b/route/bgp_path_manager.go
new file mode 100644
index 0000000000000000000000000000000000000000..f846d3b60e108fc4be97597fabefe4ab46cb9d13
--- /dev/null
+++ b/route/bgp_path_manager.go
@@ -0,0 +1,65 @@
+package route
+
+import (
+	"log"
+	"sync"
+)
+
+// BGPPathManager is a component used to deduplicate BGP Path objects
+type BGPPathManager struct {
+	paths map[BGPPath]*BGPPathCounter
+	mu    sync.Mutex
+}
+
+// BGPPathCounter couples a counter to a particular path
+type BGPPathCounter struct {
+	usageCount uint64
+	path       *BGPPath
+}
+
+// NewBGPPathManager creates a new BGP Path Manager
+func NewBGPPathManager() *BGPPathManager {
+	m := &BGPPathManager{}
+	return m
+}
+
+func (m *BGPPathManager) lookup(p BGPPath) *BGPPath {
+	pathCounter, ok := m.paths[p]
+	if !ok {
+		return nil
+	}
+
+	return pathCounter.path
+}
+
+// AddPath adds a path to the cache if it doesn't exist. If it exist a pointer to the cached object is returned.
+func (m *BGPPathManager) AddPath(p BGPPath) *BGPPath {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	q := m.lookup(p)
+	if q == nil {
+		m.paths[p] = &BGPPathCounter{
+			path: &p,
+		}
+	}
+
+	m.paths[p].usageCount++
+	return m.paths[p].path
+}
+
+// RemovePath notifies us that there is one user less for path p
+func (m *BGPPathManager) RemovePath(p BGPPath) {
+	m.mu.Lock()
+	defer m.mu.Unlock()
+
+	if m.lookup(p) == nil {
+		log.Fatalf("Tried to remove non-existent BGPPath: %v", p)
+		return
+	}
+
+	m.paths[p].usageCount--
+	if m.paths[p].usageCount == 0 {
+		delete(m.paths, p)
+	}
+}
diff --git a/route/path.go b/route/path.go
new file mode 100644
index 0000000000000000000000000000000000000000..23a93889022bcd54d25e5f3d6103310647bdcdad
--- /dev/null
+++ b/route/path.go
@@ -0,0 +1,26 @@
+package route
+
+type Path struct {
+	Type       uint8
+	StaticPath *StaticPath
+	BGPPath    *BGPPath
+}
+
+func (p *Path) Equal(q *Path) bool {
+	if p == nil || q == nil {
+		return false
+	}
+
+	if p.Type != q.Type {
+		return false
+	}
+
+	switch p.Type {
+	case BGPPathType:
+		if *p.BGPPath != *q.BGPPath {
+			return false
+		}
+	}
+
+	return true
+}
diff --git a/route/route.go b/route/route.go
new file mode 100644
index 0000000000000000000000000000000000000000..b537da336474d668b493c56e8ab84b0adfcd07e0
--- /dev/null
+++ b/route/route.go
@@ -0,0 +1,111 @@
+package route
+
+import (
+	"sync"
+
+	"github.com/bio-routing/bio-rd/net"
+)
+
+// StaticPathType indicats a path is a static path
+const StaticPathType = 1
+
+// BGPPathType indicates a path is a BGP path
+const BGPPathType = 2
+
+// OSPFPathType indicates a path is an OSPF path
+const OSPFPathType = 3
+
+// ISISPathType indicates a path is an ISIS path
+const ISISPathType = 4
+
+// Route links a prefix to paths
+type Route struct {
+	pfx         net.Prefix
+	mu          sync.Mutex
+	bestPath    *Path
+	activePaths []*Path
+	paths       []*Path
+}
+
+// NewRoute generates a new route
+func NewRoute(pfx net.Prefix, p *Path) *Route {
+	r := &Route{
+		pfx: pfx,
+	}
+
+	if p == nil {
+		r.paths = make([]*Path, 0)
+		return r
+	}
+
+	r.paths = []*Path{p}
+	return r
+}
+
+// Prefix gets the prefix of route `r`
+func (r *Route) Prefix() net.Prefix {
+	return r.pfx
+}
+
+// Addr gets a routes address
+func (r *Route) Addr() uint32 {
+	return r.pfx.Addr()
+}
+
+// Pfxlen gets a routes prefix length
+func (r *Route) Pfxlen() uint8 {
+	return r.pfx.Pfxlen()
+}
+
+// Paths returns a copy of the list of paths associated with route r
+func (r *Route) Paths() []*Path {
+	if r.paths == nil {
+		return nil
+	}
+
+	ret := make([]*Path, len(r.paths))
+	copy(ret, r.paths)
+	return ret
+}
+
+// AddPath adds path p to route r
+func (r *Route) AddPath(p *Path) {
+	if p == nil {
+		return
+	}
+
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	r.paths = append(r.paths, p)
+}
+
+// RemovePath removes path `rm` from route `r`. Returns true if removed path was last one. False otherwise.
+func (r *Route) RemovePath(p *Path) int {
+	if p == nil {
+		return len(r.paths)
+	}
+
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	r.paths = removePath(r.paths, p)
+	return len(r.paths)
+}
+
+func removePath(paths []*Path, remove *Path) []*Path {
+	i := -1
+	for j := range paths {
+		if paths[j].Equal(remove) {
+			i = j
+			break
+		}
+	}
+
+	if i < 0 {
+		return paths
+	}
+
+	copy(paths[i:], paths[i+1:])
+	return paths[:len(paths)-1]
+}
diff --git a/route/route_test.go b/route/route_test.go
new file mode 100644
index 0000000000000000000000000000000000000000..598a38d76f0bd63052f1aee5add7c2df45784498
--- /dev/null
+++ b/route/route_test.go
@@ -0,0 +1,268 @@
+package route
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/bio-routing/bio-rd/net"
+)
+
+func TestNewRoute(t *testing.T) {
+	tests := []struct {
+		name     string
+		pfx      net.Prefix
+		path     *Path
+		expected *Route
+	}{
+		{
+			name: "BGP Path",
+			pfx:  net.NewPfx(strAddr("10.0.0.0"), 8),
+			path: &Path{
+				Type:    BGPPathType,
+				BGPPath: &BGPPath{},
+			},
+			expected: &Route{
+				pfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+				paths: []*Path{
+					&Path{
+						Type:    BGPPathType,
+						BGPPath: &BGPPath{},
+					},
+				},
+			},
+		},
+		{
+			name: "Empty Path",
+			pfx:  net.NewPfx(strAddr("10.0.0.0"), 8),
+			expected: &Route{
+				pfx:   net.NewPfx(strAddr("10.0.0.0"), 8),
+				paths: []*Path{},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		res := NewRoute(test.pfx, test.path)
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestPrefix(t *testing.T) {
+	tests := []struct {
+		name     string
+		route    *Route
+		expected net.Prefix
+	}{
+		{
+			name: "Prefix",
+			route: &Route{
+				pfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+			},
+			expected: net.NewPfx(strAddr("10.0.0.0"), 8),
+		},
+	}
+
+	for _, test := range tests {
+		res := test.route.Prefix()
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestAddr(t *testing.T) {
+	tests := []struct {
+		name     string
+		route    *Route
+		expected uint32
+	}{
+		{
+			name: "Prefix",
+			route: &Route{
+				pfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+			},
+			expected: 0xa000000,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.route.Addr()
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestPfxlen(t *testing.T) {
+	tests := []struct {
+		name     string
+		route    *Route
+		expected uint8
+	}{
+		{
+			name: "Prefix",
+			route: &Route{
+				pfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+			},
+			expected: 8,
+		},
+	}
+
+	for _, test := range tests {
+		res := test.route.Pfxlen()
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func TestAddPath(t *testing.T) {
+	tests := []struct {
+		name     string
+		route    *Route
+		newPath  *Path
+		expected *Route
+	}{
+		{
+			name: "Regular BGP path",
+			route: NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &Path{
+				Type:    BGPPathType,
+				BGPPath: &BGPPath{},
+			}),
+			newPath: &Path{
+				Type:    BGPPathType,
+				BGPPath: &BGPPath{},
+			},
+			expected: &Route{
+				pfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+				paths: []*Path{
+					{
+						Type:    BGPPathType,
+						BGPPath: &BGPPath{},
+					},
+					{
+						Type:    BGPPathType,
+						BGPPath: &BGPPath{},
+					},
+				},
+			},
+		},
+		{
+			name: "Nil path",
+			route: NewRoute(net.NewPfx(strAddr("10.0.0.0"), 8), &Path{
+				Type:    BGPPathType,
+				BGPPath: &BGPPath{},
+			}),
+			newPath: nil,
+			expected: &Route{
+				pfx: net.NewPfx(strAddr("10.0.0.0"), 8),
+				paths: []*Path{
+					{
+						Type:    BGPPathType,
+						BGPPath: &BGPPath{},
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		test.route.AddPath(test.newPath)
+		assert.Equal(t, test.expected, test.route)
+	}
+}
+
+func TestRouteRemovePath(t *testing.T) {
+	tests := []struct {
+		name     string
+		paths    []*Path
+		remove   *Path
+		expected []*Path
+	}{
+		{
+			name: "Remove middle",
+			paths: []*Path{
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 100,
+					},
+				},
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 200,
+					},
+				},
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 300,
+					},
+				},
+			},
+			remove: &Path{
+				Type: BGPPathType,
+				BGPPath: &BGPPath{
+					LocalPref: 200,
+				},
+			},
+			expected: []*Path{
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 100,
+					},
+				},
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 300,
+					},
+				},
+			},
+		},
+		{
+			name: "Remove non-existent",
+			paths: []*Path{
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 10,
+					},
+				},
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 20,
+					},
+				},
+			},
+			remove: &Path{
+				Type: BGPPathType,
+				BGPPath: &BGPPath{
+					LocalPref: 50,
+				},
+			},
+			expected: []*Path{
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 10,
+					},
+				},
+				{
+					Type: BGPPathType,
+					BGPPath: &BGPPath{
+						LocalPref: 20,
+					},
+				},
+			},
+		},
+	}
+
+	for _, test := range tests {
+		res := removePath(test.paths, test.remove)
+		assert.Equal(t, test.expected, res)
+	}
+}
+
+func strAddr(s string) uint32 {
+	ret, _ := net.StrToAddr(s)
+	return ret
+}
diff --git a/route/static.go b/route/static.go
new file mode 100644
index 0000000000000000000000000000000000000000..1ae8b302eef37709b167cbe07c75df663a7d055b
--- /dev/null
+++ b/route/static.go
@@ -0,0 +1,27 @@
+package route
+
+// StaticPath represents a static path of a route
+type StaticPath struct {
+	NextHop uint32
+}
+
+func (r *Route) staticPathSelection() (best *Path, active []*Path) {
+	if r.paths == nil {
+		return nil, nil
+	}
+
+	if len(r.paths) == 0 {
+		return nil, nil
+	}
+
+	for _, p := range r.paths {
+		if p.Type != StaticPathType {
+			continue
+		}
+
+		active = append(active, p)
+		best = p
+	}
+
+	return
+}