Merge branch 'release/4.0.2'

This commit is contained in:
Matthias Hannig 2019-09-09 14:57:49 +02:00
commit c403436dcc
No known key found for this signature in database
GPG Key ID: 62E226E47DDCE58D
20 changed files with 4238 additions and 27 deletions

View File

@ -1,6 +1,10 @@
# Changelog
## 4.0.2 (2019-09-09)
* Fixed issue with multitable bird: `getMasterPipeName` returned incorrect
pipe.
## 4.0.1 (2019-03-07)

View File

@ -38,6 +38,9 @@ client_prod:
backend_dev: client_dev
$(MAKE) -C backend/
dev:
$(MAKE) -C backend/ osx-dev
backend_prod: client_prod
$(MAKE) -C backend/ bundle

View File

@ -1 +1 @@
4.0.0
4.0.2

View File

@ -21,27 +21,31 @@ endif
LDFLAGS=-ldflags="-X main.version=$(APP_VERSION)"
FILES=$(shell find . -depth 1 ! -name "*_test.go" -name "*.go")
all: $(TARGET)
@echo "Built $(VERSION) @ $(TARGET)"
deps:
go get -v .
GO111MODULE=on go get -v .
osx-dev: deps
GO111MODULE=on go run $(FILES)
osx: deps
GOARCH=$(ARCH) GOOS=darwin go build $(LDFLAGS) -o $(PROG)-osx-$(ARCH)
GO111MODULE=on GOARCH=$(ARCH) GOOS=darwin go build $(LDFLAGS) -o $(PROG)-osx-$(ARCH)
linux: deps
GOARCH=$(ARCH) GOOS=linux go build $(LDFLAGS) -o $(PROG)-linux-$(ARCH)
GO111MODULE=on GOARCH=$(ARCH) GOOS=linux go build $(LDFLAGS) -o $(PROG)-linux-$(ARCH)
bundle:
rice embed-go
test:
go test -v
cd api/ && go test -v
cd caches/ && go test -v
cd sources/birdwatcher && go test -v
GO111MODULE=on go test -v
cd api/ && GO111MODULE=on go test -v
cd caches/ && GO111MODULE=on go test -v
cd sources/birdwatcher && GO111MODULE=on go test -v
dev: clean all

View File

@ -8,12 +8,14 @@ import (
"github.com/alice-lg/alice-lg/backend/sources"
"github.com/alice-lg/alice-lg/backend/sources/birdwatcher"
"github.com/alice-lg/alice-lg/backend/sources/gobgp"
"github.com/go-ini/ini"
)
const SOURCE_UNKNOWN = 0
const SOURCE_BIRDWATCHER = 1
const SOURCE_GOBGP = 2
type ServerConfig struct {
Listen string `ini:"listen_http"`
@ -96,6 +98,7 @@ type SourceConfig struct {
// Source configurations
Type int
Birdwatcher birdwatcher.Config
GoBGP gobgp.Config
// Source instance
instance sources.Source
@ -151,6 +154,8 @@ func getBackendType(section *ini.Section) int {
name := section.Name()
if strings.HasSuffix(name, "birdwatcher") {
return SOURCE_BIRDWATCHER
} else if strings.HasSuffix(name, "gobgp") {
return SOURCE_GOBGP
}
return SOURCE_UNKNOWN
@ -633,6 +638,15 @@ func getSources(config *ini.File) ([]*SourceConfig, error) {
backendConfig.MapTo(&c)
config.Birdwatcher = c
case SOURCE_GOBGP:
c := gobgp.Config{
Id: config.Id,
Name: config.Name,
}
backendConfig.MapTo(&c)
config.GoBGP = c
}
// Add to list of sources
@ -711,6 +725,8 @@ func (self *SourceConfig) getInstance() sources.Source {
switch self.Type {
case SOURCE_BIRDWATCHER:
instance = birdwatcher.NewBirdwatcher(self.Birdwatcher)
case SOURCE_GOBGP:
instance = gobgp.NewGoBGP(self.GoBGP)
}
self.instance = instance

View File

@ -7,6 +7,7 @@ import (
"log"
"sort"
"strconv"
"strings"
"time"
"github.com/alice-lg/alice-lg/backend/api"
@ -177,14 +178,14 @@ func parseNeighbours(bird ClientResponse, config Config) (api.Neighbours, error)
Address: mustString(protocol["neighbor_address"], "error"),
Asn: mustInt(protocol["neighbor_as"], 0),
State: mustString(protocol["state"], "unknown"),
State: strings.ToLower(mustString(protocol["state"], "unknown")),
Description: mustString(protocol["description"], "no description"),
//TODO make these changes configurable
RoutesReceived: mustInt(routesReceived, 0),
RoutesAccepted: mustInt(routes["imported"], 0),
RoutesFiltered: mustInt(routes["filtered"], 0),
RoutesExported: mustInt(routes["exported"], 0), //TODO protocol_exported?
RoutesPreferred: mustInt(routes["preferred"], 0),
RoutesReceived: mustInt(routesReceived, 0),
RoutesAccepted: mustInt(routes["imported"], 0),
RoutesFiltered: mustInt(routes["filtered"], 0),
RoutesExported: mustInt(routes["exported"], 0), //TODO protocol_exported?
RoutesPreferred: mustInt(routes["preferred"], 0),
Uptime: uptime,
LastError: lastError,
@ -212,9 +213,9 @@ func parseNeighboursShort(bird ClientResponse, config Config) (api.NeighboursSta
uptime := parseRelativeServerTime(protocol["since"], config)
neighbour := &api.NeighbourStatus{
Id: protocolId,
State: mustString(protocol["state"], "unknown"),
Since: uptime,
Id: protocolId,
State: mustString(protocol["state"], "unknown"),
Since: uptime,
}
neighbours = append(neighbours, neighbour)

View File

@ -6,19 +6,18 @@ import (
"strings"
"fmt"
"sort"
"log"
"sort"
)
type MultiTableBirdwatcher struct {
GenericBirdwatcher
}
func (self *MultiTableBirdwatcher) getMasterPipeName(table string) string {
if strings.HasPrefix(table, self.config.PeerTablePrefix) {
return self.config.PipeProtocolPrefix + table[1:]
ptPrefix := self.config.PeerTablePrefix
if strings.HasPrefix(table, ptPrefix) {
return self.config.PipeProtocolPrefix + table[len(ptPrefix):]
} else {
return ""
}
@ -51,7 +50,6 @@ func (self *MultiTableBirdwatcher) parseProtocolToTableTree(bird ClientResponse)
return response
}
func (self *MultiTableBirdwatcher) fetchProtocols() (*api.ApiStatus, map[string]interface{}, error) {
// Query birdwatcher
bird, err := self.client.GetJson("/protocols")
@ -259,7 +257,6 @@ func (self *MultiTableBirdwatcher) fetchRequiredRoutes(neighborId string) (*api.
return response, nil
}
// Get neighbors from protocols
func (self *MultiTableBirdwatcher) Neighbours() (*api.NeighboursResponse, error) {
// Check if we hit the cache
@ -324,7 +321,7 @@ func (self *MultiTableBirdwatcher) Neighbours() (*api.NeighboursResponse, error)
if len(tree[table].(map[string]interface{})) == 1 {
// Single router
for _, protocol := range tree[table].(map[string]interface{}) {
filtered[protocol.(map[string]interface{})["protocol"].(string)] = int(allRoutesImported-pipeRoutesImported)
filtered[protocol.(map[string]interface{})["protocol"].(string)] = int(allRoutesImported - pipeRoutesImported)
}
} else {
// Multiple routers
@ -497,7 +494,7 @@ func (self *MultiTableBirdwatcher) AllRoutes() (*api.RoutesResponse, error) {
}
response := &api.RoutesResponse{
Api: apiStatus,
Api: apiStatus,
}
// Parse the routes

View File

@ -0,0 +1,25 @@
package birdwatcher
import (
"testing"
)
func TestGetMasterPipeName(t *testing.T) {
config := Config{
PipeProtocolPrefix: "pp",
PeerTablePrefix: "pb",
}
bw := &MultiTableBirdwatcher{
GenericBirdwatcher: GenericBirdwatcher{
config: config,
},
}
peerProto := "pb_0200_as123456"
expected := "pp_0200_as123456"
if res := bw.getMasterPipeName(peerProto); res != expected {
t.Error("Expected:", peerProto, "but got:", res)
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,246 @@
// Copyright (C) 2018 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiutil
import (
"fmt"
proto "github.com/golang/protobuf/proto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
api "github.com/osrg/gobgp/api"
"github.com/osrg/gobgp/pkg/packet/bgp"
)
func NewMultiProtocolCapability(a *bgp.CapMultiProtocol) *api.MultiProtocolCapability {
afi, safi := bgp.RouteFamilyToAfiSafi(a.CapValue)
return &api.MultiProtocolCapability{
Family: ToApiFamily(afi, safi),
}
}
func NewRouteRefreshCapability(a *bgp.CapRouteRefresh) *api.RouteRefreshCapability {
return &api.RouteRefreshCapability{}
}
func NewCarryingLabelInfoCapability(a *bgp.CapCarryingLabelInfo) *api.CarryingLabelInfoCapability {
return &api.CarryingLabelInfoCapability{}
}
func NewExtendedNexthopCapability(a *bgp.CapExtendedNexthop) *api.ExtendedNexthopCapability {
tuples := make([]*api.ExtendedNexthopCapabilityTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
tuples = append(tuples, &api.ExtendedNexthopCapabilityTuple{
NlriFamily: ToApiFamily(t.NLRIAFI, uint8(t.NLRISAFI)),
NexthopFamily: ToApiFamily(t.NexthopAFI, bgp.SAFI_UNICAST),
})
}
return &api.ExtendedNexthopCapability{
Tuples: tuples,
}
}
func NewGracefulRestartCapability(a *bgp.CapGracefulRestart) *api.GracefulRestartCapability {
tuples := make([]*api.GracefulRestartCapabilityTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
tuples = append(tuples, &api.GracefulRestartCapabilityTuple{
Family: ToApiFamily(t.AFI, t.SAFI),
Flags: uint32(t.Flags),
})
}
return &api.GracefulRestartCapability{
Flags: uint32(a.Flags),
Time: uint32(a.Time),
Tuples: tuples,
}
}
func NewFourOctetASNumberCapability(a *bgp.CapFourOctetASNumber) *api.FourOctetASNumberCapability {
return &api.FourOctetASNumberCapability{
As: a.CapValue,
}
}
func NewAddPathCapability(a *bgp.CapAddPath) *api.AddPathCapability {
tuples := make([]*api.AddPathCapabilityTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
afi, safi := bgp.RouteFamilyToAfiSafi(t.RouteFamily)
tuples = append(tuples, &api.AddPathCapabilityTuple{
Family: ToApiFamily(afi, safi),
Mode: api.AddPathMode(t.Mode),
})
}
return &api.AddPathCapability{
Tuples: tuples,
}
}
func NewEnhancedRouteRefreshCapability(a *bgp.CapEnhancedRouteRefresh) *api.EnhancedRouteRefreshCapability {
return &api.EnhancedRouteRefreshCapability{}
}
func NewLongLivedGracefulRestartCapability(a *bgp.CapLongLivedGracefulRestart) *api.LongLivedGracefulRestartCapability {
tuples := make([]*api.LongLivedGracefulRestartCapabilityTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
tuples = append(tuples, &api.LongLivedGracefulRestartCapabilityTuple{
Family: ToApiFamily(t.AFI, uint8(t.SAFI)),
Flags: uint32(t.Flags),
Time: t.RestartTime,
})
}
return &api.LongLivedGracefulRestartCapability{
Tuples: tuples,
}
}
func NewRouteRefreshCiscoCapability(a *bgp.CapRouteRefreshCisco) *api.RouteRefreshCiscoCapability {
return &api.RouteRefreshCiscoCapability{}
}
func NewUnknownCapability(a *bgp.CapUnknown) *api.UnknownCapability {
return &api.UnknownCapability{
Code: uint32(a.CapCode),
Value: a.CapValue,
}
}
func MarshalCapability(value bgp.ParameterCapabilityInterface) (*any.Any, error) {
var m proto.Message
switch n := value.(type) {
case *bgp.CapMultiProtocol:
m = NewMultiProtocolCapability(n)
case *bgp.CapRouteRefresh:
m = NewRouteRefreshCapability(n)
case *bgp.CapCarryingLabelInfo:
m = NewCarryingLabelInfoCapability(n)
case *bgp.CapExtendedNexthop:
m = NewExtendedNexthopCapability(n)
case *bgp.CapGracefulRestart:
m = NewGracefulRestartCapability(n)
case *bgp.CapFourOctetASNumber:
m = NewFourOctetASNumberCapability(n)
case *bgp.CapAddPath:
m = NewAddPathCapability(n)
case *bgp.CapEnhancedRouteRefresh:
m = NewEnhancedRouteRefreshCapability(n)
case *bgp.CapLongLivedGracefulRestart:
m = NewLongLivedGracefulRestartCapability(n)
case *bgp.CapRouteRefreshCisco:
m = NewRouteRefreshCiscoCapability(n)
case *bgp.CapUnknown:
m = NewUnknownCapability(n)
default:
return nil, fmt.Errorf("invalid capability type to marshal: %+v", value)
}
return ptypes.MarshalAny(m)
}
func MarshalCapabilities(values []bgp.ParameterCapabilityInterface) ([]*any.Any, error) {
caps := make([]*any.Any, 0, len(values))
for _, value := range values {
a, err := MarshalCapability(value)
if err != nil {
return nil, err
}
caps = append(caps, a)
}
return caps, nil
}
func unmarshalCapability(a *any.Any) (bgp.ParameterCapabilityInterface, error) {
var value ptypes.DynamicAny
if err := ptypes.UnmarshalAny(a, &value); err != nil {
return nil, fmt.Errorf("failed to unmarshal capability: %s", err)
}
switch a := value.Message.(type) {
case *api.MultiProtocolCapability:
return bgp.NewCapMultiProtocol(ToRouteFamily(a.Family)), nil
case *api.RouteRefreshCapability:
return bgp.NewCapRouteRefresh(), nil
case *api.CarryingLabelInfoCapability:
return bgp.NewCapCarryingLabelInfo(), nil
case *api.ExtendedNexthopCapability:
tuples := make([]*bgp.CapExtendedNexthopTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
var nhAfi uint16
switch t.NexthopFamily.Afi {
case api.Family_AFI_IP:
nhAfi = bgp.AFI_IP
case api.Family_AFI_IP6:
nhAfi = bgp.AFI_IP6
default:
return nil, fmt.Errorf("invalid address family for nexthop afi in extended nexthop capability: %s", t.NexthopFamily)
}
tuples = append(tuples, bgp.NewCapExtendedNexthopTuple(ToRouteFamily(t.NlriFamily), nhAfi))
}
return bgp.NewCapExtendedNexthop(tuples), nil
case *api.GracefulRestartCapability:
tuples := make([]*bgp.CapGracefulRestartTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
var forward bool
if t.Flags&0x80 > 0 {
forward = true
}
tuples = append(tuples, bgp.NewCapGracefulRestartTuple(ToRouteFamily(t.Family), forward))
}
var restarting bool
if a.Flags&0x08 > 0 {
restarting = true
}
var notification bool
if a.Flags&0x04 > 0 {
notification = true
}
return bgp.NewCapGracefulRestart(restarting, notification, uint16(a.Time), tuples), nil
case *api.FourOctetASNumberCapability:
return bgp.NewCapFourOctetASNumber(a.As), nil
case *api.AddPathCapability:
tuples := make([]*bgp.CapAddPathTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
tuples = append(tuples, bgp.NewCapAddPathTuple(ToRouteFamily(t.Family), bgp.BGPAddPathMode(t.Mode)))
}
return bgp.NewCapAddPath(tuples), nil
case *api.EnhancedRouteRefreshCapability:
return bgp.NewCapEnhancedRouteRefresh(), nil
case *api.LongLivedGracefulRestartCapability:
tuples := make([]*bgp.CapLongLivedGracefulRestartTuple, 0, len(a.Tuples))
for _, t := range a.Tuples {
var forward bool
if t.Flags&0x80 > 0 {
forward = true
}
tuples = append(tuples, bgp.NewCapLongLivedGracefulRestartTuple(ToRouteFamily(t.Family), forward, t.Time))
}
return bgp.NewCapLongLivedGracefulRestart(tuples), nil
case *api.RouteRefreshCiscoCapability:
return bgp.NewCapRouteRefreshCisco(), nil
case *api.UnknownCapability:
return bgp.NewCapUnknown(bgp.BGPCapabilityCode(a.Code), a.Value), nil
}
return nil, fmt.Errorf("invalid capability type to unmarshal: %s", a.TypeUrl)
}
func UnmarshalCapabilities(values []*any.Any) ([]bgp.ParameterCapabilityInterface, error) {
caps := make([]bgp.ParameterCapabilityInterface, 0, len(values))
for _, value := range values {
c, err := unmarshalCapability(value)
if err != nil {
return nil, err
}
caps = append(caps, c)
}
return caps, nil
}

View File

@ -0,0 +1,269 @@
// Copyright (C) 2018 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiutil
import (
"testing"
"github.com/golang/protobuf/ptypes"
api "github.com/osrg/gobgp/api"
"github.com/osrg/gobgp/pkg/packet/bgp"
"github.com/stretchr/testify/assert"
)
func Test_MultiProtocolCapability(t *testing.T) {
assert := assert.New(t)
input := &api.MultiProtocolCapability{
Family: &api.Family{
Afi: api.Family_AFI_IP,
Safi: api.Family_SAFI_UNICAST,
},
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapMultiProtocol)
assert.Equal(bgp.RF_IPv4_UC, c.CapValue)
output := NewMultiProtocolCapability(c)
assert.Equal(input, output)
}
func Test_RouteRefreshCapability(t *testing.T) {
assert := assert.New(t)
input := &api.RouteRefreshCapability{}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
output := NewRouteRefreshCapability(n.(*bgp.CapRouteRefresh))
assert.Equal(input, output)
}
func Test_CarryingLabelInfoCapability(t *testing.T) {
assert := assert.New(t)
input := &api.CarryingLabelInfoCapability{}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
output := NewCarryingLabelInfoCapability(n.(*bgp.CapCarryingLabelInfo))
assert.Equal(input, output)
}
func Test_ExtendedNexthopCapability(t *testing.T) {
assert := assert.New(t)
input := &api.ExtendedNexthopCapability{
Tuples: []*api.ExtendedNexthopCapabilityTuple{
{
NlriFamily: &api.Family{
Afi: api.Family_AFI_IP,
Safi: api.Family_SAFI_UNICAST,
},
NexthopFamily: &api.Family{
Afi: api.Family_AFI_IP6,
Safi: api.Family_SAFI_UNICAST,
},
},
},
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapExtendedNexthop)
assert.Equal(1, len(c.Tuples))
assert.Equal(uint16(bgp.AFI_IP), c.Tuples[0].NLRIAFI)
assert.Equal(uint16(bgp.SAFI_UNICAST), c.Tuples[0].NLRISAFI)
assert.Equal(uint16(bgp.AFI_IP6), c.Tuples[0].NexthopAFI)
output := NewExtendedNexthopCapability(c)
assert.Equal(input, output)
}
func Test_GracefulRestartCapability(t *testing.T) {
assert := assert.New(t)
input := &api.GracefulRestartCapability{
Flags: 0x08 | 0x04, // restarting|notification
Time: 90,
Tuples: []*api.GracefulRestartCapabilityTuple{
{
Family: &api.Family{
Afi: api.Family_AFI_IP,
Safi: api.Family_SAFI_UNICAST,
},
Flags: 0x80, // forward
},
},
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapGracefulRestart)
assert.Equal(1, len(c.Tuples))
assert.Equal(uint8(0x08|0x04), c.Flags)
assert.Equal(uint16(90), c.Time)
assert.Equal(uint16(bgp.AFI_IP), c.Tuples[0].AFI)
assert.Equal(uint8(bgp.SAFI_UNICAST), c.Tuples[0].SAFI)
assert.Equal(uint8(0x80), c.Tuples[0].Flags)
output := NewGracefulRestartCapability(c)
assert.Equal(input, output)
}
func Test_FourOctetASNumberCapability(t *testing.T) {
assert := assert.New(t)
input := &api.FourOctetASNumberCapability{
As: 100,
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapFourOctetASNumber)
assert.Equal(uint32(100), c.CapValue)
output := NewFourOctetASNumberCapability(c)
assert.Equal(input, output)
}
func Test_AddPathCapability(t *testing.T) {
assert := assert.New(t)
input := &api.AddPathCapability{
Tuples: []*api.AddPathCapabilityTuple{
{
Family: &api.Family{
Afi: api.Family_AFI_IP,
Safi: api.Family_SAFI_UNICAST,
},
Mode: api.AddPathMode_MODE_BOTH,
},
},
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapAddPath)
assert.Equal(1, len(c.Tuples))
assert.Equal(bgp.RF_IPv4_UC, c.Tuples[0].RouteFamily)
assert.Equal(bgp.BGP_ADD_PATH_BOTH, c.Tuples[0].Mode)
output := NewAddPathCapability(c)
assert.Equal(input, output)
}
func Test_EnhancedRouteRefreshCapability(t *testing.T) {
assert := assert.New(t)
input := &api.EnhancedRouteRefreshCapability{}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
output := NewEnhancedRouteRefreshCapability(n.(*bgp.CapEnhancedRouteRefresh))
assert.Equal(input, output)
}
func Test_LongLivedGracefulRestartCapability(t *testing.T) {
assert := assert.New(t)
input := &api.LongLivedGracefulRestartCapability{
Tuples: []*api.LongLivedGracefulRestartCapabilityTuple{
{
Family: &api.Family{
Afi: api.Family_AFI_IP,
Safi: api.Family_SAFI_UNICAST,
},
Flags: 0x80, // forward
Time: 90,
},
},
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapLongLivedGracefulRestart)
assert.Equal(1, len(c.Tuples))
assert.Equal(uint16(bgp.AFI_IP), c.Tuples[0].AFI)
assert.Equal(uint8(bgp.SAFI_UNICAST), c.Tuples[0].SAFI)
assert.Equal(uint8(0x80), c.Tuples[0].Flags)
assert.Equal(uint32(90), c.Tuples[0].RestartTime)
output := NewLongLivedGracefulRestartCapability(c)
assert.Equal(input, output)
}
func Test_RouteRefreshCiscoCapability(t *testing.T) {
assert := assert.New(t)
input := &api.RouteRefreshCiscoCapability{}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
output := NewRouteRefreshCiscoCapability(n.(*bgp.CapRouteRefreshCisco))
assert.Equal(input, output)
}
func Test_UnknownCapability(t *testing.T) {
assert := assert.New(t)
input := &api.UnknownCapability{
Code: 0xff,
Value: []byte{0x11, 0x22, 0x33, 0x44},
}
a, err := ptypes.MarshalAny(input)
assert.Nil(err)
n, err := unmarshalCapability(a)
assert.Nil(err)
c := n.(*bgp.CapUnknown)
assert.Equal(bgp.BGPCapabilityCode(0xff), c.CapCode)
assert.Equal([]byte{0x11, 0x22, 0x33, 0x44}, c.CapValue)
output := NewUnknownCapability(c)
assert.Equal(input, output)
}

View File

@ -0,0 +1,128 @@
// Copyright (C) 2016 Nippon Telegraph and Telephone Corporation.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
// implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package apiutil
import (
"encoding/json"
"net"
"time"
"github.com/golang/protobuf/ptypes"
api "github.com/osrg/gobgp/api"
"github.com/osrg/gobgp/pkg/packet/bgp"
)
// workaround. This for the json format compatibility. Once we update senario tests, we can remove this.
type Path struct {
Nlri bgp.AddrPrefixInterface `json:"nlri"`
Age int64 `json:"age"`
Best bool `json:"best"`
Attrs []bgp.PathAttributeInterface `json:"attrs"`
Stale bool `json:"stale"`
Withdrawal bool `json:"withdrawal,omitempty"`
SourceID net.IP `json:"source-id,omitempty"`
NeighborIP net.IP `json:"neighbor-ip,omitempty"`
}
type Destination struct {
Paths []*Path
}
func (d *Destination) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Paths)
}
func NewDestination(dst *api.Destination) *Destination {
l := make([]*Path, 0, len(dst.Paths))
for _, p := range dst.Paths {
nlri, _ := GetNativeNlri(p)
attrs, _ := GetNativePathAttributes(p)
t, _ := ptypes.Timestamp(p.Age)
l = append(l, &Path{
Nlri: nlri,
Age: t.Unix(),
Best: p.Best,
Attrs: attrs,
Stale: p.Stale,
Withdrawal: p.IsWithdraw,
SourceID: net.ParseIP(p.SourceId),
NeighborIP: net.ParseIP(p.NeighborIp),
})
}
return &Destination{Paths: l}
}
func NewPath(nlri bgp.AddrPrefixInterface, isWithdraw bool, attrs []bgp.PathAttributeInterface, age time.Time) *api.Path {
t, _ := ptypes.TimestampProto(age)
return &api.Path{
Nlri: MarshalNLRI(nlri),
Pattrs: MarshalPathAttributes(attrs),
Age: t,
IsWithdraw: isWithdraw,
Family: ToApiFamily(nlri.AFI(), nlri.SAFI()),
Identifier: nlri.PathIdentifier(),
}
}
func getNLRI(family bgp.RouteFamily, buf []byte) (bgp.AddrPrefixInterface, error) {
afi, safi := bgp.RouteFamilyToAfiSafi(family)
nlri, err := bgp.NewPrefixFromRouteFamily(afi, safi)
if err != nil {
return nil, err
}
if err := nlri.DecodeFromBytes(buf); err != nil {
return nil, err
}
return nlri, nil
}
func GetNativeNlri(p *api.Path) (bgp.AddrPrefixInterface, error) {
if len(p.NlriBinary) > 0 {
return getNLRI(ToRouteFamily(p.Family), p.NlriBinary)
}
return UnmarshalNLRI(ToRouteFamily(p.Family), p.Nlri)
}
func GetNativePathAttributes(p *api.Path) ([]bgp.PathAttributeInterface, error) {
pattrsLen := len(p.PattrsBinary)
if pattrsLen > 0 {
pattrs := make([]bgp.PathAttributeInterface, 0, pattrsLen)
for _, attr := range p.PattrsBinary {
a, err := bgp.GetPathAttribute(attr)
if err != nil {
return nil, err
}
err = a.DecodeFromBytes(attr)
if err != nil {
return nil, err
}
pattrs = append(pattrs, a)
}
return pattrs, nil
}
return UnmarshalPathAttributes(p.Pattrs)
}
func ToRouteFamily(f *api.Family) bgp.RouteFamily {
return bgp.AfiSafiToRouteFamily(uint16(f.Afi), uint8(f.Safi))
}
func ToApiFamily(afi uint16, safi uint8) *api.Family {
return &api.Family{
Afi: api.Family_Afi(afi),
Safi: api.Family_Safi(safi),
}
}

View File

@ -0,0 +1,11 @@
package gobgp
type Config struct {
Id string
Name string
Host string `ini:"host"`
Insecure bool `ini:"insecure"`
TLSCert string `ini:"tls_crt"`
TLSCommonName string `ini:"tls_common_name"`
}

View File

@ -0,0 +1,194 @@
package gobgp
import (
"github.com/alice-lg/alice-lg/backend/sources/gobgp/apiutil"
"github.com/osrg/gobgp/pkg/packet/bgp"
"github.com/alice-lg/alice-lg/backend/api"
gobgpapi "github.com/osrg/gobgp/api"
"context"
"fmt"
"io"
"log"
"time"
)
var families []gobgpapi.Family = []gobgpapi.Family{gobgpapi.Family{
Afi: gobgpapi.Family_AFI_IP,
Safi: gobgpapi.Family_SAFI_UNICAST,
}, gobgpapi.Family{
Afi: gobgpapi.Family_AFI_IP6,
Safi: gobgpapi.Family_SAFI_UNICAST,
},
}
func NewRoutesResponse() api.RoutesResponse {
routes := api.RoutesResponse{}
routes.Imported = make(api.Routes, 0)
routes.Filtered = make(api.Routes, 0)
routes.NotExported = make(api.Routes, 0)
return routes
}
func (gobgp *GoBGP) lookupNeighbour(neighborId string) (*gobgpapi.Peer, error) {
peers, err := gobgp.GetNeighbours()
if err != nil {
return nil, err
}
for _, peer := range peers {
peerId := PeerHash(peer)
if neighborId == "" || peerId == neighborId {
return peer, nil
}
}
return nil, fmt.Errorf("Could not lookup neighbour")
}
func (gobgp *GoBGP) GetNeighbours() ([]*gobgpapi.Peer, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
peerStream, err := gobgp.client.ListPeer(ctx, &gobgpapi.ListPeerRequest{EnableAdvertised: true})
if err != nil {
return nil, err
}
peers := make([]*gobgpapi.Peer, 0)
for {
peer, err := peerStream.Recv()
if err == io.EOF {
break
}
peers = append(peers, peer.Peer)
}
return peers, nil
}
func (gobgp *GoBGP) parsePathIntoRoute(path *gobgpapi.Path, prefix string) (error, *api.Route) {
route := api.Route{}
route.Id = fmt.Sprintf("%s_%s", path.SourceId, prefix)
route.NeighbourId = PeerHashWithASAndAddress(path.SourceAsn, path.NeighborIp)
route.Network = prefix
route.Interface = "Unknown"
route.Age = time.Now().Sub(time.Unix(path.Age.GetSeconds(), int64(path.Age.GetNanos())))
route.Primary = path.Best
attrs, err := apiutil.GetNativePathAttributes(path)
if err != nil {
return err, nil
}
route.Bgp.Communities = make(api.Communities, 0)
route.Bgp.LargeCommunities = make(api.Communities, 0)
route.Bgp.ExtCommunities = make(api.ExtCommunities, 0)
for _, attr := range attrs {
switch attr.(type) {
case *bgp.PathAttributeMultiExitDisc:
med := attr.(*bgp.PathAttributeMultiExitDisc)
route.Bgp.Med = int(med.Value)
case *bgp.PathAttributeNextHop:
nh := attr.(*bgp.PathAttributeNextHop)
route.Gateway = nh.Value.String()
route.Bgp.NextHop = nh.Value.String()
case *bgp.PathAttributeLocalPref:
lp := attr.(*bgp.PathAttributeLocalPref)
route.Bgp.LocalPref = int(lp.Value)
case *bgp.PathAttributeOrigin:
origin := attr.(*bgp.PathAttributeOrigin)
switch origin.Value {
case bgp.BGP_ORIGIN_ATTR_TYPE_IGP:
route.Bgp.Origin = "IGP"
case bgp.BGP_ORIGIN_ATTR_TYPE_EGP:
route.Bgp.Origin = "EGP"
case bgp.BGP_ORIGIN_ATTR_TYPE_INCOMPLETE:
route.Bgp.Origin = "Incomplete"
}
case *bgp.PathAttributeAsPath:
aspath := attr.(*bgp.PathAttributeAsPath)
for _, aspth := range aspath.Value {
for _, as := range aspth.GetAS() {
route.Bgp.AsPath = append(route.Bgp.AsPath, int(as))
}
}
case *bgp.PathAttributeCommunities:
communities := attr.(*bgp.PathAttributeCommunities)
for _, community := range communities.Value {
_community := api.Community{int((0xffff0000 & community) >> 16), int(0xffff & community)}
route.Bgp.Communities = append(route.Bgp.Communities, _community)
}
case *bgp.PathAttributeExtendedCommunities:
communities := attr.(*bgp.PathAttributeExtendedCommunities)
for _, community := range communities.Value {
if _community, ok := community.(*bgp.TwoOctetAsSpecificExtended); ok {
route.Bgp.ExtCommunities = append(route.Bgp.ExtCommunities, api.ExtCommunity{_community.AS, _community.LocalAdmin})
}
}
case *bgp.PathAttributeLargeCommunities:
communities := attr.(*bgp.PathAttributeLargeCommunities)
for _, community := range communities.Values {
route.Bgp.LargeCommunities = append(route.Bgp.LargeCommunities, api.Community{int(community.ASN), int(community.LocalData1), int(community.LocalData2)})
}
}
}
route.Metric = (route.Bgp.LocalPref + route.Bgp.Med)
return nil, &route
}
func (gobgp *GoBGP) GetRoutes(peer *gobgpapi.Peer, tableType gobgpapi.TableType, response *api.RoutesResponse) error {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
for _, family := range families {
pathStream, err := gobgp.client.ListPath(ctx, &gobgpapi.ListPathRequest{
Name: peer.State.NeighborAddress,
TableType: tableType,
Family: &family,
EnableFiltered: true,
})
if err != nil {
log.Print(err)
continue
}
rib := make([]*gobgpapi.Destination, 0)
for {
_path, err := pathStream.Recv()
if err == io.EOF {
break
} else if err != nil {
log.Print(err)
return err
}
rib = append(rib, _path.Destination)
}
for _, destination := range rib {
for _, path := range destination.Paths {
err, route := gobgp.parsePathIntoRoute(path, destination.Prefix)
if err != nil {
log.Println(err)
continue
}
if path.Filtered {
response.Filtered = append(response.Filtered, route)
} else {
response.Imported = append(response.Imported, route)
}
}
}
}
return nil
}

View File

@ -0,0 +1,310 @@
package gobgp
import (
api "github.com/alice-lg/alice-lg/backend/api"
"github.com/alice-lg/alice-lg/backend/caches"
gobgpapi "github.com/osrg/gobgp/api"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc"
"context"
"fmt"
"io"
"log"
"time"
)
type GoBGP struct {
config Config
client gobgpapi.GobgpApiClient
// Caches: Neighbors
neighborsCache *caches.NeighborsCache
// Caches: Routes
routesRequiredCache *caches.RoutesCache
routesReceivedCache *caches.RoutesCache
routesFilteredCache *caches.RoutesCache
routesNotExportedCache *caches.RoutesCache
}
func NewGoBGP(config Config) *GoBGP {
dialOpts := make([]grpc.DialOption, 0)
if config.Insecure {
dialOpts = append(dialOpts, grpc.WithInsecure())
} else {
creds, err := credentials.NewClientTLSFromFile(config.TLSCert, config.TLSCommonName)
if err != nil {
log.Fatalf("could not load tls cert: %s", err)
}
dialOpts = append(dialOpts, grpc.WithTransportCredentials(creds))
}
conn, err := grpc.Dial(config.Host, dialOpts...)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
client := gobgpapi.NewGobgpApiClient(conn)
// Cache settings:
// TODO: Maybe read from config file
neighborsCacheDisable := false
routesCacheDisabled := false
routesCacheMaxSize := 128
// Initialize caches
neighborsCache := caches.NewNeighborsCache(neighborsCacheDisable)
routesRequiredCache := caches.NewRoutesCache(
routesCacheDisabled, routesCacheMaxSize)
routesReceivedCache := caches.NewRoutesCache(
routesCacheDisabled, routesCacheMaxSize)
routesFilteredCache := caches.NewRoutesCache(
routesCacheDisabled, routesCacheMaxSize)
routesNotExportedCache := caches.NewRoutesCache(
routesCacheDisabled, routesCacheMaxSize)
return &GoBGP{
config: config,
client: client,
neighborsCache: neighborsCache,
routesRequiredCache: routesRequiredCache,
routesReceivedCache: routesReceivedCache,
routesFilteredCache: routesFilteredCache,
routesNotExportedCache: routesNotExportedCache,
}
}
func (gobgp *GoBGP) ExpireCaches() int {
count := gobgp.routesRequiredCache.Expire()
count += gobgp.routesNotExportedCache.Expire()
return count
}
func (gobgp *GoBGP) NeighboursStatus() (*api.NeighboursStatusResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
response := api.NeighboursStatusResponse{}
response.Neighbours = make(api.NeighboursStatus, 0)
resp, err := gobgp.client.ListPeer(ctx, &gobgpapi.ListPeerRequest{})
if err != nil {
return nil, err
}
for {
_resp, err := resp.Recv()
if err == io.EOF {
break
}
ns := api.NeighbourStatus{}
ns.Id = PeerHash(_resp.Peer)
switch _resp.Peer.State.SessionState {
case gobgpapi.PeerState_ESTABLISHED:
ns.State = "up"
default:
ns.State = "down"
}
if _resp.Peer.Timers.State.Uptime != nil {
ns.Since = time.Now().Sub(time.Unix(_resp.Peer.Timers.State.Uptime.Seconds, int64(_resp.Peer.Timers.State.Uptime.Nanos)))
}
}
return &response, nil
}
func (gobgp *GoBGP) Status() (*api.StatusResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
resp, err := gobgp.client.GetBgp(ctx, &gobgpapi.GetBgpRequest{})
if err != nil {
return nil, err
}
response := api.StatusResponse{}
response.Status.RouterId = resp.Global.RouterId
response.Status.Backend = "gobgp"
return &response, nil
}
func (gobgp *GoBGP) Neighbours() (*api.NeighboursResponse, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
response := api.NeighboursResponse{}
response.Neighbours = make(api.Neighbours, 0)
resp, err := gobgp.client.ListPeer(ctx, &gobgpapi.ListPeerRequest{EnableAdvertised: true})
if err != nil {
return nil, err
}
for {
_resp, err := resp.Recv()
if err == io.EOF {
break
}
neigh := api.Neighbour{}
neigh.Address = _resp.Peer.State.NeighborAddress
neigh.Asn = int(_resp.Peer.State.PeerAs)
switch _resp.Peer.State.SessionState {
case gobgpapi.PeerState_ESTABLISHED:
neigh.State = "up"
default:
neigh.State = "down"
}
neigh.Description = _resp.Peer.Conf.Description
neigh.Id = PeerHash(_resp.Peer)
response.Neighbours = append(response.Neighbours, &neigh)
for _, afiSafi := range _resp.Peer.AfiSafis {
neigh.RoutesReceived += int(afiSafi.State.Received)
neigh.RoutesExported += int(afiSafi.State.Advertised)
neigh.RoutesAccepted += int(afiSafi.State.Accepted)
neigh.RoutesFiltered += (neigh.RoutesReceived - neigh.RoutesAccepted)
}
if _resp.Peer.Timers.State.Uptime != nil {
neigh.Uptime = time.Now().Sub(time.Unix(_resp.Peer.Timers.State.Uptime.Seconds, int64(_resp.Peer.Timers.State.Uptime.Nanos)))
}
}
return &response, nil
}
// Get neighbors from neighbors summary
func (gobgp *GoBGP) summaryNeighbors() (*api.NeighboursResponse, error) {
return nil, fmt.Errorf("Not implemented summaryNeighbors")
}
// Get neighbors from protocols
func (gobgp *GoBGP) bgpProtocolsNeighbors() (*api.NeighboursResponse, error) {
return nil, fmt.Errorf("Not implemented protocols")
}
// Get filtered and exported routes
func (gobgp *GoBGP) Routes(neighbourId string) (*api.RoutesResponse, error) {
neigh, err := gobgp.lookupNeighbour(neighbourId)
if err != nil {
return nil, err
}
routes := NewRoutesResponse()
err = gobgp.GetRoutes(neigh, gobgpapi.TableType_ADJ_IN, &routes)
if err != nil {
return nil, err
}
return &routes, nil
}
/*
RoutesRequired is a specialized request to fetch:
- RoutesExported and
- RoutesFiltered
from Birdwatcher. As the not exported routes can be very many
these are optional and can be loaded on demand using the
RoutesNotExported() API.
A route deduplication is applied.
*/
func (gobgp *GoBGP) getRoutes(neighbourId string) (*api.RoutesResponse, error) {
neigh, err := gobgp.lookupNeighbour(neighbourId)
if err != nil {
return nil, err
}
routes := NewRoutesResponse()
err = gobgp.GetRoutes(neigh, gobgpapi.TableType_ADJ_IN, &routes)
if err != nil {
return nil, err
}
return &routes, nil
}
func (gobgp *GoBGP) RoutesRequired(neighbourId string) (*api.RoutesResponse, error) {
return gobgp.getRoutes(neighbourId)
}
// Get all received routes
func (gobgp *GoBGP) RoutesReceived(neighbourId string) (*api.RoutesResponse, error) {
neigh, err := gobgp.lookupNeighbour(neighbourId)
if err != nil {
return nil, err
}
routes := NewRoutesResponse()
err = gobgp.GetRoutes(neigh, gobgpapi.TableType_ADJ_IN, &routes)
if err != nil {
return nil, err
}
routes.Filtered = nil
return &routes, nil
}
// Get all filtered routes
func (gobgp *GoBGP) RoutesFiltered(neighbourId string) (*api.RoutesResponse, error) {
routes, err := gobgp.getRoutes(neighbourId)
if err != nil {
log.Print(err)
}
routes.Imported = nil
return routes, err
}
// Get all not exported routes
func (gobgp *GoBGP) RoutesNotExported(neighbourId string) (*api.RoutesResponse, error) {
neigh, err := gobgp.lookupNeighbour(neighbourId)
if err != nil {
return nil, err
}
routes := NewRoutesResponse()
err = gobgp.GetRoutes(neigh, gobgpapi.TableType_ADJ_OUT, &routes)
if err != nil {
return nil, err
}
routes.NotExported = routes.Filtered
return &routes, nil
}
// Make routes lookup
func (gobgp *GoBGP) LookupPrefix(prefix string) (*api.RoutesLookupResponse, error) {
return nil, fmt.Errorf("Not implemented LookupPrefix")
}
/*
AllRoutes:
Here a routes dump (filtered, received) is returned, which is used to learn all prefixes to build up a local store for searching.
*/
func (gobgp *GoBGP) AllRoutes() (*api.RoutesResponse, error) {
routes := NewRoutesResponse()
peers, err := gobgp.GetNeighbours()
if err != nil {
return nil, err
}
for _, peer := range peers {
err = gobgp.GetRoutes(peer, gobgpapi.TableType_ADJ_IN, &routes)
if err != nil {
log.Print(err)
}
}
return &routes, nil
}

View File

@ -0,0 +1,24 @@
package gobgp
import (
// Standard imports
"crypto/sha1"
"fmt"
"io"
// External imports
api "github.com/osrg/gobgp/api"
// Internal imports
)
func PeerHash(peer *api.Peer) string {
return PeerHashWithASAndAddress(peer.State.PeerAs, peer.State.NeighborAddress)
}
func PeerHashWithASAndAddress(asn uint32, address string) string {
h := sha1.New()
io.WriteString(h, string(asn))
io.WriteString(h, address)
sum := h.Sum(nil)
return fmt.Sprintf("%x", sum[0:5])
}

View File

@ -24,7 +24,7 @@ class Status extends React.Component {
if (rsStatus) {
statusInfo.push(
<div className="bird-version" key="status-version">
Bird {rsStatus.version}
{rsStatus.backend} {rsStatus.version}
</div>
);
}

15
go.mod Normal file
View File

@ -0,0 +1,15 @@
module github.com/alice-lg/alice-lg
require (
github.com/GeertJohan/go.rice v0.0.0-20181229193832-0af3f3b09a0a
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb // indirect
github.com/go-ini/ini v1.41.0
github.com/golang/protobuf v1.2.0
github.com/julienschmidt/httprouter v1.2.0
github.com/osrg/gobgp v0.0.0-20190502094614-fd6618fed499
github.com/sirupsen/logrus v1.3.0
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a // indirect
github.com/stretchr/testify v1.2.2
google.golang.org/grpc v1.17.0
gopkg.in/ini.v1 v1.42.0 // indirect
)

107
go.sum Normal file
View File

@ -0,0 +1,107 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.0/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/go.rice v0.0.0-20181229193832-0af3f3b09a0a h1:QgnJzkfb29JXtLXJN8alxzPWZhiNcAYZOa06dU5O46w=
github.com/GeertJohan/go.rice v0.0.0-20181229193832-0af3f3b09a0a/go.mod h1:DgrzXonpdQbfN3uYaGz1EG4Sbhyum/MMIn6Cphlh2bw=
github.com/armon/go-radix v0.0.0-20170727155443-1fca145dffbc/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb h1:tUf55Po0vzOendQ7NWytcdK0VuzQmfAgvGBUOQvN0WA=
github.com/daaku/go.zipexe v0.0.0-20150329023125-a5fe2436ffcb/go.mod h1:U0vRfAucUOohvdCxt5MWLF+TePIL0xbCkbKIiV8TQCE=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-farm v0.0.0-20171119141306-ac7624ea8da3/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/eapache/channels v1.1.0/go.mod h1:jMm2qB5Ubtg9zLd+inMZd2/NUvXgzmWXsDaLyQIGfH0=
github.com/eapache/queue v1.0.2/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I=
github.com/fsnotify/fsnotify v1.4.2/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/go-ini/ini v1.41.0 h1:526aoxDtxRHFQKMZfcX2OG9oOI8TJ5yPLM0Mkno/uTY=
github.com/go-ini/ini v1.41.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/protobuf v1.0.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/hashicorp/hcl v0.0.0-20170509225359-392dba7d905e/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jessevdk/go-flags v1.3.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
github.com/julienschmidt/httprouter v1.2.0 h1:TDTW5Yz1mjftljbcKqRcrYhd4XeOoI98t+9HbQbYf7g=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.0.0-20160823170715-cfb55aafdaf3/go.mod h1:Bvhd+E3laJ0AVkG0c9rmtZcnhV0HQ3+c3YxxqTvc/gA=
github.com/kr/text v0.0.0-20160504234017-7cafcd837844/go.mod h1:sjUstKUATFIcff4qlB53Kml0wQPtJVc/3fWrmuUmcfA=
github.com/magiconair/properties v1.7.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mitchellh/mapstructure v0.0.0-20170523030023-d0303fe80992/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/osrg/gobgp v0.0.0-20190502094614-fd6618fed499 h1:uukk7LjpCIRDOnLORZG8m39q9y47SNsi56w0oUj3Xrg=
github.com/osrg/gobgp v0.0.0-20190502094614-fd6618fed499/go.mod h1:ORFhbKMbE5PuTrFOETR32zPLBMJUGIP1uMOqVyEhTAU=
github.com/osrg/gobgp v2.0.0+incompatible h1:91ARQbE1AtO0U4TIxHPJ7wYVZIqduyBwS1+FjlHlmrY=
github.com/osrg/gobgp v2.0.0+incompatible/go.mod h1:vGVJPLW6JFDD7WA1vJsjB8OKmbbC2TKwHtr90CZS/u4=
github.com/pelletier/go-buffruneio v0.2.0/go.mod h1:JkE26KsDizTr40EUHkXVtNPvgGtbSNq5BcowyYOWdKo=
github.com/pelletier/go-toml v1.0.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/satori/go.uuid v0.0.0-20180103174451-36e9d2ebbde5/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/sirupsen/logrus v0.0.0-20170713114250-a3f95b5c4235/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a h1:pa8hGb/2YqsZKovtsgrwcDH1RZhVbTKCjLp47XpqCDs=
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
github.com/spf13/afero v0.0.0-20170217164146-9be650865eab/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/cast v1.1.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cobra v0.0.0-20170731170427-b26b538f6930/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/jwalterweatherman v0.0.0-20170523133247-0efa5202c046/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.0.0/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/vishvananda/netlink v0.0.0-20170802012344-a95659537721/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netns v0.0.0-20170707011535-86bef332bfc3/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d h1:g9qWBGx4puODJTMVyoPrpoxPFgVGd+z1DZwjfRu4d0I=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3 h1:0GoQqolDA55aaLxZyTzK/Y2ePZzZTUrRacwib7cNsYQ=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522 h1:Ve1ORMCxvRmSXBwJK+t3Oy+V2vRW2OetUQBq4rJIkZE=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67 h1:1Fzlr8kkDLQwqMP8GxrhptBLqZG/EDpiATneiZHY998=
golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/genproto v0.0.0-20170731182057-09f6ed296fc6/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/grpc v1.5.1/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
google.golang.org/grpc v1.17.0 h1:TRJYBgMclJvGYn2rIMjj+h9KtMt5r1Ij7ODVRIZkwhk=
google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/ini.v1 v1.42.0 h1:7N3gPTt50s8GuLortA00n8AqRTk75qOP98+mTPpgzRk=
gopkg.in/ini.v1 v1.42.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
gopkg.in/yaml.v2 v2.0.0-20170721122051-25c4ec802a7d/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=