2017-04-17 10:33:09 +05:30

2090 lines
62 KiB
Go

// Copyright (C) 2014-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 gobgpapi
import (
"fmt"
"io"
"net"
"reflect"
"regexp"
"strconv"
"strings"
"sync"
"time"
log "github.com/Sirupsen/logrus"
"github.com/osrg/gobgp/config"
"github.com/osrg/gobgp/packet/bgp"
"github.com/osrg/gobgp/server"
"github.com/osrg/gobgp/table"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
type Server struct {
bgpServer *server.BgpServer
grpcServer *grpc.Server
hosts string
}
func NewGrpcServer(b *server.BgpServer, hosts string) *Server {
return NewServer(b, grpc.NewServer(), hosts)
}
func NewServer(b *server.BgpServer, g *grpc.Server, hosts string) *Server {
grpc.EnableTracing = false
server := &Server{
bgpServer: b,
grpcServer: g,
hosts: hosts,
}
RegisterGobgpApiServer(g, server)
return server
}
func (s *Server) Serve() error {
var wg sync.WaitGroup
l := strings.Split(s.hosts, ",")
wg.Add(len(l))
serve := func(host string) {
for {
defer wg.Done()
lis, err := net.Listen("tcp", fmt.Sprintf(host))
if err != nil {
log.WithFields(log.Fields{
"Topic": "grpc",
"Key": host,
"Error": err,
}).Warn("listen failed")
return
}
err = s.grpcServer.Serve(lis)
log.WithFields(log.Fields{
"Topic": "grpc",
"Key": host,
"Error": err,
}).Warn("accept failed")
}
}
for _, host := range l {
go serve(host)
}
wg.Wait()
return nil
}
func NewPeerFromConfigStruct(pconf *config.Neighbor) *Peer {
var families []uint32
for _, f := range pconf.AfiSafis {
if family, ok := bgp.AddressFamilyValueMap[string(f.Config.AfiSafiName)]; ok {
families = append(families, uint32(family))
}
}
applyPolicy := &ApplyPolicy{}
if len(pconf.ApplyPolicy.Config.ImportPolicyList) != 0 {
applyPolicy.ImportPolicy = &PolicyAssignment{
Type: PolicyType_IMPORT,
}
for _, pname := range pconf.ApplyPolicy.Config.ImportPolicyList {
applyPolicy.ImportPolicy.Policies = append(applyPolicy.ImportPolicy.Policies, &Policy{Name: pname})
}
}
if len(pconf.ApplyPolicy.Config.ExportPolicyList) != 0 {
applyPolicy.ExportPolicy = &PolicyAssignment{
Type: PolicyType_EXPORT,
}
for _, pname := range pconf.ApplyPolicy.Config.ExportPolicyList {
applyPolicy.ExportPolicy.Policies = append(applyPolicy.ExportPolicy.Policies, &Policy{Name: pname})
}
}
if len(pconf.ApplyPolicy.Config.InPolicyList) != 0 {
applyPolicy.InPolicy = &PolicyAssignment{
Type: PolicyType_IN,
}
for _, pname := range pconf.ApplyPolicy.Config.InPolicyList {
applyPolicy.InPolicy.Policies = append(applyPolicy.InPolicy.Policies, &Policy{Name: pname})
}
}
prefixLimits := make([]*PrefixLimit, 0, len(pconf.AfiSafis))
for _, family := range pconf.AfiSafis {
if c := family.PrefixLimit.Config; c.MaxPrefixes > 0 {
k, _ := bgp.GetRouteFamily(string(family.Config.AfiSafiName))
prefixLimits = append(prefixLimits, &PrefixLimit{
Family: uint32(k),
MaxPrefixes: c.MaxPrefixes,
ShutdownThresholdPct: uint32(c.ShutdownThresholdPct),
})
}
}
timer := pconf.Timers
s := pconf.State
localAddress := pconf.Transport.Config.LocalAddress
if pconf.Transport.State.LocalAddress != "" {
localAddress = pconf.Transport.State.LocalAddress
}
var remoteCap, localCap [][]byte
for _, cap := range pconf.State.RemoteCapabilityList {
c, _ := cap.Serialize()
remoteCap = append(remoteCap, c)
}
for _, cap := range pconf.State.LocalCapabilityList {
c, _ := cap.Serialize()
localCap = append(localCap, c)
}
return &Peer{
Families: families,
ApplyPolicy: applyPolicy,
Conf: &PeerConf{
NeighborAddress: pconf.Config.NeighborAddress,
Id: s.RemoteRouterId,
PeerAs: pconf.Config.PeerAs,
LocalAs: pconf.Config.LocalAs,
PeerType: uint32(pconf.Config.PeerType.ToInt()),
AuthPassword: pconf.Config.AuthPassword,
RemovePrivateAs: uint32(pconf.Config.RemovePrivateAs.ToInt()),
RouteFlapDamping: pconf.Config.RouteFlapDamping,
SendCommunity: uint32(pconf.Config.SendCommunity.ToInt()),
Description: pconf.Config.Description,
PeerGroup: pconf.Config.PeerGroup,
RemoteCap: remoteCap,
LocalCap: localCap,
PrefixLimits: prefixLimits,
LocalAddress: localAddress,
NeighborInterface: pconf.Config.NeighborInterface,
Vrf: pconf.Config.Vrf,
},
Info: &PeerState{
BgpState: string(s.SessionState),
AdminState: PeerState_AdminState(s.AdminState.ToInt()),
Messages: &Messages{
Received: &Message{
NOTIFICATION: s.Messages.Received.Notification,
UPDATE: s.Messages.Received.Update,
OPEN: s.Messages.Received.Open,
KEEPALIVE: s.Messages.Received.Keepalive,
REFRESH: s.Messages.Received.Refresh,
DISCARDED: s.Messages.Received.Discarded,
TOTAL: s.Messages.Received.Total,
},
Sent: &Message{
NOTIFICATION: s.Messages.Sent.Notification,
UPDATE: s.Messages.Sent.Update,
OPEN: s.Messages.Sent.Open,
KEEPALIVE: s.Messages.Sent.Keepalive,
REFRESH: s.Messages.Sent.Refresh,
DISCARDED: s.Messages.Sent.Discarded,
TOTAL: s.Messages.Sent.Total,
},
},
Received: s.AdjTable.Received,
Accepted: s.AdjTable.Accepted,
Advertised: s.AdjTable.Advertised,
PeerAs: s.PeerAs,
PeerType: uint32(s.PeerType.ToInt()),
},
Timers: &Timers{
Config: &TimersConfig{
ConnectRetry: uint64(timer.Config.ConnectRetry),
HoldTime: uint64(timer.Config.HoldTime),
KeepaliveInterval: uint64(timer.Config.KeepaliveInterval),
},
State: &TimersState{
KeepaliveInterval: uint64(timer.State.KeepaliveInterval),
NegotiatedHoldTime: uint64(timer.State.NegotiatedHoldTime),
Uptime: uint64(timer.State.Uptime),
Downtime: uint64(timer.State.Downtime),
},
},
RouteReflector: &RouteReflector{
RouteReflectorClient: pconf.RouteReflector.Config.RouteReflectorClient,
RouteReflectorClusterId: string(pconf.RouteReflector.Config.RouteReflectorClusterId),
},
RouteServer: &RouteServer{
RouteServerClient: pconf.RouteServer.Config.RouteServerClient,
},
Transport: &Transport{
RemotePort: uint32(pconf.Transport.Config.RemotePort),
LocalAddress: pconf.Transport.Config.LocalAddress,
},
}
}
func (s *Server) GetNeighbor(ctx context.Context, arg *GetNeighborRequest) (*GetNeighborResponse, error) {
if arg == nil {
return nil, fmt.Errorf("invalid request")
}
p := []*Peer{}
for _, e := range s.bgpServer.GetNeighbor(arg.EnableAdvertised) {
p = append(p, NewPeerFromConfigStruct(e))
}
return &GetNeighborResponse{Peers: p}, nil
}
func ToPathApi(path *table.Path) *Path {
nlri := path.GetNlri()
n, _ := nlri.Serialize()
family := uint32(bgp.AfiSafiToRouteFamily(nlri.AFI(), nlri.SAFI()))
pattrs := func(arg []bgp.PathAttributeInterface) [][]byte {
ret := make([][]byte, 0, len(arg))
for _, a := range arg {
aa, _ := a.Serialize()
ret = append(ret, aa)
}
return ret
}(path.GetPathAttrs())
p := &Path{
Nlri: n,
Pattrs: pattrs,
Age: path.GetTimestamp().Unix(),
IsWithdraw: path.IsWithdraw,
Validation: int32(path.Validation().ToInt()),
Filtered: path.Filtered("") == table.POLICY_DIRECTION_IN,
Family: family,
Stale: path.IsStale(),
IsFromExternal: path.IsFromExternal(),
NoImplicitWithdraw: path.NoImplicitWithdraw(),
Uuid: path.UUID().Bytes(),
IsNexthopInvalid: path.IsNexthopInvalid,
}
if s := path.GetSource(); s != nil {
p.SourceAsn = s.AS
p.SourceId = s.ID.String()
p.NeighborIp = s.Address.String()
}
return p
}
func (s *Server) GetRib(ctx context.Context, arg *GetRibRequest) (*GetRibResponse, error) {
if arg == nil || arg.Table == nil {
return nil, fmt.Errorf("invalid request")
}
f := func() []*table.LookupPrefix {
l := make([]*table.LookupPrefix, 0, len(arg.Table.Destinations))
for _, p := range arg.Table.Destinations {
l = append(l, &table.LookupPrefix{
Prefix: p.Prefix,
LookupOption: func() table.LookupOption {
if p.LongerPrefixes {
return table.LOOKUP_LONGER
} else if p.ShorterPrefixes {
return table.LOOKUP_SHORTER
}
return table.LOOKUP_EXACT
}(),
})
}
return l
}
var in bool
var err error
var tbl *table.Table
family := bgp.RouteFamily(arg.Table.Family)
switch arg.Table.Type {
case Resource_LOCAL, Resource_GLOBAL:
tbl, err = s.bgpServer.GetRib(arg.Table.Name, family, f())
case Resource_ADJ_IN:
in = true
fallthrough
case Resource_ADJ_OUT:
tbl, err = s.bgpServer.GetAdjRib(arg.Table.Name, family, in, f())
case Resource_VRF:
tbl, err = s.bgpServer.GetVrfRib(arg.Table.Name, family, []*table.LookupPrefix{})
default:
return nil, fmt.Errorf("unsupported resource type: %v", arg.Table.Type)
}
if err != nil {
return nil, err
}
dsts := []*Destination{}
for _, dst := range tbl.GetDestinations() {
dsts = append(dsts, &Destination{
Prefix: dst.GetNlri().String(),
Paths: func(paths []*table.Path) []*Path {
l := make([]*Path, 0, len(paths))
for i, p := range paths {
pp := ToPathApi(p)
switch arg.Table.Type {
case Resource_LOCAL, Resource_GLOBAL:
if i == 0 {
pp.Best = true
}
}
l = append(l, pp)
}
return l
}(dst.GetAllKnownPathList()),
})
}
return &GetRibResponse{Table: &Table{
Type: arg.Table.Type,
Family: uint32(tbl.GetRoutefamily()),
Destinations: dsts},
}, err
}
func (s *Server) MonitorRib(arg *MonitorRibRequest, stream GobgpApi_MonitorRibServer) error {
if arg == nil || arg.Table == nil {
return fmt.Errorf("invalid request")
}
t := arg.Table
w, err := func() (*server.Watcher, error) {
switch t.Type {
case Resource_GLOBAL:
return s.bgpServer.Watch(server.WatchBestPath(arg.Current)), nil
case Resource_ADJ_IN:
if t.PostPolicy {
return s.bgpServer.Watch(server.WatchPostUpdate(arg.Current)), nil
}
return s.bgpServer.Watch(server.WatchUpdate(arg.Current)), nil
default:
return nil, fmt.Errorf("unsupported resource type: %v", t.Type)
}
}()
if err != nil {
return nil
}
return func() error {
defer func() { w.Stop() }()
sendPath := func(pathList []*table.Path) error {
dsts := make(map[string]*Destination)
for _, path := range pathList {
if path == nil || (t.Family != 0 && bgp.RouteFamily(t.Family) != path.GetRouteFamily()) {
continue
}
if dst, y := dsts[path.GetNlri().String()]; y {
dst.Paths = append(dst.Paths, ToPathApi(path))
} else {
dsts[path.GetNlri().String()] = &Destination{
Prefix: path.GetNlri().String(),
Paths: []*Path{ToPathApi(path)},
}
}
}
for _, dst := range dsts {
if err := stream.Send(dst); err != nil {
return err
}
}
return nil
}
for {
select {
case ev := <-w.Event():
switch msg := ev.(type) {
case *server.WatchEventBestPath:
if err := sendPath(func() []*table.Path {
if len(msg.MultiPathList) > 0 {
l := make([]*table.Path, 0)
for _, p := range msg.MultiPathList {
l = append(l, p...)
}
return l
} else {
return msg.PathList
}
}()); err != nil {
return err
}
case *server.WatchEventUpdate:
if err := sendPath(msg.PathList); err != nil {
return err
}
}
}
}
}()
}
func (s *Server) MonitorPeerState(arg *Arguments, stream GobgpApi_MonitorPeerStateServer) error {
if arg == nil {
return fmt.Errorf("invalid request")
}
return func() error {
w := s.bgpServer.Watch(server.WatchPeerState(false))
defer func() { w.Stop() }()
for {
select {
case ev := <-w.Event():
switch msg := ev.(type) {
case *server.WatchEventPeerState:
if len(arg.Name) > 0 && arg.Name != msg.PeerAddress.String() && arg.Name != msg.PeerInterface {
continue
}
if err := stream.Send(&Peer{
Conf: &PeerConf{
PeerAs: msg.PeerAS,
LocalAs: msg.LocalAS,
NeighborAddress: msg.PeerAddress.String(),
Id: msg.PeerID.String(),
NeighborInterface: msg.PeerInterface,
},
Info: &PeerState{
PeerAs: msg.PeerAS,
LocalAs: msg.LocalAS,
NeighborAddress: msg.PeerAddress.String(),
BgpState: msg.State.String(),
AdminState: PeerState_AdminState(msg.AdminState),
},
Transport: &Transport{
LocalAddress: msg.LocalAddress.String(),
LocalPort: uint32(msg.LocalPort),
RemotePort: uint32(msg.PeerPort),
},
}); err != nil {
return err
}
}
}
}
}()
}
func (s *Server) ResetNeighbor(ctx context.Context, arg *ResetNeighborRequest) (*ResetNeighborResponse, error) {
return &ResetNeighborResponse{}, s.bgpServer.ResetNeighbor(arg.Address, arg.Communication)
}
func (s *Server) SoftResetNeighbor(ctx context.Context, arg *SoftResetNeighborRequest) (*SoftResetNeighborResponse, error) {
var err error
addr := arg.Address
if addr == "all" {
addr = ""
}
family := bgp.RouteFamily(0)
switch arg.Direction {
case SoftResetNeighborRequest_IN:
err = s.bgpServer.SoftResetIn(addr, family)
case SoftResetNeighborRequest_OUT:
err = s.bgpServer.SoftResetOut(addr, family)
default:
err = s.bgpServer.SoftReset(addr, family)
}
return &SoftResetNeighborResponse{}, err
}
func (s *Server) ShutdownNeighbor(ctx context.Context, arg *ShutdownNeighborRequest) (*ShutdownNeighborResponse, error) {
return &ShutdownNeighborResponse{}, s.bgpServer.ShutdownNeighbor(arg.Address, arg.Communication)
}
func (s *Server) EnableNeighbor(ctx context.Context, arg *EnableNeighborRequest) (*EnableNeighborResponse, error) {
return &EnableNeighborResponse{}, s.bgpServer.EnableNeighbor(arg.Address)
}
func (s *Server) DisableNeighbor(ctx context.Context, arg *DisableNeighborRequest) (*DisableNeighborResponse, error) {
return &DisableNeighborResponse{}, s.bgpServer.DisableNeighbor(arg.Address, arg.Communication)
}
func (s *Server) api2PathList(resource Resource, ApiPathList []*Path) ([]*table.Path, error) {
var nlri bgp.AddrPrefixInterface
var nexthop string
var pi *table.PeerInfo
var err error
pathList := make([]*table.Path, 0, len(ApiPathList))
for _, path := range ApiPathList {
seen := make(map[bgp.BGPAttrType]bool)
pattr := make([]bgp.PathAttributeInterface, 0)
extcomms := make([]bgp.ExtendedCommunityInterface, 0)
if path.SourceAsn != 0 {
pi = &table.PeerInfo{
AS: path.SourceAsn,
LocalID: net.ParseIP(path.SourceId),
}
}
if len(path.Nlri) > 0 {
afi, safi := bgp.RouteFamilyToAfiSafi(bgp.RouteFamily(path.Family))
if nlri, err = bgp.NewPrefixFromRouteFamily(afi, safi); err != nil {
return nil, err
}
err := nlri.DecodeFromBytes(path.Nlri)
if err != nil {
return nil, err
}
}
for _, attr := range path.Pattrs {
p, err := bgp.GetPathAttribute(attr)
if err != nil {
return nil, err
}
err = p.DecodeFromBytes(attr)
if err != nil {
return nil, err
}
if _, ok := seen[p.GetType()]; !ok {
seen[p.GetType()] = true
} else {
return nil, fmt.Errorf("the path attribute apears twice. Type : " + strconv.Itoa(int(p.GetType())))
}
switch p.GetType() {
case bgp.BGP_ATTR_TYPE_NEXT_HOP:
nexthop = p.(*bgp.PathAttributeNextHop).Value.String()
case bgp.BGP_ATTR_TYPE_EXTENDED_COMMUNITIES:
value := p.(*bgp.PathAttributeExtendedCommunities).Value
if len(value) > 0 {
extcomms = append(extcomms, value...)
}
case bgp.BGP_ATTR_TYPE_MP_REACH_NLRI:
mpreach := p.(*bgp.PathAttributeMpReachNLRI)
if len(mpreach.Value) != 1 {
return nil, fmt.Errorf("include only one route in mp_reach_nlri")
}
nlri = mpreach.Value[0]
nexthop = mpreach.Nexthop.String()
default:
pattr = append(pattr, p)
}
}
if nlri == nil || (!path.IsWithdraw && nexthop == "") {
return nil, fmt.Errorf("not found nlri or nexthop")
}
if resource != Resource_VRF && bgp.RouteFamily(path.Family) == bgp.RF_IPv4_UC {
pattr = append(pattr, bgp.NewPathAttributeNextHop(nexthop))
} else {
pattr = append(pattr, bgp.NewPathAttributeMpReachNLRI(nexthop, []bgp.AddrPrefixInterface{nlri}))
}
if len(extcomms) > 0 {
pattr = append(pattr, bgp.NewPathAttributeExtendedCommunities(extcomms))
}
newPath := table.NewPath(pi, nlri, path.IsWithdraw, pattr, time.Now(), path.NoImplicitWithdraw)
newPath.SetIsFromExternal(path.IsFromExternal)
pathList = append(pathList, newPath)
}
return pathList, nil
}
func (s *Server) AddPath(ctx context.Context, arg *AddPathRequest) (*AddPathResponse, error) {
pathList, err := s.api2PathList(arg.Resource, []*Path{arg.Path})
var uuid []byte
if err == nil {
uuid, err = s.bgpServer.AddPath(arg.VrfId, pathList)
}
return &AddPathResponse{Uuid: uuid}, err
}
func (s *Server) DeletePath(ctx context.Context, arg *DeletePathRequest) (*DeletePathResponse, error) {
pathList, err := func() ([]*table.Path, error) {
if arg.Path != nil {
arg.Path.IsWithdraw = true
return s.api2PathList(arg.Resource, []*Path{arg.Path})
}
return []*table.Path{}, nil
}()
if err != nil {
return nil, err
}
return &DeletePathResponse{}, s.bgpServer.DeletePath(arg.Uuid, bgp.RouteFamily(arg.Family), arg.VrfId, pathList)
}
func (s *Server) EnableMrt(ctx context.Context, arg *EnableMrtRequest) (*EnableMrtResponse, error) {
return &EnableMrtResponse{}, s.bgpServer.EnableMrt(&config.MrtConfig{
RotationInterval: arg.Interval,
DumpType: config.IntToMrtTypeMap[int(arg.DumpType)],
FileName: arg.Filename,
})
}
func (s *Server) DisableMrt(ctx context.Context, arg *DisableMrtRequest) (*DisableMrtResponse, error) {
return &DisableMrtResponse{}, s.bgpServer.DisableMrt(&config.MrtConfig{})
}
func (s *Server) InjectMrt(stream GobgpApi_InjectMrtServer) error {
for {
arg, err := stream.Recv()
if err == io.EOF {
break
} else if err != nil {
return err
}
if arg.Resource != Resource_GLOBAL && arg.Resource != Resource_VRF {
return fmt.Errorf("unsupported resource: %s", arg.Resource)
}
if pathList, err := s.api2PathList(arg.Resource, arg.Paths); err != nil {
return err
} else {
if _, err = s.bgpServer.AddPath("", pathList); err != nil {
return err
}
}
}
return stream.SendAndClose(&InjectMrtResponse{})
}
func (s *Server) AddBmp(ctx context.Context, arg *AddBmpRequest) (*AddBmpResponse, error) {
t, ok := config.IntToBmpRouteMonitoringPolicyTypeMap[int(arg.Type)]
if !ok {
return nil, fmt.Errorf("invalid bmp route monitoring policy: %d", arg.Type)
}
return &AddBmpResponse{}, s.bgpServer.AddBmp(&config.BmpServerConfig{
Address: arg.Address,
Port: arg.Port,
RouteMonitoringPolicy: t,
})
}
func (s *Server) DeleteBmp(ctx context.Context, arg *DeleteBmpRequest) (*DeleteBmpResponse, error) {
return &DeleteBmpResponse{}, s.bgpServer.DeleteBmp(&config.BmpServerConfig{
Address: arg.Address,
Port: arg.Port,
})
}
func (s *Server) ValidateRib(ctx context.Context, arg *ValidateRibRequest) (*ValidateRibResponse, error) {
return &ValidateRibResponse{}, s.bgpServer.ValidateRib(arg.Prefix)
}
func (s *Server) AddRpki(ctx context.Context, arg *AddRpkiRequest) (*AddRpkiResponse, error) {
return &AddRpkiResponse{}, s.bgpServer.AddRpki(&config.RpkiServerConfig{
Address: arg.Address,
Port: arg.Port,
RecordLifetime: arg.Lifetime,
})
}
func (s *Server) DeleteRpki(ctx context.Context, arg *DeleteRpkiRequest) (*DeleteRpkiResponse, error) {
return &DeleteRpkiResponse{}, s.bgpServer.DeleteRpki(&config.RpkiServerConfig{
Address: arg.Address,
Port: arg.Port,
})
}
func (s *Server) EnableRpki(ctx context.Context, arg *EnableRpkiRequest) (*EnableRpkiResponse, error) {
return &EnableRpkiResponse{}, s.bgpServer.EnableRpki(&config.RpkiServerConfig{
Address: arg.Address,
})
}
func (s *Server) DisableRpki(ctx context.Context, arg *DisableRpkiRequest) (*DisableRpkiResponse, error) {
return &DisableRpkiResponse{}, s.bgpServer.DisableRpki(&config.RpkiServerConfig{
Address: arg.Address,
})
}
func (s *Server) ResetRpki(ctx context.Context, arg *ResetRpkiRequest) (*ResetRpkiResponse, error) {
return &ResetRpkiResponse{}, s.bgpServer.ResetRpki(&config.RpkiServerConfig{
Address: arg.Address,
})
}
func (s *Server) SoftResetRpki(ctx context.Context, arg *SoftResetRpkiRequest) (*SoftResetRpkiResponse, error) {
return &SoftResetRpkiResponse{}, s.bgpServer.SoftResetRpki(&config.RpkiServerConfig{
Address: arg.Address,
})
}
func (s *Server) GetRpki(ctx context.Context, arg *GetRpkiRequest) (*GetRpkiResponse, error) {
servers, err := s.bgpServer.GetRpki()
if err != nil {
return nil, err
}
l := make([]*Rpki, 0, len(servers))
for _, s := range servers {
received := &s.State.RpkiMessages.RpkiReceived
sent := &s.State.RpkiMessages.RpkiSent
rpki := &Rpki{
Conf: &RPKIConf{
Address: s.Config.Address,
RemotePort: strconv.Itoa(int(s.Config.Port)),
},
State: &RPKIState{
Uptime: s.State.Uptime,
Downtime: s.State.Downtime,
Up: s.State.Up,
RecordIpv4: s.State.RecordsV4,
RecordIpv6: s.State.RecordsV6,
PrefixIpv4: s.State.PrefixesV4,
PrefixIpv6: s.State.PrefixesV6,
Serial: s.State.SerialNumber,
ReceivedIpv4: received.Ipv4Prefix,
ReceivedIpv6: received.Ipv6Prefix,
SerialNotify: received.SerialNotify,
CacheReset: received.CacheReset,
CacheResponse: received.CacheResponse,
EndOfData: received.EndOfData,
Error: received.Error,
SerialQuery: sent.SerialQuery,
ResetQuery: sent.ResetQuery,
},
}
l = append(l, rpki)
}
return &GetRpkiResponse{Servers: l}, nil
}
func (s *Server) GetRoa(ctx context.Context, arg *GetRoaRequest) (*GetRoaResponse, error) {
roas, err := s.bgpServer.GetRoa(bgp.RouteFamily(arg.Family))
if err != nil {
return nil, err
}
l := make([]*Roa, 0, len(roas))
for _, r := range roas {
host, port, _ := net.SplitHostPort(r.Src)
l = append(l, &Roa{
As: r.AS,
Maxlen: uint32(r.MaxLen),
Prefixlen: uint32(r.Prefix.Length),
Prefix: r.Prefix.Prefix.String(),
Conf: &RPKIConf{
Address: host,
RemotePort: port,
},
})
}
return &GetRoaResponse{Roas: l}, nil
}
func (s *Server) EnableZebra(ctx context.Context, arg *EnableZebraRequest) (*EnableZebraResponse, error) {
l := []config.InstallProtocolType{}
for _, p := range arg.RouteTypes {
if err := config.InstallProtocolType(p).Validate(); err != nil {
return &EnableZebraResponse{}, err
} else {
l = append(l, config.InstallProtocolType(p))
}
}
return &EnableZebraResponse{}, s.bgpServer.StartZebraClient(&config.ZebraConfig{
Url: arg.Url,
RedistributeRouteTypeList: l,
Version: uint8(arg.Version),
NexthopTriggerEnable: arg.NexthopTriggerEnable,
NexthopTriggerDelay: uint8(arg.NexthopTriggerDelay),
})
}
func (s *Server) GetVrf(ctx context.Context, arg *GetVrfRequest) (*GetVrfResponse, error) {
toApi := func(v *table.Vrf) *Vrf {
f := func(rts []bgp.ExtendedCommunityInterface) [][]byte {
ret := make([][]byte, 0, len(rts))
for _, rt := range rts {
b, _ := rt.Serialize()
ret = append(ret, b)
}
return ret
}
rd, _ := v.Rd.Serialize()
return &Vrf{
Name: v.Name,
Rd: rd,
Id: v.Id,
ImportRt: f(v.ImportRt),
ExportRt: f(v.ExportRt),
}
}
vrfs := s.bgpServer.GetVrf()
l := make([]*Vrf, 0, len(vrfs))
for _, v := range vrfs {
l = append(l, toApi(v))
}
return &GetVrfResponse{Vrfs: l}, nil
}
func (s *Server) AddVrf(ctx context.Context, arg *AddVrfRequest) (r *AddVrfResponse, err error) {
if arg == nil || arg.Vrf == nil {
return nil, fmt.Errorf("invalid request")
}
rd := bgp.GetRouteDistinguisher(arg.Vrf.Rd)
f := func(bufs [][]byte) ([]bgp.ExtendedCommunityInterface, error) {
ret := make([]bgp.ExtendedCommunityInterface, 0, len(bufs))
for _, rt := range bufs {
r, err := bgp.ParseExtended(rt)
if err != nil {
return nil, err
}
ret = append(ret, r)
}
return ret, nil
}
im, err := f(arg.Vrf.ImportRt)
if err != nil {
return &AddVrfResponse{}, err
}
ex, err := f(arg.Vrf.ExportRt)
if err != nil {
return &AddVrfResponse{}, err
}
return &AddVrfResponse{}, s.bgpServer.AddVrf(arg.Vrf.Name, arg.Vrf.Id, rd, im, ex)
}
func (s *Server) DeleteVrf(ctx context.Context, arg *DeleteVrfRequest) (*DeleteVrfResponse, error) {
if arg == nil || arg.Vrf == nil {
return nil, fmt.Errorf("invalid request")
}
return &DeleteVrfResponse{}, s.bgpServer.DeleteVrf(arg.Vrf.Name)
}
func NewNeighborFromAPIStruct(a *Peer) (*config.Neighbor, error) {
pconf := &config.Neighbor{}
if a.Conf != nil {
pconf.Config.NeighborAddress = a.Conf.NeighborAddress
pconf.Config.PeerAs = a.Conf.PeerAs
pconf.Config.LocalAs = a.Conf.LocalAs
pconf.Config.AuthPassword = a.Conf.AuthPassword
pconf.Config.RemovePrivateAs = config.RemovePrivateAsOption(a.Conf.RemovePrivateAs)
pconf.Config.RouteFlapDamping = a.Conf.RouteFlapDamping
pconf.Config.SendCommunity = config.CommunityType(a.Conf.SendCommunity)
pconf.Config.Description = a.Conf.Description
pconf.Config.PeerGroup = a.Conf.PeerGroup
pconf.Config.NeighborAddress = a.Conf.NeighborAddress
pconf.Config.NeighborInterface = a.Conf.NeighborInterface
pconf.Config.Vrf = a.Conf.Vrf
f := func(bufs [][]byte) ([]bgp.ParameterCapabilityInterface, error) {
var caps []bgp.ParameterCapabilityInterface
for _, buf := range bufs {
cap, err := bgp.DecodeCapability(buf)
if err != nil {
return nil, err
}
caps = append(caps, cap)
}
return caps, nil
}
localCaps, err := f(a.Conf.LocalCap)
if err != nil {
return nil, err
}
remoteCaps, err := f(a.Conf.RemoteCap)
if err != nil {
return nil, err
}
pconf.State.LocalCapabilityList = localCaps
pconf.State.RemoteCapabilityList = remoteCaps
pconf.State.RemoteRouterId = a.Conf.Id
for _, f := range a.Families {
family := bgp.RouteFamily(f)
pconf.AfiSafis = append(pconf.AfiSafis, config.AfiSafi{
Config: config.AfiSafiConfig{
AfiSafiName: config.AfiSafiType(family.String()),
Enabled: true,
},
})
}
for _, pl := range a.Conf.PrefixLimits {
for _, f := range pconf.AfiSafis {
if f.Config.AfiSafiName == config.AfiSafiType(bgp.RouteFamily(pl.Family).String()) {
f.PrefixLimit.Config.MaxPrefixes = pl.MaxPrefixes
f.PrefixLimit.Config.ShutdownThresholdPct = config.Percentage(pl.ShutdownThresholdPct)
}
}
}
}
if a.Timers != nil {
if a.Timers.Config != nil {
pconf.Timers.Config.ConnectRetry = float64(a.Timers.Config.ConnectRetry)
pconf.Timers.Config.HoldTime = float64(a.Timers.Config.HoldTime)
pconf.Timers.Config.KeepaliveInterval = float64(a.Timers.Config.KeepaliveInterval)
pconf.Timers.Config.MinimumAdvertisementInterval = float64(a.Timers.Config.MinimumAdvertisementInterval)
}
if a.Timers.State != nil {
pconf.Timers.State.KeepaliveInterval = float64(a.Timers.State.KeepaliveInterval)
pconf.Timers.State.NegotiatedHoldTime = float64(a.Timers.State.NegotiatedHoldTime)
pconf.Timers.State.Uptime = int64(a.Timers.State.Uptime)
pconf.Timers.State.Downtime = int64(a.Timers.State.Downtime)
}
}
if a.RouteReflector != nil {
pconf.RouteReflector.Config.RouteReflectorClusterId = config.RrClusterIdType(a.RouteReflector.RouteReflectorClusterId)
pconf.RouteReflector.Config.RouteReflectorClient = a.RouteReflector.RouteReflectorClient
}
if a.RouteServer != nil {
pconf.RouteServer.Config.RouteServerClient = a.RouteServer.RouteServerClient
}
if a.ApplyPolicy != nil {
if a.ApplyPolicy.ImportPolicy != nil {
pconf.ApplyPolicy.Config.DefaultImportPolicy = config.DefaultPolicyType(a.ApplyPolicy.ImportPolicy.Default)
for _, p := range a.ApplyPolicy.ImportPolicy.Policies {
pconf.ApplyPolicy.Config.ImportPolicyList = append(pconf.ApplyPolicy.Config.ImportPolicyList, p.Name)
}
}
if a.ApplyPolicy.ExportPolicy != nil {
pconf.ApplyPolicy.Config.DefaultExportPolicy = config.DefaultPolicyType(a.ApplyPolicy.ExportPolicy.Default)
for _, p := range a.ApplyPolicy.ExportPolicy.Policies {
pconf.ApplyPolicy.Config.ExportPolicyList = append(pconf.ApplyPolicy.Config.ExportPolicyList, p.Name)
}
}
if a.ApplyPolicy.InPolicy != nil {
pconf.ApplyPolicy.Config.DefaultInPolicy = config.DefaultPolicyType(a.ApplyPolicy.InPolicy.Default)
for _, p := range a.ApplyPolicy.InPolicy.Policies {
pconf.ApplyPolicy.Config.InPolicyList = append(pconf.ApplyPolicy.Config.InPolicyList, p.Name)
}
}
}
if a.Transport != nil {
pconf.Transport.Config.LocalAddress = a.Transport.LocalAddress
pconf.Transport.Config.PassiveMode = a.Transport.PassiveMode
pconf.Transport.Config.RemotePort = uint16(a.Transport.RemotePort)
}
if a.EbgpMultihop != nil {
pconf.EbgpMultihop.Config.Enabled = a.EbgpMultihop.Enabled
pconf.EbgpMultihop.Config.MultihopTtl = uint8(a.EbgpMultihop.MultihopTtl)
}
if a.Info != nil {
pconf.State.SessionState = config.SessionState(a.Info.BgpState)
pconf.State.AdminState = config.IntToAdminStateMap[int(a.Info.AdminState)]
pconf.State.AdjTable.Received = a.Info.Received
pconf.State.AdjTable.Accepted = a.Info.Accepted
pconf.State.AdjTable.Advertised = a.Info.Advertised
pconf.State.PeerAs = a.Info.PeerAs
pconf.State.PeerType = config.IntToPeerTypeMap[int(a.Info.PeerType)]
if a.Info.Messages != nil {
if a.Info.Messages.Sent != nil {
pconf.State.Messages.Sent.Update = a.Info.Messages.Sent.UPDATE
pconf.State.Messages.Sent.Notification = a.Info.Messages.Sent.NOTIFICATION
pconf.State.Messages.Sent.Open = a.Info.Messages.Sent.OPEN
pconf.State.Messages.Sent.Refresh = a.Info.Messages.Sent.REFRESH
pconf.State.Messages.Sent.Keepalive = a.Info.Messages.Sent.KEEPALIVE
pconf.State.Messages.Sent.Discarded = a.Info.Messages.Sent.DISCARDED
pconf.State.Messages.Sent.Total = a.Info.Messages.Sent.TOTAL
}
if a.Info.Messages.Received != nil {
pconf.State.Messages.Received.Update = a.Info.Messages.Received.UPDATE
pconf.State.Messages.Received.Open = a.Info.Messages.Received.OPEN
pconf.State.Messages.Received.Refresh = a.Info.Messages.Received.REFRESH
pconf.State.Messages.Received.Keepalive = a.Info.Messages.Received.KEEPALIVE
pconf.State.Messages.Received.Discarded = a.Info.Messages.Received.DISCARDED
pconf.State.Messages.Received.Total = a.Info.Messages.Received.TOTAL
}
}
}
return pconf, nil
}
func (s *Server) AddNeighbor(ctx context.Context, arg *AddNeighborRequest) (*AddNeighborResponse, error) {
c, err := NewNeighborFromAPIStruct(arg.Peer)
if err != nil {
return nil, err
}
return &AddNeighborResponse{}, s.bgpServer.AddNeighbor(c)
}
func (s *Server) DeleteNeighbor(ctx context.Context, arg *DeleteNeighborRequest) (*DeleteNeighborResponse, error) {
return &DeleteNeighborResponse{}, s.bgpServer.DeleteNeighbor(&config.Neighbor{Config: config.NeighborConfig{
NeighborAddress: arg.Peer.Conf.NeighborAddress,
NeighborInterface: arg.Peer.Conf.NeighborInterface,
}})
}
func NewPrefixFromApiStruct(a *Prefix) (*table.Prefix, error) {
_, prefix, err := net.ParseCIDR(a.IpPrefix)
if err != nil {
return nil, err
}
rf := bgp.RF_IPv4_UC
if strings.Contains(a.IpPrefix, ":") {
rf = bgp.RF_IPv6_UC
}
return &table.Prefix{
Prefix: prefix,
AddressFamily: rf,
MasklengthRangeMin: uint8(a.MaskLengthMin),
MasklengthRangeMax: uint8(a.MaskLengthMax),
}, nil
}
func NewAPIPrefixFromConfigStruct(c config.Prefix) (*Prefix, error) {
min, max, err := config.ParseMaskLength(c.IpPrefix, c.MasklengthRange)
if err != nil {
return nil, err
}
return &Prefix{
IpPrefix: c.IpPrefix,
MaskLengthMin: uint32(min),
MaskLengthMax: uint32(max),
}, nil
}
func NewAPIDefinedSetFromTableStruct(t table.DefinedSet) (*DefinedSet, error) {
a := &DefinedSet{
Type: DefinedType(t.Type()),
Name: t.Name(),
}
switch t.Type() {
case table.DEFINED_TYPE_PREFIX:
s := t.(*table.PrefixSet)
c := s.ToConfig()
for _, p := range c.PrefixList {
ap, err := NewAPIPrefixFromConfigStruct(p)
if err != nil {
return nil, err
}
a.Prefixes = append(a.Prefixes, ap)
}
case table.DEFINED_TYPE_NEIGHBOR:
s := t.(*table.NeighborSet)
c := s.ToConfig()
for _, n := range c.NeighborInfoList {
a.List = append(a.List, n)
}
case table.DEFINED_TYPE_AS_PATH:
s := t.(*table.AsPathSet)
c := s.ToConfig()
for _, n := range c.AsPathList {
a.List = append(a.List, n)
}
case table.DEFINED_TYPE_COMMUNITY:
s := t.(*table.CommunitySet)
c := s.ToConfig()
for _, n := range c.CommunityList {
a.List = append(a.List, n)
}
case table.DEFINED_TYPE_EXT_COMMUNITY:
s := t.(*table.ExtCommunitySet)
c := s.ToConfig()
for _, n := range c.ExtCommunityList {
a.List = append(a.List, n)
}
case table.DEFINED_TYPE_LARGE_COMMUNITY:
s := t.(*table.LargeCommunitySet)
c := s.ToConfig()
for _, n := range c.LargeCommunityList {
a.List = append(a.List, n)
}
default:
return nil, fmt.Errorf("invalid defined type")
}
return a, nil
}
func NewDefinedSetFromApiStruct(a *DefinedSet) (table.DefinedSet, error) {
if a.Name == "" {
return nil, fmt.Errorf("empty neighbor set name")
}
switch table.DefinedType(a.Type) {
case table.DEFINED_TYPE_PREFIX:
prefixes := make([]*table.Prefix, 0, len(a.Prefixes))
for _, p := range a.Prefixes {
prefix, err := NewPrefixFromApiStruct(p)
if err != nil {
return nil, err
}
prefixes = append(prefixes, prefix)
}
return table.NewPrefixSetFromApiStruct(a.Name, prefixes)
case table.DEFINED_TYPE_NEIGHBOR:
list := make([]net.IP, 0, len(a.List))
for _, x := range a.List {
addr := net.ParseIP(x)
if addr == nil {
return nil, fmt.Errorf("invalid ip address format: %s", x)
}
list = append(list, addr)
}
return table.NewNeighborSetFromApiStruct(a.Name, list)
case table.DEFINED_TYPE_AS_PATH:
return table.NewAsPathSet(config.AsPathSet{
AsPathSetName: a.Name,
AsPathList: a.List,
})
case table.DEFINED_TYPE_COMMUNITY:
return table.NewCommunitySet(config.CommunitySet{
CommunitySetName: a.Name,
CommunityList: a.List,
})
case table.DEFINED_TYPE_EXT_COMMUNITY:
return table.NewExtCommunitySet(config.ExtCommunitySet{
ExtCommunitySetName: a.Name,
ExtCommunityList: a.List,
})
case table.DEFINED_TYPE_LARGE_COMMUNITY:
return table.NewLargeCommunitySet(config.LargeCommunitySet{
LargeCommunitySetName: a.Name,
LargeCommunityList: a.List,
})
default:
return nil, fmt.Errorf("invalid defined type")
}
}
func (s *Server) GetDefinedSet(ctx context.Context, arg *GetDefinedSetRequest) (*GetDefinedSetResponse, error) {
cd, err := s.bgpServer.GetDefinedSet(table.DefinedType(arg.Type))
if err != nil {
return nil, err
}
sets := make([]*DefinedSet, 0)
for _, cs := range cd.PrefixSets {
ad := &DefinedSet{
Type: DefinedType_PREFIX,
Name: cs.PrefixSetName,
Prefixes: func() []*Prefix {
l := make([]*Prefix, 0, len(cs.PrefixList))
for _, p := range cs.PrefixList {
exp := regexp.MustCompile("(\\d+)\\.\\.(\\d+)")
elems := exp.FindStringSubmatch(p.MasklengthRange)
min, _ := strconv.Atoi(elems[1])
max, _ := strconv.Atoi(elems[2])
l = append(l, &Prefix{IpPrefix: p.IpPrefix, MaskLengthMin: uint32(min), MaskLengthMax: uint32(max)})
}
return l
}(),
}
sets = append(sets, ad)
}
for _, cs := range cd.NeighborSets {
ad := &DefinedSet{
Type: DefinedType_NEIGHBOR,
Name: cs.NeighborSetName,
List: cs.NeighborInfoList,
}
sets = append(sets, ad)
}
for _, cs := range cd.BgpDefinedSets.CommunitySets {
ad := &DefinedSet{
Type: DefinedType_COMMUNITY,
Name: cs.CommunitySetName,
List: cs.CommunityList,
}
sets = append(sets, ad)
}
for _, cs := range cd.BgpDefinedSets.ExtCommunitySets {
ad := &DefinedSet{
Type: DefinedType_EXT_COMMUNITY,
Name: cs.ExtCommunitySetName,
List: cs.ExtCommunityList,
}
sets = append(sets, ad)
}
for _, cs := range cd.BgpDefinedSets.LargeCommunitySets {
ad := &DefinedSet{
Type: DefinedType_LARGE_COMMUNITY,
Name: cs.LargeCommunitySetName,
List: cs.LargeCommunityList,
}
sets = append(sets, ad)
}
for _, cs := range cd.BgpDefinedSets.AsPathSets {
ad := &DefinedSet{
Type: DefinedType_AS_PATH,
Name: cs.AsPathSetName,
List: cs.AsPathList,
}
sets = append(sets, ad)
}
return &GetDefinedSetResponse{Sets: sets}, nil
}
func (s *Server) AddDefinedSet(ctx context.Context, arg *AddDefinedSetRequest) (*AddDefinedSetResponse, error) {
if arg == nil || arg.Set == nil {
return nil, fmt.Errorf("invalid request")
}
set, err := NewDefinedSetFromApiStruct(arg.Set)
if err != nil {
return nil, err
}
return &AddDefinedSetResponse{}, s.bgpServer.AddDefinedSet(set)
}
func (s *Server) DeleteDefinedSet(ctx context.Context, arg *DeleteDefinedSetRequest) (*DeleteDefinedSetResponse, error) {
if arg == nil || arg.Set == nil {
return nil, fmt.Errorf("invalid request")
}
set, err := NewDefinedSetFromApiStruct(arg.Set)
if err != nil {
return nil, err
}
return &DeleteDefinedSetResponse{}, s.bgpServer.DeleteDefinedSet(set, arg.All)
}
func (s *Server) ReplaceDefinedSet(ctx context.Context, arg *ReplaceDefinedSetRequest) (*ReplaceDefinedSetResponse, error) {
if arg == nil || arg.Set == nil {
return nil, fmt.Errorf("invalid request")
}
set, err := NewDefinedSetFromApiStruct(arg.Set)
if err != nil {
return nil, err
}
return &ReplaceDefinedSetResponse{}, s.bgpServer.ReplaceDefinedSet(set)
}
func NewAPIStatementFromTableStruct(t *table.Statement) *Statement {
return toStatementApi(t.ToConfig())
}
func toStatementApi(s *config.Statement) *Statement {
cs := &Conditions{}
if s.Conditions.MatchPrefixSet.PrefixSet != "" {
o, _ := table.NewMatchOption(s.Conditions.MatchPrefixSet.MatchSetOptions)
cs.PrefixSet = &MatchSet{
Type: MatchType(o),
Name: s.Conditions.MatchPrefixSet.PrefixSet,
}
}
if s.Conditions.MatchNeighborSet.NeighborSet != "" {
o, _ := table.NewMatchOption(s.Conditions.MatchNeighborSet.MatchSetOptions)
cs.NeighborSet = &MatchSet{
Type: MatchType(o),
Name: s.Conditions.MatchNeighborSet.NeighborSet,
}
}
if s.Conditions.BgpConditions.AsPathLength.Operator != "" {
cs.AsPathLength = &AsPathLength{
Length: s.Conditions.BgpConditions.AsPathLength.Value,
Type: AsPathLengthType(s.Conditions.BgpConditions.AsPathLength.Operator.ToInt()),
}
}
if s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet != "" {
cs.AsPathSet = &MatchSet{
Type: MatchType(s.Conditions.BgpConditions.MatchAsPathSet.MatchSetOptions.ToInt()),
Name: s.Conditions.BgpConditions.MatchAsPathSet.AsPathSet,
}
}
if s.Conditions.BgpConditions.MatchCommunitySet.CommunitySet != "" {
cs.CommunitySet = &MatchSet{
Type: MatchType(s.Conditions.BgpConditions.MatchCommunitySet.MatchSetOptions.ToInt()),
Name: s.Conditions.BgpConditions.MatchCommunitySet.CommunitySet,
}
}
if s.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet != "" {
cs.ExtCommunitySet = &MatchSet{
Type: MatchType(s.Conditions.BgpConditions.MatchExtCommunitySet.MatchSetOptions.ToInt()),
Name: s.Conditions.BgpConditions.MatchExtCommunitySet.ExtCommunitySet,
}
}
if s.Conditions.BgpConditions.MatchLargeCommunitySet.LargeCommunitySet != "" {
cs.LargeCommunitySet = &MatchSet{
Type: MatchType(s.Conditions.BgpConditions.MatchLargeCommunitySet.MatchSetOptions.ToInt()),
Name: s.Conditions.BgpConditions.MatchLargeCommunitySet.LargeCommunitySet,
}
}
if s.Conditions.BgpConditions.RouteType != "" {
cs.RouteType = Conditions_RouteType(s.Conditions.BgpConditions.RouteType.ToInt())
}
cs.RpkiResult = int32(s.Conditions.BgpConditions.RpkiValidationResult.ToInt())
as := &Actions{
RouteAction: func() RouteAction {
switch s.Actions.RouteDisposition {
case config.ROUTE_DISPOSITION_ACCEPT_ROUTE:
return RouteAction_ACCEPT
case config.ROUTE_DISPOSITION_REJECT_ROUTE:
return RouteAction_REJECT
}
return RouteAction_NONE
}(),
Community: func() *CommunityAction {
if len(s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList) == 0 {
return nil
}
return &CommunityAction{
Type: CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetCommunity.Options)]),
Communities: s.Actions.BgpActions.SetCommunity.SetCommunityMethod.CommunitiesList}
}(),
Med: func() *MedAction {
if len(string(s.Actions.BgpActions.SetMed)) == 0 {
return nil
}
exp := regexp.MustCompile("^(\\+|\\-)?(\\d+)$")
elems := exp.FindStringSubmatch(string(s.Actions.BgpActions.SetMed))
action := MedActionType_MED_REPLACE
switch elems[1] {
case "+", "-":
action = MedActionType_MED_MOD
}
value, _ := strconv.Atoi(string(s.Actions.BgpActions.SetMed))
return &MedAction{
Value: int64(value),
Type: action,
}
}(),
AsPrepend: func() *AsPrependAction {
if len(s.Actions.BgpActions.SetAsPathPrepend.As) == 0 {
return nil
}
asn := 0
useleft := false
if s.Actions.BgpActions.SetAsPathPrepend.As != "last-as" {
asn, _ = strconv.Atoi(s.Actions.BgpActions.SetAsPathPrepend.As)
} else {
useleft = true
}
return &AsPrependAction{
Asn: uint32(asn),
Repeat: uint32(s.Actions.BgpActions.SetAsPathPrepend.RepeatN),
UseLeftMost: useleft,
}
}(),
ExtCommunity: func() *CommunityAction {
if len(s.Actions.BgpActions.SetExtCommunity.SetExtCommunityMethod.CommunitiesList) == 0 {
return nil
}
return &CommunityAction{
Type: CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetExtCommunity.Options)]),
Communities: s.Actions.BgpActions.SetExtCommunity.SetExtCommunityMethod.CommunitiesList,
}
}(),
LargeCommunity: func() *CommunityAction {
if len(s.Actions.BgpActions.SetLargeCommunity.SetLargeCommunityMethod.CommunitiesList) == 0 {
return nil
}
return &CommunityAction{
Type: CommunityActionType(config.BgpSetCommunityOptionTypeToIntMap[config.BgpSetCommunityOptionType(s.Actions.BgpActions.SetLargeCommunity.Options)]),
Communities: s.Actions.BgpActions.SetLargeCommunity.SetLargeCommunityMethod.CommunitiesList,
}
}(),
Nexthop: func() *NexthopAction {
if len(string(s.Actions.BgpActions.SetNextHop)) == 0 {
return nil
}
if string(s.Actions.BgpActions.SetNextHop) == "self" {
return &NexthopAction{
Self: true,
}
}
return &NexthopAction{
Address: string(s.Actions.BgpActions.SetNextHop),
}
}(),
LocalPref: func() *LocalPrefAction {
if s.Actions.BgpActions.SetLocalPref == 0 {
return nil
}
return &LocalPrefAction{Value: s.Actions.BgpActions.SetLocalPref}
}(),
}
return &Statement{
Name: s.Name,
Conditions: cs,
Actions: as,
}
}
func toConfigMatchSetOption(a MatchType) (config.MatchSetOptionsType, error) {
var typ config.MatchSetOptionsType
switch a {
case MatchType_ANY:
typ = config.MATCH_SET_OPTIONS_TYPE_ANY
case MatchType_ALL:
typ = config.MATCH_SET_OPTIONS_TYPE_ALL
case MatchType_INVERT:
typ = config.MATCH_SET_OPTIONS_TYPE_INVERT
default:
return typ, fmt.Errorf("invalid match type")
}
return typ, nil
}
func toConfigMatchSetOptionRestricted(a MatchType) (config.MatchSetOptionsRestrictedType, error) {
var typ config.MatchSetOptionsRestrictedType
switch a {
case MatchType_ANY:
typ = config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_ANY
case MatchType_INVERT:
typ = config.MATCH_SET_OPTIONS_RESTRICTED_TYPE_INVERT
default:
return typ, fmt.Errorf("invalid match type")
}
return typ, nil
}
func NewPrefixConditionFromApiStruct(a *MatchSet) (*table.PrefixCondition, error) {
if a == nil {
return nil, nil
}
typ, err := toConfigMatchSetOptionRestricted(a.Type)
if err != nil {
return nil, err
}
c := config.MatchPrefixSet{
PrefixSet: a.Name,
MatchSetOptions: typ,
}
return table.NewPrefixCondition(c)
}
func NewNeighborConditionFromApiStruct(a *MatchSet) (*table.NeighborCondition, error) {
if a == nil {
return nil, nil
}
typ, err := toConfigMatchSetOptionRestricted(a.Type)
if err != nil {
return nil, err
}
c := config.MatchNeighborSet{
NeighborSet: a.Name,
MatchSetOptions: typ,
}
return table.NewNeighborCondition(c)
}
func NewAsPathLengthConditionFromApiStruct(a *AsPathLength) (*table.AsPathLengthCondition, error) {
if a == nil {
return nil, nil
}
return table.NewAsPathLengthCondition(config.AsPathLength{
Operator: config.IntToAttributeComparisonMap[int(a.Type)],
Value: a.Length,
})
}
func NewAsPathConditionFromApiStruct(a *MatchSet) (*table.AsPathCondition, error) {
if a == nil {
return nil, nil
}
typ, err := toConfigMatchSetOption(a.Type)
if err != nil {
return nil, err
}
c := config.MatchAsPathSet{
AsPathSet: a.Name,
MatchSetOptions: typ,
}
return table.NewAsPathCondition(c)
}
func NewRpkiValidationConditionFromApiStruct(a int32) (*table.RpkiValidationCondition, error) {
if a < 1 {
return nil, nil
}
return table.NewRpkiValidationCondition(config.IntToRpkiValidationResultTypeMap[int(a)])
}
func NewRouteTypeConditionFromApiStruct(a Conditions_RouteType) (*table.RouteTypeCondition, error) {
if a == 0 {
return nil, nil
}
typ, ok := config.IntToRouteTypeMap[int(a)]
if !ok {
return nil, fmt.Errorf("invalid route type: %d", a)
}
return table.NewRouteTypeCondition(typ)
}
func NewCommunityConditionFromApiStruct(a *MatchSet) (*table.CommunityCondition, error) {
if a == nil {
return nil, nil
}
typ, err := toConfigMatchSetOption(a.Type)
if err != nil {
return nil, err
}
c := config.MatchCommunitySet{
CommunitySet: a.Name,
MatchSetOptions: typ,
}
return table.NewCommunityCondition(c)
}
func NewExtCommunityConditionFromApiStruct(a *MatchSet) (*table.ExtCommunityCondition, error) {
if a == nil {
return nil, nil
}
typ, err := toConfigMatchSetOption(a.Type)
if err != nil {
return nil, err
}
c := config.MatchExtCommunitySet{
ExtCommunitySet: a.Name,
MatchSetOptions: typ,
}
return table.NewExtCommunityCondition(c)
}
func NewLargeCommunityConditionFromApiStruct(a *MatchSet) (*table.LargeCommunityCondition, error) {
if a == nil {
return nil, nil
}
typ, err := toConfigMatchSetOption(a.Type)
if err != nil {
return nil, err
}
c := config.MatchLargeCommunitySet{
LargeCommunitySet: a.Name,
MatchSetOptions: typ,
}
return table.NewLargeCommunityCondition(c)
}
func NewRoutingActionFromApiStruct(a RouteAction) (*table.RoutingAction, error) {
if a == RouteAction_NONE {
return nil, nil
}
accept := false
if a == RouteAction_ACCEPT {
accept = true
}
return &table.RoutingAction{
AcceptRoute: accept,
}, nil
}
func NewCommunityActionFromApiStruct(a *CommunityAction) (*table.CommunityAction, error) {
if a == nil {
return nil, nil
}
return table.NewCommunityAction(config.SetCommunity{
Options: string(config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)]),
SetCommunityMethod: config.SetCommunityMethod{
CommunitiesList: a.Communities,
},
})
}
func NewExtCommunityActionFromApiStruct(a *CommunityAction) (*table.ExtCommunityAction, error) {
if a == nil {
return nil, nil
}
return table.NewExtCommunityAction(config.SetExtCommunity{
Options: string(config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)]),
SetExtCommunityMethod: config.SetExtCommunityMethod{
CommunitiesList: a.Communities,
},
})
}
func NewLargeCommunityActionFromApiStruct(a *CommunityAction) (*table.LargeCommunityAction, error) {
if a == nil {
return nil, nil
}
return table.NewLargeCommunityAction(config.SetLargeCommunity{
Options: config.IntToBgpSetCommunityOptionTypeMap[int(a.Type)],
SetLargeCommunityMethod: config.SetLargeCommunityMethod{
CommunitiesList: a.Communities,
},
})
}
func NewMedActionFromApiStruct(a *MedAction) (*table.MedAction, error) {
if a == nil {
return nil, nil
}
return table.NewMedActionFromApiStruct(table.MedActionType(a.Type), a.Value), nil
}
func NewLocalPrefActionFromApiStruct(a *LocalPrefAction) (*table.LocalPrefAction, error) {
if a == nil || a.Value == 0 {
return nil, nil
}
return table.NewLocalPrefAction(a.Value)
}
func NewAsPathPrependActionFromApiStruct(a *AsPrependAction) (*table.AsPathPrependAction, error) {
if a == nil {
return nil, nil
}
return table.NewAsPathPrependAction(config.SetAsPathPrepend{
RepeatN: uint8(a.Repeat),
As: func() string {
if a.UseLeftMost {
return "last-as"
}
return fmt.Sprintf("%d", a.Asn)
}(),
})
}
func NewNexthopActionFromApiStruct(a *NexthopAction) (*table.NexthopAction, error) {
if a == nil {
return nil, nil
}
return table.NewNexthopAction(config.BgpNextHopType(
func() string {
if a.Self {
return "self"
}
return a.Address
}(),
))
}
func NewStatementFromApiStruct(a *Statement) (*table.Statement, error) {
if a.Name == "" {
return nil, fmt.Errorf("empty statement name")
}
var ra table.Action
var as []table.Action
var cs []table.Condition
var err error
if a.Conditions != nil {
cfs := []func() (table.Condition, error){
func() (table.Condition, error) {
return NewPrefixConditionFromApiStruct(a.Conditions.PrefixSet)
},
func() (table.Condition, error) {
return NewNeighborConditionFromApiStruct(a.Conditions.NeighborSet)
},
func() (table.Condition, error) {
return NewAsPathLengthConditionFromApiStruct(a.Conditions.AsPathLength)
},
func() (table.Condition, error) {
return NewRpkiValidationConditionFromApiStruct(a.Conditions.RpkiResult)
},
func() (table.Condition, error) {
return NewRouteTypeConditionFromApiStruct(a.Conditions.RouteType)
},
func() (table.Condition, error) {
return NewAsPathConditionFromApiStruct(a.Conditions.AsPathSet)
},
func() (table.Condition, error) {
return NewCommunityConditionFromApiStruct(a.Conditions.CommunitySet)
},
func() (table.Condition, error) {
return NewExtCommunityConditionFromApiStruct(a.Conditions.ExtCommunitySet)
},
func() (table.Condition, error) {
return NewLargeCommunityConditionFromApiStruct(a.Conditions.LargeCommunitySet)
},
}
cs = make([]table.Condition, 0, len(cfs))
for _, f := range cfs {
c, err := f()
if err != nil {
return nil, err
}
if !reflect.ValueOf(c).IsNil() {
cs = append(cs, c)
}
}
}
if a.Actions != nil {
ra, err = NewRoutingActionFromApiStruct(a.Actions.RouteAction)
if err != nil {
return nil, err
}
afs := []func() (table.Action, error){
func() (table.Action, error) {
return NewCommunityActionFromApiStruct(a.Actions.Community)
},
func() (table.Action, error) {
return NewExtCommunityActionFromApiStruct(a.Actions.ExtCommunity)
},
func() (table.Action, error) {
return NewLargeCommunityActionFromApiStruct(a.Actions.LargeCommunity)
},
func() (table.Action, error) {
return NewMedActionFromApiStruct(a.Actions.Med)
},
func() (table.Action, error) {
return NewLocalPrefActionFromApiStruct(a.Actions.LocalPref)
},
func() (table.Action, error) {
return NewAsPathPrependActionFromApiStruct(a.Actions.AsPrepend)
},
func() (table.Action, error) {
return NewNexthopActionFromApiStruct(a.Actions.Nexthop)
},
}
as = make([]table.Action, 0, len(afs))
for _, f := range afs {
a, err := f()
if err != nil {
return nil, err
}
if !reflect.ValueOf(a).IsNil() {
as = append(as, a)
}
}
}
return &table.Statement{
Name: a.Name,
Conditions: cs,
RouteAction: ra,
ModActions: as,
}, nil
}
func (s *Server) GetStatement(ctx context.Context, arg *GetStatementRequest) (*GetStatementResponse, error) {
l := make([]*Statement, 0)
for _, s := range s.bgpServer.GetStatement() {
l = append(l, toStatementApi(s))
}
return &GetStatementResponse{Statements: l}, nil
}
func (s *Server) AddStatement(ctx context.Context, arg *AddStatementRequest) (*AddStatementResponse, error) {
if arg == nil || arg.Statement == nil {
return nil, fmt.Errorf("invalid request")
}
st, err := NewStatementFromApiStruct(arg.Statement)
if err == nil {
err = s.bgpServer.AddStatement(st)
}
return &AddStatementResponse{}, err
}
func (s *Server) DeleteStatement(ctx context.Context, arg *DeleteStatementRequest) (*DeleteStatementResponse, error) {
if arg == nil || arg.Statement == nil {
return nil, fmt.Errorf("invalid request")
}
st, err := NewStatementFromApiStruct(arg.Statement)
if err == nil {
err = s.bgpServer.DeleteStatement(st, arg.All)
}
return &DeleteStatementResponse{}, err
}
func (s *Server) ReplaceStatement(ctx context.Context, arg *ReplaceStatementRequest) (*ReplaceStatementResponse, error) {
if arg == nil || arg.Statement == nil {
return nil, fmt.Errorf("invalid request")
}
st, err := NewStatementFromApiStruct(arg.Statement)
if err == nil {
err = s.bgpServer.ReplaceStatement(st)
}
return &ReplaceStatementResponse{}, err
}
func NewAPIPolicyFromTableStruct(p *table.Policy) *Policy {
return toPolicyApi(p.ToConfig())
}
func toPolicyApi(p *config.PolicyDefinition) *Policy {
return &Policy{
Name: p.Name,
Statements: func() []*Statement {
l := make([]*Statement, 0)
for _, s := range p.Statements {
l = append(l, toStatementApi(&s))
}
return l
}(),
}
}
func NewAPIPolicyAssignmentFromTableStruct(t *table.PolicyAssignment) *PolicyAssignment {
return &PolicyAssignment{
Type: func() PolicyType {
switch t.Type {
case table.POLICY_DIRECTION_IN:
return PolicyType_IN
case table.POLICY_DIRECTION_IMPORT:
return PolicyType_IMPORT
case table.POLICY_DIRECTION_EXPORT:
return PolicyType_EXPORT
}
log.Errorf("invalid policy-type: %s", t.Type)
return PolicyType(-1)
}(),
Default: func() RouteAction {
switch t.Default {
case table.ROUTE_TYPE_ACCEPT:
return RouteAction_ACCEPT
case table.ROUTE_TYPE_REJECT:
return RouteAction_REJECT
}
return RouteAction_NONE
}(),
Name: t.Name,
Resource: func() Resource {
if t.Name != "" {
return Resource_LOCAL
}
return Resource_GLOBAL
}(),
Policies: func() []*Policy {
l := make([]*Policy, 0)
for _, p := range t.Policies {
l = append(l, NewAPIPolicyFromTableStruct(p))
}
return l
}(),
}
}
func NewPolicyFromApiStruct(a *Policy) (*table.Policy, error) {
if a.Name == "" {
return nil, fmt.Errorf("empty policy name")
}
stmts := make([]*table.Statement, 0, len(a.Statements))
for idx, x := range a.Statements {
if x.Name == "" {
x.Name = fmt.Sprintf("%s_stmt%d", a.Name, idx)
}
y, err := NewStatementFromApiStruct(x)
if err != nil {
return nil, err
}
stmts = append(stmts, y)
}
return &table.Policy{
Name: a.Name,
Statements: stmts,
}, nil
}
func (s *Server) GetPolicy(ctx context.Context, arg *GetPolicyRequest) (*GetPolicyResponse, error) {
l := make([]*Policy, 0)
for _, p := range s.bgpServer.GetPolicy() {
l = append(l, toPolicyApi(p))
}
return &GetPolicyResponse{Policies: l}, nil
}
func (s *Server) AddPolicy(ctx context.Context, arg *AddPolicyRequest) (*AddPolicyResponse, error) {
if arg == nil || arg.Policy == nil {
return nil, fmt.Errorf("invalid request")
}
x, err := NewPolicyFromApiStruct(arg.Policy)
if err != nil {
return nil, err
}
return &AddPolicyResponse{}, s.bgpServer.AddPolicy(x, arg.ReferExistingStatements)
}
func (s *Server) DeletePolicy(ctx context.Context, arg *DeletePolicyRequest) (*DeletePolicyResponse, error) {
if arg == nil || arg.Policy == nil {
return nil, fmt.Errorf("invalid request")
}
x, err := NewPolicyFromApiStruct(arg.Policy)
if err != nil {
return nil, err
}
return &DeletePolicyResponse{}, s.bgpServer.DeletePolicy(x, arg.All, arg.PreserveStatements)
}
func (s *Server) ReplacePolicy(ctx context.Context, arg *ReplacePolicyRequest) (*ReplacePolicyResponse, error) {
if arg == nil || arg.Policy == nil {
return nil, fmt.Errorf("invalid request")
}
x, err := NewPolicyFromApiStruct(arg.Policy)
if err != nil {
return nil, err
}
return &ReplacePolicyResponse{}, s.bgpServer.ReplacePolicy(x, arg.ReferExistingStatements, arg.PreserveStatements)
}
func toPolicyAssignmentName(a *PolicyAssignment) (string, table.PolicyDirection, error) {
switch a.Resource {
case Resource_GLOBAL:
switch a.Type {
case PolicyType_IMPORT:
return "", table.POLICY_DIRECTION_IMPORT, nil
case PolicyType_EXPORT:
return "", table.POLICY_DIRECTION_EXPORT, nil
default:
return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid policy type")
}
case Resource_LOCAL:
switch a.Type {
case PolicyType_IN:
return a.Name, table.POLICY_DIRECTION_IN, nil
case PolicyType_IMPORT:
return a.Name, table.POLICY_DIRECTION_IMPORT, nil
case PolicyType_EXPORT:
return a.Name, table.POLICY_DIRECTION_EXPORT, nil
default:
return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid policy type")
}
default:
return "", table.POLICY_DIRECTION_NONE, fmt.Errorf("invalid resource type")
}
}
func (s *Server) GetPolicyAssignment(ctx context.Context, arg *GetPolicyAssignmentRequest) (*GetPolicyAssignmentResponse, error) {
if arg == nil || arg.Assignment == nil {
return nil, fmt.Errorf("invalid request")
}
name, dir, err := toPolicyAssignmentName(arg.Assignment)
if err != nil {
return nil, err
}
def, pols, err := s.bgpServer.GetPolicyAssignment(name, dir)
if err != nil {
return nil, err
}
policies := make([]*table.Policy, 0, len(pols))
for _, p := range pols {
t, err := table.NewPolicy(*p)
if err != nil {
return nil, err
}
policies = append(policies, t)
}
t := &table.PolicyAssignment{
Name: name,
Type: dir,
Default: def,
Policies: policies,
}
return &GetPolicyAssignmentResponse{NewAPIPolicyAssignmentFromTableStruct(t)}, err
}
func defaultRouteType(d RouteAction) table.RouteType {
switch d {
case RouteAction_ACCEPT:
return table.ROUTE_TYPE_ACCEPT
case RouteAction_REJECT:
return table.ROUTE_TYPE_REJECT
default:
return table.ROUTE_TYPE_NONE
}
}
func toPolicyDefinition(policies []*Policy) []*config.PolicyDefinition {
l := make([]*config.PolicyDefinition, 0, len(policies))
for _, p := range policies {
l = append(l, &config.PolicyDefinition{Name: p.Name})
}
return l
}
func (s *Server) AddPolicyAssignment(ctx context.Context, arg *AddPolicyAssignmentRequest) (*AddPolicyAssignmentResponse, error) {
if arg == nil || arg.Assignment == nil {
return nil, fmt.Errorf("invalid request")
}
name, dir, err := toPolicyAssignmentName(arg.Assignment)
if err != nil {
return nil, err
}
return &AddPolicyAssignmentResponse{}, s.bgpServer.AddPolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), defaultRouteType(arg.Assignment.Default))
}
func (s *Server) DeletePolicyAssignment(ctx context.Context, arg *DeletePolicyAssignmentRequest) (*DeletePolicyAssignmentResponse, error) {
if arg == nil || arg.Assignment == nil {
return nil, fmt.Errorf("invalid request")
}
name, dir, err := toPolicyAssignmentName(arg.Assignment)
if err != nil {
return nil, err
}
return &DeletePolicyAssignmentResponse{}, s.bgpServer.DeletePolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), arg.All)
}
func (s *Server) ReplacePolicyAssignment(ctx context.Context, arg *ReplacePolicyAssignmentRequest) (*ReplacePolicyAssignmentResponse, error) {
if arg == nil || arg.Assignment == nil {
return nil, fmt.Errorf("invalid request")
}
name, dir, err := toPolicyAssignmentName(arg.Assignment)
if err != nil {
return nil, err
}
return &ReplacePolicyAssignmentResponse{}, s.bgpServer.ReplacePolicyAssignment(name, dir, toPolicyDefinition(arg.Assignment.Policies), defaultRouteType(arg.Assignment.Default))
}
func (s *Server) GetServer(ctx context.Context, arg *GetServerRequest) (*GetServerResponse, error) {
g := s.bgpServer.GetServer()
return &GetServerResponse{
Global: &Global{
As: g.Config.As,
RouterId: g.Config.RouterId,
ListenPort: g.Config.Port,
ListenAddresses: g.Config.LocalAddressList,
UseMultiplePaths: g.UseMultiplePaths.Config.Enabled,
},
}, nil
}
func (s *Server) StartServer(ctx context.Context, arg *StartServerRequest) (*StartServerResponse, error) {
if arg == nil || arg.Global == nil {
return nil, fmt.Errorf("invalid request")
}
g := arg.Global
if net.ParseIP(g.RouterId) == nil {
return nil, fmt.Errorf("invalid router-id format: %s", g.RouterId)
}
families := make([]config.AfiSafi, 0, len(g.Families))
for _, f := range g.Families {
name := config.AfiSafiType(bgp.RouteFamily(f).String())
families = append(families, config.AfiSafi{
Config: config.AfiSafiConfig{
AfiSafiName: name,
Enabled: true,
},
State: config.AfiSafiState{
AfiSafiName: name,
},
})
}
b := &config.BgpConfigSet{
Global: config.Global{
Config: config.GlobalConfig{
As: g.As,
RouterId: g.RouterId,
Port: g.ListenPort,
LocalAddressList: g.ListenAddresses,
},
AfiSafis: families,
UseMultiplePaths: config.UseMultiplePaths{
Config: config.UseMultiplePathsConfig{
Enabled: g.UseMultiplePaths,
},
},
},
}
return &StartServerResponse{}, s.bgpServer.Start(&b.Global)
}
func (s *Server) StopServer(ctx context.Context, arg *StopServerRequest) (*StopServerResponse, error) {
return &StopServerResponse{}, s.bgpServer.Stop()
}
func (s *Server) GetRibInfo(ctx context.Context, arg *GetRibInfoRequest) (*GetRibInfoResponse, error) {
if arg == nil || arg.Info == nil {
return nil, fmt.Errorf("invalid request")
}
family := bgp.RouteFamily(arg.Info.Family)
var in bool
var err error
var info *table.TableInfo
switch arg.Info.Type {
case Resource_GLOBAL, Resource_LOCAL:
info, err = s.bgpServer.GetRibInfo(arg.Info.Name, family)
case Resource_ADJ_IN:
in = true
fallthrough
case Resource_ADJ_OUT:
info, err = s.bgpServer.GetAdjRibInfo(arg.Info.Name, family, in)
default:
return nil, fmt.Errorf("unsupported resource type: %s", arg.Info.Type)
}
if err != nil {
return nil, err
}
return &GetRibInfoResponse{
Info: &TableInfo{
Type: arg.Info.Type,
Family: arg.Info.Family,
Name: arg.Info.Name,
NumDestination: uint64(info.NumDestination),
NumPath: uint64(info.NumPath),
NumAccepted: uint64(info.NumAccepted),
},
}, nil
}