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 +}