Skip to content
Snippets Groups Projects
Commit 72a60cdf authored by Oliver Herms's avatar Oliver Herms
Browse files

Stabilizing

parent c27a029b
No related branches found
No related tags found
No related merge requests found
Showing
with 1113 additions and 132 deletions
......@@ -4,18 +4,19 @@ import (
"fmt"
"net"
"sync"
"time"
"github.com/sirupsen/logrus"
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/protocols/bgp/server"
"github.com/bio-routing/bio-rd/rt"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
)
func main() {
fmt.Printf("This is a BGP speaker\n")
VRF := rt.New(true)
rib := locRIB.New()
b := server.NewBgpServer()
err := b.Start(&config.Global{
......@@ -28,26 +29,35 @@ func main() {
b.AddPeer(config.Peer{
AdminEnabled: true,
LocalAS: 65200,
PeerAS: 65100,
PeerAddress: net.IP([]byte{169, 254, 100, 0}),
LocalAddress: net.IP([]byte{169, 254, 100, 1}),
PeerAS: 65300,
PeerAddress: net.IP([]byte{169, 254, 200, 1}),
LocalAddress: net.IP([]byte{169, 254, 200, 0}),
HoldTimer: 90,
KeepAlive: 30,
Passive: true,
RouterID: b.RouterID(),
}, VRF)
}, rib)
time.Sleep(time.Second * 30)
b.AddPeer(config.Peer{
AdminEnabled: true,
LocalAS: 65200,
PeerAS: 65300,
PeerAddress: net.IP([]byte{169, 254, 200, 1}),
LocalAddress: net.IP([]byte{169, 254, 200, 0}),
PeerAS: 65100,
PeerAddress: net.IP([]byte{169, 254, 100, 0}),
LocalAddress: net.IP([]byte{169, 254, 100, 1}),
HoldTimer: 90,
KeepAlive: 30,
Passive: true,
RouterID: b.RouterID(),
}, VRF)
}, rib)
go func() {
for {
fmt.Print(rib.Print())
time.Sleep(time.Second * 10)
}
}()
var wg sync.WaitGroup
wg.Add(1)
......
package packet
const (
OctetLen = 8
BGP4Version = 4
OctetLen = 8
MaxASNsSegment = 255
BGP4Version = 4
MarkerLen = 16
HeaderLen = 19
......@@ -130,7 +131,7 @@ type PathAttribute struct {
}
type NLRI struct {
IP interface{}
IP uint32
Pfxlen uint8
Next *NLRI
}
......
......@@ -57,12 +57,12 @@ func decodeNLRI(buf *bytes.Buffer) (*NLRI, uint8, error) {
addr[i] = 0
}
}
nlri.IP = addr
nlri.IP = fourBytesToUint32(addr)
return nlri, toCopy + 1, nil
}
func (n *NLRI) serialize(buf *bytes.Buffer) uint8 {
a := convert.Uint32Byte(n.IP.(uint32))
a := convert.Uint32Byte(n.IP)
addr := [4]byte{a[0], a[1], a[2], a[3]}
nBytes := bytesInAddr(n.Pfxlen)
......
......@@ -3,6 +3,8 @@ package packet
import (
"bytes"
"fmt"
"strconv"
"strings"
"github.com/taktv6/tflow2/convert"
)
......@@ -156,7 +158,7 @@ func (pa *PathAttribute) decodeNextHop(buf *bytes.Buffer) error {
return fmt.Errorf("Unable to read next hop: buf.Read read %d bytes", n)
}
pa.Value = addr
pa.Value = fourBytesToUint32(addr)
p += 4
return dumpNBytes(buf, pa.Length-p)
......@@ -354,8 +356,8 @@ func (pa *PathAttribute) serializeNextHop(buf *bytes.Buffer) uint8 {
buf.WriteByte(NextHopAttr)
length := uint8(4)
buf.WriteByte(length)
addr := pa.Value.([4]byte)
buf.Write(addr[:])
addr := pa.Value.(uint32)
buf.Write(convert.Uint32Byte(addr))
return 7
}
......@@ -402,3 +404,96 @@ func (pa *PathAttribute) serializeAggregator(buf *bytes.Buffer) uint8 {
buf.Write(convert.Uint16Byte(pa.Value.(uint16)))
return 5
}
/*func (pa *PathAttribute) PrependASPath(prepend []uint32) {
if pa.TypeCode != ASPathAttr {
return
}
asPath := pa.Value.(ASPath)
asPathSegementCount := len(asPath)
currentSegment := asPathSegementCount - 1
newSegmentNeeded := false
if asPath[asPathSegementCount-1].Type == ASSequence {
newSegmentNeeded = true
} else {
if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment {
newSegmentNeeded = true
}
}
for _, asn := range prepend {
if newSegmentNeeded {
segment := ASPathSegment{
Type: ASSequence,
ASNs: make([]uint32, 0),
},
}
asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, asn)
if len(asPath[asPathSegementCount-1].ASNs) >= MaxASNsSegment {
newSegmentNeeded = true
}
}
}*/
// ParseASPathStr converts an AS path from string representation info an PathAttribute object
func ParseASPathStr(asPathString string) (*PathAttribute, error) {
asPath := ASPath{}
currentType := ASSequence
newSegmentNeeded := true
currentSegment := -1
for _, asn := range strings.Split(asPathString, " ") {
if isBeginOfASSet(asn) {
currentType = ASSet
newSegmentNeeded = true
asn = strings.Replace(asn, "(", "", 1)
}
if newSegmentNeeded {
seg := ASPathSegment{
Type: uint8(currentType),
ASNs: make([]uint32, 0),
}
asPath = append(asPath, seg)
currentSegment++
newSegmentNeeded = false
}
if isEndOfASSset(asn) {
currentType = ASSequence
newSegmentNeeded = true
asn = strings.Replace(asn, ")", "", 1)
}
numericASN, err := strconv.Atoi(asn)
if err != nil {
return nil, fmt.Errorf("Unable to convert ASN: %v", err)
}
asPath[currentSegment].ASNs = append(asPath[currentSegment].ASNs, uint32(numericASN))
if len(asPath[currentSegment].ASNs) == MaxASNsSegment {
newSegmentNeeded = true
}
}
return &PathAttribute{
TypeCode: ASPathAttr,
Value: asPath,
}, nil
}
func isBeginOfASSet(asPathPart string) bool {
return strings.Contains(asPathPart, "(")
}
func isEndOfASSset(asPathPart string) bool {
return strings.Contains(asPathPart, ")")
}
func fourBytesToUint32(address [4]byte) uint32 {
return uint32(address[0])<<24 + uint32(address[1])<<16 + uint32(address[2])<<8 + uint32(address[3])
}
......@@ -1291,3 +1291,122 @@ func TestSerialize(t *testing.T) {
assert.Equal(t, test.expected, res)
}
}
func TestParseASPathStr(t *testing.T) {
tests := []struct {
name string
input string
wantFail bool
expected *PathAttribute
}{
{
name: "Simple AS_SEQUENCE",
input: "3320 15169",
wantFail: false,
expected: &PathAttribute{
TypeCode: ASPathAttr,
Value: ASPath{
ASPathSegment{
Type: ASSequence,
ASNs: []uint32{3320, 15169},
},
},
},
},
{
name: "AS_SEQUENCE with more than 255 elements",
input: "123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123 123",
wantFail: false,
expected: &PathAttribute{
TypeCode: ASPathAttr,
Value: ASPath{
ASPathSegment{
Type: ASSequence,
ASNs: []uint32{123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123, 123},
},
ASPathSegment{
Type: ASSequence,
ASNs: []uint32{123, 123, 123, 123, 123},
},
},
},
},
{
name: "AS_SET only",
input: "(3320 201701 15169)",
wantFail: false,
expected: &PathAttribute{
TypeCode: ASPathAttr,
Value: ASPath{
ASPathSegment{
Type: ASSet,
ASNs: []uint32{3320, 201701, 15169},
},
},
},
},
{
name: "Mixed AS Path",
input: "199714 51324 (3320 201701 15169)",
wantFail: false,
expected: &PathAttribute{
TypeCode: ASPathAttr,
Value: ASPath{
ASPathSegment{
Type: ASSequence,
ASNs: []uint32{199714, 51324},
},
ASPathSegment{
Type: ASSet,
ASNs: []uint32{3320, 201701, 15169},
},
},
},
},
}
for _, test := range tests {
res, err := ParseASPathStr(test.input)
if err != nil {
if test.wantFail {
continue
}
t.Errorf("Unexpected failure for test %q: %v", test.name, err)
continue
}
if test.wantFail {
t.Errorf("Unexpected success for test %q", test.name)
continue
}
assert.Equal(t, test.expected, res)
}
}
func TestFourBytesToUint32(t *testing.T) {
tests := []struct {
name string
input [4]byte
expected uint32
}{
{
name: "Test #1",
input: [4]byte{0, 0, 0, 200},
expected: 200,
},
{
name: "Test #2",
input: [4]byte{1, 0, 0, 200},
expected: 16777416,
},
}
for _, test := range tests {
res := fourBytesToUint32(test.input)
if res != test.expected {
t.Errorf("Unexpected result for test %q: Got: %d Want: %d", test.name, res, test.expected)
}
}
}
......@@ -7,12 +7,16 @@ import (
"net"
"time"
"github.com/bio-routing/bio-rd/routingtable"
"github.com/bio-routing/bio-rd/config"
tnet "github.com/bio-routing/bio-rd/net"
"github.com/bio-routing/bio-rd/protocols/bgp/packet"
"github.com/bio-routing/bio-rd/rt"
"github.com/bio-routing/bio-rd/route"
"github.com/bio-routing/bio-rd/routingtable/adjRIBIn"
"github.com/bio-routing/bio-rd/routingtable/adjRIBOut"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
log "github.com/sirupsen/logrus"
"github.com/taktv6/tflow2/convert"
tomb "gopkg.in/tomb.v2"
)
......@@ -83,9 +87,9 @@ type FSM struct {
msgRecvFailCh chan msgRecvErr
stopMsgRecvCh chan struct{}
adjRIBIn *rt.RT
adjRIBOut *rt.RT
vrf *rt.RT
adjRIBIn *adjRIBIn.AdjRIBIn
adjRIBOut *adjRIBOut.AdjRIBOut
rib *locRIB.LocRIB
updateSender *UpdateSender
}
......@@ -99,7 +103,7 @@ type msgRecvErr struct {
con *net.TCPConn
}
func NewFSM(c config.Peer, vrf *rt.RT) *FSM {
func NewFSM(c config.Peer, rib *locRIB.LocRIB) *FSM {
fsm := &FSM{
state: Idle,
passive: true,
......@@ -124,7 +128,7 @@ func NewFSM(c config.Peer, vrf *rt.RT) *FSM {
conCh: make(chan *net.TCPConn),
conErrCh: make(chan error), initiateCon: make(chan struct{}),
vrf: vrf,
rib: rib,
}
fsm.updateSender = newUpdateSender(fsm)
......@@ -208,7 +212,7 @@ func (fsm *FSM) main() error {
func (fsm *FSM) idle() int {
if fsm.adjRIBOut != nil {
fsm.vrf.Unregister(fsm.adjRIBOut)
fsm.rib.Unregister(fsm.adjRIBOut)
fsm.adjRIBOut.Unregister(fsm.updateSender)
}
fsm.adjRIBIn = nil
......@@ -653,23 +657,31 @@ func (fsm *FSM) openConfirmTCPFail(err error) int {
}
func (fsm *FSM) established() int {
fsm.adjRIBIn = rt.New(false)
fsm.adjRIBIn.Register(fsm.vrf)
fsm.adjRIBIn = adjRIBIn.New()
fsm.adjRIBIn.Register(fsm.rib)
fsm.adjRIBOut = rt.New(false)
fsm.adjRIBOut = adjRIBOut.New()
fsm.adjRIBOut.Register(fsm.updateSender)
fsm.vrf.Register(fsm.adjRIBOut)
fsm.rib.RegisterWithOptions(fsm.adjRIBOut, routingtable.ClientOptions{BestOnly: true})
go func() {
/*go func() {
for {
time.Sleep(time.Second * 10)
fmt.Printf("Dumping AdjRibIn\n")
routes := fsm.adjRIBIn.Dump()
routes := fsm.adjRIBIn.RT().Dump()
for _, route := range routes {
fmt.Printf("LPM: %s\n", route.Prefix().String())
fmt.Print(route.Print())
}
}
}()*/
go func() {
for {
fmt.Printf("ADJ-RIB-OUT: %s\n", fsm.remote.String())
fmt.Print(fsm.adjRIBOut.Print())
time.Sleep(time.Second * 11)
}
}()
for {
......@@ -712,6 +724,7 @@ func (fsm *FSM) established() int {
case recvMsg := <-fsm.msgRecvCh:
msg, err := packet.Decode(bytes.NewBuffer(recvMsg.msg))
if err != nil {
fmt.Printf("Failed to decode BGP message: %v\n", recvMsg.msg)
switch bgperr := err.(type) {
case packet.BGPError:
sendNotification(fsm.con, bgperr.ErrorCode, bgperr.ErrorSubCode)
......@@ -735,23 +748,22 @@ func (fsm *FSM) established() int {
u := msg.Body.(*packet.BGPUpdate)
for r := u.WithdrawnRoutes; r != nil; r = r.Next {
x := r.IP.([4]byte)
pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen)
pfx := tnet.NewPfx(r.IP, r.Pfxlen)
fmt.Printf("LPM: Removing prefix %s\n", pfx.String())
fsm.adjRIBIn.RemoveRoute(pfx)
fsm.adjRIBIn.RemovePath(pfx, nil)
}
for r := u.NLRI; r != nil; r = r.Next {
x := r.IP.([4]byte)
pfx := tnet.NewPfx(convert.Uint32b(x[:]), r.Pfxlen)
pfx := tnet.NewPfx(r.IP, r.Pfxlen)
fmt.Printf("LPM: Adding prefix %s\n", pfx.String())
path := &rt.Path{
Type: rt.BGPPathType,
BGPPath: &rt.BGPPath{},
path := &route.Path{
Type: route.BGPPathType,
BGPPath: &route.BGPPath{},
}
for pa := u.PathAttributes; pa.Next != nil; pa = pa.Next {
for pa := u.PathAttributes; pa != nil; pa = pa.Next {
fmt.Printf("TypeCode: %d\n", pa.TypeCode)
switch pa.TypeCode {
case packet.OriginAttr:
path.BGPPath.Origin = pa.Value.(uint8)
......@@ -760,15 +772,14 @@ func (fsm *FSM) established() int {
case packet.MEDAttr:
path.BGPPath.MED = pa.Value.(uint32)
case packet.NextHopAttr:
fmt.Printf("RECEIVED NEXT_HOP: %d\n", pa.Value.(uint32))
path.BGPPath.NextHop = pa.Value.(uint32)
case packet.ASPathAttr:
path.BGPPath.ASPath = pa.ASPathString()
path.BGPPath.ASPathLen = pa.ASPathLen()
}
}
// TO BE USED WITH BGP ADD PATH:
// fsm.adjRIBIn.AddPath(rt.NewRoute(pfx, []*rt.Path{path}))
fsm.adjRIBIn.ReplaceRoute(rt.NewRoute(pfx, []*rt.Path{path}))
fsm.adjRIBIn.AddPath(pfx, path)
}
continue
......
......@@ -3,24 +3,25 @@ package server
import (
"net"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/rt"
)
type Peer struct {
addr net.IP
asn uint32
fsm *FSM
vrf *rt.RT
rib *locRIB.LocRIB
routerID uint32
}
func NewPeer(c config.Peer, vrf *rt.RT) (*Peer, error) {
func NewPeer(c config.Peer, rib *locRIB.LocRIB) (*Peer, error) {
p := &Peer{
addr: c.PeerAddress,
asn: c.PeerAS,
fsm: NewFSM(c, vrf),
vrf: vrf,
fsm: NewFSM(c, rib),
rib: rib,
}
return p, nil
}
......
......@@ -8,7 +8,7 @@ import (
"github.com/bio-routing/bio-rd/config"
"github.com/bio-routing/bio-rd/protocols/bgp/packet"
"github.com/bio-routing/bio-rd/rt"
"github.com/bio-routing/bio-rd/routingtable/locRIB"
log "github.com/sirupsen/logrus"
)
......@@ -84,12 +84,12 @@ func (b *BGPServer) incomingConnectionWorker() {
}
}
func (b *BGPServer) AddPeer(c config.Peer, vrf *rt.RT) error {
func (b *BGPServer) AddPeer(c config.Peer, rib *locRIB.LocRIB) error {
if c.LocalAS > uint16max || c.PeerAS > uint16max {
return fmt.Errorf("32bit ASNs are not supported yet")
}
peer, err := NewPeer(c, vrf)
peer, err := NewPeer(c, rib)
if err != nil {
return err
}
......
......@@ -7,7 +7,8 @@ import (
"github.com/bio-routing/bio-rd/net"
"github.com/bio-routing/bio-rd/protocols/bgp/packet"
"github.com/bio-routing/bio-rd/rt"
"github.com/bio-routing/bio-rd/route"
"github.com/bio-routing/bio-rd/routingtable"
)
type UpdateSender struct {
......@@ -20,50 +21,51 @@ func newUpdateSender(fsm *FSM) *UpdateSender {
}
}
func (u *UpdateSender) AddPath(route *rt.Route) {
log.Warningf("BGP Update Sender: AddPath not implemented")
func (u *UpdateSender) AddPath(pfx net.Prefix, p *route.Path) error {
fmt.Printf("SENDING AN BGP UPDATE\n")
asPathPA, err := packet.ParseASPathStr(fmt.Sprintf("%d %s", u.fsm.localASN, p.BGPPath.ASPath))
if err != nil {
return fmt.Errorf("Unable to parse AS path: %v", err)
}
update := &packet.BGPUpdate{
PathAttributes: &packet.PathAttribute{
TypeCode: packet.OriginAttr,
Value: uint8(packet.IGP),
Value: p.BGPPath.Origin,
Next: &packet.PathAttribute{
TypeCode: packet.ASPathAttr,
Value: packet.ASPath{
{
Type: 2,
ASNs: []uint32{15169, 3329},
},
},
Value: asPathPA.Value,
Next: &packet.PathAttribute{
TypeCode: packet.NextHopAttr,
Value: [4]byte{100, 110, 120, 130},
Value: p.BGPPath.NextHop,
},
},
},
NLRI: &packet.NLRI{
IP: route.Prefix().Addr(),
Pfxlen: route.Pfxlen(),
IP: pfx.Addr(),
Pfxlen: pfx.Pfxlen(),
},
}
updateBytes, err := update.SerializeUpdate()
if err != nil {
log.Errorf("Unable to serialize BGP Update: %v", err)
return
return nil
}
fmt.Printf("Sending Update: %v\n", updateBytes)
u.fsm.con.Write(updateBytes)
}
func (u *UpdateSender) ReplaceRoute(*rt.Route) {
log.Warningf("BGP Update Sender: ReplaceRoute not implemented")
_, err = u.fsm.con.Write(updateBytes)
if err != nil {
return fmt.Errorf("Failed sending Update: %v", err)
}
return nil
}
func (u *UpdateSender) RemovePath(*rt.Route) {
func (u *UpdateSender) RemovePath(pfx net.Prefix, p *route.Path) bool {
log.Warningf("BGP Update Sender: RemovePath not implemented")
return false
}
func (u *UpdateSender) RemoveRoute(*net.Prefix) {
log.Warningf("BGP Update Sender: RemoveRoute not implemented")
func (u *UpdateSender) UpdateNewClient(client routingtable.RouteTableClient) error {
log.Warningf("BGP Update Sender: RemovePath not implemented")
return nil
}
package route
import (
"fmt"
"github.com/taktv6/tflow2/convert"
)
// BGPPath represents a set of BGP path attributes
type BGPPath struct {
PathIdentifier uint32
......@@ -14,38 +20,62 @@ type BGPPath struct {
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
}
// ECMP determines if routes b and c are euqal in terms of ECMP
func (b *BGPPath) ECMP(c *BGPPath) bool {
return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.MED == c.MED && b.Origin == c.Origin
}
if len(active) == 0 {
active = append(active, p)
best = p
continue
}
// Compare returns negative if b < c, 0 if paths are equal, positive if b > c
func (b *BGPPath) Compare(c *BGPPath) int8 {
if c.LocalPref < b.LocalPref {
return 1
}
if active[0].BGPPath.ecmp(p.BGPPath) {
active = append(active, p)
if !r.bestPath.BGPPath.better(p.BGPPath) {
continue
}
if c.LocalPref > b.LocalPref {
return -1
}
best = p
continue
}
if c.ASPathLen > b.ASPathLen {
return 1
}
if !active[0].BGPPath.betterECMP(p.BGPPath) {
continue
}
if c.ASPathLen < b.ASPathLen {
return -1
}
active = []*Path{p}
best = p
if c.Origin > b.Origin {
return 1
}
if c.Origin < b.Origin {
return -1
}
return best, active
if c.MED > b.MED {
return 1
}
if c.MED < b.MED {
return -1
}
if c.BGPIdentifier < b.BGPIdentifier {
return 1
}
if c.BGPIdentifier > b.BGPIdentifier {
return -1
}
if c.Source < b.Source {
return 1
}
if c.Source > b.Source {
return -1
}
return 0
}
func (b *BGPPath) betterECMP(c *BGPPath) bool {
......@@ -103,3 +133,34 @@ func (b *BGPPath) better(c *BGPPath) bool {
func (b *BGPPath) ecmp(c *BGPPath) bool {
return b.LocalPref == c.LocalPref && b.ASPathLen == c.ASPathLen && b.Origin == c.Origin && b.MED == c.MED
}
func (b *BGPPath) Print() string {
origin := ""
switch b.Origin {
case 0:
origin = "Incomplete"
case 1:
origin = "EGP"
case 2:
origin = "IGP"
}
ret := fmt.Sprintf("\t\tLocal Pref: %d\n", b.LocalPref)
ret += fmt.Sprintf("\t\tOrigin: %s\n", origin)
ret += fmt.Sprintf("\t\tAS Path: %s\n", b.ASPath)
nh := uint32To4Byte(b.NextHop)
ret += fmt.Sprintf("\t\tNEXT HOP: %d.%d.%d.%d\n", nh[0], nh[1], nh[2], nh[3])
ret += fmt.Sprintf("\t\tMED: %d\n", b.MED)
return ret
}
func uint32To4Byte(addr uint32) [4]byte {
slice := convert.Uint32Byte(addr)
ret := [4]byte{
slice[0],
slice[1],
slice[2],
slice[3],
}
return ret
}
package route
import "fmt"
type Path struct {
Type uint8
StaticPath *StaticPath
BGPPath *BGPPath
}
// Compare returns negative if p < q, 0 if paths are equal, positive if p > q
func (p *Path) Compare(q *Path) int8 {
if p.Type > q.Type {
return 1
}
if p.Type < q.Type {
return -1
}
switch p.Type {
case BGPPathType:
return p.BGPPath.Compare(q.BGPPath)
case StaticPathType:
return p.StaticPath.Compare(q.StaticPath)
}
panic("Unknown path type")
}
func (p *Path) ECMP(q *Path) bool {
switch p.Type {
case BGPPathType:
return p.BGPPath.ECMP(q.BGPPath)
case StaticPathType:
return p.StaticPath.ECMP(q.StaticPath)
}
panic("Unknown path type")
}
func (p *Path) Equal(q *Path) bool {
if p == nil || q == nil {
return false
......@@ -24,3 +57,46 @@ func (p *Path) Equal(q *Path) bool {
return true
}
// PathsDiff gets the list of elements contained by a but not b
func PathsDiff(a, b []*Path) []*Path {
ret := make([]*Path, 0)
for _, pa := range a {
if !pathsContains(pa, b) {
ret = append(ret, pa)
}
}
return ret
}
func pathsContains(needle *Path, haystack []*Path) bool {
for _, p := range haystack {
if p == needle {
return true
}
}
return false
}
func (p *Path) Print() string {
protocol := ""
switch p.Type {
case StaticPathType:
protocol = "static"
case BGPPathType:
protocol = "BGP"
}
ret := fmt.Sprintf("\tProtocol: %s\n", protocol)
switch p.Type {
case StaticPathType:
ret += "Not implemented yet"
case BGPPathType:
ret += p.BGPPath.Print()
}
return ret
}
package route
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestPathsDiff(t *testing.T) {
tests := []struct {
name string
any []*Path
a []int
b []int
expected []*Path
}{
{
name: "Equal",
any: []*Path{
{
Type: 10,
},
{
Type: 20,
},
},
a: []int{
0, 1,
},
b: []int{
0, 1,
},
expected: []*Path{},
},
{
name: "Left empty",
any: []*Path{
{
Type: 10,
},
{
Type: 20,
},
},
a: []int{},
b: []int{
0, 1,
},
expected: []*Path{},
},
{
name: "Right empty",
any: []*Path{
{
Type: 10,
},
{
Type: 20,
},
},
a: []int{0, 1},
b: []int{},
expected: []*Path{
{
Type: 10,
},
{
Type: 20,
},
},
},
{
name: "Disjunkt",
any: []*Path{
{
Type: 10,
},
{
Type: 20,
},
{
Type: 30,
},
{
Type: 40,
},
},
a: []int{0, 1},
b: []int{2, 3},
expected: []*Path{{
Type: 10,
},
{
Type: 20,
}},
},
}
for _, test := range tests {
listA := make([]*Path, 0)
for _, i := range test.a {
listA = append(listA, test.any[i])
}
listB := make([]*Path, 0)
for _, i := range test.b {
listB = append(listB, test.any[i])
}
res := PathsDiff(listA, listB)
assert.Equal(t, test.expected, res)
}
}
func TestPathsContains(t *testing.T) {
tests := []struct {
name string
needle int
haystack []*Path
expected bool
}{
{
name: "Existent",
needle: 0,
haystack: []*Path{
{
Type: 100,
},
{
Type: 200,
},
},
expected: true,
},
{
name: "Non existent",
needle: -1,
haystack: []*Path{
{
Type: 100,
},
{
Type: 200,
},
},
expected: false,
},
}
for _, test := range tests {
var needle *Path
if test.needle >= 0 {
needle = test.haystack[test.needle]
} else {
needle = &Path{}
}
res := pathsContains(needle, test.haystack)
if res != test.expected {
t.Errorf("Unexpected result for test %q: %v", test.name, res)
}
}
}
package route
import (
"fmt"
"sort"
"sync"
"github.com/bio-routing/bio-rd/net"
......@@ -20,15 +22,14 @@ const ISISPathType = 4
// Route links a prefix to paths
type Route struct {
pfx net.Prefix
mu sync.Mutex
bestPath *Path
activePaths []*Path
paths []*Path
pfx net.Prefix
mu sync.Mutex
paths []*Path
ecmpPaths uint
}
// NewRoute generates a new route
func NewRoute(pfx net.Prefix, p *Path) *Route {
// NewRoute generates a new route with paths p
func NewRoute(pfx net.Prefix, p ...*Path) *Route {
r := &Route{
pfx: pfx,
}
......@@ -38,10 +39,21 @@ func NewRoute(pfx net.Prefix, p *Path) *Route {
return r
}
r.paths = []*Path{p}
r.paths = append(r.paths, p...)
return r
}
// Copy returns a copy of route r
func (r *Route) Copy() *Route {
new := &Route{
pfx: r.pfx,
ecmpPaths: r.ecmpPaths,
}
new.paths = make([]*Path, len(r.paths))
copy(new.paths, r.paths)
return new
}
// Prefix gets the prefix of route `r`
func (r *Route) Prefix() net.Prefix {
return r.pfx
......@@ -60,7 +72,7 @@ func (r *Route) Pfxlen() uint8 {
// Paths returns a copy of the list of paths associated with route r
func (r *Route) Paths() []*Path {
if r.paths == nil {
return nil
return []*Path{}
}
ret := make([]*Path, len(r.paths))
......@@ -68,6 +80,34 @@ func (r *Route) Paths() []*Path {
return ret
}
// ECMPPathCount returns the count of ecmp paths for route r
func (r *Route) ECMPPathCount() uint {
return r.ecmpPaths
}
// ECMPPaths returns a copy of the list of paths associated with route r
func (r *Route) ECMPPaths() []*Path {
r.mu.Lock()
defer r.mu.Unlock()
if len(r.paths) == 0 {
return nil
}
ret := make([]*Path, r.ecmpPaths)
copy(ret, r.paths[0:r.ecmpPaths])
return ret
}
// BestPath returns the current best path. nil if non exists
func (r *Route) BestPath() *Path {
if len(r.paths) == 0 {
return nil
}
return r.paths[0]
}
// AddPath adds path p to route r
func (r *Route) AddPath(p *Path) {
if p == nil {
......@@ -109,3 +149,60 @@ func removePath(paths []*Path, remove *Path) []*Path {
copy(paths[i:], paths[i+1:])
return paths[:len(paths)-1]
}
// PathSelection recalculates the best path + active paths
func (r *Route) PathSelection() {
r.mu.Lock()
defer r.mu.Unlock()
sort.Slice(r.paths, func(i, j int) bool {
return r.paths[i].Compare(r.paths[j]) == -1
})
r.updateEqualPathCount()
}
func (r *Route) updateEqualPathCount() {
count := uint(1)
for i := 0; i < len(r.paths)-1; i++ {
if !r.paths[i].ECMP(r.paths[i+1]) {
break
}
count++
}
r.ecmpPaths = count
}
func getBestProtocol(paths []*Path) uint8 {
best := uint8(0)
for _, p := range paths {
if best == 0 {
best = p.Type
continue
}
if p.Type < best {
best = p.Type
}
}
return best
}
// Print returns a prinatble representation of route `r`
func (r *Route) Print() string {
ret := fmt.Sprintf("%s:\n", r.pfx.String())
//ret += fmt.Sprintf("Best path:\n")
//ret += r.bestPath.Print()
/*ret += fmt.Sprintf("Active Paths:\n")
for _, p := range r.activePaths {
ret += p.Print()
}*/
ret += fmt.Sprintf("All Paths:\n")
for _, p := range r.paths {
ret += p.Print()
}
return ret
}
......@@ -262,6 +262,41 @@ func TestRouteRemovePath(t *testing.T) {
}
}
func TestCopy(t *testing.T) {
tests := []struct {
name string
route *Route
expected *Route
}{
{
name: "",
route: &Route{
pfx: net.NewPfx(1000, 8),
ecmpPaths: 2,
paths: []*Path{
{
Type: 100,
},
},
},
expected: &Route{
pfx: net.NewPfx(1000, 8),
ecmpPaths: 2,
paths: []*Path{
{
Type: 100,
},
},
},
},
}
for _, test := range tests {
res := test.route.Copy()
assert.Equal(t, test.expected, res)
}
}
func strAddr(s string) uint32 {
ret, _ := net.StrToAddr(s)
return ret
......
......@@ -5,23 +5,21 @@ type StaticPath struct {
NextHop uint32
}
func (r *Route) staticPathSelection() (best *Path, active []*Path) {
if r.paths == nil {
return nil, nil
}
func (r *Route) staticPathSelection() {
if len(r.paths) == 0 {
return nil, nil
return
}
for _, p := range r.paths {
if p.Type != StaticPathType {
continue
}
r.ecmpPaths = uint(len(r.paths))
return
}
active = append(active, p)
best = p
}
// Compare returns negative if s < t, 0 if paths are equal, positive if s > t
func (s *StaticPath) Compare(t *StaticPath) int8 {
return 0
}
return
// ECMP determines if path s and t are equal in terms of ECMP
func (s *StaticPath) ECMP(t *StaticPath) bool {
return true
}
......@@ -10,13 +10,13 @@ import (
// AdjRIBIn represents an Adjacency RIB In as described in RFC4271
type AdjRIBIn struct {
rt *routingtable.RoutingTable
routingtable.ClientManager
rt *routingtable.RoutingTable
mu sync.RWMutex
}
// NewAdjRIBIn creates a new Adjacency RIB In
func NewAdjRIBIn() *AdjRIBIn {
// New creates a new Adjacency RIB In
func New() *AdjRIBIn {
a := &AdjRIBIn{
rt: routingtable.NewRoutingTable(),
}
......@@ -79,3 +79,7 @@ func (a *AdjRIBIn) removePathsFromClients(pfx net.Prefix, paths []*route.Path) {
}
}
}
func (a *AdjRIBIn) RT() *routingtable.RoutingTable {
return a.rt
}
package adjRIBOut
import (
"fmt"
"sync"
"github.com/bio-routing/bio-rd/net"
"github.com/bio-routing/bio-rd/route"
"github.com/bio-routing/bio-rd/routingtable"
)
// AdjRIBOut represents an Adjacency RIB In as described in RFC4271
type AdjRIBOut struct {
rt *routingtable.RoutingTable
routingtable.ClientManager
mu sync.RWMutex
}
// New creates a new Adjacency RIB In
func New() *AdjRIBOut {
a := &AdjRIBOut{
rt: routingtable.NewRoutingTable(),
}
a.ClientManager = routingtable.NewClientManager(a)
return a
}
// UpdateNewClient sends current state to a new client
func (a *AdjRIBOut) UpdateNewClient(client routingtable.RouteTableClient) error {
return fmt.Errorf("Not supported")
}
// AddPath replaces the path for prefix `pfx`. If the prefix doesn't exist it is added.
func (a *AdjRIBOut) 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 *AdjRIBOut) 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 *AdjRIBOut) removePathsFromClients(pfx net.Prefix, paths []*route.Path) {
for _, path := range paths {
for _, client := range a.ClientManager.Clients() {
client.RemovePath(pfx, path)
}
}
}
func (a *AdjRIBOut) Print() string {
a.mu.RLock()
defer a.mu.RUnlock()
ret := fmt.Sprintf("DUMPING ADJ-RIB-OUT:\n")
routes := a.rt.Dump()
for _, r := range routes {
ret += fmt.Sprintf("%s\n", r.Prefix().String())
}
return ret
}
package routingtable
import (
"sync"
)
// ClientOptions represents options for a client
type ClientOptions struct {
BestOnly bool
EcmpOnly bool
MaxPaths uint
}
// GetMaxPaths calculates the maximum amount of wanted paths given that ecmpPaths paths exist
func (c *ClientOptions) GetMaxPaths(ecmpPaths uint) uint {
if c.BestOnly {
return 1
}
if c.EcmpOnly {
return ecmpPaths
}
return c.MaxPaths
}
// ClientManager manages clients of routing tables (observer pattern)
type ClientManager struct {
clients map[RouteTableClient]struct{} // Ensures a client registers at most once
clients map[RouteTableClient]ClientOptions
master RouteTableClient
mu sync.RWMutex
}
// NewClientManager creates and initializes a new client manager
func NewClientManager(master RouteTableClient) ClientManager {
return ClientManager{
clients: make(map[RouteTableClient]struct{}, 0),
clients: make(map[RouteTableClient]ClientOptions, 0),
master: master,
}
}
// GetOptions gets the options for a registred client
func (c *ClientManager) GetOptions(client RouteTableClient) ClientOptions {
c.mu.RLock()
defer c.mu.RUnlock()
return c.clients[client]
}
// 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.RegisterWithOptions(client, ClientOptions{BestOnly: true})
}
// RegisterWithOptions registers a client with options for updates
func (c *ClientManager) RegisterWithOptions(client RouteTableClient, opt ClientOptions) {
c.mu.Lock()
defer c.mu.Unlock()
c.clients[client] = opt
c.master.UpdateNewClient(client)
}
......
......@@ -64,3 +64,42 @@ func TestClients(t *testing.T) {
assert.Equal(t, test.expected, ret)
}
}
func TestGetMaxPaths(t *testing.T) {
tests := []struct {
name string
clientOptions ClientOptions
ecmpPaths uint
expected uint
}{
{
name: "Test #1",
clientOptions: ClientOptions{
BestOnly: true,
},
ecmpPaths: 8,
expected: 1,
},
{
name: "Test #2",
clientOptions: ClientOptions{
EcmpOnly: true,
},
ecmpPaths: 8,
expected: 8,
},
{
name: "Test #3",
clientOptions: ClientOptions{
MaxPaths: 100,
},
ecmpPaths: 10,
expected: 100,
},
}
for _, test := range tests {
res := test.clientOptions.GetMaxPaths(test.ecmpPaths)
assert.Equal(t, test.expected, res)
}
}
package locRIB
import (
"fmt"
"math"
"sync"
"github.com/bio-routing/bio-rd/net"
"github.com/bio-routing/bio-rd/route"
"github.com/bio-routing/bio-rd/routingtable"
)
// LocRIB represents a routing information base
type LocRIB struct {
routingtable.ClientManager
rt *routingtable.RoutingTable
mu sync.RWMutex
}
// New creates a new routing information base
func New() *LocRIB {
a := &LocRIB{
rt: routingtable.NewRoutingTable(),
}
a.ClientManager = routingtable.NewClientManager(a)
return a
}
// UpdateNewClient sends current state to a new client
func (a *LocRIB) UpdateNewClient(client routingtable.RouteTableClient) error {
a.mu.RLock()
defer a.mu.RUnlock()
routes := a.rt.Dump()
for _, r := range routes {
a.propagateChanges(&route.Route{}, r)
}
return nil
}
// AddPath replaces the path for prefix `pfx`. If the prefix doesn't exist it is added.
func (a *LocRIB) AddPath(pfx net.Prefix, p *route.Path) error {
a.mu.Lock()
defer a.mu.Unlock()
routeExisted := false
oldRoute := &route.Route{}
r := a.rt.Get(pfx)
if r != nil {
oldRoute = r.Copy()
routeExisted = true
}
a.rt.AddPath(pfx, p)
if !routeExisted {
r = a.rt.Get(pfx)
}
r.PathSelection()
newRoute := r.Copy()
fmt.Printf("NEW: %v\n", newRoute.Paths())
fmt.Printf("OLD: %v\n", oldRoute.Paths())
a.propagateChanges(oldRoute, newRoute)
return nil
}
// RemovePath removes the path for prefix `pfx`
func (a *LocRIB) RemovePath(pfx net.Prefix, p *route.Path) bool {
a.mu.Lock()
defer a.mu.Unlock()
var oldRoute *route.Route
r := a.rt.Get(pfx)
if r != nil {
oldRoute = r.Copy()
}
a.rt.RemovePath(pfx, p)
r.PathSelection()
r = a.rt.Get(pfx)
newRoute := r.Copy()
fmt.Printf("NEW: %v\n", newRoute.Paths())
fmt.Printf("OLD: %v\n", oldRoute.Paths())
a.propagateChanges(oldRoute, newRoute)
return true
}
func (a *LocRIB) propagateChanges(oldRoute *route.Route, newRoute *route.Route) {
a.removePathsFromClients(oldRoute, newRoute)
a.addPathsToClients(oldRoute, newRoute)
}
func (a *LocRIB) addPathsToClients(oldRoute *route.Route, newRoute *route.Route) {
for _, client := range a.ClientManager.Clients() {
opts := a.ClientManager.GetOptions(client)
oldMaxPaths := opts.GetMaxPaths(oldRoute.ECMPPathCount())
newMaxPaths := opts.GetMaxPaths(newRoute.ECMPPathCount())
oldPathsLimit := int(math.Min(float64(oldMaxPaths), float64(len(oldRoute.Paths()))))
newPathsLimit := int(math.Min(float64(newMaxPaths), float64(len(newRoute.Paths()))))
advertise := route.PathsDiff(newRoute.Paths()[0:newPathsLimit], oldRoute.Paths()[0:oldPathsLimit])
fmt.Printf("ADVERTISING PATHS %v TO CLIENTS\n", advertise)
for _, p := range advertise {
client.AddPath(newRoute.Prefix(), p)
}
}
}
func (a *LocRIB) removePathsFromClients(oldRoute *route.Route, newRoute *route.Route) {
for _, client := range a.ClientManager.Clients() {
opts := a.ClientManager.GetOptions(client)
oldMaxPaths := opts.GetMaxPaths(oldRoute.ECMPPathCount())
newMaxPaths := opts.GetMaxPaths(newRoute.ECMPPathCount())
oldPathsLimit := int(math.Min(float64(oldMaxPaths), float64(len(oldRoute.Paths()))))
newPathsLimit := int(math.Min(float64(newMaxPaths), float64(len(newRoute.Paths()))))
withdraw := route.PathsDiff(oldRoute.Paths()[0:oldPathsLimit], newRoute.Paths()[0:newPathsLimit])
for _, p := range withdraw {
client.RemovePath(newRoute.Prefix(), p)
}
}
}
func (a *LocRIB) Print() string {
a.mu.RLock()
defer a.mu.RUnlock()
ret := "Loc-RIB DUMP:\n"
routes := a.rt.Dump()
for _, r := range routes {
ret += fmt.Sprintf("%s\n", r.Prefix().String())
}
return ret
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment